简介:这是一份面向目标检测与实例分割任务的医疗物品数据集,包含1990张训练图片及对应YOLO格式标注,覆盖磁共振成像设备、牙科物品、医生制服、注射器四个类别。数据集由领域专家完成实例分割标注,边界精确,适配主流深度学习框架,可直接用于YOLO系列等模型的训练与评估,适用于医疗AI系统开发、医疗机器人感知、医院设备管理等场景。资源包共2000个文件,核心为1990个txt标注文件,另含8张jpg示例图片、1个yaml配置文件及1个docx说明文档,整体大小约150.19MB,结构清晰便于快速上手。目前已有212人学习使用。借助该数据集,研究者可省去大量数据采集与清洗时间,直接开展多类别医疗物品检测与分割实验,也可作为医疗环境智能监控、人员着装规范识别等项目的训练基准。
1. 医疗物品实例分割数据集:解压前先想清楚,你拿到的是原料还是半成品
“医疗物品实例分割数据集_20251117_182346.zip”这个文件名里其实藏着不少信息:2025年11月17日、18:23:46,大概率是某个标注工具导出的数据快照。实例分割要做的,是把手术钳、止血棉、注射器、缝合针这些目标从手术台、器械盘的照片里检测出来,同时给每个目标画出一整块像素级轮廓,比单纯的目标检测多一层掩膜标注。拿到这种zip的人,多半在做什么清点、复核、手术室行为识别之类的工程。下面讲的是一条能直接照着走的路:从解压、校验、转格式,一路走到能训练、能评估、能判断能不能用。适合正在给医疗物品做检测分割训练、却在数据上反复翻车的从业者。
2. 解压与体检:医疗物品实例分割数据集的目录结构、标注格式和统计脚本
拿到后缀是.zip的数据集,第一反应是解压,但医疗物品分割数据集的坑往往就埋在这第一步。很多标注平台导出的压缩包在Windows下打包,内部文件名是GBK编码,直接扔到Linux服务器上unzip就会出现乱码。我一般先建一个干净目录,再指定编码解压:
mkdir -p medical_seg unzip -O gbk 医疗物品实例分割数据集_20251117_182346.zip -d medical_seg cd medical_seg du -sh .这里的-O gbk告诉unzip用GBK解码文件名,能救回八成Windows系zip的中文乱码问题。如果你的unzip不认-O参数,macOS自带版本就不支持,那就用Python的zipfile在读取时把cp437转回gbk,后面避坑章节会给出具体写法。解压完先别急着打开训练脚本,用find . -maxdepth 2 -type d看目录层级,再配合ls -lh确认图片大小区间。医疗物品数据集里常出现两种极端:要么图片全是2MB以上的原图,要么是几十KB的压缩图。前者训练时IO吃紧,后者标注本身看不清器械边缘,两种都要提前知道。
2.1 解压后的标准目录:images、masks、labels各司其职
一个能直接用于训练的医疗物品实例分割数据集,内部目录通常长这样:
| 路径 | 内容 | 说明 |
|---|---|---|
| images/ | 原始图片,jpg或png | 手术台、器械盘、托盘实拍 |
| masks/ | 像素级掩膜,单通道png | 背景0,实例为1、2、3这类编号 |
| labels/ | YOLO分割txt | 每行:类别id + 归一化多边形点 |
| annotations/ | COCO格式json | segmentation存polygon或RLE |
| data.yaml | 类别名与路径配置 | 训练时喂给YOLO |
是不是五类都齐,决定了这份数据是“半成品”还是“成品”。很多标注平台只导出images和annotations/instances.json,masks目录要自己生成;也有平台反着来,只给掩膜不给COCO json,类别信息全部写在文件名里。我拿到手第一件事是把每个目录的文件数量拉出来对账:ls images | wc -l和ls masks | wc -l必须一致,不一致就说明标注过程有漏图或漏掩膜,后面训练必翻车。
掩膜本身也要抽查。单通道png里,0是背景,1、2、3是实例编号,训练框架会按索引去查类别。如果标注平台导出的是三通道彩色掩膜,必须做一次映射:把每个RGB值对应到“类别id + 实例id”,再转成单通道编号图。这一步在医疗物品场景很常见,因为不少标注工具有可视化需求,保存成彩色图更容易查看。别嫌麻烦,转换时一帧一帧校验叠加图,轮廓对不准就退回重标。
注意:还有一类隐蔽问题,手机或相机拍的图片带EXIF方向,直接用OpenCV读会横过来,而标注平台按显示方向标注,mask与图片差90度。第一次训练前,把所有图片统一转正再配mask,避免数据管线里出现“人眼看不出来、模型全学错”的错位。
2.2 三种标注格式的取舍:COCO JSON、YOLO分割txt、PNG掩膜
医疗物品实例分割的数据格式,就我见过的标注平台,无非三种:COCO JSON、YOLO分割txt、PNG掩膜。三者的关系是同一批标注内容的不同表达方式,但选型会直接决定后面训练和审核的工作量。
COCO JSON的核心是annotations数组,每个元素有segmentation、area、bbox、category_id,其中segmentation可以是多边形坐标,也可能是RLE压缩串。它的优势是生态最全,MMDetection、Detectron2、Mask R-CNN全是原生读取;劣势是JSON体积大,一个过万张图的数据集instances.json可能上GB,解析速度慢,而且人没法直接看图确认标注。
YOLO分割txt的每一行是“类别id + 一串归一化坐标点”,文件体积极小,YOLOv8-seg开箱即用;劣势是坐标点一旦归一化就很难还原出“原始标注到底是什么形状”,对医疗场景的审核诉求不友好。
PNG掩膜最直观,但同一个png里多个实例怎么索引、值到底是类别id还是实例id,不同平台规则不同。
我的建议很直接:如果医院方或标注平台最终交付的是COCO JSON,就把COCO当作“底账”永久留档,审核、复现、换框架都靠它;训练前再单独转换一份YOLO txt。如果团队是YOLO全家桶,确实可以直接用txt,但至少写一个“txt还原成mask”的脚本,每训练一轮就随机抽20张检查轮廓有没有错位。
2.3 首轮统计要算清的三本账:类别数、实例数、mask面积占比
数据体检比调模型参数重要得多,这是医疗物品这类小样本项目里最容易亏时间的地方。拿到COCO JSON后,我会先跑一段统计脚本:
import json from collections import Counter from PIL import Image import numpy as np, glob coco = json.load(open('annotations/instances.json', encoding='utf-8')) cats = {c['id']: c['name'] for c in coco['categories']} cnt = Counter(a['category_id'] for a in coco['annotations']) print('类别总数:', len(cnt)) for cid, n in cnt.most_common(): print(cats[cid], n) for fp in sorted(glob.glob('masks/*.png'))[:5]: mask = np.array(Image.open(fp)) print(fp.split('/')[-1], 'mask占图比例:', round((mask > 0).mean(), 4))这段脚本解决三件事。第一,类别数;如果医疗物品类别超过15类,但每个类别的实例数参差不齐,先合并相近类别再启动训练,比硬上模型快。第二,实例数;每一类至少要有50个实例,否则即使训练出轮廓,换个拍摄角度就找不回来。第三,mask面积占比;器械大多只占画面几个百分点,细的缝合针甚至只有0.1%,这类小目标要提前决定好imgsz和增强策略。我还习惯顺手统计图片分辨率分布,如果同一份数据集里混着720p和4K图,训练时要么统一resize,要么按分辨率分桶训练,不然边框和轮廓的尺度会互相干扰。这个统计结果我一般存成一个report.txt留在数据集目录里,下次任何人接手,先看它再决定动不动训练参数。
3. 转成YOLOv8能训练的数据集:COCO转YOLO分割txt与场景聚簇划分
数据体检完,接下来把标注转成训练框架能吃的格式。如果你打算用YOLOv8-seg做基线,这一步要把COCO JSON里的polygon转成归一化的txt。很多开源脚本只处理简单情况,遇到RLE或多段多边形直接报错,所以最好自己写一个能兜底的转换脚本。
3.1 用python把COCO JSON转成YOLO分割txt
import json, os, numpy as np import cv2 from pycocotools import mask as coco_mask CATS = {1: 0, 2: 1, 3: 2} # 把COCO类别id映射成连续训练id def poly_to_yolo(seg, img_w, img_h): coords = [] for i in range(0, len(seg), 2): x = min(max(seg[i] / img_w, 0.0), 1.0) y = min(max(seg[i + 1] / img_h, 0.0), 1.0) coords.extend([f"{x:.6f}", f"{y:.6f}"]) return " ".join(coords) def convert(coco_path, out_dir): os.makedirs(out_dir, exist_ok=True) with open(coco_path, encoding='utf-8') as f: coco = json.load(f) img_of = {im['id']: im for im in coco['images']} out_lines = {} for ann in coco['annotations']: seg = ann['segmentation'] im = img_of[ann['image_id']] label = CATS.get(ann['category_id']) if label is None: continue if isinstance(seg, dict): m = coco_mask.decode(coco_mask.frPyObjects(seg, im['height'], im['width'])) contours, _ = cv2.findContours(m.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) seg = [c.flatten().tolist() for c in contours] fname = im['file_name'].rsplit('.', 1)[0] + '.txt' for poly in seg: if len(poly) < 6: continue out_lines.setdefault(fname, []).append(f"{label} {poly_to_yolo(poly, im['width'], im['height'])}") for fname, lines in out_lines.items(): with open(os.path.join(out_dir, fname), 'w') as f: f.write("\n".join(lines) + "\n")这段代码的逻辑分三层。第一层是坐标归一化,把像素坐标除以图片宽高,再强制clip到[0,1],避免标注点在图片边缘被画出去后超出边界,这是YOLO训练报“label out of bounds”最常见的原因。第二层是RLE处理,当COCO里的segmentation是dict而不是list时,先解码成二值掩膜,再用OpenCV提取轮廓;如果没有pycocotools和OpenCV,这步会直接抛异常,建议先执行pip install pycocotools opencv-python。第三层是类别映射,CATS把标注里的原始类别号映射成从0开始的连续id;如果原数据集已经是0到N-1,这个映射就写成恒等即可。
参数上有两个容易踩的点。归一化坐标保留6位小数是经验值,位数太多txt体积暴涨,训练时并不会更准;位数太少会导致小物体轮廓退化。另外YOLO格式约定一个实例一行,如果一个器械被标注成两个断开的多边形环,我一般转换成两行,训练时YOLO会当成两个实例处理;对清点场景这样能数出单个器械,对像素级评估影响也不大。转换完必须做一次对账:images和生成的labels两个目录文件数相等,再把每条txt的第一列去重,确认类别id都在0到N-1之间。
3.2 数据集划分:按场景聚簇而不是按文件随机抽
划分train/val/test,新手最容易直接shuffle文件名。医疗物品数据多半来自视频抽帧或连拍,同一个托盘、同一台手术会被拍下几十张高度相似的帧,随机划分等于把“同一场景的不同帧”同时塞进训练集和验证集,验证指标虚高得离谱,部署到真实环境立刻打回原形。我一般按“场景ID”分桶,再在每个桶内做随机抽样:
import os, random from collections import defaultdict random.seed(42) groups = defaultdict(list) for name in os.listdir('images'): scene = name.rsplit('_', 1)[0] # OR_07_163254.jpg -> OR_07_163254 groups[scene].append(name) train, val, test = [], [], [] for scene, names in groups.items(): random.shuffle(names) n = len(names) train += names[:int(n * 0.7)] val += names[int(n * 0.7):int(n * 0.9)] test += names[int(n * 0.9):] with open('train.txt', 'w') as f: f.writelines(f"images/{n}\n" for n in sorted(train)) with open('val.txt', 'w') as f: f.writelines(f"images/{n}\n" for n in sorted(val)) with open('test.txt', 'w') as f: f.writelines(f"images/{n}\n" for n in sorted(test))name.rsplit('_', 1)[0]这条逻辑要求文件名里场景和帧号用下划线分隔,比如OR_07_163254.jpg会归到场景OR_07_163254。如果文件名没有这种规律,就需要一个映射表来标注每张图的拍摄场景,这个表比任何超参数都值钱。固定随机种子42是为了让团队复现同一个划分,否则每次训练都在不同的val上比,没法横向比较模型。7:2:1是我常用的分配比例;如果实例总数少,val可以再降一点,但test一定要留够一个完整场景,专门用来模拟没见过的手术台布局。
做完场景聚簇划分后,我还建议跑一遍图片感知哈希去重,把重复或近似帧从整个数据集里筛一遍,步骤放在避坑章节说明。
3.3 增强参数怎么设:翻转、变形、马赛克都要克制
医疗物品实例分割的增强策略和自然场景不一样。自然场景可以大力出奇迹,但手术器械是刚性物体,类别形态差异集中在细长、反光、遮挡上,开满增强反而会让轮廓学歪。我常用的YOLOv8-seg训练参数如下:
yolo train model=yolov8n-seg.pt data=medical.yaml epochs=200 imgsz=800 \ hsv_h=0.01 hsv_s=0.4 hsv_v=0.4 degrees=5 fliplr=0.3 mosaic=0.3 copy_paste=0.1degrees=5是控制旋转角度,细长的缝合针和刀片如果旋转太大,掩膜长宽比会被畸变,模型会对方向产生错误记忆;5度已经足够覆盖拍摄时的摆放偏移。hsv三个参数保留适度值,因为金属器械在不同光照下的反光确实需要颜色泛化,但hsv_h=0.01表示色相几乎不动,医疗器械的颜色往往是类别线索,比如止血棉是白色、手术钳是金属色,不该被大幅左右。mosaic=0.3把Mosaic增强参与概率从默认值调低,因为四图拼接会把细长目标切碎,让模型学到半截轮廓;copy_paste=0.1是分割专用增强,把小器械复制粘贴到其他图上,对样本少的小目标很有效。
如果发现某个细长类别(缝合针、穿刺针)始终学不到,优先检查imgsz而不是增强。把imgsz从640提到800再试一轮,小目标的像素占比会明显改善。数据增强不是越强越好,每次只改一个参数,用val的mask mAP对比,才找得准到底是谁在拖后腿。
4. 医疗物品实例分割训练前必查的5个坑:从乱码到泄漏的排查清单
这一章我按“现象→原因→解决”写,都是真实会踩的问题。你照着顺序排查,能省下至少一星期调参时间。
4.1 验证集mask全黑、mAP恒为0
现象:训练loss在正常下降,验证集却一直输出全黑的mask,mAP始终是0。
原因有三种。第一,val图片对应的labels文件不存在;YOLO会在日志里打印“no labels found in val”,但很多人没注意。第二,类别id不是从0开始;标注平台导出的类别id可能是1、3、7这种原始编号,映射没做干净,越界的类别被当成背景丢弃。第三,多边形坐标越界;归一化前没clip,某些点在图片边缘外侧变成负值或大于1,训练时被丢弃。解决:训练前写一个校验脚本,逐条扫描labels目录中所有txt,检查类别和坐标范围:
import os N_CLASSES = 3 bad = 0 for fn in os.listdir('labels'): for line in open(os.path.join('labels', fn), encoding='utf-8'): parts = line.split() if not (0 <= int(parts[0]) < N_CLASSES): bad += 1 continue coords = list(map(float, parts[1:])) if any(v < 0 or v > 1 for v in coords): bad += 1 if bad > 0: print(fn, '有', bad, '条非法标签')N_CLASSES要和data.yaml里的类别数一致。这个脚本跑完,再检查val目录下所有图片都有同名txt,两个条件都满足后重新启动训练。
4.2 mask轮廓锯齿严重,边缘噪声大
现象:验证集mAP还能看,但放大了看,每个器械边缘都像锯齿,一些器械边缘还拖出几根细长伪影,完全不像人工标注的平滑轮廓。
原因:一是标注阶段多边形点太稀疏,工具自动插值后本来就有锯齿;二是网络输出mask的分辨率只有输入图的四分之一左右,边缘天然被钝化;三是没有加边界相关的损失,模型对边缘不敏感。解决分三层。训练层面,打开YOLO的seg_loss相关配置或用Lovasz loss,这类loss直接优化IoU边界,对锯齿改善明显;推理层面,把预测mask上采样回原图尺寸后再和GT比较,不要在半分辨率下算指标;后处理层面,对二值mask做一次开运算去掉孤立噪点:
from scipy import ndimage import numpy as np pred = ndimage.binary_opening(pred > 0.5, structure=np.ones((3, 3)))但根本解法在标注端。如果抽查发现GT本身就锯齿严重,说明标注规范里缺少“点间距不超过5像素”的约束,这部分只能返工或抽重标,后处理救不回标注原罪。
4.3 zip解压乱码或图片加载失败
现象:解压后文件名变成“锟斤拷”或乱码开头,Python的Pillow报OSError: cannot identify image file。
原因:Windows下打包zip默认用GBK编码文件名,Linux和macOS的unzip默认按UTF-8解码,两边一撞就乱码。有些标注平台的zip里还混入Windows特有的非法时间戳,解压会报bad header。解决:优先在命令行指定编码解压:
unzip -O gbk 医疗物品实例分割数据集_20251117_182346.zip -d medical_seg如果unzip不支持-O,改用Python读取,把文件名从cp437校正回gbk:
import zipfile, os with zipfile.ZipFile('医疗物品实例分割数据集_20251117_182346.zip') as z: for name in z.namelist(): fixed = name.encode('cp437').decode('gbk') z.extract(name, 'medical_seg') os.rename(os.path.join('medical_seg', name), os.path.join('medical_seg', fixed))解压后立刻抽查三张图,用Pillow读取并打印尺寸,确认图片没有截断、文件头完好再进入下一步。这一步翻车的概率不低,尤其在标注工具从Windows导出、算法工程师在Linux服务器上接手时。
4.4 细长器械类别始终学不到
现象:手术钳、纱布这种大目标mask能出来,缝合针、刀片这种细长或微型目标在val上几乎全部漏检,mAP比大物体低十几个点。
原因:细长目标经过下采样后只剩几十个像素,加上Mosaic增强把它们切碎,模型根本看不到完整轮廓;同时Loss被大面积背景和大目标主导,小目标就算预测错了对总体loss影响也很小。解决:一是在训练参数里把mosaic降到0.2,避免细长物体被拼接切碎;二是打开copy_paste增强,把小器械实例复制到更多样化的背景上,相当于免费扩充样本;三是做按实例数的类别加权,在小器械类别的loss项上加权重,让梯度不再被大类别淹没。如果项目目标是清点数量,可以先把这个细类合并进“锋利器械”粗类训练,稳定后再细分,比在一开始就逼模型分细类稳得多。
4.5 同场景泄漏导致指标虚高,部署就翻车
现象:训练日志上val的mask mAP超过90%,团队都很高兴,拿到医院真实环境一测,准确率只有76%,肉眼可见漏检。
原因:随机划分train/val时,同一个场景的连拍帧被同时分到了两边。模型在训练时见过这张托盘的布局,验证时等于“开卷考试”,指标自然虚高;真实环境里器械摆位、背景、光线全变了,立刻现形。解决参考3.2节,按场景分桶划分,并用感知哈希把近似帧去重:
from PIL import Image import imagehash, os hashes = {} for fn in os.listdir('images'): h = imagehash.phash(Image.open(os.path.join('images', fn))) if any(h - old <= 4 for old in hashes): print('疑似重复:', fn, '与已有图片相似') hashes[h] = fnphash差小于4表示图片结构几乎相同,医疗连拍里两台相机同时拍同一个托盘就是这种情况。把重复帧剔除后重新划分,再用保留的test场景做一次完全没见过的盲测,才敢谈部署。这个坑最容易出现在“标注数据来之不易”的项目里,越是舍不得删数据,越要警惕指标泡沫。
5. 用mask mAP、Dice与连通域分析验证医疗物品分割的可部署性
模型训练完,先别急着写技术文档,先用三个层面的验证把结论钉死:mask mAP看整体、Dice看掩膜贴合度、连通域分析看预测是否符合解剖常识。
评估层面,我至少会同时报告mask mAP@0.5、mask mAP@0.5:0.95、Dice均值。mAP@0.5是宽松指标,适合看全体类别的粗检情况;mAP@0.5:0.95对边缘拟合更敏感,医疗物品要求轮廓精确,不能只看前者。Dice对像素重叠敏感,计算时用预测mask与GTmask,两个都二值化再套公式:
def dice(pred, gt): inter = (pred & gt).sum() return 2 * inter / (pred.sum() + gt.sum() + 1e-6)医疗物品里,dice低于0.7的类别基本不能用于自动清点,只能做人工复核辅助。再往下走,用连通域分析看预测mask有没有“碎片化”,这对细长器械特别有效:
from scipy import ndimage lab, num = ndimage.label(pred > 0.5) pieces = lab == lab.max() area_ratio = pieces.sum() / pred.sum()一个器械的mask本应是一个连通域,如果num远大于GT的实例数,说明模型在过分割,手术钳被切成好几块;area_ratio明显小于1说明主连通域占比低,输出碎片太多,后处理或阈值需要调整。我自己的习惯是每轮训练后固定抽50张val图,同时看这三个数,任何一个明显波动就停训练改配置。去年做手术器械清点项目时,我用随机划分得出val mAP 92%,按场景分桶后掉到84%,再去医院用一段没参与训练的手术视频做盲测,只有76%。从此我拿到任何医疗物品实例分割数据集,第一件事不是写网络结构,而是先跑场景聚簇划分和去重脚本,把数据账算明白再开训练。指标是给人看的,部署效果才是给患者和医生用的,希望帮到你。
本文还有配套的精品资源,点击获取