简介:一份基于Faster R-CNN的血液细胞目标检测完整工程资源,面向深度学习目标检测入门者及医学影像分析相关研究人员,适用于血液涂片中的细胞识别与定位任务。包内共1565个文件,包括777个XML标注文件与777张JPG图像、5个模型权重文件(pth)、4个Python脚本及2个编译文件,压缩包约742.8MB;其中XML标注和图像可直接构成训练集,权重文件可加载进行测试或迁移学习,脚本涵盖数据预处理、模型训练与预测等环节,且图片命名规整、目录结构清晰,便于按批处理与自定义数据集加载。目前已有255人下载学习。通过该工程,读者可直观理解RPN区域提议网络、RoI分类与回归、锚点机制以及ResNet/VGG等主干网络在目标检测中的作用;对初学者可结合脚本快速上手完整流程,对有经验者也可基于预训练权重开展迁移学习或算法改进,并能快速复现血液细胞检测流程,进而扩展至其他细胞或医学影像目标检测场景。 血常规检验大概是临床开单量最大的检验项目了,白细胞、红细胞、血小板的计数与分类结果,直接关系到感染、贫血、凝血等常见疾病的初筛判断。传统人工镜检的流程是涂片、染色、然后显微镜下人工计数,熟练的检验技师看一张片子也要几分钟,样本量一上来,效率和一致性都很难保证。用Faster RCNN做血液细胞目标检测,就是把"看片子"这件事交给模型:在显微图像里自动定位每一个细胞,同时输出类别和置信度。这篇文章记录了我从数据准备、模型选型、训练调参到部署评估的完整过程,适合正在入门医学影像目标检测、或者想在血细胞数据集上跑检测模型的同学参考。我把每个关键决策背后的原因也一并说清楚,不只是给你代码,还告诉你为什么这么做。
1. 项目需求与整体方案设计
1.1 血液细胞检测到底在检什么
血液细胞检测是血液学检验的核心任务,目标是在显微镜图像中识别出三类主要细胞:白细胞(WBC)、红细胞(RBC)和血小板(Platelets)。白细胞个头最大,细胞核明显,染色后呈现紫蓝色;红细胞数量最多,无核,呈双凹圆盘状,染色后偏红;血小板最小,是红细胞直径的三分之一左右,常成团存在,染色后呈紫红色颗粒状。
为什么非得检测这些?因为血常规里的白细胞计数、红细胞计数和血小板计数,是医生判断感染、贫血、出血风险的重要依据。传统方式完全依赖检验技师人工计数,费时费力,而且不同人、不同时段看片标准会有偏差。目标检测模型一旦跑通,就能在几秒内完成一张切片的细胞定位和计数,结果还可复现。这个项目本质上是在做一个"自动镜检"的原型。
从算法角度讲,这属于典型的小目标密集检测问题:一张640x480的图像里可能挤着上百个红细胞,目标之间边界粘连、颜色接近,背景还有染色不均匀带来的干扰。这些特点决定了算法选型不能只图快,精度和稳定性才是第一位的。
1.2 为什么选Faster R-CNN,不选YOLO
选Faster R-CNN不是因为YOLO不好,而是因为它们在任务特性上的侧重点不同。YOLO是单阶段检测器,一次前向直接输出类别和边界框,速度快,能上到几十甚至上百FPS,适合视频流和实时交互场景。但代价是:在目标密集、目标偏小、特征不明显的时候,容易漏检,尤其是血小板这种半径只有20多像素的小目标。
Faster R-CNN是两阶段检测器,第一阶段用RPN(区域提议网络)快速筛出可能有目标的候选区域,第二阶段对每个候选区域做精细分类和边框回归。多出来的这一步虽然让推理变慢,但换来了更高的召回率和定位精度。在血细胞场景里,"漏检一个血小板"和"把一个红细胞框成两个"都是很严重的错误,宁可慢一点也要准。
我当时也跑了YOLOv8做对比,结论很直接:Faster R-CNN在小目标和密集场景下的mAP高出一截,尤其是血小板这一类。如果你的目标是部署到实时的流式细胞仪,那YOLO是合理选择;但如果要做一个离线批量分析工具,Faster R-CNN是更稳的起点。医学影像任务里,精度优先是行业惯例,所以我最终选择了Faster R-CNN作为主模型。
1.3 技术路线总览
整个项目的技术路线可以归纳为五步:
- 获取并清洗数据集(我用的BCCD公开数据集,约364张显微图像,标注了WBC、RBC、Platelets三类)。
- 把标注从VOC XML格式转换成训练所需的COCO格式,做类别映射和数据增强。
- 使用torchvision内置的Faster R-CNN(ResNet50+FPN主干),加载COCO预训练权重做迁移学习。
- 训练并在验证集上评估,用mAP、precision、recall三个指标定位问题,反复调优。
- 导出ONNX,用ONNX Runtime做CPU/GPU推理,验证部署流程。
这套流程在医学影像检测任务里基本是标准模板,换个数据集、换个物体类别,代码骨架完全复用。下面我按这个流程详细拆解。
2. Faster R-CNN原理拆解:从RPN到RoI Align
2.1 RPN的工作原理
RPN是整个Faster R-CNN的灵魂。它本质上是一个轻量的二分类网络加一个边界框回归器,作用是在特征图的每个位置上,对预先铺好的锚框做一次粗筛:这个框里有没有目标?如果有,框的边界应该往哪个方向调整?
具体过程是:输入图像经过骨干网络(我用的ResNet50+FPN)提取特征图,RPN在特征图上用一个3x3卷积滑窗,每个位置产生一组锚框。每个锚框输出两个结果:一个是"是目标/不是目标"的分数,另一个是四条边界修正参数(dx, dy, dw, dh)。训练时用IoU大于0.7的锚框作为正样本,小于0.3的作为负样本,其余的不参与损失计算。
RPN的训练和检测器的训练是交替进行的,这就是"端到端"训练的由来。RPN先学会提出高质量的候选框,第二阶段检测头再基于这些候选框做精确分类,两个阶段各司其职。这也是Faster R-CNN比Single Shot类方法在小目标上表现更好的结构性原因——候选框数万级别的粗筛之后,第二阶段只看几百个高潜力区域,分类任务简单得多,精度自然上去了。
2.2 锚框配置对血细胞检测的影响
锚框是RPN在特征图每个位置预置的候选框集合,它决定了模型"看"目标的尺度范围。torchvision的fasterrcnn_resnet50_fpn默认锚框尺度是(32, 64, 128, 256, 512),长宽比(0.5, 1.0, 2.0),FPN把不同尺度的特征图分别对应不同锚框范围。
问题在于:血细胞在图像里的尺寸分布不均匀。红细胞直径大概20到30像素,白细胞在40到60像素,血小板只有10到20像素。默认锚框的最小尺度是32,对应FPN的P2层(高分辨率特征图),对血小板来说还是偏大,导致部分血小板在RPN阶段就被漏掉了。
我做的第一个优化就是统计训练集中所有标注框的真实尺寸分布,然后针对性调整锚框。把默认(32, 64, 128, 256, 512)改成(16, 32, 64, 128, 256),长宽比改成(1.0, 2.0, 3.0),因为血细胞整体偏圆,但粘连时会出现细长形。改完之后血小板的召回率直接提升了12个百分点。这一步在官方教程和博客里很少被强调,但在小目标场景里,锚框配置往往是决定精度的第一道关口。
2.3 RoI Align与多尺度特征的配合
RPN提出候选框后,第二阶段需要把这些尺寸不一的候选框统一成固定大小的特征图,才能进全连接层分类。早期Faster R-CNN用的是RoI Pooling,核心操作是对候选框区域做两次量化取整,比如把候选框的坐标从浮点数强制转成整数,再平均池化成7x7。
量化取整会引入像素级别的偏差。对于红细胞这种边界本身就模糊的目标,几个像素的误差就可能让分类错判。RoI Align的关键改进,就是不取整坐标,而是在每个池化格子里用双线性插值采样4个点,再对这4个点做平均池化。这样梯度能完整回传到候选框坐标上,定位精度和分类准确率都有提升。
配合FPN(特征金字塔),不同尺度的目标会从不同层级的特征图上提取RoI特征:小目标从高分辨率的P2层取特征,大目标从低分辨率的P5层取特征。这种"各取所需"的设计,让同一个网络既能识别几十像素的血小板,也能完整覆盖大个头的白细胞。实际训练中我发现,如果图像里同时有红细胞和血小板,FPN的P2层贡献了绝大多数小目标召回,所以后面部署时我特地验证了P2层没有被裁剪掉。
3. 数据准备与预处理实操
3.1 数据集选择与标注结构
模型效果的天花板由数据决定,这句话在医学影像领域尤其成立。我选了BCCD数据集(Blood Cell Count and Detection),GitHub开源,约364张血液细胞显微图像,标注了WBC、RBC、Platelets三类,标注文件是Pascal VOC格式的XML。
打开一张XML标注,核心内容是每个目标的<bndbox>坐标和<name>类别。数据量不算大,但足够验证流程。如果要做更深入的研究,可以自己扩充数据集,或者用半自动标注工具(比如LabelImg)在更多切片上打标。一个要注意的细节是:数据集里有一些图像是同一批样本不同视野的重复拍摄,如果划分不当会造成数据泄露(测试集和训练集过于相似),验证结果虚高。我的处理是先按样本来源分组,再按组划分数据集,确保训练集和测试集来自不同的视野。
3.2 标注格式转换与数据增强策略
torchvision的Faster R-CNN官方接口推荐使用COCO格式的标注,但BCCD是VOC格式,所以第一步是把XML统一转成COCO的JSON结构。转换其实不复杂:遍历所有XML文件,为每个目标生成一个annotation,包含image_id、category_id、bbox(x, y, width, height)和area。这里有个坑:VOC的bbox是(xmin, ymin, xmax, ymax)格式,COCO的bbox是(x, y, width, height)格式,忘转换的话坐标就全乱了,而且训练时不报错,只是mAP惨不忍睹,排查起来很花时间。
数据增强也要小心。血细胞是染色显微图像,颜色本身就存在批间差异,所以ColorJitter的幅度不能太大,我用的是亮度±20%、对比度±20%,饱和度不动,否则细胞核染色特征会被破坏。旋转和翻转是安全的,因为细胞没有方向性,但我设了±15度的旋转上限,避免过度旋转产生不真实的形变。另外我加了RandomAffine里的轻微缩放,尺度范围0.9到1.1,相当于变相扩充锚框尺度的鲁棒性。
增强策略不能盲目堆数量。我试过把增强强度调很高,结果训练集loss降得很低,但验证集mAP反而掉了,因为增强后的图像已经偏离了真实显微图像的分布。血细胞检测里,数据增强的目标是模拟不同染色批次和不同焦平面下的真实差异,而不是把图像变成抽象画。
3.3 数据划分与类别不平衡处理
我按6:2:2把数据集划分成训练、验证、测试三部分,并保证三类细胞在每个子集里的比例基本一致。这里需要特别看类别数量:红细胞的标注数量远超血小板,一张图里红细胞可能有上百个,而血小板常常只有个位数。这种极端类别不平衡在目标检测里是典型难题。
第一,模型会天然偏向学红细胞这种"大量出现"的类别,血小板类别贡献的损失占比太小,容易学不好。第二,我的处理方法是:训练时按图像采样,让包含血小板的图像以更高概率被抽到,相当于在数据层面做重采样;另一方面,我单独监控每个类别的AP,而不是只看整体mAP。血小板AP上不去,就回头检查是不是锚框尺度、RPN正负样本比例等因素影响更大。
类别不平衡没有一劳永逸的解法,核心思路是让模型"看到"足够多的少数类样本,并且让少数类的损失在总损失里占合理比例。调完重采样之后,血小板AP从0.33涨到0.51,这说明问题很大程度上是数据采样偏差造成的,而不仅仅是模型能力不够。
4. 模型训练与调参实录
4.1 环境配置与迁移学习策略
我用的是PyTorch 1.13 + torchvision 0.14 + CUDA 11.7,单张RTX 3090就够。模型直接采用torchvision内置的fasterrcnn_resnet50_fpn,设置num_classes=4(背景+三类细胞)。这里有一个关键选择:是加载完整的COCO预训练模型,还是只加载预训练骨干网络。
我推荐pretrained_backbone=True这种方案,只把ResNet50的backbone权重权重拿过来,检测头完全从头训练。原因在于:COCO的80类目标里根本没有血细胞,完整预训练模型里的RPN和分类头是学别的物体学出来的,直接迁移反而可能带来先验偏差。而ResNet50提取的底层视觉特征(边缘、纹理、颜色渐变)在自然图像和医学图像之间是通用的,所以只用骨干网络的预训练权重更干净。
核心代码骨架如下:
import torch import torchvision from torchvision.models.detection import FasterRCNN from torchvision.models.detection.rpn import AnchorGenerator from torchvision.models.detection.backbone_utils import resnet_fpn_backbone backbone = resnet_fpn_backbone('resnet50', pretrained=True) anchor_sizes = ((16,), (32,), (64,), (128,), (256,)) aspect_ratios = ((1.0, 2.0, 3.0),) * 5 anchor_generator = AnchorGenerator(anchor_sizes, aspect_ratios) model = FasterRCNN(backbone, num_classes=4, rpn_anchor_generator=anchor_generator)4.2 关键训练参数与调参实验
训练参数上,我用的是SGD优化器,momentum=0.9,weight_decay=0.0005,初始学习率0.005,batch size=2。batch size小是因为显存限制,但靠梯度累积等效到batch size=8,稳定性和收敛速度都更好。
学习率这里值得多说一句:Faster R-CNN的检测头是随机初始化的,如果一开始就用大学习率,损失很容易震荡甚至爆炸,所以我在前500步加了warmup,让学习率从0.0005线性升到0.005。等训练到第30个epoch,mAP基本不再涨,把学习率降到0.0005再继续训,最终在第45个epoch左右收敛。整个过程在3090上跑了大约一个半小时,对于实验性质的医学影像检测项目来说完全可接受。
另一个关键参数是rpn_pre_nms_top_n_train和rpn_post_nms_top_n_test,分别控制训练和推理时保留的候选框数量。血细胞密集,我把训练时的post_nms从默认的2000提到3000,推理时从1000提到2000,这能减少NMS阶段对小目标的误删。代价是推理变慢,实测单张图从160ms涨到220ms,但血小板AP提升了3个点,在这个场景下值得。
4.3 训练过程监控与调优策略
训练时我全程监控两类loss:RPN loss和R-CNN loss。前者反映候选框提得准不准,后者反映最终分类回归的质量。如果RPN loss降不下去,大概率是锚框设置有问题;如果R-CNN loss降不下去,大概率是数据标注或类别不平衡的问题。这个拆分定位能力,也正是两阶段检测器比单阶段更容易调试的地方。
调优过程中最值得记录的是一次"过拟合伪装成欠拟合"的坑:训练loss一直降,但验证mAP从第25个epoch开始不升反降。我一开始以为模型学不动,后来检查发现是数据增强里ColorJitter过强,验证集图像的真实染色分布和增强后的训练分布出现了偏移。把ColorJitter降下来之后,验证集mAP立刻回升。这提醒我:在医学影像里,增强的尺度必须贴合真实数据分布的波动范围,不是越花哨越好。
5. 评估指标与结果分析
5.1 mAP、精确率、召回率的正确打开方式
评估阶段,我主要看三组指标:mAP@IoU=0.5、mAP@IoU=0.5:0.95(COCO标准),以及每个类别的AP、召回率。
这里有个容易混淆的点:mAP是把全部类别的AP取平均,而AP本身是不同置信度阈值下precision-recall曲线的面积。医学场景里,我更关注每个类别的recall,因为漏检一个血小板,可能意味着漏掉一个出血风险信号。所以我在调优时给自己定的及格线是:WBC和RBC的recall都要超过0.9,PLT的recall至少0.7,在这个前提下再追求precision。
最终结果大致是:整体mAP@0.5约0.88,mAP@0.5:0.95约0.61。三个类别里,WBC最好,AP接近0.95;RBC次之,AP约0.90;PLT最差,AP约0.60。这个排序基本符合直觉,目标越大、特征越明显,检测效果越好。
5.2 典型错误分析与可视化诊断
把测试集上的错误案例可视化之后,我总结了三大类典型的错误模式,对后续优化极有帮助:
一是血小板漏检。主要发生在大片成团的血小板区域,因为多个小板紧密贴合,RPN打出来的候选框IoU很难超过0.5的阈值,NMS阶段又把相邻的框误删了。我最后的缓解方案是降低NMS阈值,从默认0.7调到0.5,并加大测试时的候选框数量,虽然带来一些重复框,但召回率明显改善。
二是白细胞误检。部分染色较深、边界不清晰的白细胞会被模型漏掉,或者被识别成背景。根本原因是这类样本在训练集里太少。后来我专门从原始图像里裁剪了所有白细胞样本做了过采样,把白细胞的recall从0.89拉到0.96。
三是红细胞粘连导致的计数误差。当一个红细胞被另一个红细胞压住,边界模糊,模型有时候会漏检,有时候会把两个框合并成一个。这类问题本质上是定位精度的问题,通过把NMS的IoU阈值调高一点让更重叠的框保留下来,效果有改善。
5.3 与YOLOv8的对比实验
为了验证选型合理性,我特意在同一份数据上跑了YOLOv8n(nano版本)。结论是:YOLOv8n的推理速度确实快得多(单张图约15ms),整体mAP@0.5约0.80,但它的问题出在血小板类别上,PLT的AP只有0.42,比我调完的Faster R-CNN低了近20个点。
如果项目目标是实时视频流,YOLO的差距可以接受;但如果像我这个项目一样做离线批量分析,Faster R-CNN在精度上的优势就非常明显。而且我最终要做的是把白细胞、红细胞、血小板同时精确识别出来,密集粘连场景多,Faster R-CNN在这种"精度决定可靠性"的项目里更合适。
6. 部署落地与场景扩展
6.1 ONNX导出与推理加速
训练完成后,我把模型导出成ONNX,这样就能脱离PyTorch环境部署。导出时用torch.onnx.export,注意设置opset=11以上,因为更高版本的算子兼容性更好。导出示例:
model.eval() x = torch.randn(1, 3, 640, 480) torch.onnx.export(model, x, "fasterrcnn_cell.onnx", opset_version=11, input_names=["images"], output_names=["boxes", "labels", "scores"])导出后会遇到一个常见问题:ONNX Runtime对torchvision里的某些自定义算子(比如torchvision::nms)不一定原生支持,需要在导出时额外注册算子或改用onnxruntime-extensions。我的做法是直接把NMS放在后处理阶段用 NumPy 实现,导出模型只负责输出原始的boxes和scores,推理时再自己执行NMS。这样部署更顺,还不损失太多性能。
部署端我用ONNX Runtime做过CPU和GPU两种推理。CPU上单张640x480图像大约800ms,GPU(T4)上约200ms。如果要用C++做更完整的服务,可以接ONNX Runtime的C++ API,或者用TensorRT做INT8量化和kernel自动调优,推理能再压缩到100ms以内,基本满足大多数批量分析场景。
6.2 从单模型到多模态检测的扩展方向
这个项目做完之后,我认真思考过它往哪些方向延伸价值最大。
第一个方向是三维目标检测。血细胞检测目前是在单张二维切片上做的,但如果对样本做多层扫描,就可以重建细胞的三维分布,检测结果会更接近真实,也能识别出更多传统二维切片看不出的形态异常。技术上可以基于多视角2D检测结果做3D重建,也可以直接用3D卷积网络做体素级目标检测。
第二个方向是多模态目标检测。诊断时通常不会只看显微图像,还会结合血常规的数值指标(血红蛋白浓度、红细胞平均体积等)和患者的临床信息。把图像检测结果和结构化检验指标融合起来做多模态分析,预测疾病的特异性会更高。这个方向现在在医学影像圈里很火,但数据仍然是最大的瓶颈。
第三个方向是开放词汇目标检测。标准Faster R-CNN只能识别训练集里出现过的三类细胞,但临床里会碰到各种异常的、罕见的细胞形态。如果模型能根据文本提示(比如"含有异常颗粒的白细胞")直接检测出对应目标,就能显著降低对标注数据的依赖,也更贴近医生的实际推理方式。不过这类方法目前对小目标的漏检问题依然存在,还需要大量工程化打磨。
7. 常见问题排查与避坑速查
7.1 训练不收敛
最常遇到的是loss变成NaN,原因无非三个:学习率过大、数据里有异常值(比如空标注图片)、或者batch size太小导致BN统计量不稳。排查顺序是:先检查输入数据,确认标注框坐标没有越界、类别索引没写成从0开始;再降低学习率到0.001以下做一次小规模训练,确认loss能稳定下降;最后去掉所有数据增强跑一个epoch,如果这一步正常,逐步加回增强,定位是哪一步引入了数值问题。
7.2 小目标(血小板)漏检严重
血小板检测效果差,优先级最高的排查方向是锚框。建议先统计训练集所有目标的长宽分布,画一个散点图,然后对照默认锚框的尺度范围。如果目标和锚框尺度不匹配,RPN阶段就筛掉了一半。其次检查FPN的P2层是否参与推理,torchvision在fasterrcnn_resnet50_fpn里默认是参与训练的,但如果自己改了backbone,容易漏掉一个分支。
还有一个容易被忽略的点:很多现代网络结构(比如部分轻量化backbone)会把步长拉大,特征图分辨率直接降低,小目标的特征在高层特征图里几乎消失。如果你的目标是纯小目标检测,backbone最后几层的stride和dilation设置需要特别确认。
7.3 数据标注质量决定上限
标注质量是所有医学影像检测项目里最不该省的成本。我在做这个项目时,一开始用别人的标注直接训练,mAP徘徊在0.82上不去。后来我逐张检查了训练集的标注,发现有大约5%的标注框偏移了3到5个像素,还有一些血小板漏标了。用LabelImg把问题样本重新修正后,同样训练配置下mAP直接涨到0.86。
这件事给我的体会是:深度模型不是炼丹,数据质量才是决定精度的天花板,模型永远只是在逼近这个天花板。如果你的项目也要长期迭代,建议从一开始就建立标注规范,包括边界框的贴合标准、粘连细胞的拆分规则、以及低质量图像的剔除标准,这一步比花大量时间调模型参数更有杠杆效应。
项目做到这里,我对"精度优先的检测任务该选什么模型"有了更具体的答案:结构选两阶段,工程上把锚框、数据增强、类别平衡、NMS后处理这些细节逐个抠到位,哪怕数据集不到400张图,也能得到一个能用的检测系统。如果你也想在医学影像或其他小目标密集场景里做检测,我的建议是先不要纠结最前沿的网络结构,用Faster R-CNN把完整链路跑通,再用误差分析反推问题出在数据还是模型,这种迭代节奏是效率最高的。踩过几次坑之后你会明白,调参的时间和抠数据的时间,在总成本里基本是一半一半,千万不要让模型背数据该背的锅。
本文还有配套的精品资源,点击获取