news 2026/10/7 1:40:33

血细胞图像数据集实战:从解压到跑通第一个检测模型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
血细胞图像数据集实战:从解压到跑通第一个检测模型

简介:血细胞图像数据集面向医学图像处理、血液疾病辅助诊断及深度学习分类任务的学习者与研究者,可用于血细胞亚型识别模型的训练与验证。资源包共13227个文件,以12515张jpeg增强图像为主体,另含366张jpg原始图像、343个xml边界框标注、2个csv标签文件及1个txt说明,压缩包约108.14MB。数据集包含约12500张血细胞增强图像,按嗜酸性粒细胞、淋巴细胞、单核细胞和嗜中性粒细胞四类分文件夹存放,每类约3000张;同时附带dataset-master中410张原始图像及其子类型标签与边界框,以及dataset2-master中2500张增强图像和对应csv标签。已有546人学习下载。读者可据此开展细胞分类建模、目标检测与数据增强对比实验,xml与csv标注便于直接接入训练流程,适合作为医学图像入门到进阶的实战素材。

1. 血细胞图像数据集.zip:从解压到跑通第一个检测模型

你拿到一个叫「血细胞图像数据集.zip」的压缩包,双击解压,里面大概率是一堆按类别分好的文件夹,或者一堆图像配一份 CSV 标注。问题从来不是「有没有数据」,而是「这批数据能不能直接喂给模型、标注格式对不对、类别平不平衡、分辨率够不够」。血细胞图像本身有它的特殊性:红细胞、白细胞、血小板在形态上差异明显,但白细胞内部还有中性粒、淋巴、单核等亚型,染色差异、光照差异、扫描仪差异会让同一类细胞看起来像两类。这个数据集能解决的核心问题,是让你在本地快速搭起一条「图像分类或目标检测」的训练流水线,验证预处理、增强、模型选型这一整套链路。适合谁?做医学图像入门的学生、想验证检测算法在细胞场景表现的工程师、以及需要快速出 baseline 的算法团队。下面我按实际动手顺序,把这条链路拆开讲。

2. 先看清数据长什么样:目录结构、标注格式与类别分布

2.1 解压后先跑一遍数据体检脚本

拿到压缩包别急着写模型,先做数据体检。血细胞数据集常见的组织方式有两种:一种是class_name/xxx.jpg的目录分类格式,另一种是images/加annotations.csv或 COCO 风格的 JSON。先写个脚本把基本情况摸清楚。

import os import json from collections import Counter from PIL import Image DATA_ROOT = "./blood_cells" # 解压后的根目录 # 1. 统计目录分类格式的类别分布 def scan_class_folders(root): stats = {} for cls in sorted(os.listdir(root)): cls_dir = os.path.join(root, cls) if not os.path.isdir(cls_dir): continue files = [f for f in os.listdir(cls_dir) if f.lower().endswith((".jpg", ".png", ".jpeg", ".bmp"))] stats[cls] = len(files) return stats # 2. 抽样看分辨率,血细胞图常见 320x240 到 1024x768 def sample_resolution(root, n=20): sizes = [] for cls in os.listdir(root): cls_dir = os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for f in os.listdir(cls_dir)[:n]: p = os.path.join(cls_dir, f) try: with Image.open(p) as im: sizes.append(im.size) except Exception as e: print("坏图:", p, e) return Counter(sizes).most_common(10) if __name__ == "__main__": dist = scan_class_folders(DATA_ROOT) print("类别分布:", dist) print("分辨率TOP:", sample_resolution(DATA_ROOT))

这段脚本干三件事:统计每个类别的图像数量、抽样统计分辨率分布、顺带把打不开的坏图揪出来。参数上DATA_ROOT指向解压目录,n=20是每个类别抽样数量,数据量大时可以调小。跑完你会得到两个关键信息:类别是否平衡、分辨率是否统一。血细胞数据集里,血小板样本往往远少于红细胞,如果某类只有几十张,后面训练必须做重采样或强增强。

2.2 标注格式决定你后面用什么框架

如果是分类任务,目录名就是标签,直接接ImageFolder或tf.keras.utils.image_dataset_from_directory。如果是检测任务,标注通常是 VOC XML 或 COCO JSON。VOC 每张图一个 XML,COCO 是一个大 JSON。先确认格式再决定转换路径。

格式典型文件转换目标常用工具
目录分类class/img.jpgImageFoldertorchvision
VOC XMLimg.xmlYOLO txt / COCO自写脚本
COCO JSONannotations.jsonYOLO txtpycocotools
CSV 坐标labels.csv自定义 Datasetpandas

我一般会先写一个格式探测函数:遍历前 50 个文件,看有没有.xml、.json、.csv,再决定走哪条转换路径。别小看这一步,标注格式判断错,后面训练 loss 不降你会怀疑人生。

2.3 类别不平衡与染色差异的初步判断

血细胞数据集的坑在于「同类不同染」。瑞氏染色和吉姆萨染色下,同一类白细胞的色调差别很大。体检阶段可以抽每个类别 5 张图,拼成一张网格图肉眼看。如果发现某类里混着明显不同色调的图,要么是标注噪声,要么是多个来源合并的数据。这时候有两个选择:一是按色调做数据增强(颜色抖动幅度调大),二是直接清洗掉离群样本。新手建议先保留,用强增强扛过去;熟手可以做一次聚类,把离群点挑出来人工复核。

3. 把血细胞图像喂进模型:预处理、增强与 DataLoader 落地

3.1 预处理管线的四个必调参数

血细胞图像预处理和自然图像不一样。自然图像常用 ImageNet 均值方差,但血细胞图背景偏白或偏紫,直接套 ImageNet 归一化会偏。我一般先算自己数据集的均值和方差,再决定归一化参数。

import torch from torchvision import transforms, datasets from torch.utils.data import DataLoader # 先算数据集均值方差(跑一次即可) def compute_mean_std(root, img_size=224): tf = transforms.Compose([ transforms.Resize((img_size, img_size)), transforms.ToTensor() ]) ds = datasets.ImageFolder(root, transform=tf) loader = DataLoader(ds, batch_size=64, shuffle=False, num_workers=4) mean = torch.zeros(3) std = torch.zeros(3) n = 0 for imgs, _ in loader: b = imgs.size(0) imgs = imgs.view(b, 3, -1) mean += imgs.mean(2).sum(0) std += imgs.std(2).sum(0) n += b return mean / n, std / n # 训练用增强管线 train_tf = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), # 细胞可裁切 transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip(), # 细胞无方向性,可翻转 transforms.RandomRotation(30), # 旋转不变性 transforms.ColorJitter(0.3, 0.3, 0.2, 0.05), # 染色差异靠它扛 transforms.ToTensor(), transforms.Normalize(mean=[0.72, 0.58, 0.66], std=[0.18, 0.19, 0.17]) ])

参数说明:RandomResizedCrop的scale=(0.7,1.0)是因为细胞通常占画面主体,裁太狠会丢信息;RandomVerticalFlip对细胞图安全,因为细胞没有上下之分;ColorJitter的亮度、对比度、饱和度都开到 0.3 左右,专门对付染色差异。归一化那三个数是我在类似血细胞数据上算出来的经验值,你用自己的compute_mean_std跑一遍替换掉。

3.2 DataLoader 的 batch 与 worker 设置

血细胞数据集通常几千到几万张,单机单卡足够。batch_size从 32 起步,显存够就 64。num_workers设成 CPU 核数的 0.7 倍左右,太多反而抢内存。如果数据集里图像分辨率差异大,Resize统一到 256 再裁 224,别直接 Resize 到 224,否则小图放大糊、大图缩小丢细节。

train_ds = datasets.ImageFolder("./blood_cells/train", transform=train_tf) val_ds = datasets.ImageFolder("./blood_cells/val", transform=val_tf) train_loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=8, pin_memory=True, drop_last=True) val_loader = DataLoader(val_ds, batch_size=64, shuffle=False, num_workers=8, pin_memory=True)

drop_last=True在训练集上避免最后一个不满 batch 影响 BN 统计。验证集不 drop,保证评估完整。pin_memory=True在 GPU 训练时能加速主机到显存的数据搬运。

3.3 从分类到检测:标注转换的边界

如果数据集是检测标注,转 YOLO 格式时注意坐标归一化。VOC 的xmin,ymin,xmax,ymax是绝对像素,YOLO 要(x_center/w, y_center/h, bw/w, bh/h)。转换时最容易翻车的是图像尺寸读错——用 PIL 读的宽高和标注里记录的宽高不一致,框就全偏了。

import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() w, h = Image.open(img_path).size # 以实际图像尺寸为准 lines = [] for obj in root.findall("object"): cls = obj.find("name").text if cls not in class_map: continue bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 边界裁剪,防止标注越界 xmin, ymin = max(0, xmin), max(0, ymin) xmax, ymax = min(w, xmax), min(h, ymax) xc = (xmin + xmax) / 2 / w yc = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{class_map[cls]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") return lines

关键点是w, h必须用Image.open实际读,不能信 XML 里的size字段,很多数据集这两者不一致。转换完抽 10 张用可视化脚本画框确认,别直接开训。

4. 训练与评估:血细胞分类/检测的模型选型与指标

4.1 模型选型:轻量骨干优先

血细胞图像特征相对固定,不需要上超大模型。分类任务我一般从 ResNet18 或 EfficientNet-B0 起步,检测任务用 YOLOv8n 或 Faster R-CNN + ResNet50。原因是细胞形态特征在浅层就能捕捉,深模型容易过拟合小数据集。如果数据量超过 5 万张,再考虑 ResNet50 或 ConvNeXt-T。

import torch.nn as nn from torchvision import models def build_classifier(num_classes, backbone="resnet18"): if backbone == "resnet18": model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) model.fc = nn.Linear(model.fc.in_features, num_classes) elif backbone == "efficientnet_b0": model = models.efficientnet_b0(weights="IMAGENET1K_V1") model.classifier[1] = nn.Linear(model.classifier[1].in_features, num_classes) return model

用 ImageNet 预训练权重,即使血细胞和自然图像差异大,浅层边缘纹理特征仍然可迁移。num_classes按你体检脚本统计出的类别数填。

4.2 训练循环里的三个关键设置

学习率用 1e-3 配 AdamW,或者 1e-2 配 SGD + cosine。血细胞数据集小,我倾向 AdamW + 1e-3,收敛稳。早停 patience 设 7 到 10 个 epoch。类别不平衡时给 CrossEntropyLoss 加 weight,weight 取类别频率的倒数归一化。

from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR import torch device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = build_classifier(num_classes=5).to(device) # 类别权重:频率倒数 class_counts = torch.tensor([1200, 800, 300, 150, 90], dtype=torch.float) weights = (1.0 / class_counts) weights = weights / weights.sum() * len(class_counts) criterion = torch.nn.CrossEntropyLoss(weight=weights.to(device)) optimizer = AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = CosineAnnealingLR(optimizer, T_max=30) for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() loss = criterion(model(imgs), labels) loss.backward() optimizer.step() scheduler.step() # 验证略,见下节

weight_decay=1e-4防过拟合,T_max=30和总 epoch 对齐。类别权重那段,class_counts换成你体检脚本的真实数字。

4.3 评估指标别只看准确率

血细胞分类里,稀有类别(比如嗜碱性粒细胞)的召回率比整体准确率重要。评估时打印混淆矩阵和每类 F1。检测任务看 mAP@0.5 和 mAP@0.5:0.95,但细胞检测框通常小,mAP@0.5 更有参考价值。

from sklearn.metrics import classification_report, confusion_matrix def evaluate(model, loader, device): model.eval() preds, trues = [], [] with torch.no_grad(): for imgs, labels in loader: imgs = imgs.to(device) out = model(imgs).argmax(1).cpu().numpy() preds.extend(out) trues.extend(labels.numpy()) print(confusion_matrix(trues, preds)) print(classification_report(trues, preds, digits=4))

classification_report会给出每类 precision/recall/F1,重点看稀有类。如果稀有类 F1 低于 0.5,回去加权重或做过采样。

5. 血细胞数据集实操避坑:5 个我踩过的坑

5.1 坑一:图像和标注尺寸不一致导致框全偏

现象:转完 YOLO 格式训练,loss 不降,可视化发现框整体偏移或缩放。原因:XML 里记录的size和实际图像尺寸不同,转换脚本用了 XML 里的尺寸。解决:一律用PIL.Image.open读实际宽高,转换后抽 10 张画框核对。

5.2 坑二:染色差异被当成类别特征

现象:模型在验证集准确率 95%,换一批新染色的图掉到 60%。原因:训练集里某类恰好都是某种染色,模型学了颜色而不是形态。解决:ColorJitter 幅度加大,或做灰度化 + 直方图均衡,逼模型看形状。

5.3 坑三:类别极度不平衡导致稀有类全预测错

现象:整体准确率 90%,但血小板类召回率 0。原因:血小板样本只有几十张,被多数类淹没。解决:CrossEntropyLoss 加类别权重,或对稀有类做随机过采样、复制增强。

5.4 坑四:验证集和训练集来自同一批图像

现象:验证准确率虚高,实际部署翻车。原因:随机划分时同一张图的增强版本同时进了训练和验证。解决:按图像来源或患者 ID 划分,确保验证集图像在训练集里没出现过。

5.5 坑五:Resize 直接压到 224 丢小目标

现象:检测任务里小血小板框检测不到。原因:原图 1024 直接 Resize 到 224,小目标缩成几个像素。解决:用 Letterbox 保持长宽比,或切 patch 训练,检测任务输入至少 640。

6. 让血细胞模型真正可用:进阶技巧与验证习惯

走到这一步,你已经有了一条能跑的流水线。但「能跑」和「能用」之间还差一层。我自己的习惯是,每次训练完不只看指标,还要做一次「盲测」:从验证集里随机抽 30 张,不看标签让模型预测,人工核对。这一步能揪出指标看不出的问题,比如某类总是和另一类混淆、某些背景被误判。

进阶方向有三个。第一是 Test Time Augmentation(TTA),对同一张图做多次增强预测再平均,血细胞分类上通常能涨 1 到 2 个点,代价是推理变慢。第二是切图训练,高分辨率血细胞涂片直接缩小会丢小目标,把大图切成 512×512 的 patch,分别预测再合并,检测小血小板效果明显。第三是伪标签半监督,用训练好的模型对未标注数据打伪标签,筛高置信度的加入训练集,适合你手头还有大量没标注的涂片图。

验证习惯上,我坚持两条:一是固定随机种子,保证每次训练可复现;二是保存最佳模型时同时存下对应的预处理参数和类别映射,否则过两周你自己都忘了当时用的什么归一化。血细胞数据集这类医学图像,类别映射和归一化参数就是模型的「后悔药」,丢了就得重训。

最后说个我自己的教训:早期做血细胞分类,我图省事直接用 ImageNet 的均值和方差,结果模型收敛慢、验证波动大,折腾了一周才发现是归一化不对。后来养成习惯,拿到任何新数据集,第一件事就是跑compute_mean_std,把这个数记在实验日志第一行。这个习惯帮我省下的时间,远比写模型结构多。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 1:40:16

SM4 ECB模式下的ANSI X9.8 PIN Block加解密实现与踩坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:39:06

智能车四层板PCB设计实战:从叠层设置到生产文件输出

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:38:02

吸烟人群检测数据集构建全链路:从场景覆盖到YOLO训练

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:37:51

电芯容量平衡设计:N/P比计算与实战案例解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:37:16

Allegro Modify Shape实战:动态铜皮边界编辑与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 1:37:14

基于VGG-16的图像检索系统实战:特征提取、余弦相似度与Top3检索

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华