做药片外观缺陷检测的朋友,多少都遇到过这样一个痛点:药片崩边、缺角、掉底这类正背面缺失问题,靠传统图像处理很难稳定捕捉,靠分类网络又只能给个“有没有问题”的结论,定位不了具体位置。最近我在整理一套药片正背面缺失识别分割数据集,labelme格式,1111张图,4个类别,算是把这块的标注、清洗、训练流程完整跑了一遍。这篇文章就把这套数据集的整体设计思路、标注细节、踩过的坑和训练策略一次讲清楚,给正在做工业质检、医药包装检测或相关视觉项目的朋友一个可以直接参考的样本。1. 项目整体设计与思路拆解1.1 为什么要用分割模型来识别药片缺失药品外观缺陷检测里,药片的“缺失”比想象中复杂。缺失不一定是一大块崩掉,也可能只是边角磨掉了一小块、模具压片时填充不足导致边缘圆缺、运输过程磕碰产生的小豁口。这类缺陷用目标检测框去框,会出现两个问题:一是检测框会把整个药片框进去,模型很难从“框内有什么”判断出缺陷区域到底在哪;二是崩缺的形状极不规则,矩形框的监督信号太粗糙,导致小豁口经常漏检。分割模型天然适合这个场景。它输出的是像素级的掩膜,能告诉你在药片正面的哪一个具体坐标区域出现了缺失,也能算出来缺了多少像素、占整个药片面积的百分比。有了这个面积比例,才能制定清晰的质量判定阈值,比如缺失比例超过5%就判不合格,而不是凭模型概率拍脑袋。这个差别在产线落地时非常关键,质检员和审核人员需要的是一个可解释、可量化的判定依据。另外,分割模型对不规则边缘的拟合能力远强于检测框。药片是高光曲面物体,边缘会有复杂的光晕和反射,分割模型通过逐像素分类,天然对边缘区域有更强的区分度,比较抗光照干扰。这是我当初选择分割路線而不是检测或分类路线的核心原因。1.2 4个类别到底怎么划分标题里写着“4类别”,这个需要具体解释一下。正背面缺失识别,本质上有两个维度:药片的面(正面、背面)和药片的状态(正常、缺失)。所以最常见的4类别划分方式就是:类别ID类别名含义0normal_front正常药片正面1normal_back正常药片背面2defect_front存在缺失的药片正面3defect_back存在缺失的药片背面这样划分有一个明显的好处:模型在训练时同时学习“区分正反面”和“区分缺失区域”两个任务,分割结果天然带有类别语义。推理的时候,模型输出一个分割掩膜,每个像素的类别标签直接告诉你这是正面的正常区域、背面的正常区域、正面的缺失区域还是背面的缺失区域。后续只需要统计各类别的像素数,就能算出缺失比例,再结合药片轮廓得到缺陷位置的精确坐标。有人可能会问,为什么不直接只标注“缺失区域”这一个类别,把正背面留给另一个模型去判断?我在实际对比中发现,分开独立建模会带来误差累积,而且两个模型之间的坐标对齐很麻烦。药片在传送带上可能有轻微旋转和位移,正面模型输出的掩膜和背面模型输出的掩膜如果不能精确对齐,后续的特征融合就全错了。用一个模型同时输出4类分割掩膜,就绕开了这个问题,类别之间的空间关系天然对齐。1.3 labelme格式的选型理由数据集的标注格式我选了labelme,这是目前最通用的开源图像标注工具之一,自动驾驶、医学影像、工业质检领域的很多公开数据集都用它。它的核心优势在于:第一,标注文件是独立的JSON,一个JSON对应一张图,方便并行标注和逐步验收。多人协作时,每个人标注一批图片,每批图片对应一批JSON,合并时不会产生冲突,不像某些标注格式需要统一维护一个巨大的数据库。第二,JSON结构极其透明。labelme的JSON文件本质是一个字典,包含imagePath、imageData、imageHeight、imageWidth、shapes等字段,shapes里是每个标注多边形的顶点坐标和类别标签。这个结构对程序员来说理解成本为零,解析起来也简单,走Python json库读进来就行。第三,格式转换生态成熟。labelme转COCO、转YOLO分割格式、转VOC格式的工具一抓一大把,不管后续想用什么训练框架,都能方便地转过去。我这次做训练时就是先保持labelme格式做标注和质量筛选,确认无误后再统一转成训练用的格式,流程上非常灵活。2. 标注数据集的构成与核心细节2.1 1111张样本的分布比例与采集设计1111张这个数量,说实话不算大,但对于分割标注任务来说已经是一个很实在的体量了。做过分割标注的人都知道,勾一个复杂的多边形要几十个点,一张图复杂的可能要标注三四个面,一个熟练标注员一天能保质保量完成50张就已经很快了。1111张图对应的是真实的时间成本和人力成本。样本分布上,我采用的是“正常为主、缺陷加量、正背均衡”的策略。粗略统计下来,正常药片的正面和背面各占约30%,带缺失的正面和背面各占约20%。这样分布的理由是:正常样本是生产线上最常见的情况,模型必须充分见过正常样本才能保证不误报;而缺陷样本虽然少,但它是模型真正要抓的目标,所以比例不能太低,否则模型会倾向于把所有区域都预测成正常。采集场景我覆盖了三种:明亮背景下的单粒药片、流水线上的密集排布药片、以及模拟复杂背景的散放药片。前两种贴近实际产线,第三种是为了增强模型的泛化能力,防止模型把背景特征学进类别特征里。拍摄角度上,大部分是垂直俯拍,也故意加入了一些小角度倾斜的样本,因为实际应用中很难保证药片完全水平。2.2 标注对象和方法要点标注的对象不是“整张图”,而是“药片的所有可见区域”。一张图里如果有多个药片,需要把每一个药片的正面或背面完整勾出来。注意,不是只勾缺陷区域,而是把整个药片的面部区域都勾成多边形,然后通过类别的不同来区分正常和缺失。这个策略很多人一开始不理解,但训练时就会明白:分割模型需要看到完整的上下文,如果只标注残缺区域,模型缺少“这个药片原本应该长什么样”的参照,等于把问题从“区域分割”变成“盲猜异常”,效果会差很多。具体到勾多边形的操作,我用的是labelme的polygon工具,逐点点击药片轮廓边缘。关键点是:正常的药片边缘要贴合真实的弧线,点距保持在3到8个像素之间;药片边缘的高光部分要小心,不要因为高光看不清轮廓就随手拉直线;凡是肉眼能看到明显崩缺的轮廓段,要顺着缺口边缘走,把缺失区域尽量精确地围出来。缺失区域的判断标准,我定了一条简单的经验规则:轮廓凹陷深度超过2毫米(对常见片剂直径而言约8像素左右)才标为缺失,小于这个阈值的轻微毛刺不标。这能显著减少标注标准不一致的问题,后面模型训练阶段我踩过这方面的坑,这个阈值规则帮我挽救了大部分不一致样本。2.3 类别的标注规范和拒收标准多人协作标注时,最大的风险就是标准不统一。我在项目里专门写了标注规范文档,把每个类别的定义和边界情况做成带示例图的对照表格。比如normal_front必须满足:无刻痕破损、边缘无缺口、表面无碎裂纹路;defect_front必须满足:有明显轮廓凹陷、有碎片缺失或大块崩边。类似照片/对比图里药片正面的刻字或者压痕,绝对不能标成缺失,这是最容易出现的误标。还有一个容易混淆的点:药片背面的凹槽和模印。有些药片背面本身就带一个弧形凹槽,这是正常工艺特征,不是缺失。标注新人极易把这部分勾成defect。我的解决方案是在规范文档里单独列出“常见的正常压痕形态”,并让标注员遇到不确定的样本先集体讨论,形成共识再继续标。拒收标准方面,我设置了3条红线:多边形面积小于药片总面积50%的标注直接拒收;类别标签与示例图明显不符的整批退回;轮廓点数量少于15个且形状与药片实际轮廓偏差明显的拒收。设置红线不是为了刁难,而是为了确保后续模型训练时不会因为垃圾标注产生方向性错误。3. 实操过程与关键环节实现3.1 从拍摄到标注的完整流程整个数据集的制作流程,我拆成了六个环节:样本采集、初筛清洗、图片预处理、标注执行、双人复核、导出打包。每个环节都有明确的产出物,方便随时检查进度和回滚。样本采集环节,我用的是工业级的俯拍支架配合环形LED光源,尽量减小药片表面反光。光源亮度调到中等偏上,太亮了药片边缘会曝光过度,太暗了轮廓细节看不清。每张图保持药片在画面中间,分辨率统一输出为1920x1080左右。拍摄时重点保证药片占比足够大,过小的目标会让标注和模型都很难受。初筛清洗环节,我会把模糊的、反光过度的、景深明显不对的图片筛掉。这一步很多人偷懒,觉得后期模型增强能救回来,但实际上训练数据里的模糊样本会让分割边界变得混乱。清洗后还剩1100多张,这个数量基本就是最终的数据集规模。标注执行环节我用的labelme命令行工具启动,python环境装好之后直接labelme --labels labels.txt --nodata启动标注界面。labels.txt里预先写好了4个类别名加一个背景类。注意我用的是--nodata参数,这样标注时JSON里不会嵌入base64图片数据,文件体积小很多,打开保存都更快,也方便后续数据版本管理。3.2 labelme JSON文件结构和字段说明很多人直接拿labelme导出的JSON当最终产物,但对字段的意义一知半解,这里我把核心字段拆开讲一遍。一个典型的labelme JSON结构如下:{ "version": "4.5.9", "flags": {}, "shapes": [ { "label": "defect_front", "points": [[x1, y1], [x2, y2], ...], "group_id": null, "shape_type": "polygon", "flags": {} } ], "imagePath": "000123.png", "imageData": null, "imageHeight": 1080, "imageWidth": 1920 }这里最重要的字段是shapes数组,数组里的每个对象代表一个标注多边形。label字段是类别名,shape_type固定为polygon,points是顶点坐标列表。注意,labelme的坐标是像素坐标,顺序按你点击的先后顺序排列,没有强制要求顺时针或逆时针,但在后续转COCO格式时需要统一处理。还有个关键点是imageData字段,当前值为null。因为我用了--nodata启动,所以没有把图片以base64形式塞进JSON,只保留了imagePath引用。有些模型训练框架直接读JSON里的imageData,如果你的JSON里是null就会报错,这个后面处理时要留意。另外,多药片图片里如果同时出现正面和背面,比如透明泡罩包装里的药片,shapes数组里就会有不同的类别标注。同一个类别可以有多组多边形,比如两个药片都是normal_front,那shapes里就有两组label都是normal_front的polygon,这种格式模型训练代码需要支持多实例解析,不能默认每个label只出现一次。3.3 数据集的清洗和转换labelme JSON不能直接喂给所有训练模型,所以标注完成后我做了两步处理:转成COCO格式和转成通用的语义分割掩膜。这里分享我实际用下来比较稳定的代码逻辑。COCO格式转换,核心工作是读取所有JSON的shapes,生成一个annotations数组,每个polygon对应一个annotation对象,并把点坐标归一化成COCO需要的[x1, y1, x2, y2, ...]扁平数组。同时还要计算每个polygon的边界框和面积,这些COCO的annotation对象都需要带上。我写过一个大概120行的转换脚本,处理1111张图大约耗时1分钟,效率完全可以接受。语义分割掩膜转换则更简单直接:创建一张与原始图片同尺寸的单通道灰度图,背景像素为0,然后对每个类别的polygon执行cv2.fillPoly,把对应区域填充为类别的索引值(1、2、3、4)。这样得到的PNG掩膜图,配一个类别名称映射字典,很多分割框架直接就能用。清洗环节要重点检查的是类别标签的一致性。比如有人标了“defect_front”,有人标了“defect front”,还有“缺陷正面”,这些看起来很像但不是同一个标识符,模型训练时会被当作不同的类别。我用脚本统计了所有JSON中出现过的label值集合,发现不一致的情况就统一替换。这一步虽然不起眼,但能避免训练时类别数莫名其妙的变成7个8个。4. 模型训练与数据增强策略4.1 分割模型的选型与输入尺寸数据集准备好了之后,模型选型我先后对比过三个方案:U-Net、DeepLabV3+和Mask2Former。U-Net在医学和工业小数据集上稳定性好,参数量小,训练快,缺点是感受野有限,对拟合大范围形状的约束弱一些。DeepLabV3+用ASPP模块捕获多尺度上下文,对边缘略模糊的缺陷更鲁棒。Mask2Former是较新的方案,效果不错但相对重,1111张图的体量下容易过拟合。最终我用了DeepLabV3+搭配ResNet50骨干,这个组合在速度、精度、显存占用之间平衡得比较好。输入尺寸上,我选了640x640。这个尺寸是权衡后的结果:药片轮廓细节需要足够的分辨率才能标得清楚,但训练显存和推理速度也要考虑。如果你用512x512也能跑,只是小缺口的边缘会模糊一些;如果条件允许,768或者1024效果会更好,但训练时间明显拉长。4.2 针对药片场景的增强技巧数据增强我分成了常规增强和针对性增强两类。常规的随机旋转、水平垂直翻转、随机裁剪我都有用,但对药片场景来说,有几个增强手段效果特别显著。第一个是亮度对比度扰动合成。我按0.7到1.3的缩放范围随机调整亮度和对比度,这样模型能适应不同光照条件下的药片表面反光变化。实测下来,加了这个增强后,模型在新光照场景下的mIoU提升了约2个点。第二个是随机填充遮挡。模拟药片边缘被其他物体部分遮挡的情况,随机添加一些黑色矩形块覆盖药片边缘区域,逼迫模型学习从遮挡中恢复完整轮廓。这个增强在分割任务里挺管用的,但也别用太狠,我控制在20%的概率,否则会引入太多假样本。第三个是弹性形变和网格畸变。工业产线上药片的姿态和形状有一定随机性,弹性形变能让模型对轮廓的轻微变形更鲁棒。我用的是OpenCV的重映射实现,随机生成控制点做插值变形。需要特别注意一个禁忌:不要在训练数据上做会影响类别语义的增强,比如随机擦除把整个药片核心区域抹掉,或者贴一个假的药片图案上去。这会干扰模型理解“缺失”和“正常”的区别,得不偿失。增强的本质是模拟真实场景的变化,不是创造新的语义类别。4.3 训练参数和评估指标训练参数方面,我用了AdamW优化器,初始学习率1e-4,配合Poly学习率衰减策略,批量大小8,训练80个epoch。损失函数用的是交叉熵损失加Dice损失的加权组合,比例大致是1:1。Dice损失能缓解前景背景类别不平衡的问题,对药片区域占比小的情况帮助很明显。评估指标不能只看整体mIoU。因为语义分割的背景占大比例,即使模型啥也没学到,背景类的IoU也很高,整体mIoU照样好看。我重点看三个指标:前景类别的平均IoU、缺陷类别的Dice系数、以及缺陷类别的像素召回率。最后一个尤其重要,它衡量的是“真正的缺陷区域有多少被识别出来了”,这才是质检场景最关心的指标,漏检比误检更致命。训练完成后我还做了一次后处理优化:对分割输出的概率图做条件随机场或简单的形态学闭运算,去掉孤立的小噪点,让边缘更干净。形态学闭运算效果够用且速度快,条件随机场在1111张这种规模上收益有限,还拖慢推理速度。5. 常见问题与避坑实录5.1 标注边界不齐导致模型边缘模糊训练初期我遇到最典型的问题:同一张图片的标注边界在不同类别的交界线上有明显锯齿,模型预测出来的边缘也跟着糊成一片。排查后发现原因是标注员在画多边形时,相邻两个标注实体的边缘没有严格保持一致,留下了重叠或空隙区域。解决方法是增加一个“边缘对齐检查”环节,对两张图以上且标有同一药片区域的样本,用脚本检查不同多边形之间的Intersection over Union,如果两个相邻类别的IoU异常高,说明重叠标注了。后来我直接在标注规范里约定:相邻区域的多边形边界需要贴合同一条线,不能凭感觉各自发挥。这个改进让模型的边界IoU指标上升了大约3个点。5.2 正反面类别混淆药片正反两面在很多情况下长得几乎一样,尤其是没有刻痕的素片。类别混淆直接导致模型把defect_back预测成defect_front。根源在于采集时没有记录好每张图对应的是正面还是背面,标注员只能凭经验判断,难免出错。我在新版数据制作规范里引入了一种“配对拍摄法”:同一粒药片,先拍正面,再翻转拍背面,两张图建立映射关系。同时让标注工具显示上一张关联图片的标注结果,作为当前标注的参考。这个方法简单粗暴但极其有效,正反面混淆的比例几乎降到零。如果你的场景里药片单面有压痕或刻字,那问题会小很多,有纹理特征可以参考。5.3 图片暗角与边缘高光干扰药片是高光物体,拍摄时中心区域亮、边缘暗的暗角问题会导致暗部边缘轮廓模糊,标注员看不清真实边界。我的处理方法是先用平场校正算法对原始图片做预处理,把背景亮度归一化,同时保留药片本身的灰度差异。具体做法是先对空白背景拍摄一张参考图,然后把每个像素的灰度除以参考图灰度再乘以均值,这是工业视觉里很成熟的做法。另外,环形光源的角度最好低一点,让光线从侧面照过来,这样药片边缘会形成一圈暗轮廓,反而更容易看清缺口的轮廓线。这个拍摄技巧比任何后处理都管用。我把这个写进了拍摄规范里,后续扩充数据集时会受益。5.4 标签数量对训练结果的影响我特意做了一组消融实验,分别用400张、700张、1111张训练,观察缺陷类别的Dice指标变化。结果是400张时Dice约0.82,700张时约0.87,1111张时约0.91。明显的边际递减效应出现在700到1000张之间。如果你的目标是进一步提升,与其继续盲目加量,不如精挑那些困难样本——边界模糊的、小豁口的、复杂背景的,这些对指标提升的贡献远大于简单重复的正常样本,也就是俗称的“难例挖掘”。从我这次实践来看,药片缺失分割数据集的制作核心不在于堆数量,而在于保证标注质量、类别语义和场景覆盖。一份精心设计的1111张分割数据集,配合合理的训练策略,完全可以支撑起一个初步可用的药片外观缺陷检测Demo。后续如果要把模型部署到产线实时检测,再结合知识蒸馏和模型剪枝,推理速度还能再压一截。我自己在做这个项目时最大的感触是,数据分析、标注和清洗的时间不应该省,这些繁琐的步骤决定了模型的上限,而模型结构只是在逼近这个上限而已。最后分享一个小技巧:如果你以后也要做类似的数据集,建议在标注阶段就同步记录每个样本的“质量标记”字段,比如是否高光严重、是否边缘模糊、是否有遮挡。训练时可以根据这些标记做加权采样,把低质量样本的影响降下来。这个习惯我在多个项目里都受益了,比事后清洗省力得多。
做药片外观缺陷检测的朋友,多少都遇到过这样一个痛点:药片崩边、缺角、掉底这类正背面缺失问题,靠传统图像处理很难稳定捕捉,靠分类网络又只能给个“有没有问题”的结论,定位不了具体位置。最近我在整理一套药片正背面缺失识别分割数据集,labelme格式,1111张图,4个类别,算是把这块的标注、清洗、训练流程完整跑了一遍。这篇文章就把这套数据集的整体设计思路、标注细节、踩过的坑和训练策略一次讲清楚,给正在做工业质检、医药包装检测或相关视觉项目的朋友一个可以直接参考的样本。1. 项目整体设计与思路拆解1.1 为什么要用分割模型来识别药片缺失药品外观缺陷检测里,药片的“缺失”比想象中复杂。缺失不一定是一大块崩掉,也可能只是边角磨掉了一小块、模具压片时填充不足导致边缘圆缺、运输过程磕碰产生的小豁口。这类缺陷用目标检测框去框,会出现两个问题:一是检测框会把整个药片框进去,模型很难从“框内有什么”判断出缺陷区域到底在哪;二是崩缺的形状极不规则,矩形框的监督信号太粗糙,导致小豁口经常漏检。分割模型天然适合这个场景。它输出的是像素级的掩膜,能告诉你在药片正面的哪一个具体坐标区域出现了缺失,也能算出来缺了多少像素、占整个药片面积的百分比。有了这个面积比例,才能制定清晰的质量判定阈值,比如缺失比例超过5%就判不合格,而不是凭模型概率拍脑袋。这个差别在产线落地时非常关键,质检员和审核人员需要的是一个可解释、可量化的判定依据。另外,分割模型对不规则边缘的拟合能力远强于检测框。药片是高光曲面物体,边缘会有复杂的光晕和反射,分割模型通过逐像素分类,天然对边缘区域有更强的区分度,比较抗光照干扰。这是我当初选择分割路線而不是检测或分类路线的核心原因。1.2 4个类别到底怎么划分标题里写着“4类别”,这个需要具体解释一下。正背面缺失识别,本质上有两个维度:药片的面(正面、背面)和药片的状态(正常、缺失)。所以最常见的4类别划分方式就是:类别ID类别名含义0normal_front正常药片正面1normal_back正常药片背面2defect_front存在缺失的药片正面3defect_back存在缺失的药片背面这样划分有一个明显的好处:模型在训练时同时学习“区分正反面”和“区分缺失区域”两个任务,分割结果天然带有类别语义。推理的时候,模型输出一个分割掩膜,每个像素的类别标签直接告诉你这是正面的正常区域、背面的正常区域、正面的缺失区域还是背面的缺失区域。后续只需要统计各类别的像素数,就能算出缺失比例,再结合药片轮廓得到缺陷位置的精确坐标。有人可能会问,为什么不直接只标注“缺失区域”这一个类别,把正背面留给另一个模型去判断?我在实际对比中发现,分开独立建模会带来误差累积,而且两个模型之间的坐标对齐很麻烦。药片在传送带上可能有轻微旋转和位移,正面模型输出的掩膜和背面模型输出的掩膜如果不能精确对齐,后续的特征融合就全错了。用一个模型同时输出4类分割掩膜,就绕开了这个问题,类别之间的空间关系天然对齐。1.3 labelme格式的选型理由数据集的标注格式我选了labelme,这是目前最通用的开源图像标注工具之一,自动驾驶、医学影像、工业质检领域的很多公开数据集都用它。它的核心优势在于:第一,标注文件是独立的JSON,一个JSON对应一张图,方便并行标注和逐步验收。多人协作时,每个人标注一批图片,每批图片对应一批JSON,合并时不会产生冲突,不像某些标注格式需要统一维护一个巨大的数据库。第二,JSON结构极其透明。labelme的JSON文件本质是一个字典,包含imagePath、imageData、imageHeight、imageWidth、shapes等字段,shapes里是每个标注多边形的顶点坐标和类别标签。这个结构对程序员来说理解成本为零,解析起来也简单,走Python json库读进来就行。第三,格式转换生态成熟。labelme转COCO、转YOLO分割格式、转VOC格式的工具一抓一大把,不管后续想用什么训练框架,都能方便地转过去。我这次做训练时就是先保持labelme格式做标注和质量筛选,确认无误后再统一转成训练用的格式,流程上非常灵活。2. 标注数据集的构成与核心细节2.1 1111张样本的分布比例与采集设计1111张这个数量,说实话不算大,但对于分割标注任务来说已经是一个很实在的体量了。做过分割标注的人都知道,勾一个复杂的多边形要几十个点,一张图复杂的可能要标注三四个面,一个熟练标注员一天能保质保量完成50张就已经很快了。1111张图对应的是真实的时间成本和人力成本。样本分布上,我采用的是“正常为主、缺陷加量、正背均衡”的策略。粗略统计下来,正常药片的正面和背面各占约30%,带缺失的正面和背面各占约20%。这样分布的理由是:正常样本是生产线上最常见的情况,模型必须充分见过正常样本才能保证不误报;而缺陷样本虽然少,但它是模型真正要抓的目标,所以比例不能太低,否则模型会倾向于把所有区域都预测成正常。采集场景我覆盖了三种:明亮背景下的单粒药片、流水线上的密集排布药片、以及模拟复杂背景的散放药片。前两种贴近实际产线,第三种是为了增强模型的泛化能力,防止模型把背景特征学进类别特征里。拍摄角度上,大部分是垂直俯拍,也故意加入了一些小角度倾斜的样本,因为实际应用中很难保证药片完全水平。2.2 标注对象和方法要点标注的对象不是“整张图”,而是“药片的所有可见区域”。一张图里如果有多个药片,需要把每一个药片的正面或背面完整勾出来。注意,不是只勾缺陷区域,而是把整个药片的面部区域都勾成多边形,然后通过类别的不同来区分正常和缺失。这个策略很多人一开始不理解,但训练时就会明白:分割模型需要看到完整的上下文,如果只标注残缺区域,模型缺少“这个药片原本应该长什么样”的参照,等于把问题从“区域分割”变成“盲猜异常”,效果会差很多。具体到勾多边形的操作,我用的是labelme的polygon工具,逐点点击药片轮廓边缘。关键点是:正常的药片边缘要贴合真实的弧线,点距保持在3到8个像素之间;药片边缘的高光部分要小心,不要因为高光看不清轮廓就随手拉直线;凡是肉眼能看到明显崩缺的轮廓段,要顺着缺口边缘走,把缺失区域尽量精确地围出来。缺失区域的判断标准,我定了一条简单的经验规则:轮廓凹陷深度超过2毫米(对常见片剂直径而言约8像素左右)才标为缺失,小于这个阈值的轻微毛刺不标。这能显著减少标注标准不一致的问题,后面模型训练阶段我踩过这方面的坑,这个阈值规则帮我挽救了大部分不一致样本。2.3 类别的标注规范和拒收标准多人协作标注时,最大的风险就是标准不统一。我在项目里专门写了标注规范文档,把每个类别的定义和边界情况做成带示例图的对照表格。比如normal_front必须满足:无刻痕破损、边缘无缺口、表面无碎裂纹路;defect_front必须满足:有明显轮廓凹陷、有碎片缺失或大块崩边。类似照片/对比图里药片正面的刻字或者压痕,绝对不能标成缺失,这是最容易出现的误标。还有一个容易混淆的点:药片背面的凹槽和模印。有些药片背面本身就带一个弧形凹槽,这是正常工艺特征,不是缺失。标注新人极易把这部分勾成defect。我的解决方案是在规范文档里单独列出“常见的正常压痕形态”,并让标注员遇到不确定的样本先集体讨论,形成共识再继续标。拒收标准方面,我设置了3条红线:多边形面积小于药片总面积50%的标注直接拒收;类别标签与示例图明显不符的整批退回;轮廓点数量少于15个且形状与药片实际轮廓偏差明显的拒收。设置红线不是为了刁难,而是为了确保后续模型训练时不会因为垃圾标注产生方向性错误。3. 实操过程与关键环节实现3.1 从拍摄到标注的完整流程整个数据集的制作流程,我拆成了六个环节:样本采集、初筛清洗、图片预处理、标注执行、双人复核、导出打包。每个环节都有明确的产出物,方便随时检查进度和回滚。样本采集环节,我用的是工业级的俯拍支架配合环形LED光源,尽量减小药片表面反光。光源亮度调到中等偏上,太亮了药片边缘会曝光过度,太暗了轮廓细节看不清。每张图保持药片在画面中间,分辨率统一输出为1920x1080左右。拍摄时重点保证药片占比足够大,过小的目标会让标注和模型都很难受。初筛清洗环节,我会把模糊的、反光过度的、景深明显不对的图片筛掉。这一步很多人偷懒,觉得后期模型增强能救回来,但实际上训练数据里的模糊样本会让分割边界变得混乱。清洗后还剩1100多张,这个数量基本就是最终的数据集规模。标注执行环节我用的labelme命令行工具启动,python环境装好之后直接labelme --labels labels.txt --nodata启动标注界面。labels.txt里预先写好了4个类别名加一个背景类。注意我用的是--nodata参数,这样标注时JSON里不会嵌入base64图片数据,文件体积小很多,打开保存都更快,也方便后续数据版本管理。3.2 labelme JSON文件结构和字段说明很多人直接拿labelme导出的JSON当最终产物,但对字段的意义一知半解,这里我把核心字段拆开讲一遍。一个典型的labelme JSON结构如下:{ "version": "4.5.9", "flags": {}, "shapes": [ { "label": "defect_front", "points": [[x1, y1], [x2, y2], ...], "group_id": null, "shape_type": "polygon", "flags": {} } ], "imagePath": "000123.png", "imageData": null, "imageHeight": 1080, "imageWidth": 1920 }这里最重要的字段是shapes数组,数组里的每个对象代表一个标注多边形。label字段是类别名,shape_type固定为polygon,points是顶点坐标列表。注意,labelme的坐标是像素坐标,顺序按你点击的先后顺序排列,没有强制要求顺时针或逆时针,但在后续转COCO格式时需要统一处理。还有个关键点是imageData字段,当前值为null。因为我用了--nodata启动,所以没有把图片以base64形式塞进JSON,只保留了imagePath引用。有些模型训练框架直接读JSON里的imageData,如果你的JSON里是null就会报错,这个后面处理时要留意。另外,多药片图片里如果同时出现正面和背面,比如透明泡罩包装里的药片,shapes数组里就会有不同的类别标注。同一个类别可以有多组多边形,比如两个药片都是normal_front,那shapes里就有两组label都是normal_front的polygon,这种格式模型训练代码需要支持多实例解析,不能默认每个label只出现一次。3.3 数据集的清洗和转换labelme JSON不能直接喂给所有训练模型,所以标注完成后我做了两步处理:转成COCO格式和转成通用的语义分割掩膜。这里分享我实际用下来比较稳定的代码逻辑。COCO格式转换,核心工作是读取所有JSON的shapes,生成一个annotations数组,每个polygon对应一个annotation对象,并把点坐标归一化成COCO需要的[x1, y1, x2, y2, ...]扁平数组。同时还要计算每个polygon的边界框和面积,这些COCO的annotation对象都需要带上。我写过一个大概120行的转换脚本,处理1111张图大约耗时1分钟,效率完全可以接受。语义分割掩膜转换则更简单直接:创建一张与原始图片同尺寸的单通道灰度图,背景像素为0,然后对每个类别的polygon执行cv2.fillPoly,把对应区域填充为类别的索引值(1、2、3、4)。这样得到的PNG掩膜图,配一个类别名称映射字典,很多分割框架直接就能用。清洗环节要重点检查的是类别标签的一致性。比如有人标了“defect_front”,有人标了“defect front”,还有“缺陷正面”,这些看起来很像但不是同一个标识符,模型训练时会被当作不同的类别。我用脚本统计了所有JSON中出现过的label值集合,发现不一致的情况就统一替换。这一步虽然不起眼,但能避免训练时类别数莫名其妙的变成7个8个。4. 模型训练与数据增强策略4.1 分割模型的选型与输入尺寸数据集准备好了之后,模型选型我先后对比过三个方案:U-Net、DeepLabV3+和Mask2Former。U-Net在医学和工业小数据集上稳定性好,参数量小,训练快,缺点是感受野有限,对拟合大范围形状的约束弱一些。DeepLabV3+用ASPP模块捕获多尺度上下文,对边缘略模糊的缺陷更鲁棒。Mask2Former是较新的方案,效果不错但相对重,1111张图的体量下容易过拟合。最终我用了DeepLabV3+搭配ResNet50骨干,这个组合在速度、精度、显存占用之间平衡得比较好。输入尺寸上,我选了640x640。这个尺寸是权衡后的结果:药片轮廓细节需要足够的分辨率才能标得清楚,但训练显存和推理速度也要考虑。如果你用512x512也能跑,只是小缺口的边缘会模糊一些;如果条件允许,768或者1024效果会更好,但训练时间明显拉长。4.2 针对药片场景的增强技巧数据增强我分成了常规增强和针对性增强两类。常规的随机旋转、水平垂直翻转、随机裁剪我都有用,但对药片场景来说,有几个增强手段效果特别显著。第一个是亮度对比度扰动合成。我按0.7到1.3的缩放范围随机调整亮度和对比度,这样模型能适应不同光照条件下的药片表面反光变化。实测下来,加了这个增强后,模型在新光照场景下的mIoU提升了约2个点。第二个是随机填充遮挡。模拟药片边缘被其他物体部分遮挡的情况,随机添加一些黑色矩形块覆盖药片边缘区域,逼迫模型学习从遮挡中恢复完整轮廓。这个增强在分割任务里挺管用的,但也别用太狠,我控制在20%的概率,否则会引入太多假样本。第三个是弹性形变和网格畸变。工业产线上药片的姿态和形状有一定随机性,弹性形变能让模型对轮廓的轻微变形更鲁棒。我用的是OpenCV的重映射实现,随机生成控制点做插值变形。需要特别注意一个禁忌:不要在训练数据上做会影响类别语义的增强,比如随机擦除把整个药片核心区域抹掉,或者贴一个假的药片图案上去。这会干扰模型理解“缺失”和“正常”的区别,得不偿失。增强的本质是模拟真实场景的变化,不是创造新的语义类别。4.3 训练参数和评估指标训练参数方面,我用了AdamW优化器,初始学习率1e-4,配合Poly学习率衰减策略,批量大小8,训练80个epoch。损失函数用的是交叉熵损失加Dice损失的加权组合,比例大致是1:1。Dice损失能缓解前景背景类别不平衡的问题,对药片区域占比小的情况帮助很明显。评估指标不能只看整体mIoU。因为语义分割的背景占大比例,即使模型啥也没学到,背景类的IoU也很高,整体mIoU照样好看。我重点看三个指标:前景类别的平均IoU、缺陷类别的Dice系数、以及缺陷类别的像素召回率。最后一个尤其重要,它衡量的是“真正的缺陷区域有多少被识别出来了”,这才是质检场景最关心的指标,漏检比误检更致命。训练完成后我还做了一次后处理优化:对分割输出的概率图做条件随机场或简单的形态学闭运算,去掉孤立的小噪点,让边缘更干净。形态学闭运算效果够用且速度快,条件随机场在1111张这种规模上收益有限,还拖慢推理速度。5. 常见问题与避坑实录5.1 标注边界不齐导致模型边缘模糊训练初期我遇到最典型的问题:同一张图片的标注边界在不同类别的交界线上有明显锯齿,模型预测出来的边缘也跟着糊成一片。排查后发现原因是标注员在画多边形时,相邻两个标注实体的边缘没有严格保持一致,留下了重叠或空隙区域。解决方法是增加一个“边缘对齐检查”环节,对两张图以上且标有同一药片区域的样本,用脚本检查不同多边形之间的Intersection over Union,如果两个相邻类别的IoU异常高,说明重叠标注了。后来我直接在标注规范里约定:相邻区域的多边形边界需要贴合同一条线,不能凭感觉各自发挥。这个改进让模型的边界IoU指标上升了大约3个点。5.2 正反面类别混淆药片正反两面在很多情况下长得几乎一样,尤其是没有刻痕的素片。类别混淆直接导致模型把defect_back预测成defect_front。根源在于采集时没有记录好每张图对应的是正面还是背面,标注员只能凭经验判断,难免出错。我在新版数据制作规范里引入了一种“配对拍摄法”:同一粒药片,先拍正面,再翻转拍背面,两张图建立映射关系。同时让标注工具显示上一张关联图片的标注结果,作为当前标注的参考。这个方法简单粗暴但极其有效,正反面混淆的比例几乎降到零。如果你的场景里药片单面有压痕或刻字,那问题会小很多,有纹理特征可以参考。5.3 图片暗角与边缘高光干扰药片是高光物体,拍摄时中心区域亮、边缘暗的暗角问题会导致暗部边缘轮廓模糊,标注员看不清真实边界。我的处理方法是先用平场校正算法对原始图片做预处理,把背景亮度归一化,同时保留药片本身的灰度差异。具体做法是先对空白背景拍摄一张参考图,然后把每个像素的灰度除以参考图灰度再乘以均值,这是工业视觉里很成熟的做法。另外,环形光源的角度最好低一点,让光线从侧面照过来,这样药片边缘会形成一圈暗轮廓,反而更容易看清缺口的轮廓线。这个拍摄技巧比任何后处理都管用。我把这个写进了拍摄规范里,后续扩充数据集时会受益。5.4 标签数量对训练结果的影响我特意做了一组消融实验,分别用400张、700张、1111张训练,观察缺陷类别的Dice指标变化。结果是400张时Dice约0.82,700张时约0.87,1111张时约0.91。明显的边际递减效应出现在700到1000张之间。如果你的目标是进一步提升,与其继续盲目加量,不如精挑那些困难样本——边界模糊的、小豁口的、复杂背景的,这些对指标提升的贡献远大于简单重复的正常样本,也就是俗称的“难例挖掘”。从我这次实践来看,药片缺失分割数据集的制作核心不在于堆数量,而在于保证标注质量、类别语义和场景覆盖。一份精心设计的1111张分割数据集,配合合理的训练策略,完全可以支撑起一个初步可用的药片外观缺陷检测Demo。后续如果要把模型部署到产线实时检测,再结合知识蒸馏和模型剪枝,推理速度还能再压一截。我自己在做这个项目时最大的感触是,数据分析、标注和清洗的时间不应该省,这些繁琐的步骤决定了模型的上限,而模型结构只是在逼近这个上限而已。最后分享一个小技巧:如果你以后也要做类似的数据集,建议在标注阶段就同步记录每个样本的“质量标记”字段,比如是否高光严重、是否边缘模糊、是否有遮挡。训练时可以根据这些标记做加权采样,把低质量样本的影响降下来。这个习惯我在多个项目里都受益了,比事后清洗省力得多。