YOLO血细胞检测实战:红细胞、白细胞、血小板识别与计数全流程
发布时间:2026/9/28 16:42:36来源:尧图网络
简介本资源面向医学图像处理与深度学习入门者提供一套基于YOLO模型的红细胞、白细胞、血小板识别与计数系统完整方案可用于临床辅助诊断研究或课程设计实践。压缩包共约2000个文件以1974个txt标注数据为主辅以yaml配置、py源码、xml标注及css样式文件整体约276.9MB涵盖数据集、训练脚本与界面程序。项目包含UIProgram用户界面、TestFiles测试图片、models预训练模型、datasets数据集及save_data训练记录等模块并集成wandb实验跟踪工具便于调试与优化。已有85人学习下载读者可获取可直接运行的Python源码、标注数据与模型文件快速复现血细胞检测计数流程理解YOLO在医学图像中的应用与工程组织方式。1. 血涂片里的三行数字为什么用 YOLO 做红细胞、白细胞、血小板识别与计数一张外周血涂片显微镜下几百个细胞检验科医生要数出红细胞、白细胞、血小板三类目标的数量和比例。手工计数一个样本动辄十几分钟还受疲劳和主观判断影响。用 YOLO 做红细胞、白细胞、血小板识别与计数本质是把「找细胞」和「数细胞」这两件事交给一个目标检测模型红细胞数量最多、白细胞有内核、血小板体积最小三类目标在形态和尺度上差异极大正好是检验检测模型能力的场景。这套系统适合医学图像方向的学生、做体外诊断设备的工程师以及想找一个真实数据集练手 YOLO 全流程的人。Python 源代码加数据集的组合意味着你能从数据标注一路跑到推理计数而不是只调一个现成接口。下面把我实际做这类项目时的选型、训练、计数和踩坑过程拆开讲。2. 数据准备与标注三类细胞怎么标才不返工2.1 红细胞、白细胞、血小板的数据集长什么样血涂片数据集通常来自显微镜拍摄的染色图像常见格式是每张图对应一个标注文件。三类目标的分布极不均衡一张图里红细胞可能上百个白细胞只有几个血小板零散分布且和红细胞碎片、染色杂质容易混淆。我一般先统计每类目标的实例数如果白细胞占比低于 2%训练时就要考虑类别权重或者过采样否则模型会把白细胞当成背景。标注工具用 LabelImg 或 CVAT 都行输出 YOLO 格式的 txt每行是类别 中心x 中心y 宽 高坐标全部归一化到 0 到 1。这里有个血泪经验红细胞边界模糊标注时容易把相邻两个红细胞框成一个或者把重叠区域切错。我的做法是框尽量贴合细胞膜外缘重叠细胞各自独立成框宁可多标一个也不合并。血小板最难直径可能只有十几个像素标注前先把图像放大到 200% 以上再画框。数据集划分按 8:1:1 分训练、验证、测试。注意同一张原始涂片不要同时出现在训练集和验证集里否则验证指标虚高这是很多人翻车的地方。划分脚本可以这样写import os import random import shutil # 原始图片和标注目录 img_dir raw/images label_dir raw/labels # 输出目录 out_root dataset splits {train: 0.8, val: 0.1, test: 0.1} files [f for f in os.listdir(img_dir) if f.endswith((.jpg, .png))] random.seed(42) # 固定随机种子保证可复现 random.shuffle(files) n len(files) n_train int(n * splits[train]) n_val int(n * splits[val]) assign {} for i, f in enumerate(files): if i n_train: assign[f] train elif i n_train n_val: assign[f] val else: assign[f] test for f, split in assign.items(): # 复制图片 shutil.copy(os.path.join(img_dir, f), os.path.join(out_root, images, split, f)) # 复制同名标注 stem os.path.splitext(f)[0] src_lab os.path.join(label_dir, stem .txt) if os.path.exists(src_lab): shutil.copy(src_lab, os.path.join(out_root, labels, split, stem .txt)) print(划分完成:, {k: list(assign.values()).count(k) for k in splits})这段脚本做了三件事固定随机种子打乱文件、按比例分配 split、把图片和同名标注一起复制到对应目录。参数上random.seed(42)保证每次划分一致方便复现splits字典可以按数据集大小调整小数据集可以把验证集比例提到 0.15。跑完后检查dataset/labels/train里每个 txt 是否和图片一一对应缺标注的图片要单独处理不能直接丢进去训练。2.2 标注格式转换与数据增强的边界如果你拿到的标注是 VOC 的 xml需要转成 YOLO 的 txt。转换时注意 VOC 的坐标是左上角和右下角的绝对像素值要除以图像宽高归一化并且中心点坐标是(xminxmax)/2/width。转换脚本里最容易错的是类别名到类别 id 的映射红细胞、白细胞、血小板三个名字必须和data.yaml里的names顺序完全一致否则训练出来的类别全乱。数据增强方面血涂片图像不适合做水平翻转以外的几何变换。因为细胞形态有方向性垂直翻转和大幅旋转会造出不存在的细胞朝向模型学到的是伪特征。我一般只开水平翻转、轻微亮度和对比度扰动HSV 的色调扰动要关掉染色颜色是判断白细胞类型的重要线索改色调等于破坏标签。Mosaic 增强可以用但血小板太小四图拼接后容易在边缘被裁掉建议把 Mosaic 概率降到 0.3 以下。注意标注完成后一定要用可视化脚本把框画回原图检查一遍尤其是血小板漏标和错标在训练 loss 上不会明显报警但会直接拉低召回。3. YOLO 训练配置从预训练权重到三类细胞收敛3.1 环境搭建与预训练模型选择环境用 Python 3.9 以上PyTorch 装对应 CUDA 版本。YOLO 系列里我一般选 YOLOv8 或 YOLOv11 的检测模型因为它们的 anchor-free 结构对小目标更友好血小板这种十几个像素的目标用 v5 的 anchor 机制容易漏。预训练权重从官方发布页下载不要用来路不明的权重否则训练初期 loss 会异常震荡。安装依赖和验证环境# 创建虚拟环境 python -m venv yolo_env source yolo_env/bin/activate # Windows 用 yolo_env\Scripts\activate # 安装 ultralytics 和基础依赖 pip install ultralytics opencv-python matplotlib # 验证 GPU 是否可用 python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))如果输出True和显卡型号说明环境通了。V100 这类卡跑 640 尺寸的 YOLO 训练batch 可以开到 32 以上消费级 8G 显存建议 batch 设 8 到 16配合ampTrue混合精度省显存。这里有个常见坑torch.cuda.is_available()返回 False 但驱动明明装了多半是 PyTorch 版本和 CUDA 版本不匹配去 PyTorch 官网按命令重装别自己猜版本号。3.2 data.yaml 与训练参数怎么设data.yaml是训练入口三个关键字段path指向数据集根目录train/val/test指向划分后的图片目录names是类别名列表。写成这样path: ./dataset train: images/train val: images/val test: images/test names: 0: RBC 1: WBC 2: PLT类别顺序必须和标注时的类别 id 一致。RBC 是红细胞WBC 是白细胞PLT 是血小板用英文缩写是为了避免中文路径在某些系统下的编码问题。训练命令yolo detect train \ modelyolov8s.pt \ datadataset/data.yaml \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ mosaic0.3 \ hsv_h0.0 \ hsv_s0.3 \ hsv_v0.3 \ fliplr0.5 \ projectruns/blood \ nameexp1参数逐个说modelyolov8s.pt是预训练权重s 版本在精度和速度间平衡血小板多的话可以换 m 版本epochs150配合patience30早停验证集 30 轮不提升就停省时间imgsz640是输入尺寸血小板太小可以提到 1024但显存和速度要重新评估lr00.01初始学习率lrf0.01是最终学习率系数余弦退火到初始的百分之一mosaic0.3降低拼接概率保护小目标hsv_h0.0关掉色调扰动前面说过原因fliplr0.5水平翻转保留。训练过程中重点看三个指标metrics/mAP50看整体检测能力metrics/mAP50-95看框的精度以及每类的precision和recall。如果白细胞 recall 一直上不去先查标注里白细胞是不是漏标再考虑加类别权重。YOLO 的损失函数由框回归、置信度和分类三部分组成训练日志里box_loss下降慢说明框不准cls_loss高说明类别混淆红细胞和血小板在低倍镜下颜色接近时容易混。3.3 训练不收敛时的排查顺序训练翻车的表现有好几种按这个顺序查效率最高。第一loss 从第一轮就是 nan检查标注文件里有没有坐标超过 1 或者负数归一化算错会直接让损失爆炸。第二loss 下降但 mAP 不动多半是学习率太大或者数据增强过猛先把lr0降到 0.001 试。第三某一类指标始终为 0去验证集可视化里看这类目标有没有被预测出来没有的话检查data.yaml的names顺序和标注 id 是否对得上。第四BN 层崩溃表现为 loss 突然飙升这是 batch 太小或者学习率太高导致的把 batch 调大或者加梯度裁剪。这些排查动作我一般做成一个 checklist每次训练前过一遍能省下大量重跑时间。4. 计数逻辑与后处理从检测框到三行数字4.1 检测结果去重与计数规则模型输出的是检测框直接数框会重复计数因为同一细胞可能被多个框命中。标准做法是用 NMS 非极大值抑制YOLO 推理时已经内置但 IoU 阈值要按细胞类型调。红细胞密集重叠IoU 阈值设 0.5 容易把相邻细胞合并我一般设 0.3 到 0.4让重叠框保留更多。血小板小且分散IoU 可以设 0.5。白细胞数量少阈值影响不大。推理和计数的代码from ultralytics import YOLO import cv2 model YOLO(runs/blood/exp1/weights/best.pt) # 推理单张图conf 是置信度阈值iou 是 NMS 阈值 results model.predict( sourcetest_sample.jpg, conf0.25, iou0.35, imgsz640, saveTrue ) # 统计每类数量 counts {RBC: 0, WBC: 0, PLT: 0} names results[0].names # {0: RBC, 1: WBC, 2: PLT} for box in results[0].boxes: cls_id int(box.cls[0]) cls_name names[cls_id] counts[cls_name] 1 print(红细胞:, counts[RBC]) print(白细胞:, counts[WBC]) print(血小板:, counts[PLT]) # 计算白细胞比例临床常看这个 total_wbc_rbc counts[WBC] counts[RBC] if total_wbc_rbc 0: ratio counts[WBC] / total_wbc_rbc * 100 print(f白细胞占比: {ratio:.2f}%)conf0.25是置信度阈值低于这个值的框直接丢血小板可以降到 0.15 提高召回但会引入假阳性需要看具体场景取舍。iou0.35控制 NMS 合并程度前面解释过。计数逻辑就是遍历boxes按类别累加。白细胞占比是临床参考指标正常范围在千分之一到百分之一量级如果算出来偏差大先回头查检测精度。4.2 把计数结果做成可复现的批量脚本单张推理只能验证流程实际用要批量跑整个测试集并输出汇总表。批量脚本要处理图片读取失败、空检测结果、类别缺失这些边界情况import os import csv from ultralytics import YOLO model YOLO(runs/blood/exp1/weights/best.pt) test_dir dataset/images/test out_csv counts_result.csv rows [] for fname in sorted(os.listdir(test_dir)): if not fname.lower().endswith((.jpg, .png)): continue img_path os.path.join(test_dir, fname) results model.predict(sourceimg_path, conf0.25, iou0.35, verboseFalse) counts {RBC: 0, WBC: 0, PLT: 0} if len(results) 0: names results[0].names for box in results[0].boxes: counts[names[int(box.cls[0])]] 1 rows.append([fname, counts[RBC], counts[WBC], counts[PLT]]) with open(out_csv, w, newline) as f: writer csv.writer(f) writer.writerow([image, RBC, WBC, PLT]) writer.writerows(rows) print(f共处理 {len(rows)} 张结果写入 {out_csv})verboseFalse关掉每张图的日志批量跑的时候屏幕干净。results为空说明这张图没检测到任何目标计数保持 0不要报错中断。输出 CSV 方便后续用 pandas 做统计和可视化。跑完拿几张图人工核对计数如果某张图红细胞数明显偏少去看是不是 NMS 阈值把重叠细胞合并了。提示批量推理前先用model.val()在测试集上跑一遍拿到每类的 mAP 和混淆矩阵确认模型没有把红细胞系统性误判成血小板再进入计数环节。5. 避坑与常见问题血细胞检测里最容易翻车的五件事现象一训练 mAP 很高实际计数却差很多。原因通常是验证集和测试集来自同一张涂片的不同区域模型记住了这张涂片的染色风格换一张涂片就失效。解决方法是划分数据时按涂片来源分组同一张涂片的图只进一个 split测试集必须包含训练时没见过的涂片。现象二血小板几乎检测不到。原因是血小板在 640 输入尺寸下只有几个像素特征在 backbone 下采样后基本丢失。解决办法是把imgsz提到 1024 或 1280或者在模型里加 P2 小目标检测层。如果显存不够可以切图推理把大图裁成小块分别检测再合并计数。现象三白细胞被识别成红细胞。低倍镜下白细胞内核不明显颜色和红细胞接近。先确认标注时白细胞框是否准确再看训练时hsv_h是不是被误开了色调扰动会让模型分不清两类颜色。把hsv_h设为 0并适当提高白细胞的类别权重。现象四同一细胞被数了两次。NMS 的 IoU 阈值设太高重叠框没被抑制。红细胞密集区域把iou降到 0.3同时检查推理时有没有重复调用模型。如果用了切图推理块与块之间的重叠区域要做去重否则边界上的细胞会被两块各数一次。现象五训练到一半 loss 突然爆炸。常见原因是学习率太高或者 batch 太小导致 BN 统计量失稳。先把lr0减半batch加到显存允许的最大值还不行就加warmup_epochs让前几轮学习率慢慢升。如果用的是预训练权重确认权重和模型结构匹配不匹配的权重加载会静默失败训练从随机初始化开始loss 自然难看。6. 把计数误差压下去一个可落地的验证习惯模型训完、计数脚本跑通不代表这套系统能用。我最后一定会做一件事拿一批人工计数的样本做对照算每类细胞的计数误差。具体做法是选 20 到 30 张测试图让标注人员逐图数出红细胞、白细胞、血小板真实数量然后和模型输出做对比算平均绝对误差和相对误差。红细胞基数大相对误差控制在 5% 以内算可用白细胞数量少差一个就是百分之十几所以看绝对误差更合理血小板最难相对误差 15% 以内我已经能接受。对照表长这样图片真实 RBC预测 RBC真实 WBC预测 WBC真实 PLT预测 PLT001.jpg186179442319002.jpg210205331721003.jpg154160543126从表里能看出误差分布红细胞偶尔多算或少算几个多半是重叠区域 NMS 的取舍白细胞基本准血小板波动最大漏检和误检都有。针对血小板我会单独把conf从 0.25 降到 0.15 再跑一遍看召回提升多少、假阳性增加多少取一个平衡点。这个调参过程没有捷径就是拿数据说话。还有一个习惯每次改完参数重新训练都在runs/blood下新建一个exp目录不要覆盖旧结果。YOLO 默认会递增命名但手动改配置时容易覆盖。保留每次实验的权重和日志后面对比不同参数的效果时才有依据。我吃过这个亏一次把lr0从 0.01 改到 0.001 重新训练结果把之前效果好的权重覆盖了只能重跑一遍。如果要把这套系统往产品方向推下一步是量化模型和推理加速把 PyTorch 权重转成 ONNX 或 TensorRT在边缘设备上跑实时计数。但那是另一个话题了先把检测精度和计数逻辑做扎实后面的优化才有意义。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网