使用YOLOv5实战血细胞检测与计数
在现代医学影像分析中,自动化识别和量化血液中的细胞类型正变得越来越重要。传统的显微镜下人工计数不仅耗时费力,还容易受到操作者主观判断的影响——尤其是在面对大量样本或密集分布的血小板时,误差难以避免。而深度学习技术的兴起,特别是目标检测模型的发展,为这一问题带来了突破性的解决方案。
其中,YOLOv5作为当前工业界最广泛采用的目标检测框架之一,凭借其简洁的设计、高效的推理速度以及出色的精度表现,成为医疗图像智能分析的理想选择。它不仅能实时定位红细胞(RBC)、白细胞(WBC)和血小板(Platelets),还能通过简单的后处理实现自动计数,极大提升了临床诊断效率。
本文将带你从零开始构建一个完整的血细胞检测系统:从原始数据解析、格式转换,到模型训练、预测可视化,再到最终可部署的API服务设计。整个过程无需深厚的理论基础,只需具备基本的Python编程能力即可上手。
我们使用的数据集是公开的BCCD(Blood Cell Count Dataset),包含12,500张显微镜下的血液涂片图像及其XML标注文件。每个标注记录了三种细胞的位置信息(xmin, ymin, xmax, ymax)。但YOLOv5要求输入标签为.txt格式,并使用归一化的中心点+宽高(xywh)表示法。
因此第一步就是完成数据格式的转换。以下是核心代码逻辑:
import os import xml.etree.ElementTree as ET import pandas as pd from glob import glob from sklearn.model_selection import train_test_split import shutil # 解析所有XML文件 annotations = sorted(glob('BCCD/Annotations/*.xml')) data = [] for file in annotations: filename = os.path.basename(file).replace('.xml', '.jpg') tree = ET.parse(file) root = tree.getroot() for obj in root.findall('object'): cls = obj.find('name').text.strip() bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) # 转换为中心坐标 + 宽高 x_center = (xmin + xmax) / 2.0 y_center = (ymin + ymax) / 2.0 width = xmax - xmin height = ymax - ymin # 归一化(假设图像尺寸为640x480) img_w, img_h = 640, 480 x_center /= img_w y_center /= img_h width /= img_w height /= img_h class_id = {'RBC': 1, 'WBC': 2, 'Platelets': 0}[cls] data.append([filename, class_id, x_center, y_center, width, height]) df = pd.DataFrame(data, columns=['filename', 'class', 'x_center', 'y_center', 'width', 'height']) print("Total samples:", len(df))接下来划分训练集与验证集,并组织成YOLOv5所需的目录结构:
os.makedirs('dataset/images/train', exist_ok=True) os.makedirs('dataset/images/val', exist_ok=True) os.makedirs('dataset/labels/train', exist_ok=True) os.makedirs('dataset/labels/val', exist_ok=True) train_df, val_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['class']) def save_dataset(df, img_dir, label_dir): for name, group in df.groupby('filename'): label_path = os.path.join(label_dir, name.replace('.jpg', '.txt')) with open(label_path, 'w') as f: for _, row in group.iterrows(): f.write(f"{row['class']} {row['x_center']:.6f} {row['y_center']:.6f} " f"{row['width']:.6f} {row['height']:.6f}\n") src_img = os.path.join('BCCD/JPEGImages', name) dst_img = os.path.join(img_dir, name) if os.path.exists(src_img): shutil.copy(src_img, dst_img) save_dataset(train_df, 'dataset/images/train', 'dataset/labels/train') save_dataset(val_df, 'dataset/images/val', 'dataset/labels/val') print("Training images:", len(os.listdir('dataset/images/train'))) print("Validation images:", len(os.listdir('dataset/images/val')))输出结果通常如下:
Total samples: 364 Training images: 291 Validation images: 73这一步完成后,数据已准备就绪,可以进入模型训练阶段。
要运行YOLOv5,首先需要克隆官方仓库并安装依赖:
git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt然后创建配置文件data/bcc.yaml:
train: ../dataset/images/train val: ../dataset/images/val nc: 3 names: ['Platelets', 'RBC', 'WBC']这里nc表示类别数量,names定义类别名称顺序。YOLOv5会根据该配置自动调整网络输出层结构。
我们选用轻量级的yolov5s模型进行实验,兼顾速度与精度:
python train.py \ --img 640 \ --batch 16 \ --epochs 150 \ --data bcc.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --name blood_cell_detection关键参数说明:
---img: 输入分辨率,默认640×640
---batch: 批次大小,需根据GPU显存调整
---weights: 使用ImageNet预训练权重初始化,显著加快收敛
---name: 实验命名,日志和权重保存在runs/train/blood_cell_detection/
训练过程中可通过TensorBoard监控指标变化:
tensorboard --logdir runs/train典型训练结果(epoch=150)如下:
| 指标 | 数值 |
|---|---|
| mAP@0.5 | 0.89 |
| mAP@0.5:0.95 | 0.56 |
| Precision | 0.87 |
| Recall | 0.83 |
mAP@0.5达到0.89表明模型对三类细胞的定位准确率很高,尤其在血小板这类小目标上的表现稳定,说明数据增强策略(如Mosaic、随机缩放)起到了良好作用。
训练完成后,使用detect.py进行推理预测非常简单。
单张图像测试:
python detect.py \ --source ../dataset/images/val/BloodImage_00001.jpg \ --weights runs/train/blood_cell_detection/weights/best.pt \ --conf-thres 0.5 \ --iou-thres 0.45 \ --device 0批量处理整个文件夹:
python detect.py \ --source ../dataset/images/val/ \ --weights runs/train/blood_cell_detection/weights/best.pt \ --output inference_output \ --save-txt \ --save-conf添加--save-txt参数后,系统会在输出目录生成对应.txt文件,内容示例如下:
1 0.712 0.345 0.045 0.067 0.93 2 0.201 0.889 0.123 0.145 0.98每行代表一个检测结果:class_id x_center y_center width height confidence。这些数据可用于后续统计分析或集成到其他系统中。
为了更直观地查看效果,我们可以编写一个自定义可视化函数:
import matplotlib.pyplot as plt import matplotlib.patches as patches from PIL import Image import numpy as np def plot_detections(image_path, txt_path, class_names=['Platelets', 'RBC', 'WBC']): img = Image.open(image_path) fig, ax = plt.subplots(1, figsize=(12, 8)) ax.imshow(img) colors = {'Platelets': 'green', 'RBC': 'red', 'WBC': 'blue'} with open(txt_path, 'r') as f: for line in f.readlines(): parts = list(map(float, line.strip().split())) cls_id, xc, yc, w, h, conf = int(parts[0]), *parts[1:] cls_name = class_names[cls_id] # 反归一化 img_w, img_h = img.size xc *= img_w; yc *= img_h w *= img_w; h *= img_h # 转换为中心 -> 左上角 x1 = xc - w / 2 y1 = yc - h / 2 rect = patches.Rectangle((x1, y1), w, h, linewidth=2, edgecolor=colors[cls_name], facecolor='none', label=cls_name if not ax.get_legend() else "") ax.add_patch(rect) ax.text(x1, y1, f'{cls_name} {conf:.2f}', color='white', bbox=dict(facecolor='black', alpha=0.7), fontsize=10) ax.legend() ax.axis('off') plt.show() # 示例调用 plot_detections( '../dataset/images/val/BloodImage_00001.jpg', 'inference_output/BloodImage_00001.txt' )该函数会显示原图,并用不同颜色框出各类细胞,同时标注类别名和置信度。实际运行中你会发现,即使在细胞重叠区域,模型也能较好地区分个体边界。
检测只是第一步,真正的价值在于“理解”图像内容。比如,在临床上我们更关心的是:“这张血片里有多少个白细胞?”、“血小板密度是否正常?”
为此,只需几行代码就能实现自动计数功能:
def count_cells(txt_path): counts = {'Platelets': 0, 'RBC': 0, 'WBC': 0} class_names = ['Platelets', 'RBC', 'WBC'] with open(txt_path, 'r') as f: for line in f: cls_id = int(line.split()[0]) counts[class_names[cls_id]] += 1 return counts # 示例调用 counts = count_cells('inference_output/BloodImage_00001.txt') print("Detected cell counts:") for k, v in counts.items(): print(f" {k}: {v}")输出可能如下:
Detected cell counts: Platelets: 42 RBC: 18 WBC: 3这个数字可以直接用于辅助诊断。例如,血小板计数低于正常范围可能提示出血风险;而WBC异常升高则可能是感染或炎症信号。
进阶思路还包括:
- 结合像素比例估算真实物理尺寸(如 cells/mm²)
- 根据细胞面积分布判断形态异常(如巨红细胞症)
- 将结果导出为CSV或PDF报告,供医生审阅
当模型性能满足需求后,下一步就是将其部署到生产环境中。以下是几种常见的部署方案,可根据应用场景灵活选择。
方案一:本地脚本运行(适合科研或小型实验室)
打包以下组件:
- 训练好的best.pt权重文件
-detect.py
-requirements.txt
-data/bcc.yaml
用户只需执行:
pip install -r requirements.txt python detect.py --source input_folder/ --weights best.pt即可完成批量分析。这种方式轻便快捷,适合离线处理任务。
方案二:基于Flask的Web API(推荐用于系统集成)
将模型封装为RESTful接口,便于前端调用或接入医院信息系统(HIS):
from flask import Flask, request, jsonify import subprocess import os app = Flask(__name__) @app.route('/predict', methods=['POST']) def predict(): file = request.files['image'] filepath = os.path.join('uploads', file.filename) file.save(filepath) # 调用YOLOv5推理 result = subprocess.run([ 'python', 'yolov5/detect.py', '--source', filepath, '--weights', 'best.pt', '--save-txt' ], capture_output=True) # 读取检测结果 txt_file = filepath.replace('uploads/', 'inference_output/').replace('.jpg', '.txt') counts = count_cells(txt_file) if os.path.exists(txt_file) else {} return jsonify({ 'status': 'success', 'counts': counts, 'output_image': f'/output/{file.filename}' }) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)这样,任何支持HTTP请求的客户端都可以上传图像并获取结构化结果,极大增强了系统的通用性。
方案三:ONNX导出 + OpenCV DNN(适用于嵌入式或移动端)
若目标平台不支持PyTorch或Python环境,可将模型导出为ONNX格式:
python export.py --weights best.pt --include onnx随后使用OpenCV加载并推理:
import cv2 import numpy as np net = cv2.dnn.readNetFromONNX('best.onnx') image = cv2.imread('test.jpg') blob = cv2.dnn.blobFromImage(image, 1/255.0, (640,640), swapRB=True) net.setInput(blob) outputs = net.forward()这种方案特别适合部署在安卓/iOS设备、边缘计算盒子或无GPU服务器上,真正实现“端侧智能”。
整个流程走下来,你会发现YOLOv5确实做到了“开箱即用”。无论是数据预处理的灵活性、训练脚本的稳定性,还是部署方式的多样性,都体现了其强大的工程化能力。更重要的是,它让AI不再局限于算法工程师的笔记本,而是能够快速落地到真实的医疗场景中。
当然,这只是一个起点。未来还可以尝试升级到YOLOv8甚至YOLOv10,进一步提升检测精度;或者引入实例分割模型(如YOLOv8-Seg)来分析细胞形状特征;甚至构建端到端流水线:从显微镜图像采集 → 自动检测 → 智能计数 → 生成诊断建议报告。
技术的意义在于解决问题。当一个简单的Python脚本能帮助医生更快、更准地做出判断时,它的价值就已经超越了代码本身。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考