ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

药品小样本目标检测实战:板蓝根颗粒数据集VOC转YOLO与训练全解析

药品小样本目标检测实战:板蓝根颗粒数据集VOC转YOLO与训练全解析 简介针对板蓝根颗粒袋装药品检测任务这份数据集面向计算机视觉初学者和工业质检应用开发者提供111张真实拍摄的jpg原图以及完全对应的VOC格式xml与YOLO格式txt标注文件覆盖“999ganmaoling”和“banlangen”两个类别可用于训练目标检测模型、验证标签格式转换或搭建药品包装识别小项目。压缩包内共335个文件主体为jpg图像、xml标注和txt标注三类整体仅3.71MB轻量易用所有标注由labelImg工具完成共包含134个目标框其中banlangen类85个、999ganmaoling类49个类别分布清晰适合快速跑通从数据加载到模型评估的完整流程。三类文件一一对应方便对照原图检查标注效果也可直接作为YOLO、SSD、Faster R-CNN等常见检测器的训练输入。已有121人学习这一资源对入门级开发者而言是理解VOC与YOLO两种标注规范差异、练习自制数据集制作全流程的实用素材。1. 目标检测数据集里的“小样本特例”板蓝根颗粒检测该怎么做做目标检测的朋友应该都有体会通用数据集好找但像药品这种垂直场景想拿现成的标注数据基本靠缘分。这份“药品板蓝根颗粒检测数据集”一共111张JPEG图片同时给了Pascal VOC的xml和YOLO的txt两套标注类别只有两个999ganmaoling和banlangen总框数134个。数据量不大但它是真实药品袋装拍摄样本对想做药品识别、货架陈列检测、颗粒袋定位这类小目标任务的从业者来说属于难得的起步数据。本文就把这套数据集的内部结构、格式转换方法、训练配置和踩坑点一次拆完。2. 拆解数据集文件布局VOC与YOLO双格式的对应关系2.1 图片、xml、txt三者如何一一对应拿到手解压之后文件结构非常直白一张jpg对应一个同名xml再对应一个同名txt。比如banlangen_xyxr_3.jpg旁边就是banlangen_xyxr_3.xml和banlangen_xyxr_3.txt。这种命名规整的格式是labelImg直接导出的典型产物做数据集校验和二次开发都很省事。实际使用时会发现一个关键点YOLO格式的txt文件里不包含类别名称只存类别ID和归一化坐标。类别ID的顺序取决于标注时定义的classes.txt如果这份classes.txt里的顺序是999ganmaoling在前、banlangen在后那么txt第一列是0就代表999ganmaoling是1就代表banlangen。很多人会在这里翻车——把txt当成独立文件用却不核对类别顺序训练出来的模型两个类别全乱套。2.2 XML标注内容逐字段读一遍打开任意一个xml文件里面的字段是VOC标准结构filename是图片名size里记录width、height、depth三个维度object节点下name是类别名bndbox给出xmin、ymin、xmax、ymax四个整数坐标。这套结构是CV领域最通用的标注交换格式几乎所有检测框架都支持直接读。annotation folderbanlangen/folder filenamebanlangen_xyxr_3.jpg/filename size width960/width height540/height depth3/depth /size object namebanlangen/name bndbox xmin120/xmin ymin98/ymin xmax362/xmax ymax310/ymax /bndbox /object /annotationxml里最关键的是bndbox的四个值它是像素绝对坐标左上角为原点x向右增大y向下增大。如果之后要自己标数据labelImg画框存成xml是完全一致的格式这也是数据集作者选择labelImg的原因——开源免费导出格式兼容性最好。2.3 TXT标注的归一化坐标换算逻辑YOLO格式的txt每一行代表一个目标框结构是类别ID、x_center、y_center、width、height。五个值用空格分隔其中坐标全部做了归一化也就是用像素坐标除以图片宽高得到的0到1之间的浮点数。1 0.251042 0.377778 0.252083 0.392593 0 0.718125 0.427778 0.229167 0.437037第一行开头是1对应banlangen类别第二行开头是0对应999ganmaoling类别。从坐标值能反推出原图尺寸是960x540比如x_center0.251042乘以960约等于241正好落在xml标注的xmin到xmax区间内。这个换算是理解VOC和YOLO格式差异的钥匙VOC存绝对坐标方便人读YOLO存归一化坐标方便模型训练。3. 用Python脚本统一处理数据集从校验到划分3.1 校验框坐标是否越界和格式是否齐全数据集在训练前最怕的是标注错误。我写了一个快速的校验脚本逐个xml读bndbox和图片宽度高度做对比检测坐标越界、框宽高是否为负、图片是否缺失标注文件这三个最常见问题。import os import xml.etree.ElementTree as ET from PIL import Image xml_dir Annotations jpg_dir JPEGImages for xml_name in os.listdir(xml_dir): xml_path os.path.join(xml_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) for obj in root.findall(object): box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) assert xmin 0 and ymin 0, f{xml_name} 坐标出现负值 assert xmax img_w and ymax img_h, f{xml_name} 坐标越界 assert xmax xmin and ymax ymin, f{xml_name} 框宽高异常这段逻辑并不复杂但能拦下90%的脏数据。坐标越界是标注时手滑拖出画布导致的框宽高异常通常是误操作画了个点。如果xml里size的宽高和jpg实际尺寸对不上说明图片被压缩或旋转过这个也要提前发现。3.2 VOC坐标转YOLO坐标的具体实现虽然这份数据集已经同时给了VOC和YOLO两种格式但如果你之后自己扩充数据或者从网上找其他VOC数据集用转换脚本是躲不开的。转换的原理很简单用box坐标除以图片宽高得到归一化值。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, class_names): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) class_names [999ganmaoling, banlangen] voc_to_yolo(banlangen_xyxr_3.xml, banlangen_xyxr_3.txt, class_names)class_names的顺序就是YOLO模型训练时的类别ID映射写死顺序会导致ID错乱。我一般把class_names单独存成classes.txt文件每次训练前都检查一遍。3.3 数据集按8:1:1比例切分训练集和验证集111张图的数据量全部塞进训练集容易过拟合划分时我建议训练集占八成左右验证集留十来张。注意这里有一个容易被忽略的细节划分后要把图片和标注文件同步移动或者统一生成路径列表文件保证训练时正样本对得上。import os import random from sklearn.model_selection import train_test_split jpg_files [f for f in os.listdir(JPEGImages) if f.endswith(.jpg)] train_files, val_files train_test_split(jpg_files, test_size0.15, random_state42) with open(train.txt, w) as f: for name in sorted(train_files): base os.path.splitext(name)[0] f.write(fJPEGImages/{name} Annotations/{base}.xml\n) with open(val.txt, w) as f: for name in sorted(val_files): base os.path.splitext(name)[0] f.write(fJPEGImages/{name} Annotations/{base}.xml\n) print(f训练集 {len(train_files)} 张验证集 {len(val_files)} 张)random_state固定为42保证每次跑出的划分结果一致复现实验结果时不会因为随机切分产生偏差。如果你用的是新版YOLO不需要手动划分train.txt和val.txt按照目录结构放好图片YOLO会自动扫描。4. 用YOLOv8训练这套板蓝根数据集完整配置流程4.1 目录结构搭建与data.yaml配置把这份数据集喂给YOLOv8之前需要先按照YOLO系列框架的约定组织目录。我一般习惯把jpg和txt放在同一个目录下保持文件名一一对应然后写一个data.yaml描述数据集信息。dataset/ ├── images/ │ ├── train/ │ │ └── banlangen_xyxr_3.jpg │ └── val/ │ └── banlangen_xyxr_45.jpg ├── labels/ │ ├── train/ │ │ └── banlangen_xyxr_3.txt │ └── val/ │ └── banlangen_xyxr_45.txt └── data.yamldata.yaml的内容是训练入口路径、类别数、类别名都必须写对。这里有一个高频坑点paths最好写绝对路径很多新手写相对路径导致训练启动后报错找不到图片。yaml文件不能有tab缩进只能用空格。train: /home/user/dataset/images/train val: /home/user/dataset/images/val nc: 2 names: [999ganmaoling, banlangen]注意names的列表顺序和生成txt时的class_names顺序必须严格一致0对应999ganmaoling1对应banlangen顺序反了模型就是一个废物。验签方法是随机打开几个txt文件看第一列数字是否和预期类别匹配。4.2 YOLOv8训练命令与关键参数实测说明YOLOv8是目前主流框架里对标签格式兼容性最好的一个直接用yolo命令就能训练。权重建议选yolov8s.pt或者yolov8n.pt111张图属于微型数据集模型大了必然过拟合s模型已经偏大n模型更稳。yolo train datadata.yaml modelyolov8n.pt epochs200 imgsz640 batch16 device0 patience3epochs给到200是为了观察loss曲线是否收敛实际可能在几十轮就早停了。patience3是早停策略如果验证集loss连续3轮没有下降训练自动终止。batch16要看显卡显存6GB以下建议8。设备是CPU就改成devicecpu但训练时间会拉到数小时。训练结束后看results.csv里的指标重点看两个数mAP50和mAP50-95。小数据集上mAP50高只能说明模型对训练集的记忆好mAP50-95低才是真正的泛化能力差。这个项目类别是药品颗粒袋形状比较规整两个指标差异不会特别夸张。4.3 推理测试与置信度阈值调整训练完成后用best.pt做推理把验证集的图片跑一遍看效果yolo predict modelruns/detect/train/weights/best.pt sourcedataset/images/val conf0.25 iou0.45conf参数是置信度阈值默认0.25。这个数据集是近距离拍摄的药品袋目标相对大且清晰阈值可以往高调到0.4以上减少误检。iou是NMS的IoU阈值默认0.45目标密集重叠时可以调低到0.35避免两个紧挨着的药袋被合并成一个框。5. 避坑与常见问题小样本数据集训练翻车实录5.1 类别名带数字导致解析错乱现象训练时报错Class index out of range或者推理时999ganmaoling和banlangen的框混乱。原因999ganmaoling这个类别名以数字开头某些框架在解析类别字符串时会把它截断或转换成数值尤其导出ONNX或TFLite时容易出现。解决在data.yaml里保持names顺序不变但代码中所有读取类别的地方统一走names数组索引不要依赖正则提取。我在实际使用中更推荐把类名改为ganmao_999这样的格式规避数字开头的解析风险。5.2 用labelImg续标时标签序号没对齐现象自己手动补标了几十张图训练后发现某些框的类别和实际物体不符。原因labelImg打开的是默认class_list它把999ganmaoling和banlangen的ID顺序和你之前txt里的顺序弄混了。解决在labelImg的设置里加载一份写死的classes.txt每次打开都强制定位到同一份类别表标注完之后随机抽查20%的txt文件确认类别ID和框内物体一致。5.3 数据集过小直接爆内存或显存溢出现象batch设大之后CUDA out of memory或者模型训练一两轮loss就冲到NaN。原因111张图还带早停模型很容易把验证集背下来loss曲线失真显存溢出多半是图像尺寸和batch配置不合理。解决imgsz从640降到416batch降到8开启mosaic增强。如果做迁移学习加载预训练权重时记得加freeze10冻结前10层保住主干网络的特征提取能力。5.4 框与图片尺寸不一致导致训练崩溃现象ValueError: zero-size image to tensor或者训练初始化时报标签越界。原因数据集里jpg文件和xml的尺寸是匹配的但你用自己的代码转换后可能用了另一批图片。解决统一用python脚本检查每张jpg的实际尺寸和xml里size字段做比对不一致的直接用pillow重写size字段。这个操作不麻烦但很多做检测的都默认格式完美结果训练一半才发现标注和图像对不上。5.5 多标签或同一物体被画了多个框现象一张图里同一个药袋被标出两个重叠框训练时loss震荡。原因标注者手抖或者在labelImg里画完框又没删掉旧框直接再画一遍。解决写一个查重脚本计算所有框的IoU大于0.8的只保留一个。这种问题只有检查时才能发现跑训练之前一定过一遍。用表格整理一下数据集的标注分布类别999ganmaoling有49个框banlangen有85个框总框数134类别不平衡程度在可接受范围内。6. 进阶技巧用数据增强和置信度调优提升小模型实战表现先把111张原图喂给模型假设训练结果mAP50在0.85左右这在小样本场景已经算不错。想继续往上顶第一个必做的是离线数据增强。不要只依赖YOLO自带的在线增强药品袋这种目标有明显的色彩和纹理特征做HSV扰动和随机旋转能成倍扩大有效样本量。import imgaug.augmenters as iaa import cv2 aug_pipeline iaa.Sequential([ iaa.Fliplr(0.5), iaa.Affine(rotate(-15, 15)), iaa.MultiplyHue((0.8, 1.2)), iaa.LinearContrast((0.8, 1.2)) ]) image cv2.imread(banlangen_xyxr_3.jpg) augmented aug_pipeline(imageimage) cv2.imwrite(banlangen_xyxr_3_aug.jpg, augmented)增强后的图片必须同步更新标注框imgaug的Affine和Fliplr会自动做坐标变换但如果自己手写平移旋转千万记得把xml里的bndbox一起换算。我见过有人增强后只保存了图片标注框还停在原坐标上这种“半套增强”比不增强还坑。第二个进阶方向是调推理置信度。药品颗粒袋在货架上经常被遮挡遮挡后模型输出的置信度会掉到0.2左右但你其他场景又不需要这么低的阈值。做法是跑推理时对每个类别单独设阈值999ganmaoling的包装颜色偏浅阈值可以设低到0.15banlangen的包装颜色深0.3起步都够。实际调优时在验证集上跑一遍完整推理把所有的conf值存下来画直方图找到两个类别置信度分布的分水岭再分别设阈值。从那以后我做任何小样本检测任务都会强制走一遍流程校验标注、统一类别清单、按比例切分、冻结主干预训练、训练后输出置信度直方图调阈值。这套流程不需要玄学每一步都有明确产出。希望帮到你。本文还有配套的精品资源点击获取
返回列表