简介:一套YOLO目标检测配套资源面向血红细胞检测任务,适合计算机、电子信息工程、数学等专业学生用于课程设计、期末大作业或毕业设计,也适合目标检测初学者快速上手。压缩包共包含1399个文件,以709张血红细胞图像和686个XML标注文件为主体,可直接用于YOLO系列模型训练与验证,少量TXT和PKL文件负责类别配置与数据划分,整体大小仅14.56MB,下载和部署都非常轻量。目前已有453人学习下载。资源省去了手动标注的繁琐步骤,结合参数化编程和清晰注释,使用者可方便调整训练参数,快速理解检测流程;从预览看,图像与同名XML标注一一对应,便于直接开展实验和结果比对,是完成课程项目或算法验证的实用数据包。
1. 血红细胞数据集到手,先别急着用 YOLO 训练
很多朋友从压缩包里解压出几百张BloodImage_*.jpg和同名.txt,第一反应就是直接扔进 YOLO 开训。我见过不少因此翻车的案例:训练一个晚上 loss 不降,验证集 mAP 几乎为零,最后发现标签的类别 id 和配置文件里的names对不上,或者坐标没有归一化。实际上这个数据集的质量还不错,但“已标注”不代表“已就绪”,中间还差一次格式校验。下面把标签校验、目录划分、训练配置、损失观察和粘连场景调参完整走一遍,免得把时间浪费在 Debug 数据上。
2. 标签格式校验与 YOLO 坐标换算
很多人以为 YOLO 标签就是“一行五个数”,随便拿个脚本都能读。但血红细胞这类目标分布密集,标注文件里一旦出现比例换算错误,肉眼很难发现。在训练之前,我认为最值得花 20 分钟做的是三件事:检查文件映射关系、校验坐标是否越界、把标签画回原图。
2.1 文件映射关系与常见目录结构
解压后通常看到两类文件:BloodImage_00000.jpg和BloodImage_00000.txt。.txt就是 YOLO 格式标注,每一行对应一个目标,格式为:
class_id x_center y_center width height这里x_center y_center width height全部是相对图像宽高的比例值,范围在 0~1 之间。class_id是一个从 0 开始的整数。训练前要把图片和标签放在同一个数据集目录下,常见结构如下:
blood_cell/ ├── images/ │ ├── train/ │ │ └── BloodImage_00000.jpg │ └── val/ │ └── BloodImage_00100.jpg └── labels/ ├── train/ │ └── BloodImage_00000.txt └── val/ └── BloodImage_00100.txt我一般会先写一段小脚本检查是否有jpg缺少对应txt,或者txt是空文件。空标签文件在 YOLO 中表示“该图无目标”,但如果你把无目标和漏标混在一起,训练时会引入噪声。
2.2 用 Python 校验坐标是否越界
接下来就是逐行解析标签文件。下面这段代码会把所有标签文件读一遍,报告字段数错误、非数值内容以及越界坐标:
from pathlib import Path label_dir = Path("labels") errors = [] for txt in label_dir.glob("*.txt"): for line_no, line in enumerate(txt.read_text().strip().splitlines(), 1): parts = line.split() if len(parts) != 5: errors.append((txt.stem, line_no, "字段数不是5")) continue try: cls_id, cx, cy, w, h = map(float, parts) except ValueError: errors.append((txt.stem, line_no, "包含非数字")) continue # 检查中心坐标是否归一化到 [0,1] if not (0 <= cx <= 1 and 0 <= cy <= 1): errors.append((txt.stem, line_no, "中心点越界")) # 宽高超出 [0,1] 说明可能是未归一化的像素值 if w < 0 or h < 0 or w > 1 or h > 1: errors.append((txt.stem, line_no, "宽高不在0~1")) print(f"发现 {len(errors)} 个问题") for e in errors[:20]: print(e)这段代码的逻辑是先把txt按行拆分,再用map(float, parts)转成数值。如果某一步出现异常,说明标签文件被写坏。越界判断放在最后,因为只要数值能转成浮点数,再判断它是否在合理范围内。实际处理中我发现不少老旧数据集把像素坐标直接写入 YOLO 标签,这种文件在这里会被识别为“中心点越界”,需要重新做归一化。
2.3 将 YOLO 标注还原为像素框并可视化
校验通过后,一定要把标签画回原图看一眼。下面代码读取一张图和一个标签,把归一化坐标转成左上角和右下角像素坐标并画框:
import cv2 from pathlib import Path img_path = Path("images/BloodImage_00000.jpg") label_path = Path("labels/BloodImage_00000.txt") img = cv2.imread(str(img_path)) H, W = img.shape[:2] for line in label_path.read_text().strip().splitlines(): cls_id, cx, cy, w, h = map(float, line.split()) # 注意:cx,cy 是中心点,需要减半宽高得到左上角和右下角 x1 = int((cx - w / 2) * W) y1 = int((cy - h / 2) * H) x2 = int((cx + w / 2) * W) y2 = int((cy + h / 2) * H) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("check_00000.jpg", img)这里有一个容易踩的坑:YOLO 标签里的cx, cy是目标中心点,不是左上角。很多人用cx, cy直接当x1, y1,画出来的框整体向右下偏移。正确做法是像上面这样用cx - w/2和cy - h/2换算。对血红细胞这种高密度小目标,错一点就导致框和细胞边缘贴合不上,后续训练时样本噪声会非常大。
下面这张表列出我常遇到的标签问题,供快速定位:
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 训练 loss 不降 | 坐标未归一化 | 用脚本统一除以原始图宽高 |
| 可视化框整体偏移到右下 | 把中心点当成了左上角 | 改用(cx - w/2, cy - h/2) |
| 所有框类别都是 0 | 类别映射没同步 | 检查data.yaml的names顺序 |
| 某些图片没有框 | 标签为空或文件名不一致 | 建立jpg与txt的映射检查 |
3. 训练集划分与 data.yaml 配置
数据校验只是第一步,真正让 YOLO 跑起来还需要把数据组织成框架能识别的目录结构,并正确编写配置文件。这里以 YOLOv8 为例,讲清楚目录划分、类别声明和训练命令参数。
3.1 目录结构与数据划分脚本
YOLOv8 期望图片路径写在data.yaml的train和val字段里,所以我们要先把数据集切成训练集和验证集。比较稳妥的做法是 8:2 随机划分,并固定随机种子。
import random from pathlib import Path import shutil random.seed(42) src_images = Path("images") src_labels = Path("labels") out = Path("blood_cell") for split in ["train", "val"]: (out / "images" / split).mkdir(parents=True, exist_ok=True) (out / "labels" / split).mkdir(parents=True, exist_ok=True) all_files = list(src_images.glob("*.jpg")) random.shuffle(all_files) val_count = int(len(all_files) * 0.2) for i, img_path in enumerate(all_files): split = "val" if i < val_count else "train" label_path = src_labels / (img_path.stem + ".txt") if not label_path.exists(): print(f"跳过缺少标签的 {img_path.name}") continue shutil.copy(img_path, out / "images" / split / img_path.name) shutil.copy(label_path, out / "labels" / split / label_path.name) print("划分完成")这段脚本把 20% 的图片作为验证集,剩余 80% 作为训练集。采用copy而不是move,是怕原始 zip 还需要保留。shutil.copy会保留原文件,适合后续反复实验。注意划分之前一定要确保每个jpg都有同名txt,否则 YOLO 在训练时会警告“图片无标签”,并可能影响训练收敛。
3.2 编写 data.yaml 并核对类别顺序
在blood_cell目录下新建data.yaml,内容如下:
path: ./blood_cell train: images/train val: images/val names: 0: red_blood_cell如果数据集里不止红细胞一类,比如还有白细胞和血小板,那么names必须按照标签文件里的class_id顺序列出,不能只写一类。常见错误是标签里类别是 1,但names只有 0 一项,框架会报“class index out of range”。另外,path推荐写相对路径,如果和 YOLO 工作目录不同,容易出现找不到图片的诡异问题。你也可以用绝对路径,但这样项目换机器后又要改配置。
3.3 训练命令与关键参数选择
目录和配置文件准备好后,就可以启动训练。以 YOLOv8n 为例:
yolo detect train \ data=blood_cell/data.yaml \ model=yolov8n.pt \ epochs=100 \ batch=16 \ imgsz=640 \ patience=20 \ project=runs \ name=blood_rbc解释一下这里的参数:
model=yolov8n.pt:n 代表 nano,速度和显存都最友好,适合先跑通流程。epochs=100:血红细胞检测任务目标不大,100 轮通常够用,早停可以提前退出。batch=16:根据显存调整。16GB 显存跑 640 精度时这个值比较稳,如果显示 OOM,先降到 8。imgsz=640:默认分辨率。血红细胞直径一般在几十像素,640 下已经能覆盖。patience=20:验证集指标连续 20 轮不提升就停,避免无效等待。
训练过程中会看到每个 epoch 的box_loss、cls_loss、dfl_loss以及验证集的precision、recall、mAP50等指标。下一章专门讲这些数值该怎么盯。
4. YOLO 损失函数曲线与训练过程观察
训练过程中最常被问到的问题是“loss 降到多少才算好”。对 YOLOv8 来说,光看总 loss 不够,因为它由多个部分叠加而成。你需要分别关注 box、cls、dfl 三个分量,才能判断模型到底卡在哪里。
4.1 box_loss / cls_loss / dfl_loss 分别代表什么
YOLOv8 的损失函数由三部分组成,下面这张表总结它们的作用和典型下降趋势:
| 损失项 | 全称 | 作用 | 常见问题 |
|---|---|---|---|
| box_loss | Bounding Box Regression Loss | 衡量预测框和真实框的位置误差 | 不降说明定位没学准 |
| cls_loss | Classification Loss | 衡量目标类别判断误差 | 单类数据集通常很小 |
| dfl_loss | Distribution Focal Loss | 优化边界框边缘分布,让框更贴合目标 | 过高会导致框偏大或偏小 |
训练日志里box_loss一般从 1.x 降到 0.05 以下,cls_loss对于单类检测很快降到 0.01 量级,dfl_loss会缓慢下降。如果某个损失项在训练后期还在剧烈波动,常见原因是学习率过大或标签噪声太高。血红细胞数据集里如果存在边缘模糊的细胞,box_loss可能会有小幅波动,这是正常的,不要一看到波动就疯狂调参。
4.2 训练日志里值得盯的三个信号
在训练到中段时,我会重点看三个信号:
- 验证集
precision和recall是否同步上升。如果precision高而recall低,说明模型倾向保守,宁可漏检也不误检;反之则说明模型输出大量框,需要检查 NMS 阈值。 mAP50是否达到 0.8 以上。红细胞目标大且数量多,通常很容易到 0.85 以上,如果你的数据低于 0.6,先回看可视化标注结果。- 早停后的 best epoch 出现在哪个位置。如果 best epoch 是最后一轮,说明训练可能没收敛,可以把
epochs加长;如果早停发生在 30 轮以内,说明学习率偏大或数据量太小。
举个例子,我曾经用这个数据集训练时,发现cls_loss一直是 0,原因是names里只写了一个类别,而标签里实际有 0 和 1 两类,模型强行把两个类合并成一个类去学,训练指标看起来很漂亮,但推理时会把白细胞也识别成红细胞。最后重新整理data.yaml,把类别列表补全后才恢复正常。
4.3 显存不足时的常用参数调整
常见的 OOM 提示是CUDA out of memory。我的应对顺序是:先降 batch,再降 imgsz,最后才换更小的模型。比如:
yolo detect train \ data=blood_cell/data.yaml \ model=yolov8n.pt \ batch=8 \ imgsz=480 \ device=0imgsz从 640 降到 480,显存占用会大幅降低,但分辨率太低可能导致小目标漏检。如果还想恢复 batch=16 的统计稳定性,可以手动把有效 batch 提高,Ultralytics 框架内部会使用nbs参数做梯度累积,但对 BatchNorm 统计量有一定影响。对血红细胞这类目标尺寸比较均匀的数据集,imgsz=512是一个不错的折中。
5. 评估结果与错检样本的排查
训练结束后,runs/detect/blood_rbc/weights/下会生成best.pt和last.pt。很多人直接拿best.pt去推理,却忽略了验证集上还有大量信息可以用来定位问题。这一章讲如何跑验证、看混淆矩阵,以及从失败样本反推数据集问题。
5.1 用 val 脚本计算 mAP 和 PR 曲线
在 YOLOv8 中,一条命令就能完成验证:
yolo detect val \ data=blood_cell/data.yaml \ model=runs/detect/blood_rbc/weights/best.pt \ imgsz=640 \ conf=0.001 \ iou=0.6这里把conf设成 0.001 是为了让验证阶段尽量保留所有预测框,方便计算完整的 PR 曲线;iou=0.6是计算 mAP 时对预测框和真实框的匹配阈值。验证完成后,在runs/detect/val下会生成PR_curve.png和confusion_matrix.png。对红细胞检测来说,confusion_matrix.png中背景类(background)如果有较多预测落入,说明模型存在大量低置信度误检,此时可以调高推理时的conf阈值。
5.2 红细胞粘连导致的漏检原因分析
红细胞在显微镜图像中经常粘连成串,默认 NMS 参数很容易把两个相邻细胞当做一个目标。这时如果只看整体 mAP,可能只比正常情况低 0.02,但实际视觉感受会差很多。我一般会手动挑出验证集里几组粘连明显的图片,运行下面的推理并保存结果:
yolo detect predict \ model=runs/detect/blood_rbc/weights/best.pt \ source=path/to/sticky_images \ conf=0.25 \ iou=0.6 \ save=Trueiou=0.6是 NMS 的 IoU 阈值,值越小对重叠框的抑制越严格。对于粘连细胞,我会把iou调低到 0.3 再试一次,可以看到更多细胞被保留。注意 NMS 调低会带来重复框,需要在下一步用置信度排序和宽度约束过滤。
5.3 用小 Batch 复现问题与 Debug 方式
如果你怀疑是数据划分导致的偶然偏差,一个简单做法是把训练集缩小到 1/10 再训 50 轮,看训练 loss 能不能降到接近 0。如果小数据集上 loss 都降不下去,说明标签噪声或格式问题依旧存在。另一种 Debug 方式是直接读取训练日志里的results.csv,画一张训练曲线:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/blood_rbc/results.csv") # 对比训练和验证的 box_loss,观察是否过拟合 plt.plot(df["epoch"], df["train/box_loss"], label="train box_loss") plt.plot(df["epoch"], df["val/box_loss"], label="val box_loss") plt.legend() plt.savefig("loss_curve.png")这段代码把训练和验证的 box_loss 画在一起,能直观看出是不是在过拟合。如果 val box_loss 在某个 epoch 后开始反弹,而 train box_loss 仍在下降,就说明模型开始记住训练集中的噪声,此时应恢复更早的best.pt或调大patience提前停止。
6. 粘连场景 NMS 调参与批量推理脚本
血红细胞检测的最终目的往往是计数或统计形态,这就需要在推理阶段输出每个细胞的框坐标和置信度。默认参数在粘连场景下会丢框,下面给出一个可直接用的调参思路和批量导出脚本。
6.1 调低 IoU 阈值保留粘连目标
YOLO 推理时的iou参数控制 NMS 对重叠框的抑制强度。默认iou=0.45在红细胞粘连严重的图上会把相邻的框合并成一个。我通常先试iou=0.25,同时把conf从 0.25 提到 0.35,用置信度过滤掉 NMS 带来的重复框:
yolo detect predict \ model=runs/detect/blood_rbc/weights/best.pt \ source=images/val \ conf=0.35 \ iou=0.25 \ save_txt=True调低iou后,同一目标可能被输出两个框,此时优先保留置信度高的那个。如果还是双框,可以在后处理里计算框中心距,合并距离小于 5 像素的框。这里的关键是记住:iou是 NMS 阈值,conf是置信度阈值,两者配合使用,不要只调一个。
6.2 把预测结果导出成 CSV 供下游统计
当数据量达到几百张图时,手动翻可视化图片不现实。我一般会把结果写到 CSV,后续用 Pandas 按图像统计细胞数量、平均面积等:
from ultralytics import YOLO import csv model = YOLO("runs/detect/blood_rbc/weights/best.pt") results = model("images/val", conf=0.35, iou=0.25) with open("rbc_boxes.csv", "w", newline="") as f: writer = csv.writer(f) writer.writerow(["image", "class", "x1", "y1", "x2", "y2", "conf"]) for r in results: for box in r.boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() cls = int(box.cls[0]) conf = float(box.conf[0]) writer.writerow([r.path, cls, int(x1), int(y1), int(x2), int(y2), round(conf, 4)])这段脚本把验证集里每张图的预测框都写到rbc_boxes.csv。r.path是输入图片路径,box.xyxy是像素坐标的左上角和右下角,box.cls是类别 id。注意box.conf已经被conf参数过滤过,所以 CSV 里不会再出现置信度低于 0.35 的框。统计时可以直接按 image 分组,数出每张图的红细胞数量,再和标签中的真实数量对比,不一致的样本回到 CSV 里检查x1/y1/x2/y2的坐标范围,通常能定位是 NMS 丢框还是标签漏标。
本文还有配套的精品资源,点击获取