简介:一套基于机器视觉的旋转编码器缺陷检测项目,面向伺服电机生产线质检人员、机器视觉工程师及智能制造相关专业学习者。方案采用工业相机采集图像,结合自适应感兴趣区域提取与形态学腐蚀、膨胀、开闭运算等处理,实现断裂、孔洞、凸起缺陷的自动识别,可直接用于产线质检场景验证。压缩包共30个文件,容量约76.82MB,包含4个Python源程序,覆盖主检测、分割、间隙检测等功能;另有2个pyc编译文件、19张BMP与PNG测试图像及硬件软件框架图,方便直观理解系统结构。随包附有说明文档、README及额外docx资料,目录清晰,便于按模块阅读与二次开发。目前已有60人学习浏览,适合希望快速搭建视觉检测原型、掌握缺陷识别算法流程的开发者。
1. 编码器缺陷检测为什么非上机器视觉不可:断裂、孔洞、凸起在伺服产线上意味着什么
一条伺服电机产线每天要过几千个旋转编码器,码盘上的断裂、孔洞、凸起只要漏掉一个,装到电机上就是定位抖动、零位漂移,严重一点的直接飞车。基于机器视觉的旋转编码器缺陷检测系统,正是用工业相机把编码器表面拍成图像,再用自适应ROI提取和形态学算法自动挑出缺陷,全程不靠人工目检。人工目检的问题在于疲劳和标准漂移,同样的凸起,早班判不良、晚班可能就放行了;而视觉检测一旦标定好阈值,同一批活的标准是锁死的。这套方案适合伺服电机生产线上的装配工位、来料质检工位,以及想从抽检改成全检的出厂测试环节。硬件只要工业相机、镜头、光源加一台工控机,算法用OpenCV就能落地,投入远低于一台中端伺服电机检测仪的价格,但换来的是一次性拦截全部结构缺陷。
2. 先把缺陷拍清楚:工业相机、镜头与光源的三步选型
2.1 三类缺陷的成像特征决定了光学方案
旋转编码器的码盘要么是玻璃光栅,要么是金属或塑料码盘,表面缺陷集中在断裂、孔洞、凸起三类。断裂是码道上的线条出现中断,透射光下表现为一道亮线或暗线断口;孔洞是材料局部缺失,背光下直接透光,呈圆形暗斑或亮斑;凸起则是表面鼓包,背光下几乎隐形,只有在低角度光照射时才会拉出阴影。这三类缺陷的成像特性完全不同,所以光学方案不能一套打天下。常见做法是分两个工位:第一个工位用平行背光拍码盘轮廓,负责断裂和孔洞;第二个工位用低角度环形光或同轴光拍表面,负责凸起和划痕。如果只能上一台相机,就优先保证背光工位,因为断裂和孔洞是功能性缺陷,直接影响编码器输出脉冲的准确性,凸起更多是装配干涉风险。
2.2 相机与镜头选型里的三个必算参数
相机选型先算分辨率。编码器码盘直径常见的在30到50毫米之间,最小缺陷设定为0.1毫米。按经验,要稳定检出0.1毫米的缺陷,一个缺陷至少占3乘3个像素,那么视场方向需要的像素数等于视场尺寸除以最小特征尺寸再乘以3。视场按50毫米算,50除以0.1再乘3,得到1500像素,200万像素相机(1600乘1200)刚好满足,留一点余量的话上500万像素更稳。别一上来就买1200万像素的工业相机,像素高了数据量大,传输和处理都会拖慢节拍,产线上真正瓶颈往往是处理速度而不是分辨率。
镜头选型主要算焦距。公式是焦距等于工作距离乘以传感器靶面高度再除以视场高度。比如工作距离定在200毫米,传感器靶面高度4.8毫米,视场高度50毫米,焦距就是200乘4.8除以50,约19.2毫米,选16毫米或25毫米定焦镜头都行,优先选25毫米让码盘在画面里占得更大。光圈建议收到F8到F11,工业镜头的景深本来就浅,收光圈能减少码盘轻微倾斜带来的虚焦。第三个必算参数是曝光时间,码盘在产线上大多是静态拍照,曝光时间设到1到5毫秒就够了,如果要在运动状态下拍照,曝光时间必须小于允许的模糊量除以运动速度,这个后面第5章会展开说。
2.3 光源角度与工装固定上的常见做法
背光工位用的是平行背光源加漫射板,码盘放在光源和相机之间,断裂和孔洞在图像里会形成高对比度的暗斑或亮斑。凸起工位则用低角度环形光,光从侧面以10到20度的角度打向码盘表面,凸起会在迎光面形成亮区、背光面形成阴影,这个阴影就是判定凸起的关键特征。光源控制器要配常亮模式,别用频闪,频闪虽然能延长光源寿命,但在产线上调试时会多一个变量,出了问题不好排查。
工装这块有个细节容易被忽略:码盘夹具的定位精度决定了自适应ROI要承担多少校正量。如果夹具定位精度能做到正负0.5毫米以内,自适应ROI只是微调;如果定位精度差到正负2毫米,ROI就必须完全依赖图像自己找圆心。我的建议是夹具能做精度就做精度,图像算法永远不要用来弥补机械缺陷,否则产线一振动,算法就跟着翻车。
3. 自适应ROI提取:让检测框追着码盘跑而不是靠定位夹具硬撑
3.1 固定ROI为什么在产线上撑不过一个班次
很多入门方案上来就画一个固定矩形ROI,把码盘框住就完事。这种方案在实验室里跑得很漂亮,一上产线就露馅:码盘在夹具里每次放下的位置有偏差,旋转角度也完全随机,固定ROI要么把码道切掉一块,要么把黑色背景圈进来。背景的灰度分布和码盘完全不一样,形态学处理一跑,背景边缘直接被当成缺陷,过检率瞬间飙到30%以上。自适应ROI的思路是每帧图像先自己找到码盘中心和半径,再基于这个中心去做检测,相当于检测框追着码盘跑,位置偏了也能自己找回来。
3.2 用灰度投影和轮廓查找定位码盘中心
在OpenCV里定位码盘中心,常见做法是先二值化,再找最大轮廓,用最小外接圆拟合出圆心和半径。下面是这个步骤的核心代码:
import cv2 import numpy as np def locate_encoder_roi(gray): # 高斯模糊去噪,核大小根据图像分辨率调整 blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 大津二值化:自动找阈值,避免手动调 _, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 找所有轮廓,只保留面积最大的那个(码盘本体) contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 取最大轮廓做最小外接圆 largest = max(contours, key=cv2.contourArea) (cx, cy), radius = cv2.minEnclosingCircle(largest) return (int(cx), int(cy)), int(radius)这段代码的逻辑是先用5乘5的高斯核做平滑,去掉传感器噪点。二值化用大津法自动算阈值,好处是不用针对不同批次码盘反复调灰度阈值。findContours用RETR_EXTERNAL模式只取外轮廓,避免码道内部图案产生干扰。minEnclosingCircle返回的是浮点圆心和半径,转成整数后用于后续裁剪。参数上要注意高斯核大小和图像分辨率挂钩,200万像素图像用5乘5够了,500万像素可以试7乘7,太大反而会模糊掉码道边缘。大津阈值对背景和码盘对比度有要求,背光工位没问题,但前向光源工位的凸起检测图背景偏暗,大津法不一定分得开,这时候要改用灰度投影定位。
3.3 极坐标展开:把环形检测变成矩形检测
圆心和半径定位之后,下一个关键操作是极坐标展开。码盘是环形的,码道是同心圆环,直接在环形区域里跑形态学算法,核的方向和尺寸很难适配圆弧曲率。极坐标展开把环形区域拉成矩形,码道从圆弧变成水平直线,缺陷检测就从二维形态学简化成接近一维的线条分析。
def polar_unwrap(gray, center, radius, ring_width=80): # 展开半径范围:从内圆到外圆,ring_width决定检测带的宽度 unwrapped = cv2.warpPolar( gray, dsize=(360 * 2, ring_width), # 角度分辨率360*2,宽度取80 center=center, maxRadius=radius, flags=cv2.WARP_POLAR_LINEAR ) # warpPolar默认输出是竖着的,转成横着方便处理 unwrapped = cv2.rotate(unwrapped, cv2.ROTATE_90_CLOCKWISE) return unwrappedwarpPolar的参数里,dsize第一维是角度方向的采样点数,360乘2表示每0.5度采一个点,码盘码道最密的地方也不会超过这个频率;第二维是径向的采样宽度,取80个像素覆盖整个码道环带。maxRadius取码盘最大半径,配合center参数把整个环形区域纳入展开范围。展开之后,码道在图像里变为水平条纹,断裂缺陷表现为条纹上的断口,孔洞表现为条纹上的圆形暗斑,凸起表现为条纹上的局部扭曲。后续形态学处理统一用水平方向的结构元素就能搞定,这是整个算法链里最省心的一步。
4. 形态学算法分割缺陷:膨胀、腐蚀与开闭运算的产线实调参数
4.1 预处理和形态学基础操作怎么组合
极坐标展开后的图像要先做预处理再进形态学链。所谓形态学算法,核心就是膨胀和腐蚀两种操作:膨胀让亮区变大,腐蚀让亮区变小。先腐蚀后膨胀叫开运算,能去掉小的亮噪点;先膨胀后腐蚀叫闭运算,能填补小的暗缺口。在编码器缺陷检测里,开运算用来清掉灰尘和反光噪点,闭运算用来连接码道上的轻微断线,两者组合起来就是一套完整的缺陷分离链。下面这段代码就是我在产线上常用的预处理加形态学组合:
import cv2 import numpy as np def defect_segment(unwrapped): # 第一步:中值滤波去椒盐噪点,核大小取3,保留边缘 denoised = cv2.medianBlur(unwrapped, 3) # 第二步:大津二值化,区分码道和背景 _, binary = cv2.threshold(denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 第三步:开运算去亮噪点,核取3x3,试探性参数 kernel_open = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel_open) # 第四步:闭运算连接码道断口,核取5x1,沿码道方向 kernel_close = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 1)) closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel_close) # 第五步:闭运算结果和原始二值相减,差值就是断裂和孔洞候选 diff = cv2.absdiff(closed, opened) return diff参数上最需要调的是两个核。开运算的3乘3矩形核只能去掉孤立噪点,如果产线上灰尘颗粒比较大,核要放大到5乘5,代价是小孔洞缺陷也可能被一起抹掉,所以核尺寸宁小勿大。闭运算的5乘1核沿水平方向(码道延伸方向)做闭运算,能把码道上细小的断裂填起来。这里有个关键逻辑:闭运算把正常码道填平了,但真正的缺陷区域因为面积大、形态不规则,闭运算填不满,最后absdiff求差,缺陷区域就浮出来了。如果断裂缺陷比核宽还大,5乘1填不满,需要在闭运算前先对原始图做一次膨胀增强断口对比度,不过那样会把孔洞缺陷也放大,操作时要看具体缺陷占比来取舍。
4.2 断裂、孔洞、凸起三类缺陷的判定差异
断裂和孔洞在背光图里都是暗色区域,但形态特征不同。断裂是线条方向上的断口,面积小、长宽比大;孔洞是圆形或近圆形的闭合空洞,面积可能比断裂大,圆形度接近1。用connectedComponentsWithStats可以一次性拿到每个连通域的面积、外接框和中心点,然后按特征筛选。凸起在低角度光图像里的表现完全不一样,它是一个局部高亮区域加一条阴影,处理逻辑是先膨胀增强亮区,再和原图求差,得到的是凸起的轮廓。
def classify_defects(diff, min_area=5, max_area=500): # 连通域分析 num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(diff, 8) defects = [] for i in range(1, num_labels): # 跳过背景标签0 area = stats[i, cv2.CC_STAT_AREA] width = stats[i, cv2.CC_STAT_WIDTH] height = stats[i, cv2.CC_STAT_HEIGHT] # 面积过滤:小于min_area的是噪点,大于max_area的是码道本身 if area < min_area or area > max_area: continue # 长宽比判断:断裂偏细长,孔洞偏方正 aspect = width / max(height, 1) if aspect > 3.0: defects.append(('fracture', i, area)) elif height > 5 and width > 5: defects.append(('hole', i, area)) return defects这段代码里的关键参数是面积上下限和长宽比阈值。min_area取5个像素是底线,低于这个数的都是噪点;max_area取500是上限,超过这个面积的基本是把码道整条都框进去了,属于误检。断裂的长宽比阈值取3.0,实际码道断裂多数长宽比在5到10之间,取3是留了余量。孔洞判定要求宽高都大于5像素,避免把细长裂纹误判成孔洞。实际产线调试时,这几个参数需要拿坏品样件去标定,第6章会讲具体做法。
4.3 缺陷判定参数的物理含义与调参顺序
把所有判定条件整理成一张参数表,方便在现场对着调:
| 缺陷类型 | 成像光源 | 形态学组合 | 判定特征 | 建议初始阈值 |
|---|---|---|---|---|
| 断裂 | 平行背光 | 开运算 + 5x1闭运算 + 差图 | 长宽比大于3,面积5到200像素 | 长宽比3.0,面积下限5 |
| 孔洞 | 平行背光 | 开运算 + 闭运算 + 差图 | 近圆形,宽高均大于5像素 | 面积下限8,圆形度0.5 |
| 凸起 | 低角度环形光 | 膨胀 + 原图差值 | 高亮点加阴影对,面积10到300像素 | 面积下限10,对比度30 |
调参顺序有讲究,先调面积下限滤噪点,再调长宽比区分断裂和孔洞,最后才动形态学核的尺寸。很多人一上来就改核大小,结果噪点滤不干净,缺陷也连成一片,越调越乱。记住一个原则:形态学核是滤波器,不是分类器,分类靠的是连通域特征,两者职责不要混。按这个顺序调,新批次产品换型时,通常只需要改面积阈值,核参数可以稳定复用。
5. 产线落地避坑:光照、码道误检、节拍与过检的四笔账
5.1 环境光干扰导致ROI漂移
现象是白天产线光照好的时候检测正常,傍晚或者头顶日光灯一换,误检率突然升高,ROI定位的圆心位置在图像上来回跳。原因是车间环境光里有低频闪烁成分,灰度图像的整体亮度随之波动,大津阈值跟着漂,二值化后码盘轮廓就变形了。解决方法是给视觉工位加遮光罩,把码盘区域和车间环境光隔开,同时在光源控制器上把背光亮度调到让码盘和背景的对比度足够大,让环境光的波动占比小到可以忽略。还有一招是在算法侧加动态亮度校正,统计ROI区域的平均灰度,低于某个下限就报警提示光源衰减,而不是硬着头皮继续检测。
5.2 码盘透光码道被当成孔洞误检
现象是合格品被连续判成孔洞缺陷,而且位置集中在码道区域,放大图像看码道本身就有规律分布的透光狭缝。原因是码盘码道图案本身就是透光的,二值化之后码道和孔洞在灰度上无法区分,算法把码道间隙当成了孔洞。解决方法是把检测掩膜和码道掩膜分开:先用形态学膨胀把码道区域整体扩一圈,生成一个排除掩膜,检测时把落在掩膜内的连通域直接忽略。码道掩膜的生成用闭运算就行,核取大一点比如15乘1,把码道间隙全部填上,剩下的区域才是真正的缺陷候选区。
5.3 凸起缺陷在背光下完全不可见
现象是背光工位检完的坏品,拿到凸起工位复查时凸起还在,但在背光图像里完全看不出痕迹。原因是凸起是表面高度变化,对透射光没有遮挡,背光成像只会看到均匀的灰度。解决方法是凸起必须用前向光或低角度光成像,这个在光学选型时就要规划好,不能指望一套成像方案打天下。现场如果已经上了一条背光产线要补测凸起,加装一个低角度环形光源和第二个相机是最快的改造方式,别想着用图像算法从背光图里无中生有。
5.4 算法跑得动但产线节拍跟不上
现象是单张图像处理耗时120毫秒,产线节拍要求60毫秒一件,导致来料堆积,工人频繁按暂停。原因是代码里用了太多大尺寸卷积核,或者是在CPU上跑了大图缩放。解决方法是先压缩图像再处理,ROI定位其实不需要全分辨率,把图像缩放一半再找圆心,定位精度损失不到1个像素,但耗时能降一半。形态学处理只在展开后的矩形区域上跑,不要在整张原图上跑,这也是极坐标展开带来的额外好处。如果还压不到节拍以内,把OpenCV的编译版本换成带SIMD优化的版本,或者把图像转成灰度图后提前用ROI裁剪,产线实测通常能压到30到50毫秒。
6. 阈值调优与样本闭环:用灰度直方图把漏检率压下去
6.1 用直方图找阈值而不是拍脑袋
很多新手调阈值全靠试,试到漏检率看着低了就收工,换个批次又重新试一遍。我现在的做法是先看灰度直方图。打开一张典型坏品图像,统计ROI区域的灰度直方图,正常情况下会出现两个峰:一个是背景峰,一个是码道峰。二值化阈值就选在两个峰之间的谷底,而不是随便填一个值。大津法其实就是在自动找这个谷底。当形态学差图出来后,对差图再做一次直方图统计,噪点的灰度值集中在一个窄范围,缺陷的灰度值会拖出一条长尾,面积阈值的初始值就设在长尾的起点。这个方法的本质是用数据分布代替人眼估计,调出来的参数在不同批次之间的迁移性要好得多。
6.2 用坏品样件做回归测试
参数调完不能直接上产线,要先建立一个小样本回归集:20个合格品、20个各种缺陷类型的坏品,测试时把这40张图固定下来,每次调参都重跑一遍,保证合格品不漏检、坏品全部检出。我踩过最大的坑是只拿手头几个坏品调参,调到完美后上了产线,结果新来的缺陷形态没见过,漏了一整批。后来我养成了一个习惯:产线上每个被判不良的件都单独存图,每周把新缺陷类型补进回归集,重新标定阈值。这个闭环看起来费事,实际上是在给算法上保险。视觉检测系统的可靠性不是靠算法多牛,而是靠样本库覆盖了多少种坏法。做这套系统最大的教训是别把形态学参数调得太激进,给产线留一点余量,宁过检十个,不漏检一个,希望这个思路能帮你在伺服电机产线上少走几段弯路。
本文还有配套的精品资源,点击获取