简介:面向目标检测入门与进阶人群的YOLO红细胞检测数据集,使用真实场景下的高质量图像构建,包含1000张已标注图片,场景覆盖多种光照和背景条件,可支撑医学影像分析、细胞识别等方向的课程设计、毕业设计或算法预研。数据经LabelImg人工精细标注,同时输出VOC(xml)、COCO(json)、YOLO(txt)三种主流格式标签,分别存放于独立文件夹,导入YOLO系列模型即可直接训练,省去格式转换耗时。资源包共2000个文件,以1000个XML标注文件与990个TXT标注文件为主,另含6个HTML操作教程、3个Python划分脚本、1个YAML配置文件;压缩包仅19.82MB,目录轻量规整。教程覆盖Linux与Windows双平台环境搭建、YOLO训练案例修改和数据集划分,提供按需切分训练集/验证集/测试集的脚本,配合使用可快速完成从环境配置到模型训练的全流程。目前已有347人学习下载,适合希望减少数据准备成本、快速验证YOLO检测方案的学习者。
1. 用 YOLO 挑出显微镜下的红细胞:这份数据集到底解决了什么问题
拿到「YOLO 红细胞目标检测数据集」这个压缩包时,多数人其实不是缺模型,而是缺一份能直接喂给训练脚本的标注数据。显微图像里的红细胞密集、粘连、边缘不清晰,自己用 LabelImg 一格格打标,1000 张图至少耗掉两三个工作日,打完还要担心框偏了几像素。这份数据集的直接价值,是把「从零标注」压缩成「解压后跑通训练」:1000 张图片配上 VOC、COCO、YOLO 三种格式标签,等于同时覆盖了训练、评估、格式迁移三条线,再叠加划分脚本和训练教程,解决的是从数据到权重文件的最后一公里。适合做医学检验图像分析、血涂片细胞计数,或者想拿红细胞场景练手目标检测流程的从业者。我拿到这类包的习惯是:先验证标签一致性,再谈训练,否则后面所有指标都可能是错的。
2. 三种标签格式对齐:VOC、COCO 与 YOLO 怎么互相转换
解压后大概率会看到三个标注目录:一个装 VOC 的 XML 文件,一个装 COCO 的 JSON 文件,一个装 YOLO 的 TXT 文件。很多教程会告诉你三种格式都能训练,但没人说清楚它们到底差在哪、什么时候该用哪一份。不把这个问题搞清楚,后面划分数据集时很容易把三份标签切乱,训练出来的模型指标再漂亮,换个评估脚本就露馅。
2.1 VOC、COCO、YOLO 三种标签,分别对应哪个环节
三种格式不是同一份数据的三种写法,它们面向的工具链完全不同。
VOC 格式以 XML 文件为单位,每张图片对应一个同名 XML,里面用<bndbox>记录xmin、ymin、xmax、ymax四个整数坐标。优点是人眼可读,用文本编辑器打开就能检查标注位置,LabelImg 默认输出的就是这种格式。缺点是每张图一个文件,数据集大了文件数量翻倍,训练脚本读取时要逐个解析。
COCO 格式把所有标注塞进一个 JSON 文件,用images、annotations、categories三个数组互相索引。它最核心的设计是category_id和image_id这两个外键,评估工具 pycocotools 原生支持这套结构,所以做学术对比、跑 mAP 指标时基本绕不开它。缺点是 JSON 嵌套深,肉眼排查困难,文件一旦损坏整个数据集报废。
YOLO 格式是三个里面最「省事」的:每张图片对应一个 TXT,每行一个目标,格式为类别id x_center y_center width height,而且四个坐标值全部归一化到 0~1 之间。归一化意味着标注不随图像尺寸变化,模型训练时不管输入 640 还是 512,标签都不用改。Ultralytics 的 YOLO 系列训练器也只认这种格式。
三种格式的坐标体系差异是转换时最容易出错的点。以下用一个表格把它们的关键差异列出来:
| 格式 | 文件形式 | 坐标表达 | 适合环节 | 常见坑 |
|---|---|---|---|---|
| VOC | 每图一个 XML | 绝对像素整数 | 人工标注、可视化检查 | 坐标越界 |
| COCO | 全局一个 JSON | 绝对像素浮点 | 评估、对比实验 | category_id 基数 |
| YOLO | 每图一个 TXT | 归一化浮点 | 模型训练 | 坐标与图片不匹配 |
理解了这个对应关系,你就知道这份数据集为什么值得用:训练时用 YOLO 格式,跑评估对比时用 COCO 格式,出问题时用 VOC 格式人工核对。三种格式互相转换,实际上就是在这三套坐标体系之间做翻译。
2.2 把三份标签对齐:一个解析脚本看清边界框差异
拿到数据集后,先别急着训练。第一步是写个脚本随机抽三张图,把同一张图片的三种标签全部画出来叠在原图上,确认框的位置一致。这一步花不了十分钟,但能拦下 80% 的「标签错位」事故。下面是我常用的校验脚本,解压后放在数据集根目录运行即可。
import cv2 import json import random import xml.etree.ElementTree as ET def load_voc_boxes(xml_path): """从 VOC XML 中读取边界框,返回 (xmin, ymin, xmax, ymax) 列表""" tree = ET.parse(xml_path) root = tree.getroot() boxes = [] for obj in root.iter("object"): bndbox = obj.find("bndbox") boxes.append(( int(bndbox.findtext("xmin")), int(bndbox.findtext("ymin")), int(bndbox.findtext("xmax")), int(bndbox.findtext("ymax")), )) return boxes def load_yolo_boxes(txt_path, img_w, img_h): """读取 YOLO 归一化坐标,转回绝对像素坐标""" boxes = [] with open(txt_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue _, cx, cy, w, h = parts cx, cy, w, h = float(cx), float(cy), float(w), float(h) xmin = (cx - w / 2) * img_w ymin = (cy - h / 2) * img_h xmax = (cx + w / 2) * img_w ymax = (cy + h / 2) * img_h boxes.append((int(xmin), int(ymin), int(xmax), int(ymax))) return boxes def load_coco_boxes(json_path, image_id): """从 COCO JSON 中按 image_id 提取边界框""" with open(json_path, "r") as f: data = json.load(f) boxes = [] for ann in data["annotations"]: if ann["image_id"] != image_id: continue x, y, w, h = ann["bbox"] boxes.append((int(x), int(y), int(x + w), int(y + h))) return boxes # 随机抽一张图做比对,路径按你自己的目录结构调整 img_files = os.listdir("images/all") sample = random.choice(img_files) img = cv2.imread(os.path.join("images/all", sample)) ih, iw = img.shape[:2] # 加载三种标签 voc_boxes = load_voc_boxes(f"annotations/voc/{sample.replace('.jpg', '.xml')}") yolo_boxes = load_yolo_boxes(f"annotations/yolo/{sample.replace('.jpg', '.txt')}", iw, ih) coco_id = int(os.path.splitext(sample)[0]) coco_boxes = load_coco_boxes("annotations/coco/annotations.json", coco_id) # 分别画框,颜色不同便于区分 for box in voc_boxes: cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), (0, 255, 0), 2) for box in yolo_boxes: cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), (0, 0, 255), 2) for box in coco_boxes: cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), (255, 0, 0), 2) cv2.imwrite("check_boxes.jpg", img) print("三种标签框数:", len(voc_boxes), len(yolo_boxes), len(coco_boxes))这段脚本把 VOC、YOLO、COCO 三份标注分别用绿、红、蓝三种颜色的框画在同一张图上。注意 COCO 的bbox字段记录的是左上角坐标加宽高,而 VOC 和 YOLO 转换后得到的是左上角与右下角坐标,我在load_coco_boxes里做了x + w的换算。如果三条标注是同一份数据转出来的,三种颜色的框应该基本重合,如果同一目标的框错开几个像素,说明转换时坐标没对齐;如果框数都不一样,说明某一份标签漏了目标。
2.3 三份标签互相转换时的三个必查点
如果你打算基于这份数据扩展标注或迁移到其他模型,一定会遇到自己写转换脚本的场景。下面三个点我每次都会查一遍。
第一,YOLO 的归一化坐标必须落在 0~1 之间。转换脚本里很容易写错除以的高度还是宽度。x_center和box_w一律除以图片宽度,y_center和box_h一律除以图片高度。如果出现大于 1 的值,要么是除反了,要么是原标注本身越界,后者要回到 VOC XML 里检查坐标是否超出图像尺寸。
第二,COCO 的category_id从 1 开始,YOLO 的class_id从 0 开始。官方 COCO 数据集的类别 id 从 1 排起,0 被留给背景。把 COCO 转成 YOLO 时,要对类别 id 减 1;反向转换时加 1。这个偏差不会导致训练报错,但会让评估曲线的 AP 值全部变成 0,因为它把背景当成了一个真实类别。我在帮别人排查这种问题时,十次里有七次是这里出错。
第三,VOC 的 XML 中有可能出现difficult或truncated标记。转换时如果不过滤掉difficult=1的样本,训练时模型会对这些难例产生错误的梯度信号。常见处理是转换时直接跳过带difficult标记的目标,保留反而容易让模型对遮挡目标产生误判。
这三份标签本身的价值是给你一个交叉验证的机会:用 VOC 生成 YOLO,再用 COCO 生成 YOLO,两份结果做 diff,如果是同一份图像数据,这两份 YOLO 标签应该逐行一致。任何不一致都说明原始标注被某一步处理动过。数据这关过了,再聊训练才有意义。
3. 划分脚本与训练教程:从解压到跑通第一轮训练
数据验证完,接下来就是把数据集切成训练集、验证集、测试集,然后开始训练。这份压缩包标题里带了划分脚本和训练教程,意味着你不需要自己从头写数据处理逻辑,但你需要知道脚本在干什么,以及训练命令里的每个参数为什么是这个值。
3.1 按 8:1:1 切分数据集:固定随机种子是关键
目标检测数据集的划分比例,常见做法是训练集 80%、验证集 10%、测试集 10%。1000 张图不算多,验证集和测试集各 100 张是底线,少于 60 张时评估指标波动会非常大,一个误检框就能让 mAP 掉几个点。下面这个划分脚本,我每次拿到新数据集都会改改用,它按文件名排序后再打乱,保证脚本每次运行结果一致。
import os import random import shutil # 固定随机种子,这是标签与图片不错位的前提 random.seed(42) dataset_root = "red_cell_dataset" img_all_dir = os.path.join(dataset_root, "images", "all") label_all_dir = os.path.join(dataset_root, "annotations", "yolo") # 只处理 jpg,避免隐藏文件混入 all_images = [f for f in os.listdir(img_all_dir) if f.endswith(".jpg")] all_images.sort() # 排序后再 shuffle,保证跨平台结果一致 random.shuffle(all_images) train_ratio, val_ratio = 0.8, 0.1 n_train = int(len(all_images) * train_ratio) n_val = int(len(all_images) * val_ratio) split_result = { "train": all_images[:n_train], "val": all_images[n_train:n_train + n_val], "test": all_images[n_train + n_val:], } # 为每个子集创建图片和标签目录 for split_name, img_list in split_result.items(): img_dest = os.path.join(dataset_root, "images", split_name) label_dest = os.path.join(dataset_root, "labels", split_name) os.makedirs(img_dest, exist_ok=True) os.makedirs(label_dest, exist_ok=True) for img_name in img_list: # 复制图片 src_img = os.path.join(img_all_dir, img_name) dst_img = os.path.join(img_dest, img_name) shutil.copy2(src_img, dst_img) # 复制同名标签,注意扩展名从 .jpg 换成 .txt label_name = img_name.replace(".jpg", ".txt") src_label = os.path.join(label_all_dir, label_name) dst_label = os.path.join(label_dest, label_name) if os.path.exists(src_label): shutil.copy2(src_label, dst_label) print("划分完成:", {k: len(v) for k, v in split_result.items()})这个脚本里有三个细节值得注意。第一,random.seed(42)必须放在random.shuffle之前,而且all_images.sort()要先执行。如果跳过排序,Windows 和 Linux 的文件系统返回的文件顺序不同,同一台机器上两次运行可能得到不同的划分结果,导致你复现实验时指标对不上。第二,我故意用shutil.copy2而不是os.rename,因为在没确认数据集完整之前,保留原始文件给你留了后悔药。第三,标签扩展名替换用的是replace(".jpg", ".txt"),如果你图片是.png或.jpeg,记得同步修改,否则会出现图片有、标签无的情况,训练时直接报错。
划分完之后,目录结构应该是这样的:
red_cell_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/注意我这里把标签从annotations挪到了labels目录,因为 Ultralytics YOLO 默认约定是图片和标签分开放,图片在images,标签在labels,两边子目录名必须一致。你如果保留原来的目录结构,就需要在 YAML 配置文件里写清楚绝对路径,否则训练器找不到标签。
3.2 用 Anaconda 配 YOLOv8 环境:最小命令与安装参数
YOLOv8 的训练环境配置是新手翻车重灾区,主要问题不是命令复杂,而是 Python 版本和依赖库冲突。我用 Anaconda 配环境的固定流程如下,每一步都有明确目的。
conda create -n yolov8 python=3.9 -y conda activate yolov8 pip install ultralytics用 Python 3.9 而不是最新的 3.12,是因为 PyTorch 对 3.9 的预编译包支持最稳。pip install ultralytics会自动拉取依赖的 torch、torchvision、opencv-python 等库,不需要手动逐个安装。Anaconda 环境隔离的价值在于,你机器上可能还有跑 TensorFlow 或其他项目的环境,依赖版本互相污染时,排查成本远超安装成本。
如果你需要 CPU 训练(比如只有笔记本,没独显),PyTorch 默认安装的是 CPU 版本,能跑但速度慢。1000 张图 640 分辨率下,CPU 训练一个 epoch 可能要几分钟到十几分钟,作为验证流程可以接受,但调参就别想了。有 NVIDIA 显卡的话,建议确认 CUDA 可用:
python -c "import torch; print(torch.cuda.is_available())"输出True才说明 GPU 可用。如果输出False,最常见原因是 PyTorch 装成了 CPU 版,卸载后从 PyTorch 官网按你的 CUDA 版本重新安装即可。这块没有捷径,环境不对后面全是白干。
3.3 训练命令与 6 个必调超参数
环境就绪后,训练命令本身不长,但数据配置文件是坑点。先把数据集路径写进一个 YAML 文件,内容如下:
path: /absolute/path/to/red_cell_dataset train: images/train val: images/val test: images/test names: 0: red_cellpath强烈建议写绝对路径,不要写./相对路径。训练器的工作目录和脚本目录可能不在同一层,相对路径解析失败时,错误信息不会直接告诉你路径错了,而是报「Image not found」,新手很难联想到是路径问题。1000 张图的单类目标检测,names只有一个类,id 从 0 开始。
然后执行训练:
yolo train data=red_cell.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0这里model=yolov8s.pt表示使用 YOLOv8s 预训练权重作为起点,不是从零开始训练。1000 张图对目标检测来说属于小数据集,不用预训练权重的话,模型很难收敛到可用水平。下面是我会重点关注并调整的六个超参数:
| 参数 | 默认值 | 红细胞场景建议 | 调整依据 |
|---|---|---|---|
| model | yolov8n.pt | yolov8s.pt 或 yolov8m.pt | n 模型容量太小,小目标易漏检 |
| imgsz | 640 | 640 | 低于 512 会损失小细胞特征 |
| batch | 16 | 显存允许就 32 | 太小导致 BN 统计不稳定 |
| epochs | 100 | 150~200 | 小数据集收敛慢,早停耐心要大 |
| lr0 | 0.01 | 0.005~0.01 | 类别少时学习率过大会震荡 |
| patience | 50 | 50 | 连续 50 轮无提升才停 |
这六个参数里,model的选择最影响最终精度。红细胞的形态特征是圆润、尺寸小、密集排列,yolov8n的主干网络太浅,对 20×20 像素以下的小目标特征提取能力有限。我自己的经验是直接用yolov8s,显存 6G 以上可以上yolov8m。batch的坑在于显存不够时不要硬撑,优先把imgsz从 640 降到 512,比调小 batch 更保精度。训练过程中多看看终端输出的 mAP50 和 mAP50-95,前者衡量粗定位能力,后者对框的精确度更敏感。红细胞这类任务,mAP50 达到 0.9 以上才算及格,mAP50-95 一般会比 mAP50 低 0.15~0.25,这个差距是正常的。
4. 红细胞数据集训练的 5 个常见问题与避坑记录
这部分内容是血泪经验。我把训练这个数据集过程中最容易踩的坑按「现象 → 原因 → 解决」整理出来,你训练时对照排查,能省下大量无效时间。
4.1 训练 loss 掉得很快,验证 mAP 却全是 0:八成是标签错位
现象:训练前几个 epoch 的 box_loss 从 0.1 快速下降到 0.02,训练集精度看着不错,但验证集 mAP 一直是 0,或者每隔几个 epoch 突然跳一下又归零。
原因:划分数据集时图片和标签没有按同一顺序复制。比如图片按文件名排序后打乱,但标签却按原始目录顺序复制,导致images/train/001.jpg对应的其实是另一张图的标签。训练时模型学习到的框和图像内容错位,loss 因为拟合了错误标注而下降,但验证时这种错位的标签无法产生有效匹配,mAP 自然全零。
解决:回到 3.1 节的划分脚本,确保图片和标签共用同一个img_list,用文件名作为唯一关联键。不要用目录顺序复制,你必须假设操作系统返回的文件顺序是不可靠的。划分完后随机抽 10 张训练图,用 2.2 节的校验脚本画框确认。
4.2 粘连成团的红细胞漏检一片:检查标注是簇还是单细胞
现象:模型对分散的单细胞检测很好,但对三五个连成一团的红细胞几乎全部漏检,有时候把整个团识别成一个目标,框比正常细胞大三倍。
原因:红细胞在血涂片上天然容易粘连,如果原始标注把粘连的细胞簇标成了一个框,模型学到的是「簇」的特征,而不是「单个细胞」。预测时遇到类似团块,它倾向于输出一个大框,而 NMS 会把内部可能存在的多个小置信度框全部抑制掉。
解决:先统计标注框的宽高比,红细胞近似圆形,正常框的宽高比在 0.7~1.3 之间。如果出现大量宽高比大于 1.5 的框,说明标注大概率把细胞簇当成单个目标了。这时候需要回到标注阶段拆分框,或者接受这个数据集的目标定义就是「细胞簇」而不是「单细胞」,按簇来评估。如果是后者,推理时的 NMS 阈值需要调低,否则簇内多个框互相抑制,问题会更严重。
4.3 开启 mosaic 后小目标指标坐过山车
现象:训练过程前 50 个 epoch mAP 稳步上升,过了某个节点后突然掉 10 个点,然后恢复,反复波动。显存消耗也比预期高很多。
原因:Ultralytics YOLO 默认开启 mosaic 数据增强,它把四张图拼成一张训练。红细胞本身目标就小,拼接后目标占比进一步缩小,而且拼接边界可能把细胞切成两半。模型在增强数据上学到的特征与真实分布偏移,验证集表现就来回震荡。
解决:训练命令里显式关闭或降低 mosaic 概率,加上mosaic=0.0参数。如果你不想全关,可以设置mosaic=0.5,让一半样本保留原始构图。另外要注意,新版 Ultralytics 在最后 10 个 epoch 会自动关闭 mosaic,但这个行为在不同版本之间不一致,手动设置最保险。代价是训练时长略有增加,但对于小目标数据集,精度稳定性远比省那几分钟重要。
4.4 Windows 下 Anaconda 环境正常,训练却报「找不到图像」
现象:环境配置没问题,YAML 路径也检查了好几遍,训练器一启动就报AssertionError: train: No images found in ...,或者Image not found。
原因:常见是两个叠加因素。第一,Windows 下路径分隔符是反斜杠,而 YAML 文件里你必须用正斜杠或双反斜杠转义,写成C:\Users\...会被解析成转义字符。第二,数据集放在带中文或空格的路径下,OpenCV 读取文件时对中文路径支持不好,训练器扫描目录时直接跳过。我和搭档联调时就翻车在D:\红细胞数据\这种路径上。
解决:把整个数据集移动到纯英文、不含空格的路径下,YAML 里的path用正斜杠写绝对路径。更稳妥的做法是在 YAML 路径前加r前缀并不是 YAML 的语法,YAML 本身不认 Python 的 raw string,直接写成path: D:/datasets/red_cell即可。如果你非要保留中文路径,就要改 Ultralytics 源码里的图片读取逻辑,但为了一个数据集去改框架源码,性价比太低。
4.5 用 COCO 评估时 mAP 恒为 0:category_id 基数问题
现象:训练生成best.pt后,用 COCO 格式评估脚本跑验证集,mAP 输出为 0,但同一个模型用 YOLO 格式验证却正常。
原因:这是我提到过的category_id基数问题。COCO 的评估工具把category_id=0视为背景,而这份数据集的 COCO 标签里,类别 id 如果从 0 开始,评估器会认为所有预测结果都是背景,mAP 自然为 0。YOLO 格式用 0 作为第一个类别没问题,但 COCO 格式中第一类的 id 应为 1。
解决:转换时给所有category_id加 1,或者在评估脚本里检查类别 id 的偏移。最直接的办法是加载 COCO JSON 后打印categories字段,如果id为 0,写一个小脚本统一加 1 再保存。这个坑隐蔽在「训练指标正常、评估指标全零」的组合下,最容易让人误判为模型训练失败,实际上是评估数据的格式问题。
5. 把模型调到能用的最后一步:置信度、NMS 与验证集反馈
训练出best.pt只代表模型在训练集上收敛了,离「能用」还差两步:调置信度阈值和 NMS 阈值。这两个参数直接决定误检率和漏检率的取舍,尤其是红细胞这种密集小目标,默认参数往往不是最优解。
先看置信度。YOLO 推理时默认把置信度低于 0.25 的框过滤掉。但红细胞检测场景里,轻微染色差异、涂片杂质都会让模型的置信度偏低,如果目标真的被染成了浅色,0.25 的阈值会把真阳性当噪音丢掉。我做实验时习惯先跑一遍测试集,画出 precision-recall 曲线,选曲线拐点对应的置信度作为阈值。如果你不想画图,一个粗略法则是:误检多就上调到 0.35,漏检多就下调到 0.15。注意阈值不是越高越好,超过 0.5 时敏感度骤降,对临床计数类任务尤其危险。
再说 NMS。默认的 IoU 阈值是 0.5,意思是两个框重叠超过 50% 就会被合并成一个。红细胞粘连时,两个真实细胞的框重叠很容易超过 0.5,NMS 会把其中一个当成冗余框丢掉,表现就是粘连区域永远只检测出一个细胞。解决方法是把 IoU 阈值降到 0.3 到 0.4,让 NMS 更宽容,允许重叠度更高的框保留。这个参数对外接的边缘部署影响很大,误检率高的场景经常不是模型问题,而是 NMS 把相邻框压得太狠。
验证集反馈这一步,我习惯在训练完的runs/detect/train/目录下看三张图:confusion_matrix.png看误检分布,results.png看 loss 曲线,val_batch_pred.jpg看可视化结果。混淆矩阵里如果background列的值偏高,说明模型在空白区域输出了大量假框,优先调置信度;如果是真实类别互相混淆,说明特征区分度不够,需要回到数据增强或模型容量上想办法。
一个针对红细胞数据集的私有调参经验是降低颜色类增强的强度。Ultralytics 默认的hsv_h、hsv_s、hsv_v参数会把色彩饱和度随机变化,这对自然图像有效,但血涂片经过染色后颜色分布是病理诊断的重要信息,过度扰动颜色会误导模型。我把hsv_h=0.01、hsv_s=0.2、hsv_v=0.2作为起点,比默认值温和得多。
最后的习惯是:任何时候改动数据划分或训练参数,都把测试集图片固定住,不要每次随机抽。固定测试集才能让你比较不同版本权重的真实差异,否则评估波动会被误判为模型改进。这个教训来自我早期调参时被随机划分坑掉的两周时间,现在每次动手前都会先确认这一点。希望这些步骤和排查经验能帮你少走一段弯路。
本文还有配套的精品资源,点击获取