简介这是一个面向工业自动化的机器视觉分拣系统示例工程适合学习C图像处理与目标识别、准备相关课设或毕设的开发者。资源围绕图像采集、预处理、特征提取、目标检测与坐标转换等环节提供可直接运行的源码、依赖库和演示视频并配有说明文档和现场图片便于对照理解从相机标定到机械臂控制的关键实现。压缩包共12个文件含cpp/h源码、dll运行库、mp4演示、docx说明及jpg实拍图等整体大小约24.84MB结构简洁、易于按需取用。目前已有3822人浏览学习说明其具有一定的参考价值。读者可借助其中的C工程框架、OpenCV调用示例和颜色/形状识别逻辑快速搭建自己的分拣原型再针对特定工件定制阈值或特征描述符从而缩短开发调试周期。1. 基于机器视觉的自动分拣系统它到底在解决哪三个问题一条传送带以每分钟 80 件左右的速度送料末端分拣工位常常两个人轮流盯时间一长照样漏装、错装把这件事交给一套基于机器视觉的自动分拣系统识别、定位、剔除能在几百毫秒内完成良率也不再随人的疲劳波动。这套系统的本质是三条链相机代替眼睛采集图像算法代替大脑完成识别与定位控制器加推杆或机械手代替双手执行分拣。它最直接解决的问题有三个认出物料是什么告诉执行机构它在哪、什么角度以及保证整条链路的节拍跟得上产线。本文面向正在评估方案、或已经动手搭系统的自动化工程师会按硬件选型、视觉算法、通信集成、现场排错的顺序展开最后补两个进阶经验。2. 硬件选型的先算后买相机分辨率、镜头焦距与光源方案的取舍进入分拣系统的硬件选型最容易犯的错是先把相机买回来再拿视野去凑镜头。正确顺序反过来先定视野宽度、检测精度、节拍和物料材质再倒推相机分辨率、镜头焦距和光源类型。算错一步设备装到一半才发现视野不够或光源不对返工成本往往比相机本身还高。2.1 面阵还是线阵先定视野、精度与节拍常见散料分拣比如零件、食品、日用品用面阵相机就够了。它一次触发拍一帧逻辑简单适合皮带式分拣工位。线阵相机是逐行扫描适合门幅很宽的卷材、布料、板材但图像要拼接、成本高、调试周期长。普通自动分拣项目我会一律先按面阵评估除非视野宽度超过 1 米且对纵向分辨率有严格限制才会考虑线阵方案。像素精度的计算逻辑是先确定最小需要识别的特征尺寸再留出 2 到 3 个像素的覆盖余量。公式是所需分辨率等于视野宽度除以像素精度。举个例子皮带视野宽度 600mm要识别的最小物料特征尺寸是 1mm希望一个特征至少占 3 个像素那像素精度就是 1/3 约等于 0.33mm/pix水平方向至少需要 600/0.33约 1820 像素。考虑到边缘畸变和物料抖动我会直接选 500 万像素级别的相机水平像素 2448 对应该视野下实际精度约 0.245mm/pix余量充足。参数数值说明视野宽度600 mm相机能看到的最宽皮带区域最小特征尺寸1 mm需要分辨的最小缺陷或物料单元目标像素精度0.33 mm/pix1mm 特征覆盖 3 像素理论分辨率1820 px600 / 0.33工程选型500 万像素2448×2048留出畸变与抖动余量注意像素不是越高越好。像素升高意味着数据量变大、帧率下降、算法处理时间变长。在实时分拣里相机帧率和算法耗时一样是硬指标。我的习惯是按需求算出最低分辨率再往上一档选留可用的处理余量而不是只看像素数字。2.2 镜头与光源焦距按成像公式倒推光源按材质选镜头焦距由工作距离和视野共同决定。用放大倍率来算倍率等于传感器宽度除以视野宽度焦距等于倍率乘以工作距离。举一个常见配置2/3 英寸传感器宽度约 8.8mm工作距离 500mm目标视野 600mm倍率约 0.0147焦距约 7.3mm市面上定焦取 8mm。装好后视野会略微收窄这是正常的。做尺寸测量时镜头畸变率尽量控制在 0.5% 以内畸变是测量类项目的主要误差来源之一。光源选型决定识别率上限。同一个工件打光方式不对算法的努力全是白费。我常用的一套对应关系是这样的环形光适合立体小件和金属光泽件能突出边缘条形光低角度照射适合粗糙面和塑料件容易照出划痕背光适合外形测量轮廓干净同轴光处理玻璃、镜面这类高反光平面穹顶光用来消除球面和弧面反光。这几种光源没有哪个绝对好只有适不适合当前物料。光颜色的选择也有规律同色显亮互补色显暗。想在绿色背景里找出红色物料用红光照明并在镜头前加同波段滤光片背景会变暗目标反而发亮对比度一下就拉出来了。环境光干扰大的产线遮光罩加带通滤光片是标准做法让相机只接收光源波段白天晚上的灰度波动会小很多。光源控制器建议选带频闪功能的后面讲运动模糊时还会再提。2.3 触发与控制器形态不是相机加电脑这么简单分拣系统里相机不能自由地拍必须靠触发信号。常见做法是光电传感器检测到物料进入视野给相机的硬件触发引脚一个电平跳变相机立刻采集当前帧。不要靠软件循环去查询有没有物料节拍稍快就会漏拍。运动速度由编码器换算编码器装在和皮带同步的转轴上AB 相或单脉冲都可以主要用来做位置补偿。控制器形态有两条路线PC 加工业相机可编程性强适合多品类、算法复杂、需要留追溯数据的项目智能相机一体机软硬件固定适合单一型号、节拍稳定的产线。预算不是唯一决定因素后续要改检测逻辑的频次才是关键。下面这段是触发后处理流程的骨架逻辑。def on_hard_trigger(trigger_time_ms, encoder_pos): # 触发已完成这里只处理数据 image camera.grab_latest() # 取触发时刻的帧 result vision_pipeline(image) # 返回 (cx, cy, angle, class_id) # 处理耗时内皮带继续前进输出给 PLC 前先做位置补偿 moved belt_speed_mm_ms * (now_ms() - trigger_time_ms) send_pos result.x moved plc_send(send_pos, result.y, result.angle, result.class_id)这段逻辑的关键是位置补偿拍照和处理经历的时间段里物料已经在皮带上向前移动了输出给 PLC 的 X 坐标必须加上这段移动距离。belt_speed_mm_ms 这个值要通过编码器标定得到不要用固定速度猜测。另外拍照瞬间皮带震动可能导致图像模糊后续判断里要加对比度检查必要时丢弃当前帧重新触发。3. 视觉识别与定位从相机取图到输出坐标的完整处理链路硬件就位后视觉算法是整个系统里最像黑匣子的部分。分拣场景和独立质检不同算法不仅要判断有没有问题还要在几十毫秒内输出位置和角度给执行机构一个确定的结果。我的经验是先跑通经典图像处理管线再判断要不要上深度学习一上来就堆复杂模型难点和成本都在后面。3.1 经典 Blob 分析管线先跑通再优化Blob 分析适合背景简单、光照稳定、物料轮廓清晰的场景这也是大多数自动分拣项目的第一版方案。用 OpenCV 实现一版标准流程从灰度图一路处理到轮廓输出代码大致长这样。import cv2 import numpy as np def locate_parts(image, min_area500, max_area5000): # 1. 转灰度并降噪减少传感器噪点 gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray cv2.GaussianBlur(gray, (5, 5), 1.2) # 2. OTSU 自动阈值把前景和背景分开 _, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) # 3. 开运算去毛刺闭运算补内部空洞 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations1) binary cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations1) # 4. 只取外轮廓避免内部孔洞干扰 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) results [] for cnt in contours: area cv2.contourArea(cnt) if not (min_area area max_area): continue rect cv2.minAreaRect(cnt) # ((cx, cy), (w, h), angle) (cx, cy), (w, h), angle rect results.append({cx: cx, cy: cy, w: w, h: h, angle: angle, area: area}) return results处理顺序是固定的灰度、降噪、阈值分割、形态学、连通域。高斯滤波核取 5×5 适合 500 万像素图轻度降噪核再大边缘会模糊定位精度下降。OTSU 自动阈值不需要手工调但如果物料比背景暗二值图黑白是反的需要在阈值后做一次反转这一点在首版调试时最容易忽略。min_area 和 max_area 用像素面积表示按最小最大物料面积的 0.6 到 1.5 倍取区间比凭感觉给数可靠。RETR_EXTERNAL 只取外轮廓适合外形判断如果物料靠内部孔洞区分就要改用 RETR_CCOMP 并额外提取内轮廓。3.2 位置和角度输出minAreaRect 只是第一步minAreaRect 返回的最小外接矩形带有中心、宽高和角度但角度定义在不同版本里不一样有的返回 -90 到 0 度有的返回 0 到 180 度直接把角发给机械手很容易翻车。我习惯在函数出口统一做一次归一化把表示转换成物料长边相对水平线的角度范围固定在 0 到 180 度之间。def normalize_angle(angle_deg, w, h): # OpenCV 返回的角度是短边相对水平线的夹角 # 转换成“长边相对水平线”的角度 if w h: angle_deg angle_deg 90.0 return angle_deg % 180.0逻辑说明外接矩形的宽高和角度在 OpenCV 中会随版本交换定义直接信任某一个版本的返回值换环境后就会悄悄偏掉。所以这个函数先判断长边方向再做角度补偿最后取模保证范围一致。实际部署时还要用一块已知角度的标准板放到视野里验证 0 度输出确实对应水平方向否则角度符号相反的问题会在机械手上放大成转错方向的故障。另一个必须处理的问题是坐标系。图像坐标系原点在左上角Y 轴向下而机械手和 PLC 的坐标系原点通常在设备机架Y 轴向上。输出给 PLC 之前Y 坐标必须经过翻转或标定矩阵换算不能直接透传。这一条写在第 4 章九点标定时还会重点展开。3.3 何时切换深度学习特征不稳定时才需要Blob 分析最大的假设是背景和物料灰度可分。一旦物料表面反光、背景纹理复杂、多物料互相遮挡阈值法就会频繁误检。这时候再硬调参数就是浪费时间切到深度学习目标检测是更合理的选择。常见做法是用目标检测模型先把物料框出来并分好类再在框内用传统方法做精细轮廓和角度回归两者结合比纯深度学习定位更稳。数据集准备上我一般直接采集真实产线图像标注类别和框几百张起步覆盖不同角度、光照和轻微遮挡。训练集不够就做在线数据增强比如旋转、平移、亮度扰动。推理接口要和业务解耦保持和 locate_parts 完全相同的输入输出结构这样切换模型不影响 PLC 通信也不影响上层逻辑。def vision_pipeline(image): # 传统方法先跑结果为空再走模型 result locate_parts(image) if result: return result dets model_predict(image) # 深度模型推理 return convert_dets_to_result(dets)这段逻辑说明一个衔接技巧传统算法速度快大多数帧都能直接处理完只有它判不出来或置信度过低时才把图交给模型。model_predict 返回检测框和类别置信度convert_dets_to_result 负责把框中心转成和 locate_parts 相同的坐标结构。切换模型时通信帧格式完全不用动。注意误检比漏检更危险漏检最多丢一帧误检会送出一个错误坐标可能让机械手把好料当坏料剔掉所以结果里一定要带置信度低于阈值的帧交给人工位处理。4. 通信与执行把视觉坐标变成推杆或机械手动作的最后一公里视觉算完坐标只是完成了一半。数据能否送给 PLC、PLC 能否在正确时刻动作、坐标能否和机械坐标对齐这三件事没解决系统就还是一个实验室 Demo。联机调试阶段通信和执行的坑比算法还多。4.1 通信帧格式与发送逻辑先和 PLC 约定协议再写代码上位机和 PLC 之间走 TCP 是分拣系统的常见做法。协议不提前约定后面联调就是灾难。我一般用固定长度 ASCII 帧字段定长、分隔符固定PLC 侧解析容易也方便用串口助手模拟调试。习惯的帧结构如下表。字段长度说明帧头2 字节0xAA 0x55用于对齐长度1 字节数据区字节数数据N 字节形如 ID1001,X123.45,Y45.20,A30.00,C0.98校验1 字节数据区逐字节异或帧尾1 字节0x0D 0x0A发送端代码比较简单重点是帧 ID 和校验字段它们是排查丢帧和错帧的唯一依据。import socket def build_frame(cx, cy, angle, conf, frame_id): data fID{frame_id},X{cx:.2f},Y{cy:.2f},A{angle:.2f},C{conf:.2f} raw data.encode(ascii) xor 0 for b in raw: xor ^ b frame b\xAA\x55 bytes([len(raw)]) raw bytes([xor]) b\r\n return frame sock socket.create_connection((192.168.1.10, 502), timeout1.0) sock.sendall(build_frame(123.45, 45.20, 30.0, 0.98, 1001))逻辑说明build_frame 把坐标拼成 ASCII 数据区逐字节异或得到校验帧头让 PLC 在字节流里找对齐位置。PLC 收到后可以回一个 ACK也可以不回靠超时重发兜底。socket timeout 设为 1 秒避免网络异常时整个程序阻塞在发送上。参数方面IP 和端口按 PLC 侧配置来不要写死在程序常量里应该放到配置文件帧 ID 要单调递增PLC 靠它判断有没有丢帧置信度 C 低时置 0PLC 走剔除或人工位逻辑。4.2 皮带位置补偿与执行时序视觉算完目标已经往前跑了从拍照到执行机构动作目标一直在运动。直接用拍照时刻的坐标推杆会打在物料后面。补偿公式是执行位置等于拍照位置加上皮带速度乘以总延时总延时包含视觉处理、网络通信和机械动作三部分。下表列出了各环节的典型耗时。环节典型耗时说明硬件触发到取帧3~10 ms快门曝光加传感器读出视觉算法处理30~80 msBlob 快深度学习相对慢网络发送1~5 ms局域网 TCP推杆或气缸动作100~300 ms电磁阀响应加气缸行程合计150~400 ms决定补偿距离举例皮带速度 300mm/s总延时 200ms补偿距离就是 60mm。不补偿的话气缸推出时目标已经往前跑了 60mm必然打空。但固定速度乘以时间仍然有误差因为皮带线速度会有瞬时波动。更稳的做法是让 PLC 周期性读取编码器累计位置视觉帧里带上触发时刻的编码器值PLC 等到编码器走到目标位置与推杆对齐的累计值时再输出动作。两者的结合就是粗补偿用公式精定位用编码器。4.3 九点标定让像素坐标和机械坐标对齐相机坐标系和机械坐标系之间存在旋转、平移和缩放关系九点标定是解决这个问题的标准做法。流程是把标定板或标准工件放在相机视野内九个位置分别记录图像坐标和机械坐标然后用最小二乘拟合一个仿射矩阵。实现代码不需要太长。import numpy as np def fit_affine(pixel_pts, robot_pts): # pixel_pts, robot_pts 的 shape 都是 (N, 2)N 取 9 A np.hstack([pixel_pts, np.ones((len(pixel_pts), 1))]) # 分别求解 x 和 y 方向的仿射系数 coef_x, _, _, _ np.linalg.lstsq(A, robot_pts[:, 0], rcondNone) coef_y, _, _, _ np.linalg.lstsq(A, robot_pts[:, 1], rcondNone) return np.array([coef_x, coef_y]) # shape (2, 3)逻辑说明仿射模型带旋转、缩放和平移九个点用最小二乘拟合比三点直接计算更抗噪。调用时用一组像素坐标乘以这个 2×3 矩阵就得到机械坐标。如果不想手写OpenCV 的 estimateAffine2D 也能直接出同样结果原理一致。标定点要均匀分布在视野四角和中心不要集中在中间否则外推区域误差会明显变大。验证方法是取标定集之外的第 10 个点把像素坐标换算成机械坐标再实际测量误差小于 0.5mm 才可接受。很多看起来像玄学的固定偏差最后查下来都是标定失效。注意相机从支架上拆下来再装回去哪怕位置只差 1mm标定矩阵都会失效。项目上我一般把相机支架和镜头环打上记号防止清洁保养后装回时角度偏移装完先跑一遍标定验证图再开产线。5. 现场排查五个让视觉分拣项目翻车的真实坑位联机调试和试运行阶段问题往往不是某一个模块的孤立故障而是几个环节互相影响。下面的五条都是现场踩过的真实坑位每一条都按现象、原因、解决来写可以直接对照排查。5.1 同一套参数白天晚上良率差五个点现象同一台分拣设备白天良率 98%傍晚降到 93%打的还是同一批物料参数没动过。原因自然光从窗户和产线通道进入相机视野灰度整体漂移。OTSU 虽然自适应但局部反光峰值会拉扯阈值造成物料边缘断裂或相邻物料粘连。解决给检测区域加遮光罩把环境光挡在外面镜头前加与光源同波段的带通滤光片曝光时间锁死为固定值不要用自动曝光。改完后连续跑 48 小时观察灰度波动一般可以从正负 25 降到正负 3 以内良率重新稳定。5.2 皮带提速后轮廓突然变胖现象皮带速度从 0.3m/s 提到 0.5m/s面积检测结果批量偏大严重时相邻两个物料连成一片误检率骤增。原因曝光时间太长运动拖影把边缘拉长面积和宽度被系统性高估这就是典型的运动模糊。解决按公式反推曝光上限允许拖影像素乘像素精度再除以皮带速度。例如允许 1 像素拖影、精度 0.25mm/pix、速度 500mm/s曝光时间不能超过 0.5ms。常亮光源在这个曝光量下亮度不够就要打开光源控制器的频闪模式短曝光配合瞬时强光补偿。5.3 机械手抓取点沿运动方向固定偏移现象视觉静态标定时坐标是对的联机后机械手每次抓偏偏移方向固定距离也基本固定。原因静态标定坐标和联机动态位置没有对齐或者补偿没做全。最常见的是只补偿了视觉算法时间漏掉了机械手从收到指令到夹紧的动作延时。解决把皮带速度置零做一次标定验证确认静态坐标无误再带速跑一遍差值就是漏掉的补偿距离。补偿公式按第 4 章的总延时来算现场微调时每次增减 5 到 10ms不要一次加满再回退效率太低。5.4 混料测试里相似物料错检集中爆发现象两种物料尺寸接近、颜色相近面积和长宽比都区分不开准确率掉到七成怎么调阈值都没有明显改善。原因Blob 分析用的几何特征太少而且面积、外接矩形、圆度这些特征之间高度相关本质上等于一个特征判别面不够。解决增加灰度均值、纹理方差、孔洞数量这类不相关的特征再用随机森林或决策树做融合分类。特征加完仍然分不开就切深度学习。关键是把每次误检的图片保存下来积累到一批后重新训练或调整阈值不要只调单一的面积参数那是靠运气调参。5.5 视觉偶尔不发结果PLC 一直等导致停线现象产线跑几个小时后突然停线上位机显示视觉超时重启视觉程序后恢复过一阵又复发。原因TCP 连接在 PLC 或交换机侧被断开程序没有重连逻辑或者发送线程遇到异常退出后没有人把它拉回来消息就永远发不出去。解决加心跳和重连机制。PLC 每 500ms 查询一次视觉心跳视觉连续三次没收到查询就主动重建 socket发送逻辑包上异常处理异常后重连并补发最近一帧。验收测试时拔网线再插系统应在 2 秒内自动恢复并继续出结果否则不能签字。6. 进阶经验标定矩阵复用与节拍优化的两个习惯系统稳定运行后真正拉开长期维护成本差距的不是算法多先进而是标定管理和节拍瓶颈定位这两个习惯。这里分享两点我一直在用的做法。6.1 标定结果当成配置文件管理不要嵌在代码里我把九点标定得到的仿射矩阵存成 JSON 配置文件和相机参数放在同一目录程序启动时读取代码里不写死任何坐标转换常数。相机只要动过不管当时看起来动了多少我都会回到九点标定那一步重新采点覆盖配置并在工程记录里登记标定时间。这个习惯帮我躲过了好几次「昨天还好好的今天全部偏 0.3mm」的返工。另外我会提前录制三张带真实机械坐标的验证图片每次启动程序自动跑一遍换算误差超过 0.5mm 就在界面上报警提示重标而不是等产线打出废料才被发现。6.2 节拍优化先看瓶颈算法优化放在最后经常有开发者一上来就优化算法耗时但分拣系统的瓶颈往往不在算法。处理时间通常远小于气缸动作时间和通信抖动。我先做三件事相机采集和算法处理并行用双缓冲的方式采下一帧的同时算上一帧只在 ROI 区域内做精细算法粗检用降采样图通信改成批量帧一次带多个结果减少网络往返次数。大部分项目的节拍余量靠这三步就够用了。要是还差再回头看算法本身比如把高斯滤波核从 5×5 换成 3×3、裁剪不需要的图像通道通常还能再省下十几毫秒。我现在的习惯是项目验收前做连续四小时满速混料测试只盯两件事丢帧率和误分率两个指标都归零才签字。这套流程帮我避开过不少返工希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?