简介中国机器人大赛先进视觉赛工业测量方向的完整参赛项目资料面向人工智能、自动化、电子信息、计算机等专业的高校学生与从业者既可用于课程设计、毕业设计或竞赛复现也适合作为工业视觉测量入门到进阶的学习素材。压缩包共1088个文件大小约134.53MB内含大量Makefile/CMake构建脚本Python、C与CUDA视觉处理源码ROS节点及消息定义并配有png/jpg图像样本、mp4演示视频、docx设计文档和md说明文件可覆盖从环境配置、算法实现到运行验证的完整流程。目前已有95人浏览学习目录结构清晰便于按模块查阅设计思路、关键代码与实验结果。项目中包含多个ROS功能包与视觉处理节点如驱动、视频发布、检测相关模块代码层次清楚适合作为二次开发基架无论是目标检测、尺寸测量还是ROS视觉应用都能借助这套源码与文档快速搭建自己的实验或项目雏形进而扩展出契合毕设、课设或参赛需求的作品。1. 中国机器人大赛先进视觉赛的工业测量一份能直接跑测量的完整源码包搞毕业设计或者课程作业的时候翻到一份「中国机器人大赛-先进视觉赛-工业测量」源码包第一反应多半是这能直接跑吗能抄哪些部分我拿到之后把整套源码和资料拆了一遍结论是它很适合当课程设计底座。这个赛项要解决的是工业场景下的视觉测量不是常见的分类或目标检测而是拿着相机测出工件直径、孔距、圆度这些毫米级数据。整个资源把相机标定、亚像素边缘提取、圆拟合、透视校正到结果输出串成了一条完整链路适合做毕业设计或课程设计时基于 Python 和 OpenCV 二次开发。这篇笔记就是我复现时的完整记录含参数和踩坑点可以照着做。2. 赛题拆解与技术选型工业测量为什么不是简单“找圆”2.1 赛题逻辑与评分点误差以毫米计不是以像素计先进视觉赛的工业测量任务核心套路是「相机固定在上方工件放在视野内程序识别工件类型并测量指定尺寸」。放在课程作业里的场景通常会简化为一个固定支架、一个俯视相机、几个圆柱形或板形工件测量直径、长度、圆心距这些几何量。评分链路里最狠的一点是评委只认绝对误差。不是说找得到圆就给满分也不是分类正确就行而是把程序输出的数字和用卡尺量出来的真实值对比差 0.1mm 和差 0.5mm 是两档分。这就决定了技术路线不能走「深度学习检测框直接量像素宽」这种野路子。检测框是矩形包围盒它不关心工件的真实轮廓在哪框边缘和实际边缘之间本来就有几个像素的偏置。在 1920×1080 的图像里如果视野宽度是 200mm一个像素大概对应 0.104mm框边缘偏 3 个像素就已经快 0.3mm 了分数基本没了。所以这个赛项的要求就像给视觉算法加了一把游标卡尺先标定相机保证像素坐标和物理坐标有确定换算关系再用边缘提取找工件边界而且边界精度要到亚像素最后把边缘坐标转成毫米输出。我拆这份源码包时的第一印象是它的代码结构没有一上来就写深度学习而是把「测量」当主线路深度学习只在识别工件类型时用一下。这个思路很关键因为在工业测量里分类错顶多扣少量分尺寸错 1mm 直接掉一档。测量结果的可靠度优先于算法的酷炫程度这也符合课程设计评分里「功能完整、数据可靠」的要求。2.2 技术选型OpenCV 测量链路为主、深度学习只做粗定位这份资源的技术栈并不复杂核心是 Python 3 OpenCV NumPy外加一个轻量界面用于显示测量结果。深度学习部分基本是预训练好的目标检测权重用来告诉程序视野里哪一个区域是待测工件。只要工件被检测出来程序就立刻切换到 OpenCV 的边缘和几何拟合流程不会再依赖神经网络做像素级输出。这个选型是合理的。深度学习模型擅长的是语义判断比如区分这是轴承还是法兰盘但要精确输出边缘坐标训练成本很高而且对光照、角度变化极敏感。放在工业测量这类有固定光源、固定高度、固定相机的场合反而是标定加边缘检测更稳。课程作业里很多同学翻车就是把 YOLO 检测框的宽度直接当成工件直径上报结果模型在训练集里准换个角度就崩。功能环节常采用实现在该项目中的作用图像读取OpenCV VideoCapture 或读取本地图片接入相机或复现离线图片工件粗定位预训练目标检测模型输出 ROI缩小测量范围尺寸测量OpenCV 边缘检测 亚像素拟合输出直径、长度、圆心距坐标变换相机标定矩阵 单应性矩阵把像素换算成毫米结果显示PySide 或 Tkinter 简单界面展示测量值、保存结果我一般会建议复现时先把深度学习分支关掉用它的识别结果文件作为 ROI 输入先跑通测量链路。等 OpenCV 测量能输出稳定数据了再把检测模型接回去。这样拆开调试问题边界清晰不会出现「不知道是检测错了还是测量错了」的黑匣子状态。2.3 从像素到毫米的计算链路三层映射必须固定要理解源码包里标定代码在做什么先得把坐标系的换算关系理顺。被测工件放在平面上可以认为它只在二维平面内移动和旋转那么从图像坐标到物理坐标的映射就能用单应矩阵描述。随便拿一张图不做标定直接量像素长度得到的是随机的相对量做完标定后图像里每一点的坐标都能映射到毫米坐标。理论上完整的映射链路是这样的图像像素坐标先经过相机内参矩阵 K 得到归一化坐标再乘以外参 R 和 t落到世界坐标。公式写成[u, v, 1]^T s * K * [r1 r2 t] * [X, Y, 1]^T其中[r1 r2 t]是相机相对于工件平面的旋转和平移。因为工件平面是 Z0 的平面外参矩阵退化成 3×3 的单应矩阵 H所以程序实际要做的事就是估计一个 H把图像坐标直接换算到毫米坐标。看完源码包里的calib模块后会发现它做了两件事来保证这个映射稳定第一用棋盘格标定相机内参和畸变系数把镜头本身的透视畸变去掉第二用四点法求工件平面的单应矩阵把倾斜拍摄的视角纠正成俯视图。这两步做完图像里的一个像素当量就是一个固定量后续直径、圆心距都可以直接用像素当量乘出来。这里有一个常见的认知误区像素当量不是万能的它只在相机高度和工件平面姿态不变时成立只要抬高或倾斜相机同一个工件的像素长度就会变化。源码包里把标定数据和测量数据分开保存原因就在这里。复现这套项目时按「先标定、再测量、后回测」的顺序来。不要一上来就对着工件图片调阈值先把标定流程完整跑一遍把内参文件或单应矩阵保存好再进入测量环节后面遇到误差才找得到原因。3. 亚像素边缘与圆拟合把 OpenCV 边缘变成毫米级测量3.1 图像预处理与 Canny 双阈值设置如果把测量误差拆开看边缘提取是最容易产生系统误差的环节。Canny 边缘检测返回的是像素坐标真实工件边缘往往落在像素中间边缘偏 0.5 个像素是常事。所以在自带的测量工具里第一步是把图像裁到 ROI 内做高斯模糊和 Canny 检测再用连通域筛选把离散噪点去除。来看一个典型的预处理函数它基本符合源码包里utils_edge.py的思路import cv2 import numpy as np def preprocess_for_measure(gray, roi, blur_size5, canny_sigma1.2): 测量场景预处理 ROI 裁剪后高斯滤波Canny 双阈值最后用连通域过滤离散点。 x, y, w, h roi crop gray[y:yh, x:xw].copy() # 高斯核大小取奇数5x5 在工业图像里是平衡点 # sigma 太小会保留纹理太大会把边缘模糊掉 blur cv2.GaussianBlur(crop, (blur_size, blur_size), canny_sigma) # Canny 双阈值高阈值留下可信边缘低阈值补全边缘连续性 edges cv2.Canny(blur, 80, 180) # 连通域筛选小于 area_min 的碎片直接丢弃避免把灰尘和反光算进去 n, labels, stats, _ cv2.connectedComponentsWithStats(edges, connectivity8) mask np.zeros_like(edges) for i in range(1, n): if stats[i, cv2.CC_STAT_AREA] 50: mask[labels i] 255 return mask, crop逻辑说明高斯模糊的参数blur_size5是对大多数俯拍工件图像比较稳的值3×3 对噪点压制不够7×7 又可能把细小的边缘细节磨平导致后续亚像素插值失真。Canny 的 low 和 high 阈值分别设为 80 和 180保留的边缘是两侧灰度变化明显的像素点。最后的connectedComponentsWithStats是为了去掉那些只存在一两个像素的随机响应测量场景最常见的问题不是漏掉边缘而是把反光点当成了边缘。参数调整时可以看现场光照情况。强光下工件表面反光形成亮斑Canny 会把亮斑边缘也检测出来这时应该把高阈值从 180 提高到 220同时把min_area从 50 提高到 80强行过滤细小碎边。暗光下边缘对比度低出现边缘断裂则要把低阈值调到 40 左右让边缘尽量连通。这些参数在源码包里的config_measure.yaml中都有对应字段。3.2 抛物线插值亚像素坐标把 0.5 个像素的偏差找补回来Canny 给出的是像素级的边缘掩码亚像素处理就是对这些像素的位置做修正。常见做法是在梯度方向上取当前边缘像素左右各一个像素的灰度值用这三个点拟合一条抛物线抛物线顶点的位置就是亚像素边缘坐标。对于接近垂直的边缘直接沿水平方向修正就够了圆弧边缘则需要沿梯度方向修正。下面是抛物线插值的核心代码。它的输入是灰度图crop和 Canny 得到的掩码输出的是带浮点坐标的边缘点数组。后面圆拟合就会用这些浮点坐标而不是原始像素坐标def subpixel_edge_points(crop, edge_mask, roi_offset(0, 0)): 对边缘掩码上的每个像素沿水平方向做抛物线亚像素修正。 适用于竖直边缘占主导的场景圆弧边缘建议对轮廓点做投影修正。 h, w edge_mask.shape rows, cols np.where(edge_mask 0) points [] off_x, off_y roi_offset for py, px in zip(rows, cols): if px 0 or px w - 1 or py 0 or py h - 1: continue # 取当前像素和左右两个像素的灰度值 c float(crop[py, px]) l float(crop[py, px - 1]) r float(crop[py, px 1]) denom l - 2 * c r if abs(denom) 1e-9: dx 0.0 else: # 抛物线极小值点对应亚像素偏移 dx 0.5 * (l - r) / denom points.append((px dx off_x, py off_y)) return np.array(points, dtypenp.float32)逻辑说明当灰度在左右两侧对称上升再下降时l - 2 * c r是抛物线二阶导0.5 * (l - r) / denom是抛物线顶点的横坐标偏移。如果 left 和 right 灰度相等说明边缘像素正好在对称位置偏移为 0如果右侧灰度更亮顶点会向左偏移dx为负。这样每个边缘点的坐标从整数变成浮点后续圆拟合出的半径自然更连续。参数说明里有个容易忽略的坑抛物线插值只修了 x 方向。对竖直边缘是够用的但圆是横纵方向都弯曲的直接这样修会有微小系统偏差。源码包一般在圆拟合之前会做一次轮廓点投影把点投影到拟合圆的法线方向再迭代一两次等效于沿梯度方向做亚像素修正。这一点你在自己写课程设计时要注意只做一维插值的话测圆直径会有固定偏小或偏大的趋势。3.3 最小二乘圆拟合与圆度误差输出不能只有半径拿到亚像素边缘点后测量程序要拟合出圆心和半径。直接用 OpenCV 的 HoughCircles 不适合工业测量因为它在投票时用的是像素梯度精度只能到 1 个像素而且对圆半径范围、圆心距这些先验要求很别扭。课程设计更合适的是先用 RANSAC 把离群点剔掉再对剩余点做最小二乘圆拟合。最小二乘圆拟合的代数解是把圆的方程变形为x^2 y^2 ax by c 0然后解这个线性方程组。下面是源码包里常见的拟合函数返回圆心坐标和半径def fit_circle_ls(points): 最小二乘圆拟合构造线性方程组后调用 lstsq 求解。 输入 points 是亚像素边缘点输出圆心和半径。 x points[:, 0] y points[:, 1] # 圆方程变形-2xc*x - 2yc*y (xc^2yc^2-r^2) -(x^2y^2) A np.column_stack([-2 * x, -2 * y, np.ones_like(x)]) b -(x**2 y**2) sol, _, _, _ np.linalg.lstsq(A, b, rcondNone) cx, cy sol[0], sol[1] radius np.sqrt(cx**2 cy**2 - sol[2]) return cx, cy, radius逻辑说明这里把圆方程整理成关于xc、yc和常数项的线性方程待测点是(x, y)未知量是圆心坐标和圆的参数。np.linalg.lstsq直接求最小二乘解不需要手工迭代稳定性和速度都够用。注意radius的计算要用到cx和cy的平方和减去c项因为方程组里常数项是xc^2 yc^2 - r^2。拟合完成之后测量结果不能只输出一个直径。工业测量里往往还要给圆度误差用来判断工件是不是失圆。圆度误差的计算方式很简单算出所有边缘点到拟合圆心的距离最大距离减去最小距离就是圆度误差。对应这一段可以这样写def measure_radius_and_roundness(points, center): 计算平均半径和圆度误差。 圆度误差 最大半径 - 最小半径单位与 points 坐标一致。 dist np.linalg.norm(points - np.array(center), axis1) mean_r float(dist.mean()) roundness float(dist.max() - dist.min()) return mean_r, roundness参数说明roi_offset要记得加回 ROI 的左上角坐标。很多同学直接对裁剪图拟合出来的圆心只在 ROI 内有效最后换算成毫米时所有点都少了偏移量导致圆心距错得离谱。这是我在源码包的measure模块里反复确认过的一点它在每个边缘点后面都加了off_x, off_y这个习惯值得抄。4. 相机标定与透视校正单应矩阵与像素当量的两种换算4.1 棋盘格内参标定角点数量和拍摄位姿决定精度工业测量里要把像素转毫米第一步是内参标定也就是估计相机的焦距、主点坐标和畸变系数。项目里的标定脚本用的是标准的棋盘格方法标定板是 A4 纸打印的棋盘格格子边长是 2mm内部角点是 9×6。用这个格子的原因是角点检测精度高而且每个格子的几何关系已知可以用来估计相机参数。标定代码的骨架类似下面这种形式。要让它跑出可用的结果关键不是调用 OpenCV API而是拍摄足够多、位姿足够丰富的棋盘图像import cv2 import numpy as np def calibrate_from_chessboard(image_paths, pattern_size(9, 6), square_mm2.0): 用棋盘格标定相机内参和畸变系数。 pattern_size 是内角点数量square_mm 是单个小格边长单位毫米。 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-4) 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) * square_mm obj_points [] img_points [] for path in image_paths: img cv2.imread(path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCornersSB(gray, pattern_size, None) if ret: corners_sub cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners_sub) if len(obj_points) 5: raise RuntimeError(有效标定图像太少请重拍) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None ) return mtx, dist逻辑说明findChessboardCornersSB是 OpenCV 里比较新的角点检测方式对光照变化和模糊的容忍度比老版本接口更好。cornerSubPix用亚像素迭代对角点位置做精修。标定结果mtx是内参矩阵dist是畸变系数数组。实际测量前应该用cv2.undistort对图像做一次畸变校正再把校正结果交给测量链路。参数说明一个特别容易抖动的参数是square_mm。它决定世界坐标系里的尺度直接影响毫米换算结果。实际测量是否用这个 2mm 并不重要重要的是角点之间的物理距离要和真实棋盘完全一致。自制的棋盘格打印后用尺子量一下实际格子边长如果打印缩放导致变成 2.02mm一定要把这个值改掉否则所有测量结果都会有约 1% 的系统误差这个误差会直接体现为直径偏大或偏小。拍摄标定图时至少要有 15 到 20 张分布在画面中间和四角每张的棋盘倾角要有明显变化。只拍十几张完全正对着的棋盘内参矩阵里畸变系数会拟合得奇形怪状校正后直线都拉不回来。4.2 透视校正四点法把倾斜视角拉成俯视图相机标定解决了镜头本身的畸变但没解决相机和被测平面不平行的问题。很多时候相机支架装好后光轴并不严格垂直于工件平面拍出来的圆是略微拉长的椭圆。测量直径之前要先做一个透视校正把一个已知矩形的四个角点映射成标准矩形这样后续的圆拟合、直线拟合才能回到真实比例。源码包里通常提供一个apply_perspective_to_plane函数或者在标定阶段直接求出单应矩阵 H 并保存下来。下面是典型的四点单应变换实现def apply_perspective_to_plane(img, src_pts, dst_size(800, 600)): 通过四点对应关系计算单应矩阵把倾斜视角拉成俯视图。 src_pts 是被测平面上一个矩形的四个角点按顺时针传入。 dst_size 是输出图像的宽高像素。 src_pts np.array(src_pts, dtypenp.float32) dst_pts np.array( [[0, 0], [dst_size[0], 0], [dst_size[0], dst_size[1]], [0, dst_size[1]]], dtypenp.float32, ) H cv2.getPerspectiveTransform(src_pts, dst_pts) warped cv2.warpPerspective(img, H, dst_size) return warped, H逻辑说明getPerspectiveTransform输入两组对应的四个点一组是原图上矩形四个角一组是变换后的矩形角点。这里把目标设为铺满整个输出画面的矩形。warpPerspective根据单应矩阵 H 把原图映射到新图上。加透视校正之后再测量直径和长度就不会受视角倾斜影响。参数说明dst_size的取值会影响像素当量。输出分辨率越大单位长度占比越小同样一个圆在画面里像素数越多测量精度越高。但高分辨率也意味着耗时更大课程设计场景用 800×600 通常够了。要注意的是src_pts四个点的顺序必须和dst_pts完全一致常见错误是左上、右上、右下、左下的顺序没对齐导致映射后图像翻转或扭曲看起来像被压缩成斜四边形。这种情况在视觉上非常明显一旦出现先检查点的顺序。4.3 像素当量标定一张标准块算出 mm 与 pixel 的比值透视校正做完之后如果相机高度固定最直接的毫米换算是像素当量法。工件平面上放一个已知尺寸的标准块比如一块 50mm 的标准矩形块程序量出它在图像里的像素长度50mm 除以像素数就得到每毫米对应多少像素。这个方法工作量小在很多只需要测平面尺寸的课程项目里够用。像素当量的标定函数一般是搜图像里最长的直线边缘然后和真实尺寸做除法。要注意这里算出来的当量只是当前高度的比例尺相机一抬高或降低就必须重新标定。源码包的calib目录里保存了这个值每次换相机支架或重装镜头后都要重新标定一次不能沿用旧的当量文件。def calibrate_px_per_mm(calib_img, std_length_mm50.0, roiNone): 在标定图像上找一条已知长度的直线计算 px_per_mm。 std_length_mm 需要与 roi 内的标准块实际长度严格一致。 crop calib_img if roi is not None: x, y, w, h roi crop calib_img[y:yh, x:xw] gray cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 80, 180) lines cv2.HoughLinesP(edges, rho1, thetanp.pi / 180, threshold80, minLineLength30, maxLineGap10) max_len 0 best_line None for line in lines: x1, y1, x2, y2 line[0] length_px np.hypot(x2 - x1, y2 - y1) if length_px max_len: max_len length_px best_line (x1, y1, x2, y2) if best_line is None: raise RuntimeError(标定图像里没找到直线边缘) px_per_mm max_len / std_length_mm return float(px_per_mm), best_line逻辑说明HoughLinesP会从二值边缘图中提取线段筛出最长的线段作为标准块边沿。最长的边一般就是标定块的边长除非视野里还有更长的干扰物体。px_per_mm的物理意义是图像里每毫米对应的像素数量。后面测量时像素半径除以px_per_mm就得到毫米半径。参数说明std_length_mm是标准块的真实尺寸。这里有个很现实的坑标准块如果是一个圆边缘线不是直线直接用这段代码会准确失败。所以如果要标定圆的直径当量更好的是用已知直径的圆块对圆拟合结果做除法单位为pixel_per_mm。源码包里这个函数通常还接收roi避免扫描到画面外的工件边缘。单应矩阵法和像素当量法在实际工程里的取舍是单应矩阵法可以容忍相机轻微倾斜精度高但需要先做一次四点标定像素当量法简单直接但只能用于相机和工件平面完全平行的固定工况。我在做这个项目时两种方法都跑了结果相差不到 0.1%说明架设水平度较好直接用像素当量法也够。如果你们实验室的支架经常被移动建议优先保存单应矩阵 H因为它天然包含了透视信息。5. 常见问题排查从“能打开”到“测得准”的踩坑记录5.1 排查顺序与问题定位手段拿到这份源码包如果第一次运行就测不准不要急着改算法。先建立一个排查顺序把问题分成三个层级环境层、标定层、测量层。环境层指相机曝光、焦距、光源和工件反射标定层指内参矩阵、畸变系数和 px_per_mm 是否和当前机位匹配测量层指边缘提取和圆拟合的代码逻辑。我习惯在排查时打开三个调试窗口原图、Canny 边缘图、拟合结果图。边缘图能直接告诉你阈值有没有把边缘提出来拟合结果图能告诉你圆是不是被反光边缘带偏。看不到图像就调参数等于对着黑匣子猜。下面这几条是复现过程中最容易遇到的具体问题每一条我都按「现象、原因、解决」的顺序记录。你在课程作业里可以对照着检查。5.2 标定环节的典型问题第一条内参标定后畸变校正图像边缘出现明显波浪形畸变。现象是棋盘格校正后图像四角位置原应为直线的工件边缘变成弧形。原因是标定图像数量不足且位姿单一畸变系数拟合不充分。解决方法是补拍棋盘格铺满画面四角的图片每张图倾角不同让标定算法看到更多畸变形态。标定完成后查看重投影误差一般控制在 0.05 像素以内才算稳。第二条更换相机高度后测量结果整体差了一截。现象是同一工件校准后第一次测量是 25.10mm第二次换支架后变成 24.85mm偏差恒定。原因是只更新了内参文件没有更新 px_per_mm 或单应矩阵。像素当量是相机到被测平面的高度函数换支架等于改变物距。解决方法是把 px_per_mm 的标定纳入每次测量前的初始化流程不要保留历史值或者改用单应矩阵 H 方式换算因为 H 里包含了当前相机与平面的相对位置。第三条棋盘格角点总是检测不到。现象是findChessboardCornersSB返回 False或者需要反复调整棋盘角度才成功。原因是棋盘打印尺寸太小、表面反光、或者棋盘图案只占了画面不到三分之一。解决方法是把打印的棋盘贴在硬平板上保证光照均匀拍摄时让棋盘在画面中的占比尽量大并避免强光直射产生白斑。还有一点棋盘格是每个小格 2mm 还是 5mm 都行关键是所有格子的几何保持一致不能用打印后已经扭曲的纸。5.3 测量环节的典型问题第四条圆拟合直径比实际值偏大 0.3mm 左右。现象是工件边缘看起来清晰但多次测量结果稳定偏大而且每次变化的幅度都在 0.1mm 内。原因是 Canny 提取出的边缘包含了工件外沿的高光过渡带抛物线亚像素修正后把边缘位置往外推了一些。解决方法是检查光源方向工业测量中常见做法是使用低角度照明或加偏振片削弱高光。软件层面可以把 Canny 高阈值上调让边缘收敛到灰度变化最剧烈的单像素带再把连通域面积阈值提高滤掉边缘两侧的碎点。第五条同一工件连续测 10 次直径标准差超过 0.15mm。现象是均值看起来还行但单次结果跳来跳去曲线像噪音。原因是边缘点里混入了离群点比如工件表面划痕或灰尘形成的边缘碎片这些点直接进入了最小二乘拟合。解决方法是先用 RANSAC 做一轮圆拟合设置距离阈值把偏离拟合圆超过 2 个像素的点剔除再用剩余点做最小二乘。RANSAC 的迭代次数设为 200 左右足够课程设计的工件形状稳定不需要跑更重的随机采样。第六条测量画面边缘出现阴影导致 ROI 内圆缺失一部分。现象是圆轮廓只有大半个圆弧能提取出来拟合出来的圆被缺失部分拉偏。原因是视野边缘被支架或遮光板遮挡导致光源在工件上投下阴影。解决方法是调整 ROI 避开阴影区或者在硬件上提高光源的均匀性。软件层面要给拟合函数加角度约束只使用位于 ROI 中心区域的边缘点参与拟合这也是源码包measure模块里能看到的设计意图。6. 离线回测给测量结果加一道“后悔药”换支架、调曝光、改阈值之后千万不能直接拿新参数上比赛或交作业一定要先做一轮离线回测。离线回测的意思是保存一批带真实尺寸标注的工件图比如 20 张同一工件的不同角度照片跑完测量后把程序输出的数据全部存下来做统计。这比现场一帧一帧看结果要快也更容易找到系统误差和随机误差的分布。回测脚本的写法很简单就是把测量函数包在一个循环里收集结果然后输出平均偏差和标准差import numpy as np from measure_tool import run_measure def verify_offline(image_list, ref_mm): 对一批静态图像批量测量统计平均偏差和标准差。 ref_mm 是工件的真实尺寸参考值。 返回偏差和标准差判断测量链路是否需要回退参数。 results [] for path in image_list: val run_measure(path) # 资源里封装的单图测量函数 results.append(val) arr np.array(results) mean arr.mean() std arr.std() bias mean - ref_mm print(f[verify] 平均 {mean:.3f}mm, 标准差 {std:.3f}mm, 偏差 {bias:.3f}mm) return { mean: mean, std: std, bias: bias }逻辑说明run_measure是测量链路封装后的入口输入一张图片路径输出毫米单位的直径或长度。verify_offline对这些结果做统计。如果标准差大说明测量链路不稳定优先查曝光和边缘提取如果标准差小但偏差大说明标定当量或单应矩阵有系统误差优先查 px_per_mm 和棋盘格标定。有一个我自己的习惯可以分享一下回测时先在工件台上放一个已知尺寸的标准块把ref_mm设成标准块的真实尺寸跑完离线数据后再测实际工件。这样可以把标定误差和测量误差解耦开。以前我偷懒直接拿工件自身尺寸做参考结果工件本身已经磨损回测怎么调都差固定一截折腾到凌晨才发现是参考尺寸错了。从那以后我每次改完参数都强制跑一遍verify_offline而且先用标准块定标再跑工件图片。这套流程看着朴素但能避免很多“到落地才发现整个标定文件过期”的翻车事故。我说这么多就是希望你把这份源码用到课程设计或比赛时能少走我走过的这些弯路。希望这份拆解能帮到你把「能运行」真正变成「测得准」。本文还有配套的精品资源点击获取