拓冰建站拓冰建站
首页 / 资讯中心 / 正文

手术器械语义分割:从数据集解析到Unet/DeepLabV3实践

简介面向医学图像分割模型训练与评估这套手术器械图像语义分割数据集包含约1200张已处理的图片及对应标签覆盖32个类别并预先划分为860张左右的训练集和370张左右的验证集适合直接用于U-Net、Swin-Unet等分割网络的实验与调优。压缩包共2000个文件以PNG、JPG图像为主要格式涵盖原始图片、掩码标注与可视化输出另有TXT说明和Python脚本整体体积约80.79MB目录结构清晰便于批量加载。资源内附可视化脚本可随机抽取一张样本将原始图片、GT掩码以及GT在原图上的叠加效果保存为图像方便快速检查标注质量与模型输入输出。当前已有102人学习下载对有医学影像基础或正在开展语义分割研究的学生、开发者来说是一份兼具数据与工具参考价值的入门级数据集可作为算法对比与效果验证的基础基准。1. 手术器械语义分割一套已划分好的数据集为什么值得拆开看手术器械在手术视野里的表现很反直觉器械本身细长、高反光、带关节和刀头背景是组织、纱布和血液对比度不高。绝大多数通用语义分割模型在这种场景上mIoU会比城市景观低二十个百分点都是正常现象。这套医学图像分割数据集约1200张图像已经处理成images和masks双目录并且按训练集860张、验证集370张划好省掉了最繁琐的清洗和划分环节。如果你正在做医学图像分割、语义分割的模型验证或者想测Unet、DeepLabV3这类结构在细长物体上的边界能力这套数据可以直接拿来当基准。注意它是像素级语义分割任务不是目标检测标注思想跟yolov8训练自己的数据集时用的框概念不一样。2. 手术器械数据集解析目录结构、训练/验证划分与32类标签映射2.1 images与masks的双目录结构先看懂数据再动模型拿到手先别急着训练先把目录摸清楚。常见的医学图像分割数据集有两种组织方式一种是把图像和标签放在不同目录、保持同名一种是把所有样本打平后靠文件名前缀区分。这套数据采用的是第一种surgical-instrument/ ├── train/ │ ├── images/ # 约860张原始手术图像 │ └── masks/ # 对应的分割标签同名无扩展名差异 └── val/ ├── images/ # 约370张原始手术图像 └── masks/文件名看起来是892.jpg、15.jpg这种分散编号不是从0开始的连续序号。这意味着脚本里不能靠range猜文件名正确做法是遍历images目录再拼接masks路径。先写一段检查脚本确定每个图像都有对应的标签文件。import os from pathlib import Path train_img_dir Path(surgical-instrument/train/images) train_mask_dir Path(surgical-instrument/train/masks) img_files sorted(train_img_dir.glob(*.jpg)) missing [] for img in img_files: mask_path train_mask_dir / (img.stem .png) # 有的数据用.png保存标签有的用.bmp按实际扩展名改 if not mask_path.exists(): missing.append(img.name) if missing: print(f缺少mask的样本: {missing}) else: print(f检查通过共 {len(img_files)} 对样本)这段代码把mask文件假设为png因为分割标签需要无损格式jpg的压缩会破坏像素边界。如果数据集里标签是bmp或者TIF把后缀改成对应值即可。glob返回的是Path对象img.stem能拿到不带扩展名的文件名用它拼出mask路径。检查没有漏配对之后再去做尺寸和类别统计这样避免训练中途报“找不到文件”。2.2 32类别的语义含义与像素值处理手术器械语义分割数据集的mask常见有几种存储方式决定了你读取标签时怎么写代码。最省事的是单通道灰度图其中每个像素值代表一个类别ID背景通常是0器械类别从1到32。另一种是RGB彩色标签图每个RGB组合对应一个类别。还有一种PNG调色板模式也就是PIL里的P模式图像自带一个调色板映射。用OpenCV读彩色标签会得到三通道数组直接np.unique会得到一堆组合不能直接当类别数用。标签存储方式典型读法必须注意的点单通道灰度图OpenCVimread(..., 0)像素值就是类别ID类别0通常为背景RGB彩色标签OpenCVimread(...)需要先建立颜色到类别的映射表再做像素匹配PNG调色板模式PILImage.open()读出来是P模式转数组后取第一通道或按调色板处理如果拿到手的mask是灰度图处理起来最简单。但也不要直接认为train里所有mask都只有0到32这些值。常有数据在导出时把边缘抗锯齿处理了产生介于两个类别之间的像素值比如254、255。用下面代码先统计一遍import numpy as np from PIL import Image img np.array(Image.open(surgical-instrument/train/masks/892.png)) print(mask shape:, img.shape, dtype:, img.dtype) unique, counts np.unique(img, return_countsTrue) print(像素值分布:, dict(zip(unique.tolist(), counts.tolist())))如果发现类别数超过预期多数情况是抗锯齿插值造成的。解决办法是在数据加载时把像素值四舍五入或设置一个允许的类别集合超出范围的一律当作背景。手术器械类别有32个意味着背景加器械总共需要33个输出通道。不要看到“多类别”就默认class_num等于32要确认背景占一个通道很多分割网络的输入预期是类别数包含背景的。先确认再改模型的num_classes这是工程习惯。2.3 验证集不是测试集训练/验证/测试的标准切分数据摘要里已经写了训练集860张、验证集370张这是一个大约7:3的划分。但验证集和测试集是两个概念。验证集用来观察每个epoch的效果、决定是否早停测试集只跑一次用来报告最终指标。如果你准备拿这个数据跑对比实验建议从训练集里再切出约50张作为测试集而不是动用验证集。手术视频序列的图片之间通常有高度连续性相邻帧可能非常相似。如果数据集按视频帧抽的直接随机划分会让训练集和验证集之间存在信息泄露。稳妥做法是先按文件名前缀判断来源视频或病例保证同一个病例的所有帧落在同一侧。这套数据是否已经做了这个动作我不确定但拿到后需要确认一次避免指标虚高。3. 用Unet和DeepLabV3跑通前样本校验、预处理与数据加载3.1 训练前必须做的三件事文件对应、尺寸统计、类别统计torchvision的ImageFolder不可能直接处理这种双目录标签。我们需要自己写校验脚本把两张图的shape对齐。手术图像来自不同镜头尺寸很可能不统一。有的样本可能是1920×1080有的是720p。Unet的池化层要求输入尺寸能被2整除输入和mask必须完全同尺寸否则计算损失时会报错。from PIL import Image from collections import defaultdict import numpy as np sizes defaultdict(int) for img_path in sorted(train_img_dir.glob(*.jpg)): img Image.open(img_path) mask_path train_mask_dir / (img_path.stem .png) mask Image.open(mask_path) if img.size ! mask.size: print(f尺寸不一致: {img_path.name} img{img.size} mask{mask.size}) sizes[img.size] 1 print(图像尺寸分布:, dict(sizes))这个统计会告诉我们需要不需要统一resize。如果尺寸分布很散预处理阶段就要把图像和mask同步缩放。常见做法是把最小边resize到512再中心裁剪到512×512或者直接缩放到统一尺寸。注意mask不能做双线性插值因为插值会产生新的像素值把类别边界弄脏。mask缩放的正确方式是使用最近邻插值即PIL的Image.NEAREST。如果使用OpenCV的resize则要用cv2.INTER_NEAREST。这一步是语义分割新手最常踩的坑。3.2 手术器械图像的预处理归一化、裁剪与同步增强预处理本身要分两路图像走标准化mask走索引保持。先统计整组数据集的均值和标准差然后归一化。医学图像场景下我一般直接用ImageNet的均值[0.485, 0.456, 0.406]和方差[0.229, 0.224, 0.225]效果通常可以但如果数据集的亮度分布和自然图像差异很大最好单独计算。计算时把mask完全排除在外。数据增强是提高模型对器械尺度变化鲁棒性的主要手段。手术器械可能覆盖图像的大半区域也可能只占一个小角。水平翻转基本不影响语义垂直翻转在手术器械上要谨慎但也不至于破坏形态。旋转角度不宜过大超过30°会引入无意义的拍摄角度。弹性形变在医学分割里常用但参数要保守sigma设置在4到6之间否则标签形状会被破坏。增强方式对分割任务的影响建议参数水平翻转不易影响语义几乎都适用p0.5随机旋转能增加器械姿态多样性角度范围±20°随机缩放模拟镜头远近变化scale 0.8~1.2弹性形变模拟组织形变但标签会扭曲sigma5alpha20随机亮度对比度提高对金属反光的鲁棒性亮度系数±0.3用albumentations库可以同时处理image和mask这是最省事的方案。它返回的字典里包含了变换后的图像和标签且变换对image和mask使用同一套参数不需要自己维护随机种子。3.3 写一个PyTorch数据加载器图像和mask用同一随机种子下面这个Dataset实现可以直接拿去用。它假设mask是单通道PNG像素值等于类别ID图像是JPG训练时返回图像张量和mask张量mask的dtype是long。import torch from torch.utils.data import Dataset from PIL import Image import numpy as np import albumentations as A class SurgicalInstrumentDataset(Dataset): def __init__(self, img_dir, mask_dir, img_size512, augmentFalse): self.img_paths sorted(img_dir.glob(*.jpg)) self.mask_dir mask_dir self.img_size img_size self.augment augment self.transforms self._build_transforms() def _build_transforms(self): if not self.augment: return A.Compose([ A.Resize(self.img_size, self.img_size, interpolation1, mask_interpolation0) ]) return A.Compose([ A.Resize(self.img_size, self.img_size, interpolation1, mask_interpolation0), A.HorizontalFlip(p0.5), A.Rotate(limit20, border_mode0, mask_value0), A.RandomBrightnessContrast(p0.3), ]) def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img_path self.img_paths[idx] mask_path self.mask_dir / (img_path.stem .png) img np.array(Image.open(img_path).convert(RGB)) mask np.array(Image.open(mask_path)) transformed self.transforms(imageimg, maskmask) img transformed[image].astype(np.float32) / 255.0 mask transformed[mask].astype(np.int64) img torch.from_numpy(img).permute(2, 0, 1) mask torch.from_numpy(mask) return img, mask这个类有四个关键参数。img_size控制统一输出的分辨率512是速度和精度的折中点augment控制是否开启训练增强验证集必须传False。Resize里interpolation1指的是双线性插值mask_interpolation0指的是最近邻插值。这个区别已经强调过不满足会破坏标签。border_mode0表示旋转时边缘填0等效于黑色背景这对手术图像是安全的。最后返回的tensor图像通道顺序是CHW范围0到1mask是HW的长整型张量可以直接交给CrossEntropyLoss或DiceLoss。4. 可视化脚本把GT蒙版叠加到原图上找出标注错位4.1 可视化脚本核心逻辑随机抽样、颜色映射、叠加保存标注质量决定了语义分割模型的上限。数据拿到手后第一件事是把mask叠加到原图上肉眼检查。原项目里附带了一个可视化脚本随机取一张图片将原始图、GT、GT叠加图展示并保存到当前目录。下面按常见做法复刻这个逻辑。import numpy as np import cv2 import random from pathlib import Path train_img_dir Path(surgical-instrument/train/images) train_mask_dir Path(surgical-instrument/train/masks) img_path Path(random.choice(sorted(train_img_dir.glob(*.jpg)))) mask_path train_mask_dir / (img_path.stem .png) img cv2.imread(str(img_path)) mask cv2.imread(str(mask_path), cv2.IMREAD_UNCHANGED) # 如果mask是三通道RGB转成单通道索引这里按灰度图处理 if len(mask.shape) 3: mask mask[:, :, 0] color_mask np.zeros_like(img) color_mask[mask 0] (0, 0, 255) # 器械区域标红 overlay cv2.addWeighted(img, 0.5, color_mask, 0.5, 0) mask_display np.repeat(mask[:, :, None].astype(np.uint8), 3, axis2) combined np.hstack([img, mask_display, overlay]) cv2.imwrite(visual_check.png, combined)脚本随机选一张训练图像读取原始图和mask。如果是彩色标签图这里先取第一个通道作为演示正式使用时应做颜色映射。mask大于0的部分都视为器械区域统一标成红色。叠加时使用addWeighted相当于前景背景各占50%透明度这样既能看清器械位置也能看到原始纹理。最后把原图、GT、叠加图横向拼成一张图保存方便直接翻看。这段代码的局限在于无法区分32个类别只能看整体边界。要单独检查某个类别可以把mask等于该类别ID的像素单独渲染。4.2 从叠加结果中能发现哪些标注问题错位、漏标、边缘锯齿异常现象可能原因处理方式mask整体偏移半个刀身视频帧导出时image和mask帧号没对齐丢弃该样本不要硬调坐标器械中间有空洞、断成几块反光区域被判成背景回看标注标准必要时补充标注边缘像锯齿一样粗糙标注时像素精度低或用jpg保存了mask对mask做形态学闭运算但不要过度类别ID出现异常值如255带调色板PNG直接按灰度读改用PIL读取并加载调色板边缘锯齿在手术器械数据里非常常见因为器械很细若原图分辨率不高边界只占一到两个像素。做形态学闭运算时kernel不要超过3×3否则会把细小关节的位置吃掉。漏标和错位只能靠人工复核我一般先用批量拼图扫一遍再重点看mask边缘。4.3 批量可视化十分钟扫完1200张单张抽检只能看个例批量检查才能发现问题分布。我会把4.1的叠加逻辑包成一个函数传入图像路径和mask路径输出一张拼图文件。然后随机抽30到50张训练样本循环调用这个函数。抽样时固定随机种子保证每次报告能复现。拼图集中在check目录后按文件修改时间快速翻看重点看mask边缘是否贴合器械轮廓。若某一类器械漏标率明显偏高别急着调模型先回头改标注。这种检查在训练前做一遍能省掉后面很多无效epoch。5. 医学图像分割训练中的边界情况与验证技巧类别不平衡、损失函数与mIoU5.1 类别不平衡下的损失函数选型手术器械数据一个显著特点是背景像素占比可能超过90%。32个类别中类似“剪刀”“持针器”这种出现频率分布极不均匀。直接用CrossEntropyLoss模型会快速收敛到把所有像素都预测为背景mIoU看起来不高但acc很难掉。常见的解决方案是把DiceLoss和CrossEntropy叠加使用loss ce_loss dice_loss。DiceLoss对前景区域的像素数量不敏感能把注意力拉回器械本身但单独用DiceLoss容易在训练初期梯度振荡所以我会让CE占主导dice权重可以从0.5开始调。Focal Loss也适合这种极端不平衡但它需要调gamma和alpha两个超参调参成本高。损失函数不平衡敏感性工程注意点CrossEntropy高背景主导适合作为基础项DiceLoss低直接优化集合重叠梯度在极值处不稳定常与CE组合Focal Loss低聚焦难样本gamma2alpha按类频率设置5.2 用mIoU做模型保存与早停的依据验证时不要只看Accuracy背景占比高会让acc虚高到95%以上但器械全都漏掉。要看mIoU按类别分别计算IoU后取平均。如果类别32特别少单个类IoU为0会拖低整体mIoU这是合理现象不必惊讶。保存最优模型时按验证集mIoU排序而不是按loss排序因为验证loss和mIoU的曲线有时不一致。早停的patience我一般设10到20模型保存点只保留前三个快照磁盘不够时优先删早期epoch的权重。这里还有一个容易被忽视的点验证集必须关闭增强包括Resize之外的一切随机变换否则指标不可复现。最后如果同一张手术图被多次抽取确认数据划分里没有同名图像交叉这是分割实验最大的隐藏bug。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门