机器视觉编码器缺陷检测:OpenCV形态学与自适应ROI实战
简介一套基于机器视觉的旋转编码器缺陷检测项目面向伺服电机生产线质检人员、机器视觉工程师及智能制造相关专业学习者。方案采用工业相机采集图像结合自适应感兴趣区域提取与形态学腐蚀、膨胀、开闭运算等处理实现断裂、孔洞、凸起缺陷的自动识别可直接用于产线质检场景验证。压缩包共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, keycv2.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_width80): # 展开半径范围从内圆到外圆ring_width决定检测带的宽度 unwrapped cv2.warpPolar( gray, dsize(360 * 2, ring_width), # 角度分辨率360*2宽度取80 centercenter, maxRadiusradius, flagscv2.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_area5, max_area500): # 连通域分析 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张图固定下来每次调参都重跑一遍保证合格品不漏检、坏品全部检出。我踩过最大的坑是只拿手头几个坏品调参调到完美后上了产线结果新来的缺陷形态没见过漏了一整批。后来我养成了一个习惯产线上每个被判不良的件都单独存图每周把新缺陷类型补进回归集重新标定阈值。这个闭环看起来费事实际上是在给算法上保险。视觉检测系统的可靠性不是靠算法多牛而是靠样本库覆盖了多少种坏法。做这套系统最大的教训是别把形态学参数调得太激进给产线留一点余量宁过检十个不漏检一个希望这个思路能帮你在伺服电机产线上少走几段弯路。本文还有配套的精品资源点击获取