
简介本资源为血细胞图像数据集面向医学图像处理、血液疾病辅助诊断方向的研究者与算法工程师可用于血细胞亚型分类、目标检测等任务的模型训练与验证。压缩包共13227个文件以12515张jpeg增强图像为主另含366张jpg原图、343个xml边界框标注、2个csv标签文件及1个txt说明整体约108.14MB。数据集包含嗜酸性粒细胞、淋巴细胞、单核细胞和嗜中性粒细胞四类每类约3000张增强图像按细胞类型分文件夹存放同时附带dataset-master原始410张图像及WBC子类型标签与边界框元数据便于开展细粒度检测实验。目前已有546人学习下载适合需要现成标注数据快速搭建基线、对比分类模型或研究数据增强效果的读者参考使用。1. 血细胞图像数据集.zip从压缩包到可训练模型中间隔着多少坑拿到「血细胞图像数据集.zip」这个文件时多数人的第一反应是解压、看文件夹、数图片然后直接往 DataLoader 里一扔开始训练。我见过太多人卡在这一步文件夹结构看不懂、标签对不上号、类别极度不均衡、图像尺寸参差不齐最后模型训出来在验证集上看着还行一上真实涂片就翻车。这个数据集通常来自外周血涂片显微成像包含红细胞、白细胞、血小板等类别有的版本还会细分中性粒细胞、淋巴细胞、单核细胞、嗜酸性粒细胞、嗜碱性粒细胞。它解决的核心问题是让你在没有显微镜和检验科合作的情况下也能搭建一个血细胞分类或检测的基线模型。适合医学图像入门者、做细胞分类竞赛的选手以及需要快速验证算法思路的工程师。但前提是你得先把这个压缩包里的东西真正读懂而不是把它当成一个普通的 ImageFolder。2. 解压之后先别急着训练血细胞图像数据集的目录结构与标签体系2.1 常见目录布局与类别命名规律血细胞图像数据集.zip 解压后常见的目录结构有两种。第一种是按类别分文件夹类似dataset/neutrophil/、dataset/lymphocyte/、dataset/monocyte/每个文件夹下是若干张 JPG 或 PNG。第二种是图像和标注文件分离比如images/放图labels.csv或annotations.json记录文件名和类别甚至还有train/、val/、test/的划分。我一般会先跑一段脚本把目录树打印出来同时统计每个类别的文件数量因为血细胞数据集的类别不均衡是常态中性粒细胞可能占一半以上嗜碱性粒细胞可能只有几十张。import os from collections import Counter root path/to/blood_cell_dataset exts (.jpg, .jpeg, .png, .bmp) class_counter Counter() for dirpath, dirnames, filenames in os.walk(root): for f in filenames: if f.lower().endswith(exts): # 假设类别名就是上一级文件夹名 class_name os.path.basename(dirpath) class_counter[class_name] 1 for cls, cnt in class_counter.most_common(): print(f{cls}: {cnt})这段代码的逻辑很直接遍历整个数据集目录把所有图片按所在文件夹归类计数。参数上root换成你解压后的实际路径exts根据数据集实际格式调整。如果输出里出现某个类别只有个位数那后面训练时就必须做重采样或加权损失否则模型会直接把这个类别忽略掉。另一个要注意的点是有些数据集会把正常红细胞和异常红细胞混在一个文件夹里文件名里带abnormal之类的关键词这时候就不能只靠文件夹名当标签得解析文件名。2.2 标签映射与划分策略别让数据泄漏毁掉验证集血细胞图像数据集最常见的坑是同一张涂片被切成多个 patch如果随机划分训练集和验证集同一个细胞的不同 patch 可能同时出现在两边导致验证集准确率虚高。我一般会先看文件名里有没有患者 ID 或涂片编号如果有就按 ID 做 GroupShuffleSplit而不是简单的 train_test_split。如果没有 ID至少也要按图像内容的感知哈希做去重避免近似重复的图跨集。import pandas as pd from sklearn.model_selection import GroupShuffleSplit # 假设 df 有 filename, label, slide_id 三列 df pd.read_csv(labels.csv) gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(df, groupsdf[slide_id])) train_df df.iloc[train_idx] val_df df.iloc[val_idx] print(ftrain: {len(train_df)}, val: {len(val_df)})这里groups参数是关键它保证同一个slide_id的所有图片只会出现在训练集或验证集其中一边。test_size0.2是验证集比例血细胞数据集通常不大验证集太小会导致指标波动大我一般会留 15% 到 20%。如果数据集本身已经划分好了 train/val/test那就先检查三个集合的类别分布是否一致不一致的话要么重新划分要么在训练时用加权采样。2.3 图像尺寸、通道与预处理的最小检查清单血细胞显微图像常见尺寸从 128x128 到 1024x1024 都有通道数一般是 RGB 三通道但也有灰度图或带 alpha 通道的 PNG。训练前必须统一尺寸但直接 resize 到 224x224 可能会让细胞核和细胞质的比例失真。我的习惯是先把所有图缩放到一个中间尺寸比如 256x256再做随机裁剪到 224x224。另外血细胞图像的颜色受染色批次影响很大同一个类别在不同数据集里可能偏紫或偏粉所以颜色归一化比 ImageNet 的均值方差更靠谱。import cv2 import numpy as np def preprocess_cell_image(img_path, target_size224): img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 先缩放到稍大尺寸保留细节 h, w img.shape[:2] scale 256 / max(h, w) new_h, new_w int(h * scale), int(w * scale) img cv2.resize(img, (new_w, new_h), interpolationcv2.INTER_AREA) # 中心裁剪到目标尺寸 start_x (new_w - target_size) // 2 start_y (new_h - target_size) // 2 img img[start_y:start_ytarget_size, start_x:start_xtarget_size] # 简单的颜色标准化按通道减均值除标准差 img img.astype(np.float32) / 255.0 mean img.reshape(-1, 3).mean(axis0) std img.reshape(-1, 3).std(axis0) 1e-6 img (img - mean) / std return img这段预处理里interpolationcv2.INTER_AREA适合缩小图像能减少摩尔纹。颜色标准化用的是每张图自己的均值和标准差而不是 ImageNet 的固定值因为血细胞染色差异太大用固定值反而会引入偏差。如果你要做迁移学习可以在标准化之后再按 ImageNet 的均值方差做一次归一化但顺序不能反。3. 用 PyTorch 跑通血细胞分类基线从 Dataset 到第一个 epoch3.1 自定义 Dataset 的四个关键方法PyTorch 的 Dataset 类必须实现__init__、__len__、__getitem__血细胞数据集还需要一个额外的get_class_weights方法来处理不均衡。__init__里不要一次性把所有图片读进内存血细胞图像虽然不大但几万张加起来也能撑爆内存。我一般只存文件路径和标签在__getitem__里按需读取和增强。import torch from torch.utils.data import Dataset, DataLoader from sklearn.utils.class_weight import compute_class_weight class BloodCellDataset(Dataset): def __init__(self, df, transformNone): self.df df.reset_index(dropTrue) self.transform transform self.classes sorted(df[label].unique()) self.class_to_idx {c: i for i, c in enumerate(self.classes)} def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] img preprocess_cell_image(row[filepath]) label self.class_to_idx[row[label]] if self.transform: img self.transform(imageimg)[image] # 转成 CHW 格式 img torch.tensor(img).permute(2, 0, 1).float() return img, label def get_class_weights(self): labels [self.class_to_idx[l] for l in self.df[label]] weights compute_class_weight(balanced, classesnp.arange(len(self.classes)), ylabels) return torch.tensor(weights, dtypetorch.float32)__getitem__里先做预处理再做增强顺序很重要。如果你用 albumentations 做增强注意它的输入是 HWC 格式的 numpy 数组输出也是 HWC所以最后要 permute 成 CHW。get_class_weights用 sklearn 的compute_class_weight计算平衡权重后面传给 CrossEntropyLoss 的weight参数。3.2 训练循环里必须监控的三个指标血细胞分类训练时光看 loss 和 accuracy 不够。我一般会同时监控每个类别的召回率、混淆矩阵以及验证集上的 F1 分数。因为类别不均衡时模型可能把所有样本都预测成多数类accuracy 看着有 80%但少数类召回率是 0。下面是一个简化的训练循环重点在验证阶段计算 per-class recall。from sklearn.metrics import classification_report import torch.nn as nn def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(loader) def validate(model, loader, device): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in loader: imgs imgs.to(device) outputs model(imgs) preds outputs.argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, digits4)) return all_preds, all_labelsclassification_report会直接打印每个类别的 precision、recall、f1-score比只看总体 accuracy 有用得多。如果某个类别的 recall 低于 0.5就要考虑增加该类的采样权重或者用 focal loss 替代交叉熵。另外验证时记得把模型切到eval()模式否则 BatchNorm 和 Dropout 会干扰结果。3.3 迁移学习选型ResNet 还是 EfficientNet血细胞数据集通常只有几千到几万张图从零训练一个 CNN 很容易过拟合。我一般会用 ImageNet 预训练的 ResNet50 或 EfficientNet-B0 做 backbone只替换最后的全连接层。ResNet50 参数量大适合图像尺寸 224 以上、数据量超过一万张的场景EfficientNet-B0 更轻量在数据量少的时候表现更稳。如果你用的是灰度图记得把第一层卷积的输入通道改成 1并复制预训练权重的三个通道取平均。import torchvision.models as models import torch.nn as nn def build_model(num_classes, backboneresnet50, pretrainedTrue): if backbone resnet50: model models.resnet50(pretrainedpretrained) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) elif backbone efficientnet_b0: model models.efficientnet_b0(pretrainedpretrained) in_features model.classifier[1].in_features model.classifier[1] nn.Linear(in_features, num_classes) return modelpretrainedTrue会下载 ImageNet 权重如果网络环境不允许可以提前把权重文件放到~/.cache/torch/hub/checkpoints/下。替换全连接层后建议先用较小的学习率比如 1e-3只训练新加层等 loss 稳定后再解冻整个网络做微调。血细胞图像的纹理和自然图像差异较大微调时学习率不要设太大否则预训练权重会被破坏。4. 血细胞图像数据集避坑五个让我返工三次的常见问题4.1 现象验证集准确率 95%测试集只有 60%原因同一张涂片的 patch 被随机分到了训练集和验证集模型记住了涂片特有的染色和光照而不是细胞形态。解决按患者 ID 或涂片编号做 GroupShuffleSplit确保同一来源的图片只出现在一个集合里。如果数据集没有 ID用感知哈希对图像去重把相似度高于 0.9 的图归为一组再划分。4.2 现象训练 loss 震荡严重偶尔出现 NaN原因血细胞图像的颜色标准化没做好某些图片的像素值范围异常或者学习率设得太大。解决在预处理里加一步像素值截断把超过 99.9 百分位和低于 0.1 百分位的值裁掉再做标准化。学习率从 1e-4 开始试配合梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)。4.3 现象少数类如嗜碱性粒细胞召回率始终为 0原因类别极度不均衡模型倾向于预测多数类。解决在 CrossEntropyLoss 里传入weight参数权重用compute_class_weight(balanced)计算。如果效果还不够改用 focal lossgamma 设 2.0alpha 按类别频率设置。另外采样时用 WeightedRandomSampler让每个 batch 里少数类至少出现一次。4.4 现象模型在验证集上 F1 很高但部署后推理速度极慢原因输入图像尺寸太大或者模型 backbone 选得太重。解决血细胞分类不需要 512x512 的输入224x224 通常足够。如果细胞核细节重要可以先用 256x256 训练推理时再降到 224。backbone 换成 EfficientNet-B0 或 MobileNetV3推理速度能提升 3 到 5 倍精度损失通常在 1% 以内。4.5 现象换了一个血细胞数据集模型完全失效原因不同数据集的染色方案、显微镜型号、图像分辨率差异很大模型过拟合到了源数据集的颜色分布。解决在训练时加入颜色抖动增强比如随机调整亮度、对比度、饱和度和色调幅度不要太大否则细胞核颜色会失真。更彻底的做法是用 CycleGAN 做颜色风格迁移但成本较高。我一般先用强颜色增强顶一下效果不够再考虑域适应。5. 把血细胞分类推到可用TTA、阈值调优与错误分析5.1 测试时增强TTA在血细胞图像上的收益血细胞图像的方向通常不重要但轻微的旋转和缩放能提升模型鲁棒性。TTA 的做法是对同一张测试图做多次增强分别推理后取平均概率。我一般用三个变换原始图、水平翻转、旋转 90 度。注意血细胞图像里细胞核有方向性垂直翻转可能会让某些类别的形态变得不自然所以我不建议用垂直翻转。def predict_with_tta(model, img_tensor, device): model.eval() transforms [ lambda x: x, lambda x: torch.flip(x, dims[3]), # 水平翻转 lambda x: torch.rot90(x, k1, dims[2, 3]), # 旋转90度 ] probs [] with torch.no_grad(): for t in transforms: aug t(img_tensor).to(device) out model(aug.unsqueeze(0)) probs.append(torch.softmax(out, dim1).cpu()) return torch.stack(probs).mean(dim0)torch.flip的dims[3]对应宽度维度torch.rot90的dims[2,3]对应高度和宽度。TTA 的收益在血细胞分类上通常是 1 到 3 个百分点如果提升不明显说明模型本身已经比较鲁棒或者增强方式不合适。5.2 分类阈值调优什么时候该放弃 argmax默认的 argmax 假设每个类别的先验概率相同但血细胞数据集的类别分布往往差很多。如果某个类别在临床中漏检代价很高比如异常早幼粒细胞那就应该降低该类的判定阈值。做法是在验证集上画出每个类别的 precision-recall 曲线找到 F1 最大的阈值而不是直接用 0.5。from sklearn.metrics import precision_recall_curve def find_best_threshold(y_true, y_prob, class_idx): # y_prob 是模型输出的 softmax 概率 precisions, recalls, thresholds precision_recall_curve( (y_true class_idx).astype(int), y_prob[:, class_idx] ) f1_scores 2 * precisions * recalls / (precisions recalls 1e-6) best_idx f1_scores.argmax() return thresholds[best_idx], f1_scores[best_idx]这个函数返回使 F1 最大的阈值。实际部署时如果模型输出概率高于这个阈值就判为该类否则归为其他类或拒绝判定。注意阈值调优必须在验证集上做不能直接在测试集上调否则就是另一种形式的数据泄漏。5.3 错误分析把分错的图单独拿出来看模型上线前我一定会把验证集里分错的样本全部导出按真实类别和预测类别分组每类随机抽 20 张拼成网格图。血细胞分类的错误往往集中在几个模式中性粒细胞和嗜酸性粒细胞的颗粒颜色接近、淋巴细胞和单核细胞的核质比相似、染色不良导致细胞核模糊。看到这些错误模式后要么补充对应类别的训练数据要么在预处理里加去模糊或颜色校正。这一步没有代码模板但它是把模型从 90% 推到 95% 的关键。我自己的习惯是每次训练完都做一次错误分析哪怕指标已经达标因为那些分错的图往往藏着数据集的系统性问题。希望帮到你。本文还有配套的精品资源点击获取