资讯动态

开源工业质检系统实战:OpenCV+ONNX低成本方案,漏检率0.3%

发布时间:2026/9/29 17:38:23 来源:尧图企业网站定制
1. 为什么我要自己搭一套工业质检系统三年前我在一家做精密五金件的工厂里做产线自动化质检环节一直是最头疼的事。一条冲压线上四个质检工位每个工位两个人轮班盯着零件表面看划痕、毛刺、缺料一天下来眼睛花得连手机都看不清。漏检率官方数据是3%左右但实际到了夜班懂的都懂5%都打不住。客户投诉一多老板就拍桌子要上机器视觉。找了几家做视觉检测的方案商报价从十八万到四十多万不等核心算法还都是黑盒出了问题只能等他们来调。后来我一咬牙决定自己搞一套开源的方案。前后折腾了大概四个月从打光、选相机、标定到模型训练和产线集成踩了无数的坑最终做出来的系统漏检率控制在0.3%以内误检率2%左右硬件成本不到三万块。这套系统我后来整理成了开源项目陆续有几个做工业自动化的朋友拿去改吧改吧用在了自己的产线上。今天就把整个思路、技术选型、实操步骤和踩坑经验完整地写出来不管你是刚入行的机器视觉应用工程师还是想自己动手做一套质检系统的嵌入式开发者这篇文章应该都能给你省下不少时间。整套方案的核心思路是用开源工具链替代商业视觉软件用传统图像处理加轻量级深度学习模型替代纯深度学习方案用标准化硬件替代定制化光学方案。下面我会从整体设计、核心细节、实操过程和问题排查四个维度展开把每个环节的“为什么”讲清楚。2. 整体方案设计与技术选型拆解2.1 先搞清楚质检需求再动手选工具很多人一上来就问“用哪个深度学习框架好”这完全是本末倒置。工业质检的核心不是算法有多先进而是稳定、可复现、可解释。你得先把质检需求拆解清楚再倒推技术方案。我当时的质检对象是冲压五金件主要检测四类缺陷表面划痕、边缘毛刺、孔位缺料、尺寸超差。这四类缺陷里划痕和毛刺是表面纹理问题缺料是形状完整性问题尺寸超差是几何测量问题。不同的缺陷类型对应完全不同的技术路线。表面划痕和毛刺适合用传统图像处理中的滤波、边缘检测、形态学操作来提取特征也可以用轻量级CNN做分类孔位缺料适合用轮廓匹配和面积计算传统方法足够尺寸超差必须用亚像素级边缘检测加相机标定纯深度学习做不了精确测量所以我的方案是传统图像处理为主深度学习为辅。具体来说用OpenCV做预处理和特征提取用ONNX Runtime跑一个轻量级的MobileNetV3分类模型来辅助判断复杂纹理缺陷。这样既保证了测量精度又控制了计算资源需求。注意不要迷信深度学习。在工业质检场景里传统图像处理在稳定性、可解释性和调试便利性上往往优于深度学习。深度学习适合处理传统方法搞不定的复杂纹理和低对比度缺陷但前提是你有足够的标注数据。2.2 硬件选型相机、镜头、光源怎么配硬件选型是整个系统的基础选错了后面算法再牛也救不回来。我的选型逻辑是先确定视野和精度要求再选相机和镜头最后配光源。视野和精度计算我的零件最大尺寸是80mm×60mm要求检测精度0.05mm。这意味着相机单像素对应的实际尺寸要小于0.025mm通常取精度要求的1/2到1/3。如果选用500万像素的相机2592×1944视野宽度80mm时单像素精度80/2592≈0.031mm勉强够用。为了留余量我最终选了1200万像素的工业相机4024×3036单像素精度≈0.02mm满足要求。相机接口选择USB3.0接口的工业相机性价比最高带宽足够5Gbps支持硬触发驱动也成熟。GigE接口适合长距离传输但需要单独的网卡和供电成本更高。我选的是USB3.0的全局快门相机卷帘快门在运动拍摄时会有果冻效应工业质检必须用全局快门。镜头选型根据视野和相机靶面尺寸计算焦距。我的相机靶面是1/1.7英寸约7.6mm×5.7mm视野宽度80mm工作距离约200mm。焦距f 工作距离 × 靶面宽度 / 视野宽度 200 × 7.6 / 80 ≈ 19mm。所以选了一个16mm的定焦工业镜头实际工作距离调整到180mm左右视野刚好覆盖零件。光源配置这是最容易被忽视但最关键的一环。我试过环形光、条形光、同轴光、背光四种方案最终确定用低角度环形光背光组合。低角度环形光打侧面能突出划痕和毛刺的阴影背光从零件下方打能清晰勾勒出轮廓用于尺寸测量和缺料检测。光源类型适用缺陷优点缺点高角度环形光表面异物、颜色差异照明均匀划痕对比度低低角度环形光划痕、毛刺划痕阴影明显边缘过暗同轴光镜面表面缺陷反射均匀成本高对平面度要求高背光轮廓、缺料、尺寸轮廓清晰只能测通孔和外形实操心得光源选型没有万能方案必须拿实际样品反复试。我当时买了三种不同角度的环形光用可调电源逐个试最后才确定低角度方案。建议在项目初期就预留光源调试时间至少留一周。2.3 软件架构为什么选PythonOpenCVONNX软件架构的核心原则是模块化、可替换、可调试。我把系统分成四个模块图像采集、预处理、缺陷检测、结果输出。每个模块之间用标准接口通信方便单独替换和调试。图像采集用Python的OpenCV VideoCapture或者相机的SDK。我用的海康威视相机官方提供了Python SDK支持硬触发和软触发。硬触发通过PLC给相机发信号保证每次拍照时零件位置一致。预处理用OpenCV做畸变校正、ROI裁剪、灰度化、滤波、二值化。这些操作都是确定性的不涉及模型推理速度极快。缺陷检测分两条路传统图像处理路线用OpenCV的轮廓分析、模板匹配、边缘检测深度学习路线用ONNX Runtime加载训练好的MobileNetV3模型。两条路的结果做加权融合最终输出缺陷类型和位置。结果输出通过Modbus TCP或者串口把结果发给PLCPLC控制分拣机构把不良品吹到废料箱。同时把检测结果和图像存到本地数据库方便追溯。为什么选ONNX而不是直接上PyTorch因为ONNX Runtime在CPU上的推理速度比PyTorch快30%以上而且部署简单不需要装一堆依赖。对于工业场景来说稳定性比灵活性更重要。2.4 开源工具链的完整清单整套系统用到的开源工具和库如下OpenCV 4.x图像处理和传统视觉算法ONNX Runtime深度学习模型推理PyTorch模型训练只在开发阶段用NumPy数值计算pymodbusModbus TCP通信SQLite本地数据存储PyQt5上位机界面可选LabelImg数据标注工具这些工具全部开源免费社区活跃文档齐全。整套系统的代码量大概在3000行左右核心逻辑不超过1000行。3. 核心细节解析与实操要点3.1 相机标定精度测量的前提相机标定是尺寸测量的基础不标定的话像素和毫米之间的换算关系就是错的。我用的是张正友标定法OpenCV内置了calibrateCamera函数直接调用就行。标定板我用的是棋盘格规格是9×6格子边长5mm。标定的时候要注意几点标定板要覆盖整个视野至少拍15张不同角度的照片标定板要尽量平整不平整会导致标定误差标定时的光照要和实际检测时一致标定完成后要检查重投影误差一般要小于0.5像素标定完成后你会得到相机的内参矩阵和畸变系数。内参矩阵里的fx和fy就是焦距的像素表示用这个值可以计算实际尺寸实际尺寸 像素尺寸 × 工作距离 / fx。注意标定不是一劳永逸的。相机松动、镜头更换、工作距离变化都会导致标定失效。建议每次换产线或者定期比如每周重新标定一次。3.2 图像预处理把缺陷从背景里“抠”出来预处理的目标是让缺陷在图像里尽可能明显同时抑制噪声和干扰。我的预处理流程是畸变校正 → ROI裁剪 → 灰度化 → 高斯滤波 → 自适应二值化 → 形态学操作。畸变校正用标定得到的畸变系数调用cv2.undistort函数。这一步会稍微降低图像分辨率但能保证测量精度。ROI裁剪是根据零件在视野中的位置裁出感兴趣区域。这样可以减少计算量也能避免背景干扰。ROI的位置通过模板匹配自动定位不需要每次手动调整。灰度化用cv2.cvtColor转成灰度图。如果缺陷是颜色相关的比如氧化变色就保留彩色通道用HSV空间做分析。高斯滤波用cv2.GaussianBlur核大小选5×5或者7×7。核太大会模糊缺陷边缘核太小去噪效果不好。我实测下来5×5最适合我的场景。自适应二值化用cv2.adaptiveThreshold比全局阈值更适应光照不均的情况。参数blockSize选31C选5这两个值需要根据实际图像调整。形态学操作用cv2.morphologyEx做开运算和闭运算。开运算去噪点闭运算连接断裂的缺陷区域。核大小选3×3或者5×5。import cv2 import numpy as np def preprocess(image, camera_matrix, dist_coeffs, roi): # 畸变校正 undistorted cv2.undistort(image, camera_matrix, dist_coeffs) # ROI裁剪 x, y, w, h roi roi_img undistorted[y:yh, x:xw] # 灰度化 gray cv2.cvtColor(roi_img, cv2.COLOR_BGR2GRAY) # 高斯滤波 blurred cv2.GaussianBlur(gray, (5, 5), 0) # 自适应二值化 binary cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 31, 5) # 形态学开运算 kernel np.ones((3, 3), np.uint8) opened cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) return opened3.3 传统视觉算法轮廓分析和模板匹配传统视觉算法负责处理尺寸测量、缺料检测和简单划痕检测。核心是轮廓分析和模板匹配。轮廓分析用cv2.findContours找到所有连通区域然后计算每个轮廓的面积、周长、外接矩形、最小外接圆等特征。通过这些特征可以判断缺料面积小于阈值、毛刺周长面积比异常、尺寸超差外接矩形尺寸超差。模板匹配用cv2.matchTemplate把标准零件的图像作为模板在待检图像上滑动匹配。匹配得分低于阈值就说明有缺陷。模板匹配对光照变化比较敏感所以预处理一定要做好。def detect_defects(binary, template, threshold0.8): defects [] # 轮廓分析 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: area cv2.contourArea(cnt) if area 50: # 面积太小可能是噪声 continue x, y, w, h cv2.boundingRect(cnt) if w 100 or h 100: # 尺寸超差 defects.append((尺寸超差, x, y, w, h)) # 模板匹配 result cv2.matchTemplate(binary, template, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc cv2.minMaxLoc(result) if max_val threshold: defects.append((形状异常, max_loc[0], max_loc[1], 0, 0)) return defects实操心得模板匹配的阈值不要设太高否则误检率会飙升。我一般从0.7开始试根据实际效果调整。另外模板图像一定要用标准良品而且要和待检图像在同一个工位、同一种光照下拍摄。3.4 深度学习模型轻量级CNN做辅助判断传统方法搞不定的复杂纹理缺陷比如低对比度划痕、细微色差就需要上深度学习。但工业场景不能用大模型推理速度跟不上产线节拍。我选的是MobileNetV3-Small参数量只有2.5M在CPU上推理一张224×224的图像只要15ms左右。模型训练流程数据采集在产线上采集至少500张良品和500张不良品图像覆盖各种缺陷类型和光照条件数据标注用LabelImg标注缺陷位置和类别生成YOLO格式的标注文件数据增强用随机旋转、翻转、亮度调整、高斯噪声等方式扩充数据集模型训练用PyTorch训练MobileNetV3损失函数用交叉熵优化器用Adam学习率1e-3训练50个epoch模型导出把训练好的模型导出为ONNX格式用ONNX Runtime加载import onnxruntime as ort import numpy as np class DefectClassifier: def __init__(self, model_path): self.session ort.InferenceSession(model_path) self.input_name self.session.get_inputs()[0].name def predict(self, image): # 预处理resize到224x224归一化 img cv2.resize(image, (224, 224)) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1)) img np.expand_dims(img, axis0) # 推理 outputs self.session.run(None, {self.input_name: img}) # 后处理softmax probs np.exp(outputs[0]) / np.sum(np.exp(outputs[0])) return np.argmax(probs), np.max(probs)注意深度学习模型只做辅助判断最终结果要和传统方法融合。我的融合策略是传统方法检测到缺陷且深度学习也检测到判定为不良品传统方法检测到但深度学习没检测到需要人工复核传统方法没检测到但深度学习检测到也判定为不良品。这样既保证了召回率又控制了误检率。4. 完整实操流程与产线集成4.1 从零搭建一套质检系统的完整步骤我把整个搭建过程分成八个阶段每个阶段都有明确的输入和输出。第一阶段需求分析。明确检测对象、缺陷类型、精度要求、产线节拍。输出一份需求文档包括零件尺寸、缺陷定义、检测精度、每小时产量。第二阶段硬件选型。根据需求计算相机分辨率、镜头焦距、光源类型。输出硬件清单和采购订单。第三阶段光学调试。搭建实验平台调整相机位置、镜头焦距、光源角度直到图像质量满足要求。输出最优的光学参数。第四阶段相机标定。拍摄标定板图像计算相机内参和畸变系数。输出标定文件。第五阶段算法开发。编写预处理、传统视觉检测、深度学习推理的代码。输出可运行的算法模块。第六阶段模型训练。采集数据、标注、训练、导出ONNX模型。输出模型文件。第七阶段系统集成。把算法模块和PLC通信、数据库存储、上位机界面集成在一起。输出完整的软件系统。第八阶段产线部署。把系统安装到产线上调试触发信号、分拣机构、通信协议。输出验收报告。每个阶段的时间分配大概是需求分析3天硬件选型5天光学调试7天相机标定2天算法开发15天模型训练10天系统集成7天产线部署5天。总共约54天差不多两个月。4.2 产线集成触发、通信、分拣产线集成的核心是时序控制。零件从传送带过来光电传感器检测到零件给PLC发信号PLC给相机发触发信号相机拍照算法处理结果发给PLCPLC控制分拣机构动作。整个流程必须在零件到达分拣位置之前完成。我的产线节拍是每分钟60个零件也就是每个零件1秒。算法处理时间必须控制在500ms以内留500ms给机械动作。实测下来预处理50ms传统视觉100ms深度学习推理15ms总处理时间约200ms完全满足要求。触发信号用光电传感器型号是欧姆龙的E3Z-D62NPN输出接到PLC的高速计数口。PLC用三菱FX3U通过RS485和上位机通信。通信协议用Modbus TCP上位机做ServerPLC做Client。上位机把检测结果写到保持寄存器PLC读取寄存器控制分拣机构。分拣机构用气吹电磁阀控制。良品直接通过不良品被吹到废料箱。气吹的响应时间约10ms加上机械延迟总共约50ms。from pymodbus.server import StartTcpServer from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext # 初始化Modbus数据存储 store ModbusSlaveContext( hrModbusSequentialDataBlock(0, [0]*100), # 保持寄存器 coModbusSequentialDataBlock(0, [0]*100), # 线圈 ) context ModbusServerContext(slavesstore, singleTrue) # 检测结果写入保持寄存器 def write_result(context, result): # result: 0良品, 1不良品 context[0].setValues(3, 0, [result]) # 功能码3地址0 # 启动Modbus TCP Server StartTcpServer(context, address(0.0.0.0, 502))4.3 上位机界面让操作工看得懂上位机界面不是必须的但有了它操作工会方便很多。我用PyQt5做了一个简单的界面显示实时图像、检测结果、统计数据和报警信息。界面布局分三块左边是实时图像中间是检测结果良品/不良品、缺陷类型、缺陷位置右边是统计数据总产量、良品数、不良品数、漏检率、误检率。操作工只需要看三个东西图像上有没有红框红框表示缺陷位置、结果栏是绿色还是红色、报警灯有没有亮。其他的都不需要关心。实操心得上位机界面越简单越好。操作工不是工程师不需要看复杂的参数和图表。我第一版界面放了太多信息操作工根本不看后来精简到只显示图像和结果反而效果好很多。4.4 数据存储与追溯每次检测的结果和图像都要存到本地数据库方便追溯和复盘。我用SQLite轻量级不需要额外安装服务。数据库表结构很简单一张检测记录表包含时间戳、零件编号、检测结果、缺陷类型、缺陷位置、图像路径。图像存到本地文件夹数据库只存路径。CREATE TABLE inspection_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, part_id TEXT, result INTEGER, -- 0良品, 1不良品 defect_type TEXT, defect_x INTEGER, defect_y INTEGER, image_path TEXT );追溯的时候输入零件编号或者时间段就能查到所有检测记录和对应的图像。如果客户投诉某批零件有问题可以快速定位到具体的检测记录看看是漏检还是误检。5. 常见问题与排查技巧实录5.1 图像质量问题的排查思路图像质量是质检系统的基础图像不行后面全白搭。我遇到过的图像质量问题主要有四类光照不均、图像模糊、噪声过大、对比度不足。光照不均的表现是图像一部分亮一部分暗导致二值化后部分区域全白或全黑。解决方法是调整光源角度和距离或者用多个光源组合。如果实在调不好可以用自适应二值化或者背景减除来补偿。图像模糊的表现是边缘不清晰缺陷轮廓模糊。原因可能是镜头焦距没调好、工作距离不对、相机抖动、或者零件在运动。解决方法是重新调焦、固定相机、用硬触发代替软触发。噪声过大的表现是图像上有随机噪点二值化后出现大量小连通区域。解决方法是增加高斯滤波的核大小或者用中值滤波。如果噪声是周期性的可能是电源干扰需要加滤波器。对比度不足的表现是缺陷和背景的灰度值接近二值化后缺陷和背景混在一起。解决方法是调整光源角度让缺陷产生阴影或者反光。如果实在不行可以用图像增强算法比如直方图均衡化或者CLAHE。问题现象可能原因排查方法解决方案光照不均光源角度不对用灰度直方图看分布调整光源角度或加漫射板图像模糊焦距不对看边缘锐度重新调焦或固定相机噪声过大电源干扰看噪声是否周期性加滤波器或换电源对比度不足光源角度不对看缺陷和背景灰度差调整光源或图像增强5.2 误检和漏检的平衡技巧误检和漏检是质检系统的两个核心指标但它们是矛盾的。阈值设高了漏检多阈值设低了误检多。怎么平衡我的经验是先保证漏检率达标再优化误检率。因为漏检意味着不良品流到客户手里后果严重误检只是把良品当成不良品大不了人工复检一遍。具体操作上我会把检测阈值设得偏严格一些让漏检率控制在0.1%以内误检率可能到5%左右。然后通过以下方法降低误检率多特征融合不只看一个特征综合面积、周长、灰度、纹理等多个特征做判断多帧确认同一个零件拍多张照片只有多张都判定为不良才最终判定为不良人工复核误检的零件不直接扔掉而是送到复核工位由人工确认注意误检率不可能降到零只能尽量降低。如果客户对误检率要求极高那就需要增加人工复核环节或者用更高精度的硬件方案。5.3 模型部署的坑ONNX转换和推理优化从PyTorch模型到ONNX模型再到ONNX Runtime推理中间有不少坑。坑一动态维度问题。PyTorch模型导出ONNX时默认是动态维度但ONNX Runtime推理时需要固定维度。解决方法是在导出时指定dynamic_axes参数或者在推理时固定输入尺寸。坑二算子不支持。有些PyTorch算子ONNX不支持导出时会报错。解决方法是替换算子或者用ONNX的opset_version参数指定更高的版本。坑三推理速度慢。ONNX Runtime默认用CPU推理速度可能不够快。解决方法是开启intra_op_num_threads和inter_op_num_threads或者用OpenVINO、TensorRT等加速后端。# 导出ONNX模型 torch.onnx.export( model, # PyTorch模型 dummy_input, # 示例输入 model.onnx, # 输出路径 export_paramsTrue, # 导出参数 opset_version11, # ONNX算子集版本 do_constant_foldingTrue, # 常量折叠优化 input_names[input], # 输入名称 output_names[output], # 输出名称 dynamic_axes{input: {0: batch_size}, # 动态维度 output: {0: batch_size}} ) # ONNX Runtime推理优化 options ort.SessionOptions() options.intra_op_num_threads 4 # 算子内并行线程数 options.inter_op_num_threads 2 # 算子间并行线程数 options.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL session ort.InferenceSession(model.onnx, options)5.4 产线环境下的稳定性保障实验室里跑得好好的系统到了产线上可能各种问题。我遇到过的产线环境问题主要有温度变化导致相机漂移、振动导致图像模糊、电磁干扰导致通信异常、粉尘导致镜头污染。温度变化相机和光源发热会导致图像亮度变化。解决方法是预热30分钟再开始检测或者用恒温箱。如果条件不允许就定期重新标定。振动产线上的振动会导致图像模糊。解决方法是加固相机支架用减震垫或者用全局快门相机。电磁干扰变频器、伺服电机等设备会产生电磁干扰导致通信异常。解决方法是通信线用屏蔽线远离动力线加磁环。粉尘车间粉尘会污染镜头导致图像模糊。解决方法是加防护罩定期清洁镜头。我一般每周清洁一次用气吹和镜头纸。实操心得产线环境比实验室恶劣得多做方案的时候一定要留余量。相机分辨率留一倍余量光源亮度留一倍余量算法处理时间留一倍余量。这样即使环境变化系统也能稳定运行。6. 我在这套系统上踩过的坑和总结的经验这套系统从零到稳定运行我大概踩了十几个坑这里挑几个最有代表性的说一下。第一个坑光源选型太随意。一开始我觉得光源随便买一个就行结果买了个高角度环形光划痕根本拍不出来。后来换了低角度环形光划痕的阴影才明显。光源是机器视觉里最容易被忽视但最重要的环节建议在光源上多花时间和预算。第二个坑相机标定不认真。第一次标定的时候标定板拍得不够多角度也不够丰富导致标定误差很大尺寸测量偏差了0.1mm。后来重新标定拍了20张不同角度的照片误差降到0.02mm。标定一定要认真做这是精度的基础。第三个坑深度学习模型过拟合。第一次训练模型的时候只用了200张图像训练集准确率99%测试集准确率只有70%。后来扩充到2000张图像加了数据增强测试集准确率才到95%。数据量不够就不要上深度学习传统方法更靠谱。第四个坑产线集成时序不对。第一次部署到产线的时候算法处理时间超过了节拍导致零件已经过了分拣位置才出结果。后来优化了算法把处理时间从800ms降到200ms才满足要求。产线集成一定要先算好时序留足余量。第五个坑没有做异常处理。系统运行的时候偶尔会出现相机断连、通信超时、图像为空等异常情况。一开始没做异常处理系统直接崩溃。后来加了异常捕获和重试机制系统稳定多了。这套系统后来我整理成了开源项目放在GitHub上陆续有几十个star。有几个做工业自动化的朋友拿去改吧改吧用在了自己的产线上反馈还不错。如果你也想做一套类似的系统我的建议是先从简单的传统视觉方案做起跑通了再考虑加深度学习硬件选型不要省钱光源和相机是关键产线集成要留足余量异常处理一定要做。最后再分享一个小技巧如果你不确定某个缺陷能不能用传统方法检测出来先用OpenCV写个最简单的demo试一下。如果传统方法能检测出来就不要上深度学习。深度学习是最后的手段不是第一选择。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑