ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

BCCD血细胞检测数据集COCO格式解析:从标注转换到目标检测实战

BCCD血细胞检测数据集COCO格式解析:从标注转换到目标检测实战 简介这是一份COCO格式的BCCD血细胞检测数据集面向计算机视觉初学者及目标检测模型训练者适合用于血细胞检测、医学影像分析等场景。数据集包含白细胞WBC、红细胞RBC和血小板Platelets三类目标共364张原始图像并已划分为训练集205张、验证集87张、测试集72张便于直接划分数据集进行训练和评估。压缩包共366个文件其中364张JPG图片与2个JSON标注文件总体积约7.29MB。JSON文件采用COCO标准格式内含类别、边界框等关键信息可直接接入主流目标检测框架免去从VOC格式转换的繁琐过程。目前已有551人浏览学习。通过压缩包可快速上手数据读取、标注解析和模型训练流程并可在训练后对比不同检测算法的性能为进一步优化血细胞检测模型提供基础数据支撑。1. BCCD血细胞检测数据集coco格式364 张标注图能解决什么问题做血细胞检测的落地项目最难的不是模型选型而是标注数据从哪来。显微镜下红细胞、白细胞、血小板的形态差异大自己标一组图要花掉几天时间而且标注一致性很难保证。BCCD血细胞检测数据集coco格式正好卡在这个需求点上364 张显微镜图像3 个类别 RBC、WBC、Platelets标注已经整理成 COCO 的 JSON 结构意味着可以直接喂给 MMDetection、Detectron2、YOLO 系这些主流框架不用先跑一遍格式转换的弯路。这个数据集很适合三类人刚入门目标检测、想用一份干净数据跑通训练到评估全流程的新手要做血细胞检测基线模型、需要快速验证网络结构的算法工程师以及想对比不同检测框架在医学显微图像上表现的研究者。不过 364 张图对小数据集训练来说远不够宽裕类间数量悬殊、血小板目标又小这里面的坑不少。下面从标注格式开始一步步把它跑通。2. 读懂 COCO 标注BCCD 的 JSON 结构、类别映射与 bbox 约定2.1 COCO 格式的五个顶层字段BCCD 到底给了哪些一份合格的 COCO 标注 JSON顶层一般包含 info、licenses、images、annotations、categories 五个字段。网上能下载到的 BCCD COCO 版本基本都保留了完整结构但实际训练时真正被框架读取的只有后面三个images、annotations、categories。info 和 licenses 更多是元信息框架解析时会跳过。images 数组里每个元素对应一张图关键字段是 id、file_name、width、height。id 是全局唯一的整数annotations 里通过 image_id 引用它file_name 要和实际存放图像的路径对得上否则训练时加载图片会直接报 FileNotFoundError。width 和 height 是原始图像的宽高COCO 格式里 bbox 是绝对像素坐标这两个字段是校验 bbox 是否越界的重要依据。annotations 数组是核心每个元素表示一个标注实例字段包括 id、image_id、category_id、bbox、area、iscrowd。bbox 是 COCO 格式和 VOC 格式最大的区别VOC 存的是 xmin、ymin、xmax、ymax也就是左上角和右下角两个点COCO 存的是 [x, y, width, height]即左上角坐标加框的宽和高。转换脚本里最常见的 bug 就是把 xyxy 直接当成 xywh 塞进去后面训练出来的框全部偏移。categories 数组定义类别每个元素包含 id 和 name。BCCD 数据集里常见映射是 id 从 1 开始1 对应 RBC2 对应 WBC3 对应 Platelets。但不同版本的 BCCD COCO 文件类别 id 可能从 0 开始或者顺序不同训练配置里的 num_classes 只填 3 是不够的还要确保数据集的 category_id 和配置文件里的 classes 顺序一一对应。我习惯拿到 JSON 先打印一遍 categories不要想当然。2.2 用一段代码摸清 BCCD 的家底实例数、图片数、空标注训练之前先做数据体检。用下面这段 Python 代码读取 COCO 标注搞清楚三件事每张图平均有几个实例、三个类别数量差距有多大、有没有空标注的图。这在 BCCD 上尤其重要因为红细胞数量远多于白细胞和血小板不了解这个分布后面训练出来的模型大概率只会认 RBC。import json from collections import Counter ann_path annotations/bccd_coco.json with open(ann_path, r, encodingutf-8) as f: data json.load(f) # 建立 id 到名称的映射 cat_id2name {c[id]: c[name] for c in data[categories]} img_id2name {img[id]: img[file_name] for img in data[images]} print(类别映射:, cat_id2name) print(图片总数:, len(data[images])) # 统计每个类别下的实例数量 cat_counter Counter() ann_by_img {img[id]: [] for img in data[images]} for ann in data[annotations]: cat_counter[cat_id2name[ann[category_id]]] 1 ann_by_img[ann[image_id]].append(ann) print(各类别实例数量:, dict(cat_counter)) # 统计空标注图和多实例图 empty_imgs [img_id2name[i] for i, anns in ann_by_img.items() if len(anns) 0] multi_imgs [img_id2name[i] for i, anns in ann_by_img.items() if len(anns) 50] print(空标注图片数:, len(empty_imgs), empty_imgs[:5]) print(实例数超50的图片数:, len(multi_imgs)) # 检查 bbox 是否越界 out_of_bounds 0 for ann in data[annotations]: x, y, w, h ann[bbox] img_info next(img for img in data[images] if img[id] ann[image_id]) if x 0 or y 0 or x w img_info[width] or y h img_info[height]: out_of_bounds 1 print(越界 bbox 数量:, out_of_bounds)逻辑说明先解析 categories 和 images建立两个反向映射表这样后面统计时不用频繁遍历字典。然后遍历所有 annotations用 Counter 统计每个 category_id 出现的次数同时按 image_id 分组为的是后续检查空标注图和异常密集的图。最后遍历一遍 bbox检查是否超出图像边界。参数说明这里用两个辅助字典是为了把 O(n) 的查找变成 O(1) 的映射364 张图的数据规模不大性能差异不明显但养成好习惯。越界检查的阈值用的是图像原始宽高如果数据集经过了裁剪或缩放这个检查就会误报需要先用 images 字段里的 width、height 和实际图片尺寸核对。我跑过的 BCCD 版本里越界 bbox 数量一般在 0 到十几这个范围如果超过 50 个建议先修数据再训练不然后面 loss 会出现莫名奇妙的抖动。2.3 一个 COCO 标注实例长什么样拿一个具体的 annotation 拆开看比读十遍文档都有用。下面这段是典型的 BCCD 标注实例{ id: 1, image_id: 1, category_id: 1, bbox: [212.0, 118.0, 38.0, 42.0], area: 1596.0, iscrowd: 0 }这个 bbox 的意思是从图片坐标 (212, 118) 这个点开始向右延展 38 像素、向下延展 42 像素。area 字段在 COCO 里通常直接写成框宽乘高计算方式是 38*421596用于评估时的面积分组——COCO 的 AP 指标会把目标按面积分成 small、medium、large 三档分别统计BCCD 里的血小板大多属于 small 或 medium 档。iscrowd 置 0 表示这个实例是一个独立目标不是一群无法分开的目标。如果 iscrowd 是 1评估时会跳过该目标的计算。有一点要注意COCO 官方标注里 area 是目标实际像素面积对于不规则目标应该用分割 mask 计算而 BCCD 这类只有框的版本area 直接用 bbox 面积即可评测工具对此是接受的。手动构造 COCO JSON 时如果把 area 写错或者漏写pycocotools 的评估会在计算面积分组时报 KeyError排查起来很烦。3. 把原始 BCCD 标注转成 COCO 格式转换脚本与字段校验3.1 原始 BCCD 通常是 VOC 格式先确认你拿到的是什么BCCD 数据集在 GitHub 等渠道流传最广的版本是 PASCAL VOC 风格每张图像对应一个 XML 文件XML 里是 object 节点每个 object 包含 name 和 bndboxxmin、ymin、xmax、ymax。网上有些人直接发布了转好的 COCO 版本但如果你拿到的是原始仓库代码里面标注目录通常是 Annotations 文件夹放 XMLJPEGImages 文件夹放图片。先用下面命令看一眼目录结构find . -maxdepth 2 -type f | head -30正常会看到一堆 .xml 和 .jpg 文件。如果看到的是 .json 文件那就已经是 COCO 格式直接跳到第 4 章。还有一种混合情况有人把 XML 转成了 CSV 或者 TXT这是为了兼容 YOLO 格式做的中间产物遇到这种版本直接用原版 XML 重新转最干净。不要试图从 CSV 反推 VOC 的 bndbox很容易丢失信息。3.2 VOC 转 COCO 的完整脚本带越界裁剪和空框过滤import json import os import xml.etree.ElementTree as ET # BCCD 的类别定义注意 id 从 1 开始 CATEGORIES [ {id: 1, name: RBC}, {id: 2, name: WBC}, {id: 3, name: Platelets}, ] def convert_voc_to_coco(xml_dir, img_dir, output_path): images [] annotations [] ann_id 1 for xml_name in sorted(os.listdir(xml_dir)): if not xml_name.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text size root.find(size) if size is None: # 部分数据集 XML 里没有 size 节点需要从图片实际尺寸补 from PIL import Image width, height Image.open(os.path.join(img_dir, filename)).size else: width int(size.find(width).text) height int(size.find(height).text) img_id len(images) 1 images.append({ id: img_id, file_name: filename, width: width, height: height, }) for obj in root.findall(object): name obj.find(name).text if name not in (RBC, WBC, Platelets): # 原始数据里偶尔有标注成 Blood 之类的情况 continue category_id next(c[id] for c in CATEGORIES if c[name] name) box obj.find(bndbox) x_min float(box.find(xmin).text) y_min float(box.find(ymin).text) x_max float(box.find(xmax).text) y_max float(box.find(ymax).text) # 越界裁剪VOC 标注偶尔会超出图像边界 x_min max(0, min(x_min, width)) y_min max(0, min(y_min, height)) x_max max(0, min(x_max, width)) y_max max(0, min(y_max, height)) # 过滤掉空框和退化框 if x_max - x_min 1 or y_max - y_min 1: continue if x_max - x_min 2 or y_max - y_min 2: # 小于 2 像素的框在缩放后基本没意义 continue annotations.append({ id: ann_id, image_id: img_id, category_id: category_id, bbox: [x_min, y_min, x_max - x_min, y_max - y_min], area: (x_max - x_min) * (y_max - y_min), iscrowd: 0, }) ann_id 1 coco_data { info: {description: BCCD converted to COCO format}, images: images, annotations: annotations, categories: CATEGORIES, } with open(output_path, w, encodingutf-8) as f: json.dump(coco_data, f, indent2) print(f完成: images{len(images)}, annotations{len(annotations)}) convert_voc_to_coco(Annotations/, JPEGImages/, bccd_coco.json)这段脚本的工作流程是遍历 XML 文件读取每个文件的 filename 和 size为每张图分配一个递增的 image_id遍历所有 object 节点提取类别名和 bndbox 坐标把 xyxy 转成 xywh 写入 annotations最后序列化成 COCO JSON。几个关键设计第一categories 的 id 固定从 1 开始这是 COCO 的惯例MMDetection 和 Detectron2 都支持从 0 开始但和 pycocotools 的默认评估逻辑配合时从 1 开始最稳。第二越界裁剪用的是 clamp 后用退化框过滤如果原标注框大部分在图像外裁剪后留下一个几像素的小框这种框在训练时会被当成极小的真实目标拉低 AP所以直接过滤掉。第三area 用乘法计算不要用 (wh)*2 这种周长公式评测工具只看面积大小做分组写错会导致分组错乱。3.3 转换后必做的三项校验别急着开训练JSON 生成后不能直接丢给训练脚本先做三个检查。第一个是类别总账确认三个类别的实例数量和原始 XML 里统计的一致如果少了 100 多个实例大概率是某些 XML 里的 name 拼写不规范被过滤了。第二个是 image_id 连续性打印 images 数组的 id 列表确认是从 1 到 N 连续整数如果中间断了训练时 Dataloader 的索引会报错。第三个是冗余字段确认没有把 segmentation 字段落下来——BCCD 没有分割标注如果手写了一个空的 segmentation 列表pycocotools 推理时会把空列表当成无效 mask 处理。4. 用 COCO 格式在检测框架里训练 BCCD配置、预训练与参数选择4.1 框架选型MMDetection 还是 YOLO 系COCO 格式的标注最大优势是反哺了几乎所有主流检测框架。BCCD 只有 364 张图、3 个类别模型不需要很大选框架时更多考虑的是调试效率和生态成熟度。我一般分两种情况想看清楚每个模块的细节、方便日后换 backbone 做对比实验的选 MMDetection想快速迭代、卡资源有限、希望只改一个 yaml 就开跑的选 YOLO 系。两边都吃 COCO 格式差别主要体现在数据加载和训练方式上。如果走 MMDetection数据集要用 CocoDataset需要在配置里显式声明 classes 元组。如果走 YOLO 系需要把 COCO 转成 YOLO 的 txt 格式YOLOv8 的包提供了一行命令转换但转换前要注意类别 id 的映射——YOLO 的类别是从 0 开始的而 BCCD COCO 版本里 category_id 往往从 1 开始直接转会让类别整体偏移一位。下面两个小节分别给出两种思路的关键配置。4.2 MMDetection 的 Faster R-CNN 配置小数据集参数怎么调# bccd_faster_rcnn.py 基于 MMDetection 2.x 的配置写法 _base_ faster_rcnn_r50_fpn_1x_coco.py # 1. 修改模型头COCO 的 80 类改成 BCCD 的 3 类 model dict( roi_headdict( bbox_headdict(num_classes3))) # 2. 数据路径与类别名 data dict( samples_per_gpu4, workers_per_gpu2, traindict( typeCocoDataset, ann_filedata/bccd/annotations/bccd_coco_train.json, img_prefixdata/bccd/images/, classes(RBC, WBC, Platelets)), valdict( typeCocoDataset, ann_filedata/bccd/annotations/bccd_coco_val.json, img_prefixdata/bccd/images/, classes(RBC, WBC, Platelets)), testdict( typeCocoDataset, ann_filedata/bccd/annotations/bccd_coco_val.json, img_prefixdata/bccd/images/, classes(RBC, WBC, Platelets))) # 3. 训练超参小数据集降学习率加 epoch optimizer dict(typeSGD, lr0.002, momentum0.9, weight_decay0.0001) lr_config dict(step[8, 11]) runner dict(max_epochs12)这里最容易被忽略的是 classes 参数。MMDetection 配置文件里如果不写 classes模型头虽然改了 num_classes但 CocoDataset 的 class_names 仍会读 COCO 默认的 80 类名训练时会出现 category_id 对齐错误。同时注意 val 和 test 都要写 classes不能只改 train。lr 我看到很多新手直接用 coco 默认的 0.02跑两步就 NaN364 张图建议从 0.002 起调8 卡减半单卡再减半。backbone 加载预训练权重时不要用 COCO 训练过的完整模型权重用 ImageNet 预训练的 backbone 权重初始化。MMDetection 的下载脚本会默认拉对应模型的 ckpt加载时出现 shape mismatch 是正常的——因为分类头维度从 80 变到 3不用管报错框架会自动跳过不匹配的键。4.3 用 YOLOv8 跑 BCCD先转格式再定增强YOLO 系没有直接的 COCO 训练接口需要把标注转成 YOLO txt。每张图的标注一行一个目标格式是类别id 归一化中心x 归一化中心y 归一化宽 归一化高。写个脚本转换import json import os with open(annotations/bccd_coco.json, r) as f: data json.load(f) img_id2info {img[id]: img for img in data[images]} anns_by_img {} for ann in data[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) labels_dir labels os.makedirs(labels_dir, exist_okTrue) for img_id, anns in anns_by_img.items(): img img_id2info[img_id] w, h img[width], img[height] txt_path os.path.join(labels_dir, img[file_name].replace(.jpg, .txt)) lines [] for ann in anns: # 注意COCO 的 category_id 转成 YOLO 要从 0 开始 cls_id ann[category_id] - 1 x, y, bw, bh ann[bbox] # 转归一化中心坐标 cx (x bw / 2) / w cy (y bh / 2) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw / w:.6f} {bh / h:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines))转换脚本里 cls_id 必须减 1这是 COCO 转 YOLO 最高频的翻车点。做完后用下面的 data.yaml 指向数据集然后直接yolo train databccd.yaml modelyolov8s.pt epochs60 imgsz640开跑。输入尺寸建议至少 640不要为了提速度降到 320血小板这类小目标在 320 尺度下基本消失。# bccd.yaml path: data/bccd train: images/train val: images/val nc: 3 names: [RBC, WBC, Platelets]4.4 预训练策略与训练预期无论选哪个框架预训练对 364 张图都是必须的。从零训练一个 ResNet 50 的 backbone 在 BCCD 上基本收敛不了只有几百张图连 enough 的语义信息都学不到。用 ImageNet 预训练权重可以理解成模型已经学会了通用边缘、纹理、形状特征只需要在血细胞这种特定纹理上微调。训练 12 个 epoch 左右val mAP0.5 能到 0.7 以上就算正常如果低于 0.5优先检查标注转换是否出错不要盲目堆 epoch。5. BCCD 训练避坑类别不平衡、小目标血小板与 364 张图的过拟合5.1 类别不平衡RBC 上万WBC 只有几百模型只认红细胞现象是第一轮训练完val 里红细胞几乎全对白细胞时有时无血小板基本漏检。翻 Precision 和 Recall 会发现 WBC 的 AP 在 0.5 上下Platelets 的 AP 直接趋近 0。原因很直接BCCD 的 364 张图里RBC 的实例数量是 WBC 的几倍到十几倍Platelets 最少。训练时模型把特征都用来拟合 RBC 的分布了对低频类别产生的梯度信号淹没问题在目标检测里是常态。解决要分两步。第一步做数据层面重平衡对 WBC 和 Platelets 的图片做复制增强比如把包含血小板的图片在训练集中复制 3 份每份用不同的颜色抖动、缩放扰动让模型在每个 epoch 里见到这些类别的频率上来。第二步做损失函数侧调整在 MMDetection 里可以给 bbox_head 的 loss_cls 配 class_weight把低频类别的权重抬高到 1.5 到 2.0让分类损失在反向传播时给低频类别更大的增量。注意 class_weight 不要超过 3否则会引入严重的误检把零星红细胞当成血小板。5.2 小目标血小板多尺度下采样之后特征就没了现象是训练 loss 正常下降验证集上血小板的框总是小一圈框的位置漂移或者干脆把一个小红细胞当成了血小板。可视化特征图会看到血小板区域在深层 feature map 上基本没有响应。血小板在 BCCD 原图里只有十几个像素宽是典型的 small object。Faster R-CNN 这类模型 backbone 下采样 4 倍到 32 倍一个 16x16 的血小板在 P2 层还剩 4x4 个像素在 P5 层只剩 1 个像素RPN 的 anchor 在深层根本覆盖不到这种尺度。我的做法是优先保住输入分辨率。训练时 imgsz 从 640 起不要低于 512同时用多尺度训练随机在 512 到 768 之间缩放增加血小板在不同尺度下出现的机会。如果数据分布允许可以试试把图像裁成 patch 训练——把 640x640 的图切成四块 320x320每块单独进网络相当于把血小板放大了 2 倍。BCCD 的图不大切 patch 会让每张图的训练时间变成 4 倍但对检测小目标非常有效。5.3 过拟合训练 loss 和 val mAP 走势相反这是小数据集的常态现象是训练集 loss 一路降到很低验证集的 mAP 在某个 epoch 后开始波动甚至下降训练曲线和验证曲线完全背离。这是 364 张图最典型的过拟合信号。解决思路是给训练集加噪声。除了常规的随机翻转、颜色抖动我强烈建议在 BCCD 上开 Mosaic 或 MixUp 这种强增强把四张图拼在一起训练可以有效稀释模型对单张图背景纹理的记忆。同时把学习率降 3 到 5 倍让模型在收敛后期只做细微调整不猛然跳到过拟合区域。登记上要注意关闭验证集增强是必须的有些人顺手开了一个测试增强结果指标虚高上线后全部打回原形。5.4 验证集只有几十张图AP 值不可靠现象是同一次训练跑两遍设置的随机种子不变val mAP 也可能差 2 到 3 个点。这不是代码有 bug是验证集太小几十张图的评估结果方差很大。解决方法是做 K 折验证。BCCD 的 364 张图不要只分一次 train/val按 5 折切分每折 70 张左右做验证训练 5 次模型最终报告 5 次 AP 的均值和标准差。这个习惯在 BCCD 上很有必要很多博客里报的mAP 0.8其实只跑了一次换一折数据直接掉到 0.65结论完全不可复制。分折时要注意按血细胞的稀疏程度分层抽样让每一折的 WBC 和 Platelets 数量接近避免某一折验证集恰好没有血小板AP 分母为 0。6. 从验证可视化到 AP 解读BCCD 上值得试的进阶技巧训练完别急着看数字先把预测结果画出来看一遍。用框架自带的 test 脚本输出可视化结果重点关注三类错误漏检的血小板长什么样、误检的背景区域在哪里、框是不是整体偏移。这类主观检查比看指标更能暴露数据标注问题。我习惯挑五张验证集图每张图同时打印 GT 框和预测框逐类对遇到框偏移超过五个像素的情况回去检查 XML 原始标注的 bndbox 值是不是把坐标顺序写错了。COCO 评估报告里的 AP_50 和 AP_75 要分开看。AP_50 高、AP_75 低说明模型能找准目标但框不够精确这是 BCCD 上用 Faster R-CNN 的常见形态因为很多细胞边缘模糊标注框本身也不精确。不要盲目上更复杂的回归损失先看 AP_75 和 AP_50 的差值是否超过 0.2如果超过优先处理标注噪声而不是换模型。AP_small 和 AP_medium 的差距也是个信号灯血小板算 small红细胞和白细胞算 medium两者差距过大说明小目标策略还没做到位。最后可以试试 WBFWeighted Boxes Fusion把模型在不同 epoch 的预测结果或不同模型Faster R-CNN 和 YOLOv8的预测框做加权融合BCCD 这种目标数量比较多的场景效果很明显比单独调 NMS 阈值更稳定。我习惯保留每个模型的置信度和框用 WBF 库的 run_wbf 函数image_weight 设 1iou_thr 设 0.5跑完后把融合结果重新计算 AP通常能比单一模型高 1 到 2 个点。fold 验证的 5 个模型权重也可以直接做集成效果比 WBF 更稳代价是推理时间翻倍。希望这个从标注解析到避坑再到进阶的路线能帮你在 BCCD 上少走一段弯路。本文还有配套的精品资源点击获取
返回列表