ARTICLE DETAIL

资讯详情

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

血细胞检测数据集与YOLO训练全流程:从标注格式到模型部署

血细胞检测数据集与YOLO训练全流程:从标注格式到模型部署 简介面向医学影像目标检测场景的YOLO红白细胞血小板检测数据集及配套教学资源适合正在学习YOLO系列算法、需要真实血细胞检测数据训练模型的开发者和高校课程学员。数据来自真实场景图片质量高已使用LabelImg完成精准标注并分别提供VOC(xml)、COCO(json)、YOLO(txt)三种格式的标签可直接接入YOLOv5、YOLOv8等常见框架训练。包内共两千个文件以一千个xml标签、九百九十个txt标签为主另有少量Python脚本、YAML配置和6个HTML说明文档整体约29.34MB便于下载使用。资源还附带Windows与Linux双平台的环境搭建和训练教程以及三套数据集划分脚本可灵活生成训练集、验证集和测试集提升模型迭代效率。目前已有一百九十一人学习对于入门医学图像处理、快速搭建YOLO检测流程的学习者来说是一份兼具数据、脚本和指导的实用资源。1. 血细胞检测数据集怎么选1000 张图到底够不够训练 YOLO做血液细胞检测的落地项目时最卡人的往往不是模型结构而是数据本身。你要找的是那种标注出血红细胞、白细胞、血小板的显微图像并且标签得能直接喂给 YOLO 训练。市面上公开的医学影像数据集不少但真正针对血液细胞目标检测、又带 VOC/COCO/YOLO 三种标签的成套资源很稀缺所以像“YOLO红白细胞血小板检测数据集(含1000张图片)对应voc、coco和yolo三种格式标签划分脚本训练教程.rar”这种压缩包几乎是专门为工程落地准备的。1000 张图听起来不多但关键在于这批图是否聚焦在显微镜视野下的典型细胞形态以及标签是否经过严格的人工校对。如果分类正确、边界框紧贴细胞膜那这 1000 张就足以支撑一个初版模型完成从训练到部署的全流程验证。真正做过血细胞识别的人都知道这个任务的核心瓶颈在于白细胞和血小板在数量上远少于红细胞类别严重不均衡加上染色方案不同会导致色调偏移所以数据集里如果没特意保留这些难例模型训练时很容易偷懒把一切圆形的深紫色东西都当成白细胞。下面就从解包开始把这一套数据从清洗、校验到训练、验证的完整链路捋一遍。2. 解包 .rar 后的第一件事VOC/COCO/YOLO 三种标签的来龙去脉与校验拿到压缩包后别急着开训先花十分钟把目录结构和标签格式摸清楚。一个规范的数据集压缩包解压后通常应该有images、AnnotationsVOC 的 xml 文件、labelsYOLO 的 txt 文件以及对应的 COCO 的 json 文件。如果压缩包附带表格或 readme 文件里面一般会写明类别数量、图片尺寸和标注规范。常见做法是先用一条 bash 命令把根目录捋一遍看看每个文件夹里文件数是否对得上。# 进入解压后的数据集根目录统计各文件夹下的文件数量 cd ./blood_cell_dataset echo Images 总数: $(find images -type f | wc -l) echo VOC xml 总数: $(find Annotations -type f | wc -l) echo YOLO txt 总数: $(find labels -type f | wc -l) # 检查有没有空标注文件空文件会导致训练时 loss 为 nan 或直接崩 find labels -name *.txt -empty -print这段代码的作用是快速核对三个目录的文件数是否一致。正常情况下图片数、xml 数和 txt 数应该完全相等因为每张图都对应一份 VOC 标注和一份 YOLO 标注。-empty参数用来找出那些没有目标的 txt 文件这在血细胞数据里很常见因为有些视野下可能确实没有血小板或白细胞。如果发现空标注文件你需要决定是保留让 YOLO 学习“这张图没有目标”还是直接删掉对应图片我一般建议直接删除不然训练时背景占比过大会干扰收敛。VOC 和 COCO 格式的区别核心在于坐标系的表达方式。VOC 的 xml 文件里每个目标用bndbox标签存左上角和右下角的坐标即绝对值COCO 的 json 文件里框的格式是中心点坐标加宽高也是绝对值而 YOLO 的 txt 文件里五个数字依次是class_id、x_center、y_center、width、height全部除以图片宽高做了归一化。这个区别决定了转换脚本里的计算逻辑必须是x_center (xmin xmax) / 2 / img_width一旦分母用错模型训练出来的框位置会整体偏移。拿到压缩包里的三种格式后建议写一个脚本做交叉验证即随机抽 10 张图把 YOLO 格式的框画回原图上确认框是否贴合细胞边缘。这一步不能省因为标注时的错位、坐标超界会导致后续训练时 loss 异常。# 交叉验证脚本将 YOLO 格式标注画回图片 import cv2 import numpy as np # 类别映射0-red blood cell, 1-white blood cell, 2-platelet class_names [RBC, WBC, PLT] colors [(0, 0, 255), (255, 0, 0), (0, 255, 255)] # 读取图片和对应的标注文件 img cv2.imread(images/00001.jpg) h, w img.shape[:2] with open(labels/00001.txt, r) as f: lines f.readlines() # YOLO 格式是归一化坐标画框前必须乘以图片宽高恢复像素坐标 for line in lines: parts line.strip().split() cls int(parts[0]) x_c, y_c, box_w, box_h map(float, parts[1:]) x_min int((x_c - box_w / 2) * w) y_min int((y_c - box_h / 2) * h) x_max int((x_c box_w / 2) * w) y_max int((y_c box_h / 2) * h) cv2.rectangle(img, (x_min, y_min), (x_max, y_max), colors[cls], 2) cv2.putText(img, class_names[cls], (x_min, y_min - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[cls], 1) cv2.imwrite(verify_00001.jpg, img)这段脚本里最关键的是类型转换map(float, parts[1:])确保坐标是浮点数避免整数除法导致中心点偏移。cv2.rectangle的坐标参数必须是整数所以在乘完宽高后要强制int()。画完框之后你还需要把这 10 张图逐张过目重点看三个地方一是有没有框偏到细胞核外二是血小板体积很小框是否过大或过小三是同一张图里红白细胞坐标有没有串位。这个验证过程大概花 10 分钟但能省下后续训练时反复排错的时间。COCO 格式的验证方式则是把 json 文件解析出来后做同样的事json 里如果有segmentation字段且为空不影响目标检测训练但如果bbox字段的宽度或高度是负数训练时就会报尺寸错误。血细胞数据集的标签还有一个特殊风险就是类别顺序不统一。有些压缩包把类别定义成[WBC, RBC, PLT]有些是[red_blood_cell, white_blood_cell, platelet]YOLO 训练时类别索引完全由data.yaml里的names列表顺序决定如果 txt 文件里的索引是 0 代表红细胞而data.yaml里 0 却写成了白细胞模型就会把所有红细胞学成白细胞。诊断这个问题的快准狠方法是数一下训练集里各类别的标注框数量正常情况下红细胞应该是白细胞的几十倍如果出现数量和预想不符基本就是类别索引错位。3. 用划分脚本重建训练集目录结构、随机种子与样本均衡拿到数据后首要任务是把训练集、验证集和测试集划分好。压缩包里如果自带划分脚本那最好不过但脚本往往只生成了默认划分你可能需要根据自己的显存大小和训练目标重新划分。一个血细胞检测项目的标配划分比例是 7:2:1也就是 700 张训练、200 张验证、100 张测试。很多开源划分脚本只随机打乱图片列表没有考虑类别均衡这在血细胞这种极不均衡的数据集上是有问题的。假设某张图里只有红细胞没有血小板全部被分到训练集那验证集里血小板的表现就会虚低。我常用的做法是在划分脚本里加一个维度即统计每张图片包含的类别数量尽量把包含三种细胞类型的图片均匀分配到三个子集里。import random import numpy as np from pathlib import Path # 固定随机种子保证每次划分结果可复现 random.seed(42) np.random.seed(42) # 读取所有图片名 img_paths list(Path(images).glob(*.jpg)) n len(img_paths) shuffled img_paths.copy() random.shuffle(shuffled) # 按 7:2:1 划分 train_imgs shuffled[:int(n * 0.7)] val_imgs shuffled[int(n * 0.7):int(n * 0.9)] test_imgs shuffled[int(n * 0.9):] # 辅助函数将图片列表写入 txt供 YOLO 训练时读取 def write_txt(path_list, out_file): with open(out_file, w) as f: for p in path_list: f.write(str(p.resolve()) \n) write_txt(train_imgs, train.txt) write_txt(val_imgs, val.txt) write_txt(test_imgs, test.txt) print(fTrain: {len(train_imgs)}, Val: {len(val_imgs)}, Test: {len(test_imgs)})代码里的random.seed(42)非常重要它让每次运行脚本生成的划分完全一致。有些人觉得随机种子是玄学但当你调整超参数再次训练时如果划分不一致就分不清性能提升到底来自模型改动还是数据变化。Path.resolve()会把相对路径转成绝对路径这样从任何工作目录启动 YOLO 训练都不会报找不到文件。不过要注意这份 txt 文件里的路径指向的是源images目录如果你做数据增强或数据清洗时把图片复制到了新目录必须同步更新这些 txt 文件否则训练时会读到旧的路径。划分完数据后还需要确认data.yaml里的配置。Ultralytics YOLOv8 需要用户指定train、val和test三个路径以及nc和names两个字段。这里最常见的错误是按 VOC 的类别名来填写names比如写成[rbc, wbc, plt]但没有注意 txt 文件里的类别索引。最稳妥的方法是打开任意一个 YOLO 标注文件看第一列的数字再看压缩包里有没附带classes.txt或obj.names文件以那个为准填写names列表。# data.yaml 文件内容 path: ./blood_cell_dataset train: train.txt val: val.txt test: test.txt nc: 3 names: [RBC, WBC, platelet]这里的path字段是根目录train和val可以写绝对路径也可以写相对路径但如果填的是 txt 文件名就必须保证 txt 文件存在于path指定的这个目录下。我之前犯过错把train直接写成/path/to/images/train而path又指向了上一级目录结果 YOLO 把两个路径拼接后找不到任何图片。原因在于path是相对于当前工作目录的偏移量而train是相对于path的偏移量两者是叠加关系。如果压缩包自带的划分脚本已经生成了train.txt你就在train字段写上train.txt就行如果想直接写文件夹路径把train改成images/train也可以前提是目录结构必须匹配。4. 避坑指南VOC/COCO/YOLO 互转时的坐标、索引与类别顺序做血细胞目标检测最隐蔽的坑不在于模型本身而在于格式转换时对坐标定义理解不透彻。VOC 格式里xmin和xmax定义的是像素绝对坐标而 YOLO 格式里x_center和width是归一化到 0 到 1 之间的小数。转换的公式本身并不复杂x_center (xmin xmax) / 2 / img_width真正容易错的是图片尺寸取反了。有些脚本读 xml 时没取宽高直接用[W, H]做分母但实际图片是[H, W]结果所有框在横向上正常、纵向上偏移。显微镜图像的细胞分布通常比较均匀纵向偏移后框和细胞的错位一眼就能看出来就怕错位不严重模型学到的框始终偏上或偏下导致 mAP 不高但又不完全崩这种问题最难定位。COCO 格式转 YOLO 时还有第二个大坑即类别 ID 的前后偏移。COCO 数据集的类别 ID 是从 1 开始而 YOLO 是从 0 开始。很多公共脚本转换时忘了减 1导致原本的类别 2血小板被当成了类别 3如果nc3越界的类别 ID 会在训练时被忽略轻则 mAP 下降重则直接提示标签索引超范围。解决办法是在转换脚本里强制检查最大类别索引确保不超过nc-1。同时血细胞标注里如果存在同一个细胞被标注了多个框的重复框问题也会让模型在 NMS 阶段输出乱跳的框最好在转换前用脚本合并重复框。添加一个专门用于排查图像、标注不匹配的问题通常的做法是在训练脚本跑起来前先用一个校验脚本做一致性检查。比如检查图片文件名和标注文件名是否一一对应图片是否损坏可读。对血细胞这类专门的标注数据我一般会额外确认标注框的宽高是否大于等于 2 个像素因为血小板可能只有很小的尺寸如果标注时不小心给出 1 像素的框模型在前向传播时难以从中提取到有效特征反而会对网络造成负向影响。# 校验标注框的合法性 from pathlib import Path import cv2 # 全局统计超界框数量 invalid [] for label_path in Path(labels).glob(*.txt): with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: invalid.append((label_path, 字段数错误)) continue _, x_c, y_c, w, h parts if float(w) 0 or float(h) 0: invalid.append((label_path, 宽高小于等于0))血细胞数据集的典型问题在于血小板数量很多但框很小转换脚本如果做了int()取整很容易把小数坐标截断成 0从而产生w0的非法框。训练脚本遇到这类框会报累积错误可能在训练几十个 epoch 后突然爆出ValueError。上述脚本在训练之前跑一遍把所有非法框打印出来并统一过滤能够避免训练中途中断。在数据量只有 1000 张的情况下过滤掉少数异常标注比直接放弃这些图片更划算。在 COCO 转 YOLO 的场景里另一个高频踩坑点是annotations里的image_id和实际文件名对不上。比如 json 里的image_id是34但文件名是042.jpg如果你用str(image_id) .jpg去构造路径就会得到34.jpg而找不到042.jpg。遇到这种问题一定要先读取images列表的file_name字段用它作为文件名而不是想当然用image_id。这类结构性错误会让训练时大量图片无法加载报错信息又只显示FileNotFoundError排查起来极其恼人。5. 跑通 YOLOv8 训练从配置 data.yaml 到监控 loss 曲线当图片、标签和划分脚本全部校验完毕训练就是一个成熟流程了。我通常直接用 Ultralytics YOLOv8 的 Python API因为命令行模式虽然方便但参数太多时不易存档而 Python 脚本可以把配置固化下来换机器后重新跑一次即可。血细胞检测最好用yolov8n.pt或yolov8s.pt作为预训练权重因为 1000 张图的数据量不够从随机初始化开始训练迁移学习可以极大降低收敛难度。如果压缩包里有训练教程大概率也是基于 YOLOv5 或 YOLOv8 写的一行命令核心无非是指定data.yaml、epochs、imgsz和batch四个参数。from ultralytics import YOLO # 载入预训练权重只保留 backbone 和 neck 的特征提取能力 model YOLO(yolov8n.pt) # 训练入口epochs 设置 150batch 根据显存调整 results model.train( datadata.yaml, # 上一步校验过的数据配置文件 epochs150, # 血细胞数据集较小150 轮足够 imgsz640, # 显微镜原图通常较大这里统一缩放到 640 batch16, # batch 大小根据 GPU 显存调整T4 上 16 安全 patience20, # 连续 20 轮验证集指标不提升就提前停止 projectblood_cell, nameyolov8n_exp1, seed42, # 固定种子便于复现 pretrainedTrue, # 使用 COCO 预训练权重迁移 )上述命令里最重要的参数是pretrainedTrue。有些人会把它设置成False理由是血细胞和 COCO 里的类别差异太大预训练权重帮不上忙。实际训练经验告诉我哪怕 COCO 里只有几种类别与血细胞形态接近骨干网络对纹理、边缘、颜色的底层特征提取能力依然是通用的而且预训练模型收敛更快在 150 个 epoch 内更容易达到可用精度。epochs150看起来很长但配合patience20的早停机制通常到 60 到 80 个 epoch 就会停止。如果你的 GPU 显存只有 8Gbatch16跑 640 分辨率可能会爆显存此时有两种方案把batch降到 8或者把imgsz降到 512。显微镜下血小板非常小512 分辨率会丢失细节所以我宁可降 batch 也不降分辨率。训练过程中要盯住的不是打印出的box_loss而是验证集上的mAP50-95这个指标综合反映了框的定位精度和分类精度。血细胞检测里红细胞通常 mAP 很高因为样本多、形态固定血小板 mAP 偏低因为目标太小。如果训练过程中看到mAP50高但mAP50-95低说明框的大致位置是对的但边界不够精确需要更多 epoch 来微调。如果两者都低就要回头检查标签转换脚本是不是有坐标偏移。训练结束后模型会生成best.pt和last.pt两个权重文件。best.pt是验证集指标最优的模型last.pt是最后一个 epoch 的结果。部署时必须选best.pt这是基本共识但很多人忽略的是要在测试集上额外验证一次。因为验证集在训练时已经被用来做早停判断指标有一定程度的“泄露”只有测试集是完全没见过的新数据在血细胞这类已知数据分布固定的场景里通常测试集 mAP 会比验证集低 1 到 2 个点如果低得过多就要考虑是否存在过拟合。训练完成后我习惯用 TensorBoard 或直接查看runs/detect/name/results.csv来确认收敛状态。results.csv里记录了每一轮的train/box_loss、val/box_loss、val/mAP50-95等指标。核心观察点是val/box_loss是否在训练后半段出现回弹。如果回弹说明学习率没有配合好一种解法是在训练代码里加入cos_lrTrue参数让学习率按余旋曲线衰减通常能解决后期震荡的问题。Ultralytics 默认已经使用lr00.01加上自动脱落但如果换到医学小数据集上我一般会把lr0降到0.005避免前期步长过大把预训练特征冲坏。对于训练教程部分如果你是从零开始入门建议先在val子集上做一次单张推理测试确认输入输出的形状。拿一张测试图片替换掉默认的bus.jpg运行model.predict(sourceimages/test01.jpg, saveTrue)生成的标注图能直观看到框和置信度。这一步与训练无关仅用于验证模型文件是否损坏。对于血细胞这种目标密集的场景预测时还需要调conf参数默认的0.25置信度阈值对血小板来说容易漏检最终部署时建议下调到0.1左右。6. 验证与部署用混淆矩阵和裁剪策略突破血小板漏检模型训练完只是中点真正决定项目能否交付的是在验证集上的细粒度分析。血细胞检测数据集里红细胞在大部分视野中占绝对主体白细胞和血小板往往仅占少数如果用全局mAP来评价模型只需要把红细胞学好就能拿到不错的分数却掩盖了血小板全部漏检的风险。因此我训练后必做的一步是导出混淆矩阵并按类别分开看召回率。# 加载训练得到的权重重新在测试集上做验证 from ultralytics import YOLO model YOLO(runs/detect/yolov8n_exp1/weights/best.pt) metrics model.val(splittest, conf0.1, iou0.5) # 打印每个类别的召回率 print(metrics.box.mp, metrics.box.mr) # 平均精确率和召回率 print(metrics.box.ap_class_index) # 类别索引 print(metrics.box.p) # 每个类的精确率 print(metrics.box.r) # 每个类的召回率metrics.box.r返回的数组顺序与data.yaml中names的顺序一致这正是我们常说的“验证指标是跟着类别定义走的”。如果打印结果显示血小板的召回率在 0.5 以下那就说明模型对小板框的拟合能力不够不要急着改模型结构先尝试把输入分辨率从640提升到1024。显微镜图片本来就是高分辨率图像缩放至 640 对红细胞无碍但血小板也会被同步缩小甚至小于特征图上一个网格的尺寸。很多血细胞识别比赛的参赛方案都使用imgsz1024配合batch8目的就是保住小目标的空间尺寸。如果调大分辨率后显存不足另一条经过验证的路线是切图训练也就是把原图按网格切成若干 640×640 的小块再对每个小块单独做标注和训练。压缩包里如果带了划分脚本大概率没有包含切图逻辑因为切图会改变所有标注坐标需要把框的绝对坐标转换成相对于小图的坐标。这里有两个关键做法一是切图时重叠率至少设 10%防止细胞正好位于切图边界导致被截断二是过滤掉完全落在区块外部的框同时保留跨越多个区块的框让模型能学到细胞的完整形态。# 切图逻辑将原图切为 640x640 的小块并同步转换标签坐标 import cv2 from pathlib import Path def crop_image_and_labels(img_path, label_path, crop_size640, overlap64): img cv2.imread(str(img_path)) h, w img.shape[:2] step crop_size - overlap # 计算横向纵向切图次数 x_steps (w - crop_size) // step 1 y_steps (h - crop_size) // step 1 with open(label_path) as f: lines [line.strip() for line in f if line.strip()] # 遍历每个切图块 for i in range(x_steps): for j in range(y_steps): x1 i * step y1 j * step x2 x1 crop_size y2 y1 crop_size crop img[y1:y2, x1:x2] new_label_lines [] # 把 YOLO 坐标转为像素坐标再判断是否在块内 for line in lines: parts line.split() cls int(parts[0]) cx, cy, cw, ch map(float, parts[1:]) cx_px cx * w cy_px cy * h cw_px cw * w ch_px ch * h # 保留中心点落在切图块内的框 if x1 cx_px x2 and y1 cy_px y2: new_cx (cx_px - x1) / crop_size new_cy (cy_px - y1) / crop_size new_cw cw_px / crop_size new_ch ch_px / crop_size new_label_lines.append(f{cls} {new_cx:.4f} {new_cy:.4f} {new_cw:.4f} {new_ch:.6f}\n) if new_label_lines: out_img fcrops/{Path(img_path).stem}_{i}_{j}.jpg out_label fcrops/{Path(img_path).stem}_{i}_{j}.txt cv2.imwrite(out_img, crop) with open(out_label, w) as f: f.writelines(new_label_lines)切图脚本中center point落在块内就保留这个规则简单但不失有效。对于横跨两个块的细胞模型会在每个小块里学到它的残缺部分预测时如果两个块都被判定有目标NMS 会把其中一个抑制掉实际部署时再做一次全图拼接即可。你也可以选择让框完全落在块内才保留但那样会丢失边界细胞的上下文信息对血小板检测来说很不划算。经验之谈是先把不切图的模型跑通然后单独做一组切图训练对比验证集结果。血细胞数据集的公开方案里切图加高分辨率几乎总是能带来 3 到 5 个点的可观提升代价是训练时间成倍增加。如果项目时间紧迫也可以只对验证集做切图推理即把验证图片切小块分别预测再在小图层面做 NMS最后映射回原图坐标。Ultralytics 的model.predict支持直接传入一个文件夹你只需要把切好的验证图片放进去它会自动完成推理。部署端使用 TensorRT 或者 ONNX 加速时必须固定输入分辨率。因为切图方案训练用 640推理也必须用 640不能动态输入否则你得到的 mAP 数据会在部署时全部失真。我在一次项目里训练时用imgsz1024导出 ONNX 时把imgsz参数漏了默认导出成了 640结果部署后板框漂得厉害反思原因就是推理分辨率小于训练分辨率小目标的特征在降采样过程中提前消失了。所以导出模型时一定要显式指定同样的imgsz最好在同一条命令里写入导出脚本保存参数。希望这篇内容能帮你在血细胞检测的落地上少走几个来回。本文还有配套的精品资源点击获取
返回列表