简介:新冠肺炎检测数据集面向医学影像分析、计算机视觉研究及目标检测入门人群,提供1765张真实X胸透光片及对应COCO格式标注,可用于训练深度学习模型,精准区分新冠肺炎、正常与肺炎三种状态。资源包共1770个文件,其中1765张jpg原始图像、3个json标注文件、2个txt辅助文件,压缩后总大小61.41MB,命名规律清晰,便于直接开展模型训练和验证。目前已有906人学习,是医疗影像方向难得的带标注公开数据,适合科研选题、毕业设计、课程项目或实际业务场景中的模型测试。依托COCO标准格式,可无缝接入Faster R-CNN、YOLO、SSD等主流检测框架,无需额外格式转换;json文件中包含精确的目标类别与边界框坐标,可直接用于读取图片中的目标位置信息,大幅降低数据准备门槛。无论是算法初学者用于熟悉COCO数据流程,还是资深工程师进行模型微调与性能评估,这份资源都能提供扎实的数据基础与灵活的自定义空间。
1. 手里这份ZIP到底值不值得解压:1765张X光片和三分类数据的真实含金量
做医疗影像目标检测的同行应该都有同感:模型结构反而是最简单的一环,真正卡脖子的永远是数据。拿新冠肺炎X光胸透检测来说,公开数据集不少,但大多只给图片不给框;给框的又往往是Pascal VOC格式,还得自己折腾一遍转换。这份标题里写的是COCO格式、1765张原始图片、三类标注结果(新冠肺炎、正常、肺炎),正好命中项目冷启动最缺的那一环——拿到就能直接用,不用先花两周做标注。适合谁?三类人:刚入行目标检测、想用医疗影像练手的学生;需要快速出基线模型验证可行性的算法工程师;以及正在做医学影像辅助诊断项目、被标注成本压得喘不过气的团队。但ZIP不等于金子,1765张对深度学习来说不算多,值不值得解压,还得看里面的json结构是否标准、类别分布是否合理、图片尺寸是否一致。下面从格式拆解开始讲。
2. COCO格式拆到字段级:这份ZIP里的标注JSON是什么结构,以及为什么要认准COCO
2.1 COCO这种格式为什么在目标检测里成了公共语言
COCO数据集格式原本是微软发布的一个大型视觉数据集配套的标注规范,后来因为结构清晰、扩展性强,成了目标检测领域的事实标准。YOLO、Detectron2、MMDetection这些主流框架都原生支持COCO格式的标注。它的核心是一份JSON文件,里面同时承载图片信息、类别信息和标注框信息,三个信息块通过id互相关联。
常见的标注格式主要那几种:VOC用XML文件存框,每张图一个XML;YOLO用txt文本存框,每张图一个txt;COCO用一个大JSON汇总全部图片。它们的区别不只在于格式,而在于使用成本。VOC和YOLO适合小项目,改起来直观;COCO适合数据量大、类别多、需要配套评估指标的场景,因为COCO API内置了mAP、AR等标准评估算法。对1765张图这种规模,COCO格式初期会显得有点重,但后续如果想接MMDetection做对比实验,或者用Detectron2跑基线,COCO格式能省掉一次数据迁移。
我在实际项目中选COCO通常还有一个原因:标注工具的兼容性。现在主流的CVAT、X-AnyLabeling、Labelme都支持导出COCO格式,团队里谁标注、用什么工具都可以不统一,交付物统一成COCO就行。这比强制所有人用一个标注工具省心得多。如果你后续要训练YOLOv8,COCO转YOLO格式只是十几行脚本的事,反向转换也不难。
2.2 把JSON结构拆开看:80个类别的检测JSON在这里缩成了三类
COCO格式的JSON顶层固定是五个字段:info、licenses、images、annotations、categories。前两个是元信息,实际算法训练只用后三个。images数组里每项记录一张图片的id、file_name、width、height;annotations数组是核心,每项记录一个标注框的id、image_id、category_id、bbox、area、iscrowd;categories数组记录类别ID和类别名的映射。
普通检测数据集里categories动辄几十上百个,比如COCO原版就是80个。这份数据集的categories应该只有三个:新冠肺炎、正常、肺炎。类别少不意味着简单——三类之间在X光影像上的特征差异是细微的纹理和分布差异,而不是显著的外形差异,这对标注质量和模型特征提取能力的要求反而更高。
bbox字段是COCO格式最容易理解错的地方。这里要特别留意,它的格式是[x, y, width, height],即左上角x坐标、左上角y坐标、框宽、框高,全部是像素绝对坐标。如果你以前用YOLO格式习惯了,很容易把第三第四个数字理解成右下角坐标,导致转格式时框全偏。我自己就在这里翻过车,后面避坑章节会专门讲。
2.3 先干一遍JSON装载:不依赖标注工具的解析示例
拿到ZIP先不急着解压训练,第一步应该先写几行代码把JSON读进来,搞清楚里面到底是什么。尤其要注意:如果训练集的JSON里包含了验证集或测试集的图片信息,或者categories里混入了多余类别,都会让你后面的训练结果产生偏差。
import json from collections import Counter # 按实际路径调整 with open("annotations/instances_train.json", "r", encoding="utf-8") as f: coco = json.load(f) # 图片数量统计 print("图片数:", len(coco["images"])) print("标注框数:", len(coco["annotations"])) # 类别映射检查 for cat in coco["categories"]: print(cat["id"], cat["name"]) # 每个类别的数量分布 cat_id2name = {cat["id"]: cat["name"] for cat in coco["categories"]} cat_counter = Counter(ann["category_id"] for ann in coco["annotations"]) for cid, cnt in cat_counter.most_common(): print(cat_id2name[cid], "标注框数量:", cnt)这段代码做三件事:确认图片和标注框总量是否与标题描述的1765张对得上;确认类别ID和名称的映射关系;统计每个类别的标注框数量,提前发现类别不均衡。如果某个类别的框数量明显偏少,后续训练时要针对性地补强或调loss权重。不要跳过这一步,很多所谓的数据集问题,恰恰是在第一步就暴露的。
3. 把数据集喂进模型的完整路数:解析、统计、转换到YOLO格式并做数据划分
3.1 在本地用Python把ZIP解开并按COCO约定校验
拿到这个ZIP,第一步不是解压后马上开训练,而是先检查两件事:目录结构是否符合预期,JSON是否合法。我一般会写一个两段式脚本,第一步做完整性校验,第二步输出数据体检报告。
import zipfile import json import os zpath = "新冠肺炎检测数据集.zip" with zipfile.ZipFile(zpath, "r") as z: names = z.namelist() print("ZIP内文件总数:", len(names)) # 只看顶层目录 top_dirs = set(n.split("/")[0] for n in names) print("顶层内容:", top_dirs) # 找到json文件 json_files = [n for n in names if n.endswith(".json")] print("JSON文件:", json_files) # 解压全部 z.extractall("covid_dataset")解压完成后,再看目录结构。常见的数据集打包有几种习惯:图片和JSON平铺在根目录、图片放在images目录而JSON放在annotations目录、按train/val分目录。这份数据集的原始结构我没法替你看,但解压后第一件事就是搞清楚图片路径和JSON里的记录是否对得上。
很多数据集的坑藏在路径里:JSON的file_name字段写的是相对路径,可能带子目录前缀,也可能不带;如果代码里直接拼接路径,就容易找不到图。稳妥的做法是在加载数据时,用os.path.exists逐张校验,把不存在的文件单独列出来排查。
import json import os with open("covid_dataset/annotations/instances_train.json", "r") as f: coco = json.load(f) img_root = "covid_dataset/images" missing = [] for img in coco["images"]: fp = os.path.join(img_root, img["file_name"]) if not os.path.exists(fp): missing.append(img["file_name"]) print("缺失图片数量:", len(missing)) if missing[:5]: print("前5个缺失文件:", missing[:5])这里有个实际经验:很多数据集发布者打包时会把图片重新压一遍,导致ZIP里的图片文件名和JSON里记录的file_name不一致,最常见的是大小写变化或扩展名不一致(.jpg写成.jpeg)。遇到这种情况,不要急着改JSON,先看ZIP里的真实文件名,统一以实际文件为准。
3.2 对1765张图片做类别分布和尺寸统计,看数据配比够不够训练
类别分布不均在医疗数据集中几乎必然存在。正常的X光片在公开渠道容易获得,反而新冠肺炎的阳性样本少且敏感,天然不均衡。标注框数量的分布只是一个维度,还需要看第二维度:有多少张图片是完全没有标注的(负样本),以及图片本身的尺寸分布。
import json from collections import Counter with open("covid_dataset/annotations/instances_train.json", "r") as f: coco = json.load(f) # 每张图上有几个框 img_id2anns = {} for ann in coco["annotations"]: img_id2anns.setdefault(ann["image_id"], []).append(ann) no_ann_count = 0 for img in coco["images"]: if img["id"] not in img_id2anns: no_ann_count += 1 print("无标注图片数:", no_ann_count) # 图片尺寸分布 sizes = Counter((img["width"], img["height"]) for img in coco["images"]) print("尺寸种类:", len(sizes)) for size, cnt in sizes.most_common(5): print(size, cnt, "张")这一步先看无标注图片的数量。如果无标注的图片占了很大比例,且这些图片放在训练集里,模型会学到“这张图没目标”的先验,推理时容易漏检。如果这些图片放在测试集里,评估指标会虚高——因为把一切预测为空就能拿高分。更糟糕的是,如果ZIP里把无标注图片直接混进标注集,有些框架训练时会把无标注图片当作背景样本参与训练,导致漏检率上升。检查出来后,建议把无标注图片单独拎出来,只作为背景负样本使用。
尺寸统计同样重要。X光片的原始分辨率差异很大,有的设备输出1024×1024,有的输出3000×3000。如果尺寸差异过大,训练时要统一缩放到某个固定尺寸,这时候小尺寸图片会被放大导致模糊,大尺寸图片被压缩丢失细节。我一般会看一眼众数尺寸,然后以降采样为主、升采样为辅确定训练尺寸。
3.3 把COCO转成YOLO文本格式:脚本和两个必调参数
虽然这份数据集是COCO格式,但很多人最终会落到YOLOv8上训练。这里把转换脚本完整写出来,并标注两个最关键的参数:class_id映射和框坐标归一化。
import json import os # 类别映射,必须与yaml文件中的类别顺序严格一致 category_mapping = { 1: 0, # 假设原JSON里1是新冠肺炎,映射到yaml的第0类 2: 1, # 正常 3: 2, # 肺炎 } def convert_coco_to_yolo(coco_json, img_root, out_root): with open(coco_json, "r") as f: coco = json.load(f) os.makedirs(out_root, exist_ok=True) # 建立 image_id -> 图片信息的索引 img_id2info = {img["id"]: img for img in coco["images"]} # 按图片聚合标注 img_id2anns = {} for ann in coco["annotations"]: if ann["category_id"] not in category_mapping: # 遇到未知类别直接跳过或报错,别静默忽略 continue img_id2anns.setdefault(ann["image_id"], []).append(ann) for img_id, anns in img_id2anns.items(): img = img_id2info[img_id] w, h = img["width"], img["height"] txt_name = os.path.splitext(img["file_name"])[0] + ".txt" out_path = os.path.join(out_root, txt_name) lines = [] for ann in anns: x, y, bw, bh = ann["bbox"] # 归一化:中心点坐标 + 宽高,全部除以图片宽高 cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw = bw / w nh = bh / h new_cid = category_mapping[ann["category_id"]] lines.append(f"{new_cid} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") with open(out_path, "w") as f: f.write("\n".join(lines)) convert_coco_to_yolo( "covid_dataset/annotations/instances_train.json", "covid_dataset/images", "covid_dataset/labels" )两个必调参数在这里:第一,category_mapping的字典顺序必须和后续的YAML文件里names列表的顺序严格一致。YOLO格式的txt每一行的第一个数字就是类别索引,索引错了等于全部标错。第二,归一化时中心点坐标的计算公式是(x + bw/2)/w,不是x/w。COCO的bbox记录的是左上角坐标和宽高,很多人在这一步直接除以图片宽高,结果就是框整体向右下偏移半个框的长度。这两个参数错了,训练出来的模型基本全是错框。
转换完成后,必须抽几张图做可视化检查。这一步不能省,格式再标准的脚本也可能因为原始数据里的异常bbox而出错。
import cv2 def draw_yolo_boxes(img_path, txt_path, out_path): img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path, "r") as f: for line in f.readlines(): parts = line.strip().split() if len(parts) != 5: continue cid, cx, cy, bw, bh = map(float, parts) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, str(int(cid)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(out_path, img) # 随机抽3张图看一眼 import random imgs = [f for f in os.listdir("covid_dataset/images") if f.endswith(".jpg")] for f in random.sample(imgs, 3): txt = os.path.join("covid_dataset/labels", os.path.splitext(f)[0] + ".txt") draw_yolo_boxes(os.path.join("covid_dataset/images", f), txt, f"check_{f}")可视化的作用在于,能直观看到框是否贴合肺野区域、是否出现超大框或零宽高的异常标注。我遇到过一次转换后框全偏到右上角,查了半天才发现是公式里少除以了图片宽度,可视化让人一眼就能定位问题。
3.4 划分训练验证集并跑通YOLOv8的第一次训练命令
数据量只有1765张,划分时不用太复杂,按8:1:1或者8:2划分训练集和验证集就可以。关键是划分时必须按图片分,不要按标注框分,否则同一张图的不同框会同时出现在训练集和验证集里,导致验证指标虚高到没有参考价值。
import os import random import shutil image_files = [f for f in os.listdir("covid_dataset/images") if f.endswith((".jpg", ".png"))] random.seed(42) random.shuffle(image_files) val_ratio = 0.2 val_count = int(len(image_files) * val_ratio) val_files = image_files[:val_count] train_files = image_files[val_count:] for split, files in [("train", train_files), ("val", val_files)]: os.makedirs(f"yolo_dataset/images/{split}", exist_ok=True) os.makedirs(f"yolo_dataset/labels/{split}", exist_ok=True) for f in files: shutil.copy(os.path.join("covid_dataset/images", f), os.path.join("yolo_dataset/images", split, f)) label_name = os.path.splitext(f)[0] + ".txt" label_path = os.path.join("covid_dataset/labels", label_name) if os.path.exists(label_path): shutil.copy(label_path, os.path.join("yolo_dataset/labels", split, label_name))然后写data.yaml:
train: yolo_dataset/images/train val: yolo_dataset/images/val nc: 3 names: ['COVID-19', 'Normal', 'Pneumonia']注意这里names的顺序,必须和转换脚本里的category_mapping的值一一对应。顺序错了模型训练出来,推理框的类别标签全部错位,而且是那种不容易察觉的错位。
第一次训练建议用最小的模型规格,先把流程跑通:
yolo detect train \ model=yolov8n.pt \ data=covid_dataset.yaml \ epochs=50 \ imgsz=640 \ batch=16 \ device=0第一次跑的目标不是追求精度,而是确认数据管道没有坑。如果loss能正常下降、val指标能正常变化,说明数据没有大问题。如果训练中途就崩,通常不是模型问题,而是数据里有脏数据——比如某张图是损坏的、标注框跑出图片边界、或者类别索引越界。
4. 数据标注与格式转换的五个常见坑:现象、原因和我的排查固定动作
4.1 类别名不统一,模型把同一类当成两类出现漏检
现象:训练完成后,验证集里“肺炎”类别的召回率明显偏低,但精确率尚可;打开预测结果,发现部分肺炎样本被模型预测成了正常或新冠肺炎。
原因:这类问题的最常见来源不是模型能力,而是标注阶段类别名的写法不统一。比如有人标“Pneumonia”,有人标“pneumonia”,还有人标“肺炎”。在COCO格式下,如果标注工具合并数据时按名称创建了不同category_id,那同一个类别实际被拆成了两个甚至三个类别,one-hot标签在语义上就错位了。对1765张这种小规模数据集,拆类的影响尤其致命,因为每个子类的样本量更少,模型很难学进去。
解决:在第2章的JSON解析阶段就打印categories字段,人工核对是否有重复语义的类别名。如果发现同时存在“COVID-19”和“covid19”,写个批量替换脚本把category_id合并成同一个。合并后必须重新统计标注框数量,确认总数只减不增。
4.2 COCO坐标转YOLO后框的位置偏移或消失
现象:转换到YOLO格式后,可视化检查时发现框整体向右下方偏移;或者验证集里出现部分图片“空标签”,但原JSON里明明有标注框。
原因:框偏移绝大多数是坐标公式写错——没有正确地把左上角坐标转为中心点坐标。空标签的问题则通常来自两种可能:一是目标框的宽高为0(极端标注,比如点标注被误存为框),二是框完整落在图片边界之外,归一化后的坐标计算出来是负数或超过1,YOLO训练时读取不到合法框。
解决:排查时不要逐张看,先写脚本批量检查。统计所有转换后的txt文件里是否存在cx、cy、nw、nh不落在0到1区间内的边界值。出现越界值就定位到对应的原始标注,打印出原始bbox和图片尺寸,判断是标注本身的问题还是转换公式的问题。修复后重新转换,再抽图可视化一次。
4.3 1765张看着有量,实际可用正样本很少,模型对少数类几乎不学习
现象:训练三四十个epoch后,正常类别和肺炎类别基本能框对,但新冠肺炎类别的召回率一直很低,甚至所有预测框都落在正常类上。
原因:这个小数据集在标题里写明是三分类,但类别分布不一定均衡。X光片中“正常”样本易得,“肺炎”样本稍难,“新冠肺炎”阳性样本最珍贵且获取门槛高,因此很可能出现新冠肺炎疫情类别只有一两百个框、其他类别各有七八百个框的局面。模型天然会偏向样本多的类别。
解决:先统计各类别框数量,如果悬殊大于3倍,优先考虑三个办法。一是数据增强,对样本少的类别做几何增强(翻转、旋转、随机缩放)和对比度扰动,扩充出更多训练样本;二是修改loss权重,在YOLOv8中可以通过增大少数类别的loss权重来纠正;三是降低类别置信度阈值,让模型敢于对少数类输出预测框,再靠后续人工审核过滤。最不建议的做法是一开始就盲目加训练epoch,那只会让模型在多数类上过拟合得更严重。
4.4 只跑AP就发布模型,推理时在光线条件变化下翻车
现象:验证集上mAP不错,0.85以上,但把模型放到实际使用时,遇到对比度不足的X光片就漏检,或者把纹理偏暗的肺野背景误检成病灶区域。
原因:X光片的成像质量受设备品牌、曝光参数、病人体型影响很大。很多公开数据集的图片经过预处理,亮度对比度一致性好,模型在“干净”数据上学到的特征并不可靠。而验证集通常和训练集来自同一分布,所以验证指标漂亮不代表实际可用。
解决:模型训练完,不要只做程序员的随机切分验证,而是做一次“风格对抗测试”——收集一批来源不同的X光图片(哪怕是网络上公开的不同医院风格的图),跑一遍推理,统计漏检和误检。如果风格的差异确实影响大,就需要在训练阶段引入更激进的颜色增强:把亮度、对比度、gamma随机扰动范围调大。对医学影像,适度改变灰阶分布是合理的强增,不会改变病理语义。
5. 从训练到评估:把三分类检测模型调顺的关键参数
5.1 学习率、epoch、图片分辨率的配合逻辑
YOLOv8默认参数是为COCO这种大而全的数据集设计的,直接套到1765张的小规模医疗数据集上大概率不收敛或过拟合。我的习惯是先跑一轮小epoch(比如50轮)做探针实验,观察loss曲线和mAP曲线收敛到多少、在第几轮开始震荡。
对这批数据,我会重点关注三个参数。第一,imgsz,X光片通常细节在纹理和毛玻璃样阴影上,分辨率太小会把关键特征磨掉,我一般从640起步,如果显存允许试到1024或1280。但注意,图片本身如果就是1024×1024,模型用640训练等于降采样,用1280训练等于上采样,前者丢信息后者补不出来。先看第3.2节的尺寸统计结果再定。第二,epochs,1765张图的小数据集,正常50到100轮就能收敛,超过150轮基本进入过拟合区。第三,学习率,如果发现loss在初期就剧烈震荡,把lr0从默认的0.01降到0.001再试。
5.2 数据增强怎么开才不破坏X光片的病理特征
YOLOv8的增强参数很强,但医学图像和自然图像有个本质区别:自然图像翻转不会改变语义,但X光片的左右翻转会改变器官位置关系,有些病灶在左肺和右肺的表现不同,增强策略需要更保守。
# data_augment.yaml 片段 fliplr: 0.0 # 左上右下翻转,医学图像建议关闭或设很低 flipud: 0.0 # 上下翻转同样建议关闭 hsv_h: 0.0 # 色调变换对灰度图没有意义,关掉 hsv_s: 0.0 # 饱和度同理 hsv_v: 0.2 # 亮度扰动可以开一点,模拟曝光差异 degrees: 5.0 # 小角度旋转,模拟病人摆位偏差 scale: 0.3 # 缩放扰动,模拟不同设备视野差异 translate: 0.1 # 平移多一点,模拟肺野位置偏移 mosaic: 0.5 # Mosaic增强建议降低,小数据集下容易裁掉病灶区域我的建议是对这批数据先把fliplr和flipud关掉,hsv_h和hsv_s关掉,只保留细小的几何扰动和亮度扰动。Mosaic增强在目标检测里很有效,但对医学影像需要谨慎:四张图拼在一起,模型看到的是四分之一的肺野,特征被切割,对病灶检测不一定友好。我会先开0.5试一轮,对比mosaic 1.0的版本看mAP变化,如果没提升就关掉。
5.3 用训练曲线和验证集两个信号判断“能不能继续调”
训练中要同时盯两个信号,不能只看loss曲线。train/loss下降但val/mAP50停滞,一般是过拟合信号;val/mAP50上升但precision和recall倒挂明显(精确率远高于召回率),说明模型偏保守——倾向于少预测、预测准,漏检多。
YOLOv8训练日志里每行都有metrics/precision(B)和metrics/recall(B),注意别看平均值,结合混淆矩阵一起看。工具会在训练完成后自动生成confusion_matrix.png,三分类的混淆矩阵尤其要关注类别间的错分方向。如果大量肺炎被错分为正常,说明模型没学到肺炎的病灶纹理特征,光调超参数没用,得回到数据增强或loss权重上想办法。
对于小数据集,我会额外做一次K折交叉验证来估算模型稳定度。1765张切成5折,跑5次训练,看每折的mAP波动范围。如果波动超过5个点,说明模型对数据划分方式很敏感,此时加数据比调参更紧迫。
6. 验证与迭代的闭环:用错误分析反向修正标注质量,而不是盲目堆数据
跑完训练,别急着收工。最后一步是人工看错例,把验证集上漏检和误检的图片全部导出,按错误类型归类。我的固定做法是用脚本输出所有预测出来但和ground truth不匹配的框,然后分三堆看:框错位置、框对但类别错、完全没框出来。对一张医疗影像数据集来说,类别错的严重性远大于位置错,因为临床场景里把新冠肺炎漏掉和把它误判为普通肺炎的处理策略完全不同。
看完错例会得到一个常见结论:不是模型不行,而是标注本身有错。1765张图的人工标注很难百分百准确,常见的是病灶区域漏标、将陈旧性病灶误标为新冠肺炎、或者把胸腔引流管等医疗设备边缘误标为病灶。这些问题再厉害的模型也救不回来。解决办法不是马上改模型,而是把错误集中反馈给标注方重新修正。如果标注方配合不了,退而求其次的处理方案是:在清洗阶段把标注质量存疑的样本从训练集挪到验证集,避免模型被错误ground truth带偏。
我自己吃过的教训是:一开始总想着换更大的模型来提升精度,后来发现瓶颈在标注噪声上。把一批样本重新修正标注后,同样的模型架构直接涨了好几个点的mAP。所以如果模型到瓶颈期,先用错误分析排查数据质量,再谈换模型。
这个流程走通之后,这个数据集就不仅是一次性的训练素材,而是一套可以持续迭代的基线资产:COCO格式保持原样存档,转换脚本、统计脚本、可视化检查脚本另存备用。以后拿到新的医疗影像数据,直接套这套流程,从解压到出基线结果能控制在一天以内。希望帮到你。
本文还有配套的精品资源,点击获取