单目视觉测量实战:YOLOv8分割与LeNet-5实现工业零件尺寸测距与功耗监测 📅 发布时间:2026/9/3 22:02:23 👁 浏览次数: 简介本资源是一套面向嵌入式视觉开发者的单目视觉测量与功耗监测系统完整实现适用于智能交通、工业检测及智能家居等场景下的非接触式目标距离估算、几何尺寸识别与设备功耗实时分析任务。系统融合传统图像处理轮廓提取、形状识别与轻量级深度学习模型YOLOv8n用于目标分割定位LeNet-5用于电表数字识别并在STM32F1系列MCU上完成端侧部署兼顾实时性与精度。压缩包共202个文件含55个C/C头文件.h、21个源码文件.c、20个HAL驱动模块如stm32f1xx_hal_adc.c、uart.c等、10个Python脚本用于训练/评估、2个ONNX模型文件及配套PDF说明、Makefile与工程配置文件整体大小为18.75MB。目前已有58人学习下载提供从图像采集、预处理、YOLOv8n分割推理、轮廓拟合测距到LeNet-5数字识别的全链路代码与硬件适配支持具备可复现、可移植、可扩展的工程实践价值。1. 项目缘起一个“看得见”的工业质检需求最近在帮一个做小型工业零部件生产的朋友解决一个头疼的问题。他们产线上有一批零件比如螺丝、垫片、小轴承需要快速检测尺寸是否合格同时还要估算一下从传送带末端到分拣机械臂的距离好让机械臂能准确抓取。听起来简单但传统方案要么是上激光测距仪高精度卡尺相机成本直接起飞要么是人工抽检效率低还容易看走眼。朋友问我能不能用最普通的USB摄像头也就是单目视觉搞定这两件事顺便再把检测工位那台工控机的功耗给监控起来看看算法跑起来到底费不费电。这不就是典型的“既要、又要、还要”嘛——要低成本、要非接触、要自动化还得带点能耗管理。单目视觉测量确实是条路子它不依赖昂贵的立体相机或激光雷达靠一个摄像头结合图像处理和几何模型就能反推物体的距离和尺寸。但这里面坑不少比如摄像头怎么标定才准、光照变化咋办、物体遮挡了怎么处理、算法实时性够不够。正好最近YOLOv8n分割模型和经典的LeNet-5在手头项目里都用过就想着能不能搭个系统把目标检测分割、尺寸距离计算、数字识别用于读功耗仪表这几个模块串起来做个一体化的验证原型。这个项目我把它叫做“基于单目视觉的目标距离与尺寸测量及功耗监测系统”。名字有点长但说白了就是让一个普通摄像头学会“目测”物体的远近和大小同时还能“瞟一眼”旁边的功率计读数实现生产环节的视觉感知与能耗感知联动。下面我就把从方案选型、核心算法实现、到实际调试踩坑的全过程毫无保留地分享出来。2. 核心原理拆解单目视觉如何“看见”距离与尺寸很多人一听单目测距就觉得是“黑科技”其实它的基本原理离不开初中几何——相似三角形。关键在于我们需要一些先验信息作为“已知量”。2.1 单目测距的核心从像素到真实世界的映射单目摄像头本身无法直接获得深度信息。它拍下的是一张2D照片我们丢失了“远近”这个维度。要想找回深度必须引入额外的约束条件。在这个项目里我们主要用两种方法方法一基于已知物体尺寸的测距Pinhole Camera Model这是最直观的方法。假设我们已知某个物体的真实宽度或高度为W_real单位米。当这个物体放在距离摄像头Z米远的地方时它在图像上会呈现一个宽度为W_pixel单位像素的包围框。根据小孔成像模型和相似三角形原理存在以下关系焦距 f (像素) (W_pixel * Z) / W_real这里有个关键步骤相机标定。我们需要事先通过拍摄标定板比如棋盘格计算出摄像头的内参矩阵其中就包含了以像素为单位的焦距f和主点坐标(cx, cy)。一旦通过标定知道了f当我们在图像中检测到一个已知真实尺寸W_real的物体并测出它的像素宽度W_pixel时距离Z就可以反推出来Z (W_real * f) / W_pixel方法二基于相机高度的测距适用于地面物体如果摄像头固定安装并且它的高度H已知镜头光轴与地面有一定夹角通常不是垂直向下。当检测到物体底部接触地面的那个点即接地点时我们可以通过这个点在图像中的像素坐标(u, v)结合相机内参和安装俯仰角利用几何关系解算出物体到相机下方的水平距离。这种方法在自动驾驶中用于检测车辆、行人等地面目标非常常见。在本项目中由于测量对象是传送带上的零件传送带平面可以近似视为一个已知高度的平面因此我采用了第一种与第二种结合的方法。先通过标定确定相机参数再通过已知的零件尺寸或传送带平面几何来求解距离。2.2 几何尺寸识别从轮廓到毫米测出了距离尺寸识别其实已经完成了一半。当知道了相机到物体的距离Z和焦距f物体在图像中的像素尺寸S_pixel到真实尺寸S_real的转换就是线性的S_real (S_pixel * Z) / f问题的核心就变成了如何从图像中高精度地提取出物体的像素尺寸S_pixel简单用目标检测的包围框Bounding Box行不行对于近似矩形的物体可能勉强可以但对于不规则零件包围框会包含大量背景误差很大。这就需要用到实例分割——得到物体精确的像素级轮廓。我们用分割模型如YOLOv8n-seg得到物体的掩膜Mask。这个掩膜是一个二值图物体区域为1背景为0。对这个掩膜进行轮廓分析可以拟合出最小外接矩形、计算像素面积、周长甚至识别形状圆形、六边形等。例如计算最小外接矩形的宽和高作为W_pixel和H_pixel再利用上面的公式就能换算出真实的宽和高。注意这里有一个极易忽略的细节。相机镜头通常存在畸变尤其是广角镜头图像边缘的物体会被拉伸或弯曲。直接用原始图像中的像素距离计算会导致误差。必须在尺寸计算前先使用相机标定得到的畸变系数对检测到的轮廓点进行去畸变校正然后再进行后续计算。这一步对提升测量精度至关重要。2.3 系统工作流设计整个系统的Pipeline可以概括为以下几步图像采集与预处理摄像头抓取一帧图像进行去畸变、色彩增强、降噪等处理。目标检测与分割使用YOLOv8n-seg模型识别出图像中的目标零件并输出其像素级掩膜。轮廓提取与几何分析从掩膜中提取精确轮廓计算其像素尺寸宽、高、面积等并识别基础形状。距离解算结合已知的真实参考尺寸或相机-传送带平面几何模型以及标定好的相机内参解算出每个目标到相机的距离。真实尺寸计算利用距离和像素尺寸换算得到目标的真实几何尺寸。功耗监测在另一路图像中使用LeNet-5模型识别数字式功率计的读数实现实时功耗采集。数据融合与输出将距离、尺寸、功耗数据绑定时间戳输出到日志或可视化界面。3. 工具选型与模型训练为什么是YOLOv8n和LeNet-5搭建这个系统算法模型是核心。选型不仅要看精度更要权衡速度、资源消耗和易用性。3.1 目标分割YOLOv8n-seg的轻量之道YOLO系列一直是实时目标检测的标杆。YOLOv8是Ultralytics公司推出的最新版本提供了从nnano到xextra large不同大小的模型。对于工控机或边缘设备部署YOLOv8nnano是平衡精度与速度的绝佳选择。为什么选-seg分割版本而不是-det检测如前所述检测框对于尺寸测量太粗糙。分割模型能提供像素级掩膜让我们能进行精确的轮廓分析这是高精度尺寸测量的基础。YOLOv8n-seg在保持轻量化的同时分割精度对于工业零件这类轮廓相对清晰的物体已经足够。训练数据准备我们不需要像COCO数据集那样标注80类。只需要收集包含目标零件如螺丝、垫片的图像至少几百张覆盖不同的光照、角度、遮挡情况。使用标注工具如LabelStudio、CVAT进行多边形Polygon标注得到每个实例的轮廓点。这才是最耗时但最重要的环节。训练技巧数据增强必须做。包括随机旋转、亮度对比度调整、添加噪声、模拟运动模糊等可以极大地提升模型在复杂工业环境下的鲁棒性。预训练权重强烈建议使用在COCO数据集上预训练的yolov8n-seg.pt权重进行迁移学习这比从零训练快得多效果也好。关键参数imgsz图像尺寸不宜过大640x640对于小零件检测通常足够太大影响速度epochs根据数据集大小调整一般100-300轮重点关注mask_loss的下降情况。# 示例的data.yaml 数据集配置文件 path: ../datasets/parts train: images/train val: images/val names: 0: screw 1: washer 2: bearing# 训练命令示例 yolo tasksegment modetrain modelyolov8n-seg.pt datadata.yaml epochs150 imgsz640 batch163.2 数字识别LeNet-5的稳定与高效功耗监测模块需要从功率计假设是7段数码管或数字显示屏上读取数字。这是一个标准的数字识别任务。虽然可以用更复杂的CRNN或Transformer但对于固定的仪表字体、相对简单的背景经典的LeNet-5卷积神经网络完全够用而且它结构简单、参数少、推理速度极快非常适合作为系统中的一个轻量级子模块。为什么不用YOLO做数字检测可以但有点“杀鸡用牛刀”。数字识别任务更关注字符本身的分类而不是定位仪表位置通常是固定的。LeNet-5专为手写数字分类设计经过调整输入尺寸、类别数对仪表数字的识别效果很好。数据准备与训练采集功率计在不同读数下的图像。由于数字位置相对固定可以先用一个固定的ROI感兴趣区域裁剪出数字显示区域。如果显示多位数字需要先进行数字分割将每个数字字符单独切分出来。这可以通过垂直投影法分析每一列黑色像素点的数量找到数字间的空隙实现。将分割出的单个数字图像统一缩放到32x32大小归一化作为LeNet-5的输入。标注数据0-9训练一个10分类的LeNet-5模型。实操心得数字分割的稳定性。实际环境中光照可能导致数码管亮度不均投影法分割可能失效。我的经验是在分割前先对ROI区域进行二值化和形态学闭操作先膨胀后腐蚀连接同一个数字内部可能断裂的笔画能让投影分割的效果更稳定。4. 系统实现关键步骤与代码剖析理论说完了我们上点干货看看关键步骤的代码怎么实现。4.1 相机标定所有测量的基石标定不准后面全错。我使用OpenCV的cv2.calibrateCamera函数。import cv2 import numpy as np import glob # 准备标定板这里以10x7的棋盘格为例内角点数为9x6 pattern_size (9, 6) objp np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objp * 25 # 假设每个方格边长25mm obj_points [] # 3D点世界坐标系 img_points [] # 2D点图像坐标系 images glob.glob(calibration_images/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if ret: # 亚像素级角点精确化 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_refined cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria) obj_points.append(objp) img_points.append(corners_refined) # 执行标定 ret, camera_matrix, dist_coeffs, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None ) print(相机内参矩阵 K:\n, camera_matrix) print(畸变系数 dist:\n, dist_coeffs) # 保存标定结果 np.savez(calibration_params.npz, camera_matrixcamera_matrix, dist_coeffsdist_coeffs)关键点标定板要平整拍摄角度要多样覆盖图像各个区域。角点检测后一定要做cornerSubPix亚像素优化提升标定精度。畸变系数dist_coeffs很重要后续所有用于测量的图像都必须先调用cv2.undistort去畸变。4.2 YOLOv8n分割推理与轮廓提取加载训练好的模型进行推理并后处理。from ultralytics import YOLO import cv2 # 加载模型 model YOLO(best.pt) # 你训练好的分割模型权重 # 加载标定参数 calib_data np.load(calibration_params.npz) camera_matrix calib_data[camera_matrix] dist_coeffs calib_data[dist_coeffs] def process_frame(frame): # 1. 图像去畸变 frame_undistorted cv2.undistort(frame, camera_matrix, dist_coeffs) # 2. YOLOv8推理 results model(frame_undistorted, conf0.5) # 设置置信度阈值 for result in results: if result.masks is not None: masks result.masks.data.cpu().numpy() boxes result.boxes.xyxy.cpu().numpy() classes result.boxes.cls.cpu().numpy() for idx, mask in enumerate(masks): class_id int(classes[idx]) # 将mask转换为二值图像 (0-255) mask_img (mask 0.5).astype(np.uint8) * 255 # 3. 轮廓提取 contours, _ cv2.findContours(mask_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: # 取面积最大的轮廓防止mask有多个离散区域 cnt max(contours, keycv2.contourArea) # 4. 几何分析最小外接矩形 rect cv2.minAreaRect(cnt) width_pixel, height_pixel rect[1] # 像素宽高 # 注意minAreaRect返回的宽高不一定有序长边可能是宽或高 width_pixel, height_pixel sorted([width_pixel, height_pixel], reverseTrue) # 5. 距离与尺寸计算假设已知真实宽度W_real W_real 0.01 # 例如零件真实宽度为10mm 0.01m f_pixel camera_matrix[0, 0] # 焦距fx (像素) distance_Z (W_real * f_pixel) / width_pixel # 计算真实高度 H_real (height_pixel * distance_Z) / f_pixel print(f目标{idx}: 类别{class_id}, 像素尺寸({width_pixel:.1f}, {height_pixel:.1f}), f距离{distance_Z:.3f}m, 真实尺寸({W_real*1000:.1f}mm, {H_real*1000:.1f}mm))4.3 LeNet-5数字识别集成这里展示一个简化版的LeNet-5模型定义和推理流程。import torch import torch.nn as nn import torch.nn.functional as F class LeNet5(nn.Module): def __init__(self, num_classes10): super(LeNet5, self).__init__() self.conv1 nn.Conv2d(1, 6, kernel_size5, stride1, padding2) # 输入1通道灰度 self.pool1 nn.AvgPool2d(kernel_size2, stride2) self.conv2 nn.Conv2d(6, 16, kernel_size5, stride1) self.pool2 nn.AvgPool2d(kernel_size2, stride2) self.fc1 nn.Linear(16*5*5, 120) # 假设输入是32x32经过两次池化后为5x5 self.fc2 nn.Linear(120, 84) self.fc3 nn.Linear(84, num_classes) def forward(self, x): x self.pool1(F.relu(self.conv1(x))) x self.pool2(F.relu(self.conv2(x))) x x.view(-1, 16*5*5) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) x self.fc3(x) return x # 加载训练好的数字识别模型 digit_model LeNet5(num_classes10) digit_model.load_state_dict(torch.load(lenet5_digit_best.pth)) digit_model.eval() def read_power_meter(roi_image): # roi_image 是预先裁剪好的功率计数字区域图像 # 1. 预处理转灰度、二值化、形态学操作 gray cv2.cvtColor(roi_image, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV) kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3,3)) binary cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) # 2. 数字分割垂直投影法 vertical_projection np.sum(binary, axis0) # 寻找投影为0的列作为分割点简化逻辑实际需处理粘连等 digit_images [] # ... (具体分割代码将每个数字切分成单独的32x32图像) # 3. 识别每个数字 power_reading for digit_img in digit_images: digit_tensor torch.from_numpy(digit_img).float().unsqueeze(0).unsqueeze(0) / 255.0 with torch.no_grad(): output digit_model(digit_tensor) pred output.argmax(dim1).item() power_reading str(pred) return float(power_reading) # 转换为浮点数单位根据仪表定5. 实测中的挑战与调优经验系统搭起来容易跑稳定难。下面是我在调试过程中遇到的几个典型问题及解决方法。5.1 测量精度波动光源是“头号敌人”最初在实验室稳定光源下测试精度能达到±0.5mm。一到车间精度直接飘到±3mm以上。问题就出在光照不均和反光上。零件表面的金属反光会让轮廓提取出现严重错误。解决方案增加光源使用环形LED无影灯从多个角度均匀照射被测物体消除阴影和镜面反光。偏振片在镜头前加装偏振镜可以有效抑制金属表面的高光。图像预处理增强在代码中除了去畸变增加了CLAHE限制对比度自适应直方图均衡化来处理光照不均并用非局部均值去噪替代高斯模糊在平滑噪声的同时更好地保留边缘。动态二值化在提取轮廓前对分割掩膜所在的局部区域采用自适应阈值如cv2.adaptiveThreshold而非固定阈值以适应局部亮度变化。5.2 距离计算误差标定与参考物之谜即使轮廓提取准了距离算出来还是不对。排查发现两个问题标定板方格尺寸输入错误在标定代码中objp * 25这里的25是方格的真实物理尺寸毫米。我一开始误以为是厘米导致整个标定尺度错误。务必用游标卡尺精确测量标定板方格尺寸。已知参考尺寸不准确公式Z (W_real * f) / W_pixel中的W_real必须是物体在测量方向上的真实尺寸。如果零件摆放有旋转其图像宽度可能并非对应真实宽度。解决方法要么确保零件方向固定使用夹具要么通过轮廓分析计算出物体的方向角进行几何校正。5.3 实时性瓶颈模型推理与代码优化在工控机i5-8代无GPU上同时跑YOLOv8n分割和LeNet-5帧率只有8-10 FPS达不到实时15FPS要求。性能剖析使用Python的cProfile工具分析发现80%的时间花在YOLOv8的推理上。优化措施模型量化使用PyTorch的量化工具将YOLOv8n模型从FP32转换为INT8精度。推理速度提升近一倍精度损失在可接受范围内1% mAP下降。OpenCV DNN推理将PyTorch模型转换为ONNX格式用OpenCV的dnn模块加载推理。OpenCV C后端在某些CPU上优化更好比原版PyTorch推理快约30%。多线程处理将图像采集、YOLO推理、后处理计算、功耗识别分别放在不同的线程中形成流水线避免相互阻塞。降低分辨率在保证检测精度的前提下将模型输入分辨率从640x640降至480x480速度又有显著提升。经过优化最终在同一个工控机上达到了约22 FPS满足了实时性需求。5.4 功耗监测的数字识别“跳变”功率计读数识别时偶尔会出现数字跳变如从“120”突然跳到“92”。原因是数字分割时某个数字字符如“1”因为笔画细在二值化时断裂导致投影分割出错切分出了错误区域或漏切。解决方案形态学操作调参调整闭操作MORPH_CLOSE的核大小让细笔画能够连接起来。对于“1”这种字符可能需要使用细长的竖向核。加入数字位置先验如果功率计数字显示位置非常固定可以预先定义好每个数字的固定区域ROI直接按位置裁剪完全避免动态分割的不可靠性。这是最稳定的一招。结果滤波对连续几帧的识别结果进行中值滤波或滑动平均瞬间的跳变会被平滑掉。例如取最近5帧读数的中值作为当前输出。6. 系统集成与部署思考把各个模块拼装成一个稳定运行的系统还需要考虑一些工程化问题。数据流与同步距离尺寸数据来自主摄像头和功耗数据来自监测摄像头需要严格的时间同步。我采用的方式是给每一帧主图像打上时间戳功耗识别线程以不低于主帧率的频率读取仪表并将最近时间戳匹配的读数与主数据绑定。更严谨的做法是使用硬件触发让两个摄像头同步采集。结果可视化与输出使用OpenCV的cv2.putText和cv2.drawContours将测量结果实时绘制在图像上方便调试。最终数据通过JSON格式输出到文件或通过网络Socket发送给上位机如MES系统。格式类似{ timestamp: 2023-10-27T14:30:25.123, objects: [ { class: screw, distance_m: 0.856, width_mm: 5.12, height_mm: 20.34 } ], power_watt: 125.6 }误差分析与标定维护任何测量系统都有误差。我们需要定期如每周或每月用标准量块对系统进行验证和复标定。记录测量误差如果误差超出阈值如±1%则需要检查摄像头是否松动、光源是否变化并重新进行相机标定。这是一个工业系统能长期可靠运行的必要维护流程。这个项目从构思到最终在产线上试运行前后折腾了一个多月。最大的体会是单目视觉方案在成本敏感、精度要求不是极端苛刻亚毫米级的场合完全有实用价值。但它的稳定性严重依赖精细的标定、可控的光照环境以及鲁棒的图像预处理算法。模型反而不是最难的YOLOv8和LeNet-5这些现成的工具已经很强大了。真正的功夫都下在了那些看起来不那么“智能”的细节处理上。如果你也打算做类似的项目希望我踩过的这些坑能帮你把路铺得平一点。本文还有配套的精品资源点击获取