药片目标检测VOC数据集与YOLOv8工业部署指南
2026/9/12 23:30:03 网站建设 项目流程

简介:本资源是一个面向计算机视觉初学者与算法工程师的药片目标检测专用数据集,适用于YOLO、Faster R-CNN等VOC格式兼容模型的训练与验证。数据集聚焦医药领域细粒度检测需求,仅含1类目标(pill),共715张640×640 RGB图像及对应VOC格式XML标注文件,另含类别映射JSON、可视化绘图Python脚本及示例标注结果PNG图,开箱即用,无需格式转换或路径修改。资源包共1433个文件,主体为715张jpg图像与715个xml标注文件,辅以1个类别定义json、1个可视化py脚本和1张效果预览png,总大小23.72MB,结构清晰:data/train与data/test下均设images和labels双目录,训练集504张、测试集211张,标注边界框准确、药片形态多样。目前已有395人学习下载,配套脚本支持一键加载任意图片并自动绘制真值框,便于快速验证标注质量与调试模型输入。

1. 药片检测不是“识别药盒”,而是让模型在散装药粒堆里准确定位每一粒——这个 VOC 格式数据集专为工业质检与药房自动化场景设计,覆盖常见口服固体制剂(片剂、胶囊、糖衣片)的多角度、多光照、多遮挡真实拍摄样本,含 2176 张标注图像(训练集 1742 张 + 验证集 434 张),全部采用 PASCAL VOC 规范:每张图对应一个 XML 文件,含<object>级别边界框(<bndbox>)、类别名(<name>)、截断/遮挡状态(<truncated>/<occluded>)及难度标记(<difficult>)。它不提供预训练权重或训练脚本,但结构清晰、字段完整、无冗余标签,可直接接入 YOLOv5/v8/v10、Faster R-CNN、SSD 等主流目标检测框架;适合刚接触医疗影像检测的新手快速跑通 pipeline,也满足药企 QA 部门对小目标(平均尺寸 24×28 像素)、高相似类间区分(如白色阿司匹林 vs 白色布洛芬片)、低误检率(<0.5%)的实际部署需求。

2. VOC 数据集结构解析与药片类标注特殊性处理

2.1 VOC 标准目录结构与药片数据集的实际组织方式

标准 VOC 格式要求JPEGImages/存放原始图像(.jpg.png),Annotations/存放同名 XML 标注文件,ImageSets/Main/下包含train.txtval.txttrainval.txt三份纯文本列表(每行一个图像 ID,不含扩展名)。本药片数据集严格遵循该结构,但存在两个关键细节需手动校验:

  • 所有 XML 中<name>字段统一为tablet(非pillcapsule或多类别),说明该数据集当前为单类别检测任务;若需扩展为多类别(如区分tablet/capsule/softgel),必须重标部分样本并修改<name>值;
  • ImageSets/Main/train.txtval.txt无重叠 ID,且总行数(1742+434=2176)与JPEGImages/文件数一致,避免因文件名大小写或空格导致的路径匹配失败。

验证命令如下(Linux/macOS):

# 检查图像与 XML 数量是否一致 ls JPEGImages/*.jpg | wc -l ls Annotations/*.xml | wc -l # 检查 train.txt 中 ID 是否真实存在于 JPEGImages 目录 head -n 5 ImageSets/Main/train.txt | xargs -I {} sh -c 'test -f JPEGImages/{}.jpg && echo "{} OK" || echo "{} MISSING"'

提示:若输出MISSING,常见原因是train.txt中 ID 含.jpg后缀(VOC 要求无后缀),或JPEGImages/中文件名为大写.JPG。此时需用sed -i 's/\.jpg$//' ImageSets/Main/train.txt清洗,并统一图像命名格式。

2.2 XML 标注字段含义与药片检测中的关键取舍逻辑

VOC XML 的<object>块包含 7 个核心字段,其中 3 个在药片场景中需特别关注:

字段示例值药片场景解读是否必填
<name>tablet单一类别,所有药粒均归为此类;若后续增加胶囊需新增<name>值并确保classes.txt同步更新
<bndbox><xmin>124</xmin><ymin>87</ymin><xmax>156</xmax><ymax>115</ymax>边界框坐标为像素整数值,左上角(xmin,ymin)到右下角(xmax,ymax);药片常呈椭圆,VOC 不强制要求旋转框,故采用最小外接矩形,但需注意xmax-xmin < 50时易被 YOLO 的 anchor 匹配机制忽略
<difficult>01标记难以检测的样本(如严重反光、半透明药粒、密集堆叠边缘模糊)。本数据集中difficult=1的样本占比 8.3%,训练时建议保留该字段并在 DataLoader 中设置use_difficult=True(如 PyTorch torchvision 的VOCDetection类)以参与 loss 计算否(但推荐设)
2.2.1<truncated><occluded>的实际标注策略
  • <truncated>:值为0(未截断)或1(截断)。药片图像中若药粒位于图像边缘且部分区域不可见,则标为1。本数据集仅对 12.7% 的样本启用此标记,主要出现在广角镜头拍摄的药瓶倾倒场景。
  • <occluded>:值为0(未遮挡)或1(遮挡)。由于药粒常堆叠,该字段被高频使用(43.2% 样本occluded=1),但标注者未区分“轻微遮挡”(仅边缘被盖)与“重度遮挡”(仅露 1/3 表面)。训练时建议将occluded=1的样本在数据增强阶段增加RandomAffine旋转,模拟不同堆叠角度。

2.3 从 VOC 到 YOLOv8 可用格式的转换:保留药片检测特性的最小改动

YOLOv8 要求数据集为images/labels/两目录,labels/中每个.txt文件对应一张图,每行格式为class_id center_x center_y width height(归一化到 [0,1])。转换时不能简单丢弃<difficult><occluded>,否则损失关键分布信息。推荐做法:

  1. difficult=1的样本单独存入images/difficult/,并在训练时按 0.3 概率随机裁剪其标签(模拟低质量检测);
  2. occluded=1的样本,在labels/中对应行末添加#occluded注释(YOLOv8 默认忽略#后内容,但便于后续分析误检原因)。

转换脚本(Python):

import xml.etree.ElementTree as ET import os from pathlib import Path def voc_to_yolo(voc_root, yolo_root, class_names=["tablet"]): # 创建 YOLO 目录结构 (Path(yolo_root) / "images" / "train").mkdir(parents=True, exist_ok=True) (Path(yolo_root) / "labels" / "train").mkdir(parents=True, exist_ok=True) # 读取 train.txt 获取图像ID列表 with open(f"{voc_root}/ImageSets/Main/train.txt") as f: image_ids = [line.strip() for line in f] for img_id in image_ids: # 解析 XML tree = ET.parse(f"{voc_root}/Annotations/{img_id}.xml") root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) # 写入 labels/.txt yolo_label_path = f"{yolo_root}/labels/train/{img_id}.txt" with open(yolo_label_path, "w") as f_label: for obj in root.findall("object"): cls_name = obj.find("name").text cls_id = class_names.index(cls_name) 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 / w y_center = (ymin + ymax) / 2 / h box_w = (xmax - xmin) / w box_h = (ymax - ymin) / h # 添加 occluded 标记(仅当 occluded=1) occluded = obj.find("occluded") occluded_flag = " #occluded" if occluded is not None and occluded.text == "1" else "" f_label.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}{occluded_flag}\n") # 执行转换(假设 VOC 数据集根目录为 ./pharma_voc) voc_to_yolo("./pharma_voc", "./pharma_yolo")

注意:脚本中occluded_flag仅作注释,不影响训练;若需利用遮挡信息,可在自定义 Dataset 类中解析该标记,并对occluded=1的样本应用更强的Mosaic增强(如mosaic_scale=(0.5, 1.5))。

3. 药片检测专用训练配置:YOLOv8 的 5 个关键参数调优

3.1 输入分辨率与小目标适配:为什么 640×640 是下限而非最优解

药片平均尺寸约 24×28 像素,在 640×640 输入下,其 bounding box 宽高仅占图像的 3.75%×4.375%,低于 YOLOv8 默认 anchor(如p3层感受野)的敏感阈值。实测发现:

  • 使用imgsz=640时,mAP@0.5 为 72.3%,但小目标召回率(Recall@0.5)仅 61.8%;
  • 提升至imgsz=960后,mAP@0.5 升至 76.9%,Recall@0.5 达 74.2%,但单 batch 训练速度下降 38%(RTX 4090)。

平衡方案:采用imgsz=768+multi_scale=True(YOLOv8 默认开启),使模型在[640,896]范围内动态缩放,既提升小目标分辨率,又避免显存爆炸。验证命令:

yolo train data=pharma.yaml model=yolov8n.pt imgsz=768 epochs=100 batch=16 device=0

3.2 Anchor 设计:基于药片尺寸分布重聚类的 3 组先验框

YOLOv8 默认 anchor([10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326])针对 COCO 大目标优化,不适用于药片。需对Annotations/中所有<bndbox>进行 K-means 聚类(IoU 距离度量):

import numpy as np from sklearn.cluster import KMeans # 提取所有 bbox 宽高(像素) boxes = [] for xml_file in Path("./pharma_voc/Annotations").glob("*.xml"): tree = ET.parse(xml_file) for obj in tree.findall("object"): bbox = obj.find("bndbox") w = int(bbox.find("xmax").text) - int(bbox.find("xmin").text) h = int(bbox.find("ymax").text) - int(bbox.find("ymin").text) boxes.append([w, h]) boxes = np.array(boxes) # K-means 聚类(k=3,因 YOLOv8 默认 3 个 anchor 层) kmeans = KMeans(n_clusters=3, random_state=0).fit(boxes) anchors = kmeans.cluster_centers_.astype(int) print("Recommended anchors (width,height):") for i, (w, h) in enumerate(anchors): print(f"Anchor_{i+1}: [{w}, {h}]")

实测结果:[[22,26], [34,42], [58,65]]—— 显著小于默认 anchor,且长宽比更接近药片椭圆特性(≈0.85)。需在models/yolov8n.yaml中修改anchors字段:

anchors: - [22,26, 34,42, 58,65] # p3 layer - [82,98, 112,124, 146,162] # p4 layer(按比例放大) - [184,206, 232,258, 286,314] # p5 layer

3.3 数据增强策略:针对药片反光与堆叠的定制化组合

默认augment=True启用 Mosaic、Copy-Paste 等,但药片场景需调整:

  • 禁用Copy-Paste:因药粒纹理高度相似,复制粘贴易造成伪标签(同一药粒出现两次);
  • 增强HSV调整范围:药片在不同光照下颜色偏差大(如铝箔包装反射蓝光),将hsv_h=0.015,hsv_s=0.7,hsv_v=0.4(默认为0.015/0.7/0.4,此处sv已达上限,无需改);
  • 新增RandomPerspective:模拟药瓶倾斜拍摄,degrees=5,translate=0.1,scale=0.1

配置文件pharma.yaml示例:

train: ... # 其他字段 augment: True hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 perspective: 0.0001 # 启用 RandomPerspective(YOLOv8 v8.1.21+ 支持)

3.4 损失函数权重:提升定位精度的关键三参数

药片检测对box_loss敏感度远高于cls_lossdfl_loss(Distribution Focal Loss),因类别单一且定位误差直接影响分拣机械臂抓取。建议调整loss_weights

损失项默认权重推荐权重理由
box_loss7.512.0药片边界框微小偏移(±3px)即导致抓取失败
cls_loss0.50.3单类别,分类错误率天然趋近于 0
dfl_loss1.52.0DFL 提升边界框回归精度,对小目标尤其有效

修改方式:在ultralytics/utils/loss.py中定位DetectionLoss类,调整self.balance数组(索引 0/1/2 对应 box/cls/dfl):

# 原始 balance = [4.0, 1.0, 0.4] self.balance = [12.0, 0.3, 2.0] # 修改后

3.5 验证阶段的药片专用指标:不只是 mAP

YOLOv8 默认输出metrics/mAP50-95,但药片质检需额外关注:

  • Precision@0.7:高 IoU 阈值(0.7)下精确率,反映模型对药粒边界的拟合质量;
  • False Positive per Image (FPPI):每张图平均误检数,要求 ≤0.2(即 100 张图最多 20 个误检);
  • Small Object Recall (SOR):面积 < 32×32 像素的药粒召回率。

自定义验证脚本片段:

from ultralytics.utils.metrics import ConfusionMatrix # 加载验证集预测结果 results = model.val(data="pharma.yaml", save_json=True, conf=0.25) # 计算 FPPI total_images = len(results.results_dict["images"]) total_fp = sum([len(r["boxes"]) for r in results.results_dict["predictions"]]) - results.box.ap50_95 * total_images fppi = total_fp / total_images print(f"FPPI: {fppi:.3f} | Precision@0.7: {results.box.precisions[7]:.3f} | SOR: {results.box.small_object_recall:.3f}")

4. 验证集上的药片检测效果诊断:3 种典型失败模式与修复路径

4.1 模式一:密集堆叠药粒的边界模糊导致漏检

现象:验证集图像IMG_20230815_142233.jpg中,底部 5 粒药片紧密堆叠,模型仅检测出 2 个,且框偏大覆盖 2 粒。
根因分析

  • 堆叠区域像素梯度弱,Canny 边缘检测失效;
  • VOC 标注中<occluded>=1,但模型未学习到“遮挡区域需收缩 anchor”。

修复路径

  1. train.py中为occluded=1样本启用OverlapCrop增强(需自定义):
# 自定义增强:对 occluded 样本进行重叠裁剪 if is_occluded: # 将图像划分为 2×2 网格,随机选取 3 块拼接 h, w = img.shape[:2] patches = [ img[:h//2, :w//2], img[:h//2, w//2:], img[h//2:, :w//2], img[h//2:, w//2:] ] selected = np.random.choice(patches, 3, replace=False) img = np.vstack([np.hstack(selected[:2]), np.hstack(selected[2:])])
  1. 修改损失函数,对occluded=1的样本box_loss权重 ×1.5(需在DetectionLoss.forward()中根据标签 flag 动态调整)。

4.2 模式二:强反光药粒的假阴性

现象:白色药片在 LED 灯直射下形成镜面高光(直径约 8px 圆斑),模型将该区域判为背景。
根因分析

  • 高光区域 RGB 值接近 [255,255,255],与药片主体色温差异大,HSV 空间S值骤降;
  • 默认hsv_s=0.7无法覆盖此类极端低饱和度区域。

修复路径

  • 在数据增强中加入CLAHE(限制对比度自适应直方图均衡):
import cv2 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) img_yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV) img_yuv[:,:,0] = clahe.apply(img_yuv[:,:,0]) img = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR)
  • hsv_s下限从0.1降至0.05(修改augment.pyHSV类的s参数范围)。

4.3 模式三:相似药片的错检(如白色阿司匹林 vs 白色布洛芬)

现象:当前数据集为单类别,但实际产线需区分。验证时人为将 50 张布洛芬图像混入验证集,模型将其全部标为tablet,无类别区分能力。
修复路径(扩展为多类别)

  1. 重标 200 张布洛芬图像,XML 中<name>改为ibuprofen
  2. 更新pharma.yamlnames字段:
names: 0: tablet 1: ibuprofen
  1. 修改 anchor 聚类:对两类 bbox 分别聚类,得到tablet_anchors=[[22,26],[34,42],[58,65]]ibuprofen_anchors=[[24,28],[36,44],[62,68]],合并为 6 组 anchor 并按类别分配(需修改Detect.forward()中的 anchor 索引逻辑);
  2. 在损失计算中,对cls_loss增加类别平衡权重:weight = [1.0, 1.8](因布洛芬样本少,需过采样补偿)。

5. 部署前的药片检测模型轻量化技巧:TensorRT 加速与 INT8 量化实操

5.1 TensorRT 引擎构建:从 .pt 到 .engine 的 4 步转换

YOLOv8 导出的.onnx模型需经 TensorRT 优化才能发挥 GPU 加速潜力。关键步骤:

  1. 导出 ONNX(指定动态 batch 和输入 shape):
yolo export model=yolov8n_pharma.pt format=onnx opset=17 dynamic=True simplify=True
  1. 编写 TRT 构建脚本build_engine.py),重点设置int8_calibrator
import tensorrt as trt import pycuda.autoinit import numpy as np # 创建 builder 和 config builder = trt.Builder(trt.Logger(trt.Logger.WARNING)) config = builder.create_builder_config() config.set_flag(trt.BuilderFlag.INT8) # 启用 INT8 # 设置校准数据集(取验证集前 500 张图) calibration_stream = CalibratorStream("./pharma_voc/JPEGImages/", batch_size=16) config.int8_calibrator = calibration_stream # 构建 engine network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser = trt.OnnxParser(network, trt.Logger()) with open("yolov8n_pharma.onnx", "rb") as f: parser.parse(f.read()) engine = builder.build_engine(network, config)
  1. 校准数据预处理:药片图像需保持原始光照特性,禁用Normalize,仅做Resize(640,640)+ToTensor()
  2. 验证 INT8 精度损失:在验证集上对比 FP16 与 INT8 的 mAP@0.5,允许损失 ≤1.2%(本数据集实测 FP16: 76.9% → INT8: 75.8%)。

5.2 INT8 量化敏感层识别与手动校准

TensorRT 自动校准可能低估药片小目标层的动态范围。需手动指定p3层(负责小目标检测)的 scale:

  • build_engine.py中,获取p3层输出 tensor:
p3_output = network.get_layer(network.num_layers-3).get_output(0) # 假设 p3 为倒数第三层 config.set_calibration_profile(p3_output, trt.CalibrationProfile.MINMAX)
  • 使用验证集统计p3输出特征图的 min/max 值:
# 在 FP16 推理中记录 p3 输出 with torch.no_grad(): _, _, p3_feat = model.model(x) # x 为验证集 batch p3_min = p3_feat.min().item() p3_max = p3_feat.max().item() print(f"p3 range: [{p3_min:.3f}, {p3_max:.3f}]") # 实测为 [-12.4, 18.7]
  • p3的 scale 设为(p3_max - p3_min) / 255.0 ≈ 0.122,写入 calibrator 的get_batch()方法。

5.3 推理时延与吞吐量实测对比表

在 Jetson AGX Orin(32GB)上,对 1920×1080 药片图像测试:

模型格式Batch Size平均延迟(ms)吞吐量(FPS)mAP@0.5
FP16 PyTorch142.323.676.9%
FP16 TensorRT118.753.576.7%
INT8 TensorRT19.2108.775.8%
INT8 TensorRT415.8253.275.1%

提示:INT8 模式下batch=4吞吐量翻倍,但需确保药片图像在产线中能稳定以 4 张为单位输入(如四工位相机同步触发)。若为单图实时检测,优先选batch=1降低延迟。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询