血细胞检测YOLO数据集处理:格式转换、划分与训练实战
简介YOLO红白细胞血小板检测数据集定位明确面向医学图像目标检测学习者和算法工程师基于真实场景拍摄的高质量血细胞图片由LabelImg逐一标注提供VOC、COCO、YOLO三种格式标签可无缝接入YOLO系列模型训练。包体内含2000个文件具体包括1000个xml标签、990个txt标签以及HTML训练教程、Python划分脚本和YAML配置文件压缩包大小29.34MB目录结构清晰。目前已有190人学习下载适合正在做血细胞检测或需要标准医学目标检测数据集的人群。配套资料是亮点既提供Windows/Linux双环境的YOLO搭建与训练教程也内置训练集、验证集、测试集划分脚本可快速拆分数据并开始实验显著降低数据准备门槛。1. 从血涂片到一个能跑的模型这份数据集为什么值得拆包做血细胞检测落地的人拿到一个标注好的YOLO红白细胞血小板检测数据集第一件事往往不是直接训练而是拆包、验标签、审划分脚本。1000张显微图像配VOC、COCO、YOLO三种格式标签外带划分脚本和训练教程对刚起步的小团队来说这套东西能省掉从打标到配置训练的大部分重复劳动但方便之下埋着几个细节坑三种格式的坐标系换算方向、划分脚本是否按拍摄来源做了分组隔离、类别索引排得对不对。任何一个环节出错训练出来的模型在血涂片上都可能把血小板当杂质或者在红细胞上疯狂漏检。这篇笔记按拆包验数据、重写划分、训练调参、排错、部署验证的顺序把一个1000张图的显微目标检测任务完整跑通适合刚拿到同类数据集、想快速验证YOLO效果的工程师也适合准备把血细胞计数落到自有流水线的人。2. VOC、COCO、YOLO三种标签格式坐标换算和类别索引的四个算术细节2.1 VOC格式绝对坐标和类别名是它的全部VOC格式存的是独立的XML文件每张图一个根节点下有filename、size、object。每个object里是一个bndbox里面是xmin、ymin、xmax、ymax四个整数值单位是像素坐标系原点在图像左上角。object里的name存的是类别字符串比如RBC、WBC、Platelet它和训练时的类别索引没有直接绑定关系——这一点是很多人后面转格式时翻车的根源。用labelimg打标过的同学应该有印象labelimg默认存的就是VOC格式的XML打标完yolo格式的标之后会发现YOLO训练读的根本不是XML。这就涉及到第一个关键判断拿到三种格式的数据集时不要只挑一种用而是先确认它们是否来自同一套标注。常见做法是拿VOC当基准源因为XML里信息最全、最直观COCO和YOLO都可以从它推导出来反过来从YOLO的txt反推VOC就麻烦得多还要额外读图片尺寸。2.2 COCO格式1起始的category_id和bbox数组COCO格式是把所有标注汇总到一个JSON文件里里面有images、annotations、categories三个最核心的数组。images数组存每张图的id、file_name、width、heightannotations数组存每个框的image_id、category_id、bbox、areacategories数组声明类别列表。COCO的bbox是[x, y, width, height]同样是绝对像素坐标。但有一个极其常见的坑COCO的category_id通常从1开始而YOLO的class id从0开始。很多人在VOC转COCO时忘记对齐转完以后训练出来的模型类别整体错一位红细胞被识别成白细胞还不容易察觉。我自己处理这类数据时第一步永远是先打印categories列表和YOLO某张图的类别索引做交叉核对。2.3 YOLO格式归一化中心点和宽高YOLO标签是每行一个目标的txt文本五个字段class_id, x_center, y_center, width, height。这四个数值全部是相对图像的归一化值范围0到1计算方式是框的像素坐标除以图像宽高。YOLOv5到YOLOv8包括更新的系列版本训练时读的都是这个格式。这里有个隐藏问题YOLO格式不记录图像尺寸所以从txt反向转换时必须去读对应图片的宽高否则无法还原像素坐标。另外YOLO官方对坐标保留小数位数没有强制要求但实践上统一保留6位小数最稳既不会因为精度丢失让框发生肉眼可见的偏移文件体积也不大。2.4 从VOC同时生成YOLO和COCO转换脚本与参数说明我一般会把VOC作为基准源写一个转换脚本一次性生成YOLO的txt和COCO的json。下面这个脚本是针对血细胞检测场景写的可以直接跑关键在于类别顺序表CLASSES全项目只维护一份。import xml.etree.ElementTree as ET import json from pathlib import Path # 类别表是全项目的唯一事实来源顺序即训练时的索引 CLASSES [RBC, WBC, Platelet] def voc_to_yolo(xml_path: Path, img_w: int, img_h: int, out_txt: Path): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.findtext(name) if name not in CLASSES: raise ValueError(f{xml_path} 里出现未登记的类别: {name}) bnd obj.find(bndbox) xmin max(0, min(float(bnd.findtext(xmin)), img_w)) ymin max(0, min(float(bnd.findtext(ymin)), img_h)) xmax max(0, min(float(bnd.findtext(xmax)), img_w)) ymax max(0, min(float(bnd.findtext(ymax)), img_h)) if xmax - xmin 1 or ymax - ymin 1: continue cls_id CLASSES.index(name) cx ((xmin xmax) / 2) / img_w cy ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_txt.write_text(\n.join(lines) if lines else )逻辑说明先做坐标越界裁切再过滤掉宽度或高度小于1像素的退化框最后归一化。越界裁切这一步不是多余的——血涂片图像边缘经常有被裁掉一半的细胞标注工具有时会留下越界坐标不裁直接训练会让模型学出“跑到画面外的框”。空txt文件要保留YOLO训练时把无目标的背景图也作为负样本删掉空文件等于减少了难负样本。参数说明img_w和img_h必须来自图片真实尺寸不能从XML的size节点偷懒读取因为部分标注工具写进XML的尺寸和实际图片不一致尤其是转码压缩过的数据集。cls_id用CLASSES.index()动态算禁止手写死数字否则类别一调整就会错位。一个容易踩的坑是COCO的category_id。VOC转COCO时要把CLASSES.index(name)再加1因为COCO从1起编号而YOLO从0起。转换并验证之后再跑训练就踏实多了。3. 重写划分脚本按拍摄来源分组防止训练集和验证集“同图泄漏”3.1 常见的划分脚本错在哪压缩包里自带的划分脚本很多是这种逻辑把所有图片路径读进来random.shuffle按比例切成三段然后写三个txt。这种简单随机划分对自然场景数据集勉强能用对血涂片显微图像是危险的。原因在于同一张血涂片往往会拍十几个甚至几十个视野这些图像视野内容相近、染色条件一致如果其中一部分进了训练集、另一部分进了验证集验证集的指标会虚高因为它和训练集的分布几乎重合。模型真正部署到新涂片上时遇到的染色深浅、视野明暗、细胞密度分布都是新的立刻现原形。因此拿到划分脚本后先看一眼它有没有按文件名里的“拍摄来源前缀”分组而不是直接对单张图随机切。好一点的脚本至少会做组级别的划分保证同一前缀的图全部进同一个集合。3.2 按组划分的脚本实现下面是我常用的划分脚本按文件名前缀分组保证同一个拍摄来源的图像不会同时出现在train和val里。这个脚本还额外输出split.json方便回过头来核对某张图到底被分到了哪里。import random, json from pathlib import Path IMG_DIR Path(images) LABEL_DIR Path(labels) RATIO {train: 0.70, val: 0.15, test: 0.15} SEED 42 KEYS [train, val, test] # 按前缀分组假设文件名形如 slide003_0001.jpg samples sorted(IMG_DIR.glob(*.jpg)) groups {} for img_path in samples: prefix img_path.name.split(_)[0] groups.setdefault(prefix, []).append(img_path) group_items list(groups.items()) random.Random(SEED).shuffle(group_items) # 按组累计图像数量确定切分边界 total len(samples) train_end int(total * RATIO[train]) val_end train_end int(total * RATIO[val]) splits {k: [] for k in KEYS} acc 0 for prefix, img_list in group_items: if acc train_end: splits[train].extend(img_list) elif acc val_end: splits[val].extend(img_list) else: splits[test].extend(img_list) acc len(img_list) # 写出YOLO可直接读取的txt路径列表 for k in KEYS: lines [str(p) for p in splits[k]] Path(f{k}.txt).write_text(\n.join(lines) \n) # 保存分组和划分的对应关系后悔药就在这里 split_record { seed: SEED, group_to_set: {prefix: next(k for k in KEYS if any(p in splits[k] for p in img_list)) for prefix, img_list in group_items}, counts: {k: len(v) for k, v in splits.items()} } Path(split.json).write_text(json.dumps(split_record, indent2, ensure_asciiFalse))逻辑说明先把图片按前缀分组再对组做shuffle最后按累计图像数量切段。这样做的好处是即使某个组的图片数量特别多也不会被拦腰截断分到两个集合。acc记录的是已经分配到当前集合的图片数量不按组数切而是按实际图片数切比例更精确。参数说明SEED固定为42重跑脚本得到完全一样的划分结果如果发现某个集合类别分布失衡先看split.json里group_to_set的分配情况而不是直接把seed改掉——改seed能解决问题算运气好但会让后续复现变得很麻烦。train、val、test三个txt里写的是图片绝对路径YOLO会根据同名规则自动找同目录下的txt标签把.jpg后缀换成.txt就能定位到标注文件所以图片和标签文件名必须一一对应大小写也要一致。3.3 划分后的三件套自检划分完成后我至少会跑三个自检缺一不可。train_files set(Path(p) for p in open(train.txt).read().splitlines()) val_files set(Path(p) for p in open(val.txt).read().splitlines()) test_files set(Path(p) for p in open(test.txt).read().splitlines()) assert not (train_files val_files), train和val存在同图泄漏 assert not (train_files test_files), train和test存在同图泄漏 assert not (val_files test_files), val和test存在同图泄漏 # 检查每个集合里三类目标是否都出现 import collections for k in [train, val, test]: cls_counter collections.Counter() for txt in Path(labels).glob(*.txt): for line in txt.read_text().splitlines(): if not line.strip(): continue cls int(line.split()[0]) cls_counter[cls] 1 print(k, len(cls_counter), 个类别)第一个检查是查三个集合的路径交集这是划分脚本最容易翻车的点一旦断言报错就说明脚本根本是按单图随机分的必须回头改分组逻辑。第二个检查看类别覆盖血细胞检测里血小板数量本来就比红细胞少如果划分后train里血小板的目标数量低于几百个训练时的recall基本救不回来优先做类别重平衡而不是硬训练。第三个检查看标签和图片的文件名匹配率1000张图的数据集里偶尔会混进无标签图或多余标签YOLO训练时文件名对不上不会报错但会静默丢弃导致有效样本量比预期的少。4. 用YOLO训练血细胞检测的最小闭环配置、超参数和评估指标4.1 环境准备和数据配置训练环境用conda管理最省心。创建一个干净的虚拟环境再安装ultralyticsCUDA版的torch通常在安装时会被自动带上不需要手动指定版本除非你的显卡驱动比较老才需要单独装对版本号的torch。conda create -n yolo python3.11 -y conda activate yolo pip install ultralytics装完后先别急着训练把数据配置文件写好。YOLO训练时的数据集描述文件是一个yaml路径、集合划分、类别名都在这里声明。# blood_cell.yaml path: ./ train: train.txt val: val.txt test: test.txt names: 0: RBC 1: WBC 2: Platelet注意names的索引必须和标签txt里的class_id完全一致。因为VOC转YOLO时CLASSES的顺序就是RBC、WBC、Platelet这里也必须保持一致。一旦不一致训练不会报错但模型会把红细胞学成血小板推理结果一团糟属于最隐蔽的翻车方式。4.2 训练脚本和超参数怎么调训练部分我习惯写Python脚本来调用而不是用命令行因为参数和数据集路径都固化在脚本里换机器复现时少踩路径坑。from ultralytics import YOLO model YOLO(yolov8n.pt) results model.train( datablood_cell.yaml, epochs150, imgsz960, batch16, patience30, seed42, cacheTrue, workers8, lr00.001, )参数说明imgsz用960而不是默认的640是因为血涂片里的血红细本文还有配套的精品资源点击获取