1. 从一条产线需求说起:药品缺陷检测到底难在哪
药品缺陷检测这个方向,我在几个不同的产线项目里都碰过。表面上看,它跟普通的工业外观检测没什么区别——无非是拿相机拍、拿算法判、把不良品踢出去。但真正做过的人都知道,药品这一行有几个绕不开的硬约束,直接决定了你不能照搬3C或五金件那套方案。
第一个约束是零漏检的容忍度。五金件漏一个,客户顶多退货;药品漏一个,可能直接涉及用药安全。所以在这个场景里,召回率(Recall)的优先级远高于精确率(Precision),宁可误杀一批良品,也不能放过一个缺陷品。这个取向会直接影响你后面所有的阈值设定和算法选型。
第二个约束是缺陷形态极其细碎。药品的缺陷通常分几大类:外观类(缺角、裂纹、麻点、黑点、异物)、装量类(药片缺料、胶囊空壳)、包装类(泡罩破损、铝箔漏封、印刷错位)。这些缺陷的尺寸往往在零点几毫米到一两毫米之间,对成像分辨率的要求非常高。
第三个约束是产线节拍快。一条成熟的药品包装线,每分钟几百片是常态。这意味着你的算法单帧处理时间必须压到几十毫秒以内,否则再准也没用,产线不会为了你的算法降速。
我这次要聊的,就是用Halcon这套机器视觉工具,怎么把药品缺陷检测这件事从"能跑"做到"能上产线"。Halcon在这个领域算是老牌选手了,它的算子库成熟、亚像素精度高、对光照变化的鲁棒性也做得不错,尤其适合药品这种小缺陷、高节拍的场景。下面我会把整个方案的来龙去脉拆开讲,包括成像怎么搭、算法怎么设计、参数怎么调、坑在哪里。不管你是刚接触Halcon的新手,还是已经做过几个视觉项目想转药品方向的,应该都能从里面找到能直接用的东西。
2. 成像方案:为什么药品检测的成败七成在打光
做视觉项目有个老话——算法决定下限,成像决定上限。药品检测尤其如此,因为缺陷本身太小,如果成像阶段信噪比不够,后面算法再花哨也是白搭。我见过太多项目,算法调了两周没效果,最后换了个光源方案,一天就通了。
2.1 缺陷类型与成像方式的对应关系
不同的缺陷,需要不同的打光策略才能"显形"。这里我整理了一张对照表,是我在实际项目里反复验证过的组合:
| 缺陷类型 | 推荐打光方式 | 原理说明 | 注意事项 |
|---|---|---|---|
| 表面黑点、异物 | 同轴光(Coaxial) | 垂直入射,镜面反射进入镜头,平整表面亮、异物处暗 | 对曲面药片效果差,需配合远心镜头 |
| 缺角、崩边 | 低角度环形光 | 掠射光突出边缘轮廓,缺口处产生阴影 | 角度一般15-30度,太高会丢失边缘对比 |
| 裂纹 | 暗场(Dark Field) | 只有散射光进入镜头,裂纹处散射强而发亮 | 对光源均匀性要求极高 |
| 泡罩破损 | 背光(Backlight) | 透射成像,破损处透光异常 | 需要透明或半透明载具配合 |
| 印刷缺陷 | 同轴光+彩色相机 | 颜色差异检测,字符缺失/错印 | 需做颜色空间转换,RGB转HSV更稳 |
| 装量不足 | 背光+面积统计 | 透光面积直接反映装量 | 阈值需按批次标定 |
这张表不是拍脑袋来的。我最早做药片黑点检测时,用的是普通环形光,结果黑点和药片本身的深色斑点混在一起,根本分不开。后来换成同轴光,药片表面变成均匀的亮面,黑点立刻变成清晰的暗斑,对比度提升了不止一个档次。这就是打光的价值——它把"算法难题"变成了"成像送分题"。
2.2 相机与镜头的选型计算
选型不能凭感觉,得算。核心公式是:
视野(FOV)= 相机靶面尺寸 / 镜头放大倍率
反过来,如果你已知要检测的最小缺陷尺寸,可以这样推:
假设最小缺陷是0.1mm,你希望它在图像上至少占3个像素(这是可靠检测的经验下限),那么所需分辨率就是 0.1mm / 3 ≈ 0.033mm/pixel。如果视野是30mm×30mm,那么相机单边像素数至少是 30 / 0.033 ≈ 900像素,选个1000万像素(约3664×2748)的相机绰绰有余。
镜头方面,药品检测我强烈建议用远心镜头。普通镜头有透视畸变,药片在视野边缘和中心拍出来的形状不一样,测量类缺陷(比如缺角面积)就会失准。远心镜头虽然贵,但它能保证放大倍率在整个景深范围内恒定,这对药品这种要求高一致性的场景是刚需。
提示:远心镜头的景深通常很浅,药片如果有厚度差异(比如泡罩有凹凸),要提前确认景深是否覆盖,否则会出现部分区域虚焦。
2.3 光源控制器的同步问题
这一点很多人会忽略。产线速度快的时候,相机曝光和光源点亮必须严格同步。如果光源是常亮的,运动中的药片会产生拖影;如果光源频闪但和相机触发不同步,拍出来的亮度会忽明忽暗,算法阈值就没法固定。
我的做法是用相机的Strobe输出信号去触发光源控制器,让光源只在曝光的那一瞬间点亮,亮度拉满。这样既消除了拖影,又延长了光源寿命。Halcon这边通过采集卡拿到的是已经同步好的图像,不需要额外处理,但硬件层面的同步一定要在调试初期就搞定。
3. Halcon算法链路:从图像到判定结果的完整拆解
成像搭好之后,就进入Halcon的主场了。我把整个算法链路分成五个阶段:预处理、定位、ROI提取、缺陷检测、结果判定。每个阶段都有它的门道,我逐个说。
3.1 预处理:不是所有图像都需要"美颜"
新手容易犯的一个错误是一上来就做一堆滤波,把图像搞得干干净净再检测。但滤波是把双刃剑——它在去噪的同时也会削弱真实缺陷的信号。我的原则是:能不做滤波就不做,非要做就做定向滤波。
Halcon里常用的预处理算子有这么几个:
rgb1_to_gray:彩色转灰度,如果缺陷是颜色差异(比如印刷),这一步要谨慎,可能直接转HSV更好。emphasize:锐化增强,对微小缺陷的边缘有提升,但会放大噪声,慎用。median_image:中值滤波,去椒盐噪声效果好,但会糊掉小缺陷,药品检测里我基本不用。gauss_filter:高斯平滑,适合去除高频噪声,sigma一般取1-2。
我实际项目里的做法是:先看原始图像的直方图,如果对比度够,直接跳过预处理;如果背景和缺陷灰度接近,用scale_image做线性拉伸,或者用equ_histo_image做直方图均衡。这两个算子不改变图像结构,只调整灰度分布,对缺陷信号是安全的。
3.2 定位:模板匹配是稳定检测的地基
药品在视野里的位置不可能每次都一样,哪怕传送带再稳,也会有±1-2mm的偏移。如果直接在全图里找缺陷,ROI会漂移,误检率飙升。所以定位这一步必须做扎实。
Halcon的模板匹配主要有三种:
- 基于形状的匹配(
create_shape_model):最常用,对光照变化和部分遮挡鲁棒,适合药片这种轮廓清晰的物体。 - 基于灰度的匹配(
create_template):速度快但对光照敏感,适合环境非常稳定的场景。 - 基于相关性的匹配(
create_ncc_model):对纹理丰富的物体好,药品一般用不上。
我一般用形状匹配。创建模板时有个关键参数叫AngleStart和AngleExtent,控制旋转搜索范围。药品如果是规则摆放,角度范围可以设小一点(比如±5度),这样匹配速度快;如果是散乱摆放,就得设±180度,但耗时会增加。
* 创建形状模板示例 create_shape_model (ImageReduced, 'auto', -0.1, 0.2, 'auto', 'auto', \ 'use_polarity', 'auto', 'auto', ModelID) * 参数说明:-0.1到0.2弧度是角度搜索范围,use_polarity表示忽略极性匹配到之后,用find_shape_model拿到位置和角度,再通过affine_trans_region把预设的ROI变换到实际位置。这样无论药片怎么偏移旋转,ROI始终跟着它走。
注意:模板匹配的分数(Score)要设一个下限,比如0.7。低于这个值说明匹配不可靠,这时候应该报警而不是硬着头皮往下检测,否则后面的结果全是错的。
3.3 ROI提取:把检测范围收窄到该看的地方
定位做完,接下来就是划定检测区域。药品检测的ROI通常分两层:外层是药片整体轮廓,内层是真正要检测缺陷的核心区域(比如药片表面,排除边缘倒角)。
提取ROI的方法看场景:
- 如果药片是规则圆形,用
threshold+connection+select_shape就能拿到。 - 如果形状复杂,用模板匹配生成的轮廓直接
reduce_domain。 - 如果是泡罩这种阵列结构,可以用
gen_rectangle1按网格批量生成ROI。
我踩过的一个坑是:ROI划得太贴边,结果药片边缘的正常倒角反光被当成缺陷。后来把ROI往里缩了大概5个像素,误检立刻降下来了。这个"缩边"的量要根据实际成像的过渡带宽度来定,没有固定值,得实测。
3.4 缺陷检测:三种主流方法的适用边界
到了核心环节。Halcon做缺陷检测,主流就三条路,我分别说说它们各自适合什么。
第一条路:Blob分析(threshold+connection+select_shape)
这是最经典的方法,适合缺陷和背景灰度差异明显的场景,比如黑点、异物。流程是:阈值分割出暗区,连通域分析,然后按面积、圆度等特征筛选。
* Blob分析检测黑点 threshold (ImageROI, DarkRegions, 0, 80) connection (DarkRegions, ConnectedRegions) select_shape (ConnectedRegions, Defects, 'area', 'and', 10, 500)这里area的下限10是过滤噪声,上限500是过滤掉大面积的非缺陷区域(比如ROI边缘)。这两个值必须根据实际像素尺寸标定,不能照抄。
第二条路:频域滤波(fft_image+ 滤波 +fft_image_inv)
适合检测周期性纹理上的缺陷,比如泡罩铝箔的规则纹路里混入的异常。原理是把图像转到频域,把代表规则纹理的频率成分滤掉,剩下的就是异常。这个方法门槛高,参数不好调,我一般作为备选。
第三条路:局部差异检测(dyn_threshold或variation_model)
这是我最推荐的方法,尤其适合光照不均匀的场景。dyn_threshold的原理是用一个平滑后的图像作为参考,原图和参考图逐像素比较,差异超过阈值的就判为缺陷。
* 动态阈值检测微小缺陷 mean_image (ImageROI, ImageMean, 15, 15) dyn_threshold (ImageROI, ImageMean, RegionDyn, 8, 'dark')mean_image的滤波核大小(这里是15×15)要略大于缺陷尺寸,这样缺陷在参考图里被"平均掉",原图减参考图就能凸显出来。dyn_threshold的阈值8是灰度差,越小越敏感,但噪声也会被检出,需要反复试。
3.5 结果判定:从"检出缺陷"到"判定合格"
检出缺陷区域只是第一步,最终要输出的是"这片药合格还是不合格"。这里有个策略问题:是"发现任何缺陷就判不合格",还是"缺陷面积超过阈值才判不合格"?
药品场景我倾向于后者,但要分缺陷类型。黑点、异物这类,哪怕很小也可能有风险,阈值要严;而轻微的边缘毛刺,可能只是工艺公差,阈值可以放宽。我的做法是给每类缺陷设独立的判定规则,最后用逻辑或合并:
* 分类判定 * 黑点:面积>15像素即判NG * 缺角:面积>50像素判NG * 裂纹:长度>30像素判NG这样比一刀切灵活得多,也能减少不必要的误杀。
4. 参数调优与稳定性:让算法在产线上活下来
算法在实验室跑通,和它在产线上连续跑三个月不出问题,完全是两码事。这一章我讲讲怎么让方案真正稳定下来。
4.1 阈值不是拍脑袋定的,是标定出来的
新手最爱问的一句话是"这个阈值设多少合适"。答案是:没有通用值,必须用你自己的样本标定。
标定方法我一般这么做:收集至少100片良品和50片各类不良品,跑一遍算法,记录每个缺陷特征(面积、灰度差等)的分布。然后找良品分布的上限和不良品分布的下限,取中间值作为阈值。如果两个分布有重叠,说明这个特征区分度不够,得换特征或改成像。
| 特征 | 良品范围 | 不良品范围 | 建议阈值 |
|---|---|---|---|
| 黑点面积 | 0-8像素 | 20-300像素 | 15像素 |
| 灰度差 | 0-5 | 15-80 | 10 |
| 裂纹长度 | 0-10像素 | 40-200像素 | 30像素 |
这张表是示意,实际值因项目而异。关键是这个"标定"的动作不能省。
4.2 光照漂移的补偿策略
产线跑久了,光源会老化,环境光会变化,导致图像整体亮度漂移。如果阈值是固定的,漂移一大就开始误检或漏检。
补偿方法有两个层次。简单的是在算法里加一步自适应:每帧先算ROI内的平均灰度,如果偏离基准值超过一定范围,就对整幅图做灰度补偿。复杂一点的是定期用标准样片重新标定,把新阈值写进配置文件。
我在一个项目里用的是前者,加了一段intensity算子和判断逻辑,效果不错,能扛住大概±15%的亮度漂移。超过这个范围就得人工介入了。
4.3 误检的排查链路
误检是产线上最烦人的问题,因为它会频繁停机。排查误检,我有一套固定的流程:
- 先看误检图像:把被判NG的良品图存下来,这是最重要的线索。
- 定位误检区域:看算法是在哪个ROI、哪个特征上判的NG。
- 回溯成像:检查这个区域的成像是否有异常,比如反光、阴影、脏污。
- 检查阈值:如果成像正常,那就是阈值太严,适当放宽。
- 检查ROI:如果误检总在固定位置,多半是ROI划到了不该划的区域。
我遇到过一次典型的误检:药片边缘有一圈正常的高光,被dyn_threshold当成了亮缺陷。解决办法是在ROI里把边缘那一圈用difference减掉,问题立刻消失。这个坑告诉我,ROI的边界处理比想象中重要。
4.4 节拍优化:Halcon的性能调优技巧
Halcon本身性能不错,但用不好也会拖慢节拍。几个实用的优化点:
- 缩小处理区域:只对ROI做运算,不要全图跑。这一条能省一半以上的时间。
- 降低图像分辨率:如果缺陷够大,没必要用全分辨率,可以
zoom_image_factor降采样。 - 减少算子调用:每个算子都有开销,能合并的逻辑就合并。
- 用
set_system关掉不需要的显示:调试时的可视化很耗性能,产线运行时全关掉。 - 多线程:Halcon支持多线程,如果一台机器检测多个工位,可以并行处理。
我实测过一个方案,优化前单帧45ms,优化后压到18ms,翻了一倍多。关键就是缩小ROI和降采样这两招。
5. 落地实战:一个完整检测流程的代码骨架
前面讲的都是零件,这一章我把它们拼成一个完整的流程,给你一个可以直接改的骨架。
5.1 初始化阶段
* 读取标定参数 read_tuple ('calib_params.tup', CalibData) * 创建形状模板 read_shape_model ('pill_model.shm', ModelID) * 设置系统参数 set_system ('clip_region', 'false')初始化只做一次,把模板、标定参数都加载好,避免每帧重复读取。
5.2 单帧处理主循环
* 1. 采集图像 grab_image (Image, AcqHandle) * 2. 定位 find_shape_model (Image, ModelID, -0.1, 0.2, 0.7, 1, 0.5, \ 'least_squares', 0, 0.9, Row, Column, Angle, Score) * 3. 生成跟随ROI hom_mat2d_identity (HomMat2D) hom_mat2d_rotate (HomMat2D, Angle, 0, 0, HomMat2D) hom_mat2d_translate (HomMat2D, Row, Column, HomMat2D) affine_trans_region (BaseROI, ROI, HomMat2D, 'nearest_neighbor') * 4. 缺陷检测 reduce_domain (Image, ROI, ImageROI) mean_image (ImageROI, ImageMean, 15, 15) dyn_threshold (ImageROI, ImageMean, DefectRegion, 8, 'dark') * 5. 特征筛选 connection (DefectRegion, ConnectedRegions) select_shape (ConnectedRegions, Defects, 'area', 'and', 15, 99999) * 6. 判定 count_obj (Defects, NumDefects) if (NumDefects > 0) * 判NG,触发剔除 dev_display (Defects) else * 判OK endif这个骨架涵盖了从采集到判定的全流程。实际项目里还要加上结果记录、通信输出(比如通过TCP发给PLC)、异常处理等,但核心逻辑就是这些。
5.3 与产线控制系统的对接
检测结果最终要传给产线的PLC或上位机,触发剔除动作。常见的通信方式有:
- TCP/IP:最通用,Halcon这边用socket算子收发字符串。
- 串口:老设备常用,简单但速度慢。
- IO卡:最快,直接给电平信号,适合高速剔除。
我用得最多的是TCP,因为灵活。Halcon发一个"OK"或"NG"字符串,PLC收到后决定是否吹气剔除。这里有个时序问题要注意:从拍照到剔除,中间有算法处理时间和机械延迟,剔除信号必须做延时补偿,否则会踢错位置。这个延时值要在调试时用高速相机实测。
6. 那些文档里不会写的踩坑经验
最后这部分,我分享几个实际项目里踩过的坑,都是文档里查不到、但能让你少走弯路的东西。
6.1 药片颜色批次差异导致的阈值失效
有一次项目上线后一直好好的,突然某天误检率暴涨。排查半天发现是换了一批原料,药片颜色比之前深了一点,导致原本的灰度阈值全部失准。这个坑的教训是:阈值不能写死,要留自适应空间。后来我加了一段逻辑,每批开始前先用几片良品自动标定一次阈值,问题就解决了。
6.2 反光造成的假缺陷
药片如果是糖衣的,表面很亮,光源打上去容易产生镜面反光,反光点在图像里就是一片高亮,很容易被当成缺陷。解决办法有两个:一是改用漫射光源,把直射光变成散射光;二是在算法里对高亮区域做特殊处理,比如用select_shape按灰度上限过滤掉。我一般两个一起用,效果最稳。
6.3 模板匹配在药片重叠时的失效
如果药片在传送带上偶尔会重叠,形状匹配就会失败,因为模板只匹配单个药片。这时候要么在上游加一个分离机构,要么在算法里用find_shape_model的多实例模式,一次找多个。多实例模式要把NumMatches设大,同时MaxOverlap设小,避免匹配到重叠区域。
6.4 长时间运行的资源泄漏
Halcon的模型和句柄是需要手动释放的。如果程序里反复create_shape_model却不clear_shape_model,内存会慢慢涨上去,跑几天就崩了。我的习惯是:所有create开头的算子,都要有对应的clear,放在程序退出或异常处理里。这个习惯能省掉很多莫名其妙的崩溃。
6.5 调试期的可视化陷阱
调试的时候为了看效果,往往会开一堆dev_display,把中间结果都画出来。但产线运行时如果忘了关,性能会掉一大截。我的做法是把可视化封装成一个开关变量,调试时打开,上线时一键关闭。这个小小的习惯,能避免上线后"算法突然变慢"的尴尬。
药品缺陷检测这个方向,说到底是一个"细节决定成败"的活。成像、算法、参数、稳定性,每一环都不能马虎。Halcon给了你足够强大的工具,但工具怎么用、参数怎么定、坑怎么避,还是得靠一个个项目磨出来。我上面写的这些,都是真金白银换来的经验,希望能帮你少走点弯路。