
简介这套数据集面向医学影像AI研究与目标检测学习者提供1765张X光胸透片全部采用COCO格式完成标注可支持新冠肺炎、正常、肺炎三种状态的识别模型训练适合用于医疗影像分类与检测方向的实验和实践。压缩包共1770个文件包含1765张jpg原始图像、3个json标注文件及2个txt说明文件整体大小61.41MB数据规模适中既便于快速下载也方便离线使用。目前已有906人学习/下载受到医疗AI入门者的关注。借助COCO格式的标准化标注读者可直接适配主流目标检测框架省去手动标注环节将精力集中在模型调优与算法验证上同时通过原始图片与标注信息的对应关系也能深入理解X光影像中病灶区域的特征表达。整体来看这套数据既适合作为课程设计素材也可用作论文实验的数据基础是医疗影像智能化探索的实用配套资源。1. X光片三分类数据集1765张COCO标注图能带出的医疗检测避坑路径这份名为“新冠肺炎检测数据集”的压缩包装的是1765张X胸透光片用COCO格式做了目标检测标注标注类别只有三个新冠肺炎COVID-19、正常Normal、肺炎Pneumonia。很多人第一反应是拿它做图像分类但注意标注数据里有bbox框这意味着它面向的是目标检测任务不是简单图片分类。它能解决的实际问题很明确给医疗影像检测模型提供一份可直接训练的三分类数据省掉从原始DICOM图到标注JSON之间的搬运工作。适合目标检测有一定基础、想快速拿到一份医疗场景数据练手的人也适合准备用YOLOv8、MMDetection这类框架跑通全流程的从业者。下载之后别急着解压就开训先弄懂里面的JSON到底在说什么。2. 拆解COCO标注文件从JSON字段到类别ID的映射关系2.1 COCO格式为什么能同时装下“框”和“类别”核心字段先说清打开压缩包后真正决定训练结果的不是那堆JPG/PNG图片而是annotations目录下的JSON文件。COCO格式的数据集核心是一个大JSON里面按数组存放了info、licenses、images、annotations、categories五类信息。对目标检测来说只要抓住images、annotations、categories三个数组就够了。数组关键字段在医疗X光场景里的作用imagesid, file_name, width, height记录原图真实尺寸坐标换算的基准annotationsid, image_id, category_id, bbox, area每个病灶框一条记录bbox为[x, y, w, h]categoriesid, name类别ID与名称的映射决定“1”代表什么病这三个数组通过id互相挂钩。images里的id是全局唯一键文件名可以重复但id不允许重复annotations里的image_id指向images里的idcategory_id指向categories里的id。拿到数据第一件事就是打印categories数组确认类别ID到底是从0开始还是从1开始。不同标注工具导出习惯不一样CVAT默认从1开始labelimg从0开始写错一次后面全崩。这个数据集是X光胸片图片尺寸并不统一。有的图来自公开ChestX-ray库有的来自医院导出四周留白和压缩比例都不一样。所以images数组里的width/height必须以原图实际尺寸为准。一旦标注时的底图和训练时读进来的图尺寸不一致bbox坐标就整体错位表现形式就是框偏移、mAP奇低。COCO的bbox是[x, y, width, height]单位像素坐标原点在左上角。注意它不含旋转角度。如果哪天你看到带angle的rotated coco格式那是旋转目标检测比如mmrotate训练DOTA数据集用的变体不要和标准COCO混淆。一张胸片里可能同时出现多个病灶区域也可能一个病灶都没有COCO允许一张图挂任意多个annotations也允许一个都不挂。对“正常”类别来说图里可能没有框检测模型天然接受“这张图没有目标”的情况这正是这类数据集选择COCO格式标注的核心理由。2.2 用Python把annotations读出来验证图片ID、类别ID与bbox的对应先用一段脚本把标注文件读进内存看看三个核心数组的实际内容import json from collections import Counter with open(annotations/instances_default.json, r, encodingutf-8) as f: coco json.load(f) print(categories:, coco[categories]) print(images数量:, len(coco[images])) print(annotations数量:, len(coco[annotations])) # 建立 image_id - 文件名的映射 img_map {img[id]: img[file_name] for img in coco[images]} # 建立 category_id - 类别名的映射 cat_name {cat[id]: cat[name] for cat in coco[categories]} # 统计每个类别的框数量 cat_counter Counter() for ann in coco[annotations]: cat_counter[cat_name[ann[category_id]]] 1 print(各类目标框数:, dict(cat_counter)) # 抽查前5个框 for ann in coco[annotations][:5]: print(img_map[ann[image_id]], ann[category_id], ann[bbox])这段代码干三件事确认类别ID映射、确认每类框总数、抽查前几个框是否落在合理范围。cat_name字典是后面所有画框和格式转换脚本的基石。如果这步打印出来的类别名是乱码说明JSON编码出了问题先解决编码再往下走。encodingutf-8必须写。很多标注工具导出JSON时会自动加BOM头不声明编码直接open轻则报UnicodeDecodeError重则类别名全部乱码。这个我踩过不止一次。2.3 一份可抄的抽查脚本统计每类框数、检查越界坐标标注数据里最隐蔽的问题不是标错类别而是框越界。X光片上肺野边界本身就是弧线标注人员把框画大一点就很容易超出图像边界。写个脚本把所有越界框捞出来import json with open(annotations/instances_default.json, r, encodingutf-8) as f: coco json.load(f) img_map {img[id]: img for img in coco[images]} errors [] for ann in coco[annotations]: img img_map[ann[image_id]] x, y, w, h ann[bbox] if x 0 or y 0 or x w img[width] or y h img[height]: errors.append((img[file_name], ann[bbox], img[width], img[height])) print(越界框数量:, len(errors)) for e in errors[:10]: print(e)这里用x w img[width]而不是判定。像素坐标从0开始最后一个有效像素是width-1等于width其实是刚好越界不同工具的处理习惯略有差异我一般按越界即报来处理宁严勿松。越界框会影响训练吗会。YOLO系列数据增强里随机平移和缩放都会依赖边界信息越界框在增强时可能被裁掉大半模型被迫学习一批带残次坐标的样本最终表现就是相同症状在图上位置飘忽。X光片上这类问题尤其多发因为病灶边缘模糊标注者习惯“多画一点”。3. 用YOLOv8训练自己的数据集从COCO转YOLO格式的完整落地3.1 为什么先转格式再训练YOLO与COCO的坐标系差异很多人拿到COCO格式数据集后直接往YOLOv8里塞结果要么报错要么训练时一个框都学不出来。原因是YOLO自定义数据集格式和COCO完全不同。YOLO格式下每张图对应一个同名txt文件每一行代表一个目标框格式是类别ID 归一化中心x 归一化中心y 归一化宽w 归一化高h。COCO是绝对像素坐标、左上角原点YOLO是相对坐标、中心点原点。两者的宽高定义也不同。如果不转换直接训练YOLO会把COCO的x/y当成中心点把w/h当成比例框全错。转换时有三个容易出问题的参数点类别ID要重映射。COCO的category_id可能是1、2、3而YOLO要求从0开始连续编码所以要做一次“减一”或者用enumerate重新编号。如果中间有跳号比如只有1和3没有2更要以categories数组的实际顺序为准。归一化用的是原图宽高。X光片可能是灰度图读出来单通道但width/height仍然是真实像素值不会因为通道数变化而变化所以这里不需要额外处理通道。越界框要先做边界裁剪再归一化。直接用原始bbox算中心点算出来的归一化坐标可能落在[0,1]之外训练阶段数据加载器直接报锚点越界。3.2 COCO转YOLO的脚本与三个参数说明下面这段脚本把COCO的JSON转成YOLO的txt可直接复制修改import json import os from collections import defaultdict coco_path annotations/instances_default.json out_dir labels os.makedirs(out_dir, exist_okTrue) with open(coco_path, r, encodingutf-8) as f: coco json.load(f) # 1. 类别重映射COCO的category_id - YOLO从0开始的连续ID cat_map {} for new_id, cat in enumerate(coco[categories]): cat_map[cat[id]] new_id print(类别重映射:, cat_map) img_map {img[id]: img for img in coco[images]} # 按image_id把框分组 anns_by_img defaultdict(list) for ann in coco[annotations]: anns_by_img[ann[image_id]].append(ann) for image_id, anns in anns_by_img.items(): img img_map[image_id] w, h img[width], img[height] txt_name os.path.splitext(img[file_name])[0] .txt lines [] for ann in anns: x, y, bw, bh ann[bbox] # 2. 边界裁剪防止越界框污染训练 x max(0, min(x, w - 1)) y max(0, min(y, h - 1)) bw max(0, min(bw, w - x)) bh max(0, min(bh, h - y)) # 3. 剔除宽或高不足1像素的坏框 if bw 1 or bh 1: continue cx (x bw / 2) / w cy (y bh / 2) / h nw bw / w nh bh / h lines.append(f{cat_map[ann[category_id]]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines)) print(转换完成txt数量:, len(anns_by_img))逻辑说明cat_map用enumerate重新编号保证类别从0开始且连续边界裁剪放在归一化之前保证宽高不为负宽高不足1像素的框直接剔除这是X光片上经常出现的“点状误标”留着会让训练loss波动。归一化统一用6位小数精度足够覆盖常见imgsz640或1024太多位只会让txt体积变大。输出目录命名为labels是YOLO的习惯后面配合images目录数据加载器会自动按名字配对。如果转换后txt数量小于图片数量说明有图没有标注框这在“正常”类别里是正常现象不是bug。3.3 数据划分与配置文件train/val路径和类别名别写错转完txt后需要把图片和txt按比例划分到train和val两个集合。很多数据集压缩包已经分好目录但通常只有一份没有验证集这时要自己划分import os import random import shutil random.seed(42) image_dir images label_dir labels train_img_dir images/train val_img_dir images/val train_lbl_dir labels/train val_lbl_dir labels/val for d in [train_img_dir, val_img_dir, train_lbl_dir, val_lbl_dir]: os.makedirs(d, exist_okTrue) imgs [f for f in os.listdir(image_dir) if f.endswith((.jpg, .png, .jpeg))] random.shuffle(imgs) split int(len(imgs) * 0.8) for i, img_name in enumerate(imgs): stem os.path.splitext(img_name)[0] src_txt os.path.join(label_dir, stem .txt) if not os.path.exists(src_txt): continue # 无标注的图不进训练集 if i split: shutil.move(os.path.join(image_dir, img_name), train_img_dir) shutil.move(src_txt, train_lbl_dir) else: shutil.move(os.path.join(image_dir, img_name), val_img_dir) shutil.move(src_txt, val_lbl_dir)随机种子固定为42保证每次划分结果一致实验可复现。80/20是目标检测常见默认值如果样本量小或类别极不均衡可以改成90/10但要保证每个类别在val里至少有几个框否则验证指标的置信区间会很大。划分完成后写data.yamltrain: images/train val: images/val nc: 3 names: [covid-19, normal, pneumonia]names的顺序必须和转换脚本里cat_map的enumerate顺序一致。这是最容易被忽略的坑如果第0类实际是covid-19但yaml里写成了normal训练不报错、loss照降可验证集指标的语义全反了模型根本不能用。启动训练yolo detect train datadata.yaml modelyolov8n.pt epochs50 imgsz640 batch16model选yolov8n.pt是轻量配置1765张图属于小数据量级用s或m很容易过拟合。imgsz用640是因为胸片长宽比接近1:1.3能保留足够纹理显存够就调成1024对细小病灶检出更友好。batch按显存调8G显存建议816G以上可以到16。4. 避坑COCO数据集最常见的五个坑4.1 现象训练时类别数对不上模型只输出一个类训练日志里显示nc1或者推理时所有框都打同一个标签。原因通常是COCO的category_id不连续或从1开始转换脚本里没有做重映射导致txt里出现了类别ID 2而yaml的names只有3个元素YOLO自动把类别ID大于等于nc的样本丢弃或归并。解决方法是转换后立刻检查txt里的类别ID集合grep -rhoE ^[0-9] labels/train/ | sort -u如果输出不是0、1、2说明重映射有问题。每跑完一批转换我都建议先执行这条命令再开训练十秒钟能省半天排错时间。4.2 现象图片能加载但bbox全跑到图外OpenCV读图的实际尺寸和JSON里记录的width/height不一致导致归一化坐标错位。常见原因是标注工具对图片做了缩放或EXIF旋转保存JSON时没同步更新尺寸。另一种情况是灰度图被读成单通道后有人用img.shape[0]和img.shape[1]时把行列搞反width和height互换。解决方法是写个小脚本统一校验import cv2 import json with open(annotations/instances_default.json, r, encodingutf-8) as f: coco json.load(f) for img in coco[images]: p img[file_name] im cv2.imread(p) if im is None: print(读不到图:, p) continue h, w im.shape[:2] if w ! img[width] or h ! img[height]: print(尺寸不一致:, p, json:, img[width], img[height], 实际:, w, h)这个脚本在每个数据集上跑一遍能过滤掉相当一部分“玄学”训练问题。发现不一致时以实际读图的尺寸为准重新生成标注文件。4.3 现象验证集mAP很低训练loss却不降训练过程看起来很顺利loss下降曲线也正常但val的mAP就是上不去。最常见的原因是数据泄露或类别不平衡同一张X光片同时出现在train和val里模型其实是在背答案一旦遇到真正没见过的图就翻车或者“肺炎”类有上千个框“新冠肺炎”类只有几十个框多数类把mAP指标托住了。检查方法是统计每张图的文件名或MD5确保train和val没有交集。类别不平衡则要给少数类加大loss权重YOLOv8里可以给每个类别单独设置cls权重或者在数据划分时对少数类做重复采样。4.4 现象标注文件打不开JSON解析报错标注文件打不开报JSONDecodeError原因通常是三个文件带BOM、路径含中文、多人标注后手工合并JSON时缺逗号或多括号。解决方法是先用最简单的命令验证JSON合法性python -m json.tool annotations/instances_default.json /dev/null echo JSON合法不合法时用Python捕获异常拿到具体行号import json try: with open(annotations/instances_default.json, r, encodingutf-8) as f: coco json.load(f) except json.JSONDecodeError as e: print(f第{e.lineno}行 第{e.colno}列: {e.msg})拿到行号后手工修或者重新用CVAT这类数据标注工具导出一次。手工合并JSON是大忌多人标注时一定要让工具自己合并。4.5 现象X光片是灰度图但读图报通道错误X光片本质是灰度图但数据集里的JPG文件有的是单通道存有的是三通道存但内容灰。YOLO数据增强里某些操作对通道数有要求单通道图在resize或归一化时可能直接崩。解决方法是统一转成三通道再训练最稳的做法是写一个批处理脚本import cv2 import os for root, _, files in os.walk(images): for f in files: if not f.endswith((.jpg, .png, .jpeg)): continue p os.path.join(root, f) img cv2.imread(p, cv2.IMREAD_GRAYSCALE) if img is None: print(读不到图:, p) continue img_bgr cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) cv2.imwrite(p, img_bgr)逻辑说明先把图强制读成灰度再转回三通道保证所有图片都是同一格式。灰度读图还能顺带检查文件是否损坏读不到的直接打印告警。转换后原文件被覆盖操作前记得备份。这个步骤看起来笨但能避免训练到一半被单通道图打断。5. 验证模型是否真的学会了“新冠肺炎”指标解读与可视化5.1 自动生成的metrics文件里先看混淆矩阵YOLOv8训练完会在runs/detect/train目录下生成confusion_matrix.png和results.csv。对医疗三分类场景先看混淆矩阵里“新冠肺炎”和“肺炎”这两类的相互误判情况。这两种病在X光片上的影像特征有重叠都是磨玻璃样阴影只是分布位置和密度有差异。如果混淆矩阵里covid-19被大量判成pneumonia说明模型靠纹理特征区分但没学到位置信息这时要考虑增加feature map分辨率或换用更关注局部细节的模型结构。5.2 医疗场景下mAP50不是唯一指标类别不平衡时mAP会被多数类拉高看起来0.85很好看但covid-19这一类的召回率可能只有0.6。医疗场景里假阴性的代价远高于假阳性漏掉一个新冠肺炎病灶比多标一个正常框严重得多。所以重点看每个类别的Recall和F1而不是总mAP。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadata.yaml) print(各类精确率 P:, metrics.box.p) print(各类召回率 R:, metrics.box.r) print(各类AP50:, metrics.box.ap50)YOLO的val对象直接挂着每类指标不用自己去解析预测结果。关注covid-19这一类的召回率如果低于0.8说明有病灶被漏检模型只能做预筛不能直接做辅助诊断。ap50是IoU阈值0.5时的平均精度医疗小目标建议也看ap75因为病灶边缘不齐IoU达到0.5的框可能偏差出半个肺野。5.3 叠加可视化把预测框和标注框画在同一张X光片上指标说到底是数字视觉确认才最直观。用训练好的模型对验证集做推理把预测框画到图上同时叠加标注框人工肉眼比对from ultralytics import YOLO import cv2 model YOLO(runs/detect/train/weights/best.pt) img cv2.imread(images/val/example.jpg) results model.predict(img, conf0.25, iou0.5, saveTrue) for r in results: for box in r.boxes: cls int(box.cls[0]) conf float(box.conf[0]) print(r.names[cls], conf, box.xyxy[0].tolist())conf阈值0.25是YOLO默认值但医疗场景建议调到0.3以上。理由很简单宁可少报也不要在图上画一堆低置信度框干扰医生判断。iou设0.5用来抑制重复框如果发现同一病灶被画出多个重叠框适当调高到0.6。这个可视化步骤应该成为每次训练完的固定动作。我和团队的习惯是随机抽30张验证集图把预测结果拼成一张大图过一遍再回去看对应的JSON标注。很多时候mAP看起来正常但图上一看就发现模型把肋骨边缘当成病灶了这种问题靠指标根本发现不了。6. 标注质量审计用CVAT和labelimg补一轮真值模型训到能用只是第一步想把这个数据集用出价值还要回头审计标注质量。做法是把训练好的模型当预标注器对全部1765张图跑一遍推理然后用CVAT或labelimg打开重点检查两类样本模型预测框和标注框IoU低但有重叠的大概率是原始标注框画偏了模型置信度很高但标注里没有对应框的大概率是原始标注漏标了。常见做法是写脚本把这两类候选样本导出成一个子集人工集中复核。我在这个数据集上吃过亏第一次训练没做标注审计模型在自家验证集上mAP有0.87换到另一批X光片上直接掉到0.6。后来把标注框逐个拉出来看才发现相当一部分“正常”类的图根本没标全肺野边缘的小阴影都被漏了模型默认所有右下肺野都是正常的。从那以后我拿到任何数据集第一件事都不是启动训练而是先抽50张图人工过一遍标注确认bbox和类别都对得上再动手。这一步花两小时能省后面两天的排错时间。如果你也用这份数据做研究或交付建议把标注审计结果写进数据说明标明哪些框做过修正。医疗影像数据集的标注质量直接决定模型可信度真值不干净后面所有指标都是空中楼阁。希望这篇笔记能帮你把这条链路走得顺一点。本文还有配套的精品资源点击获取