OpenMV4测距与物体大小测量:焦距标定后只需一个除法 📅 发布时间:2026/9/16 10:27:47 👁 浏览次数: 简介针对OpenMV4测距与物体大小测量场景这份资料面向嵌入式机器视觉初学者及需要快速实现测距功能的开发者以MicroPython代码为核心解决了图像中目标距离与尺寸估算问题。压缩包共含2个文件包括1个Python脚本和1个Word说明文档体积仅13KB。Python脚本从摄像头初始化、图像采集到特征点识别逐步完成了基于三角测距原理的距离计算与物体大小换算Word文档则补充了焦距、像素尺寸、畸变校正系数K1/K2/K3等核心参数的含义与校准思路方便开发者对照原理理解代码逻辑并根据不同目标及时调整参数。目前已有2657人学习下载适合正在做巡检小车、智能货架或视觉定位项目的读者直接参考。借助这套代码能够快速理解OpenMV4的图像处理流程掌握像素距离与实际距离的比例换算方法在实际项目中有效减少测量误差提升测量稳定性。1. OpenMV4测距和测量物体大小先把两个标定数填对代码只是除法OpenMV4是一块运行MicroPython的嵌入式视觉模块很多开发者拿到手第一个想解决的问题就是“怎么测距离”“怎么测物体长宽”。真正写代码之前会遇到一个反直觉的事实固件里没有现成的get_distance()摄像头只会输出色块的像素宽度、AprilTag的像素边长不会输出毫米。像素到毫米之间隔着两个必须由你提供的先验值——镜头的等效像素焦距以及目标物体的实际物理尺寸。焦距标定完成之后测距和测大小本质上就是同一道除法距离 物理宽度 × 焦距 ÷ 像素宽度。这篇文章从针孔相机模型切入给出焦距标定、颜色blob测距、物体尺寸测量和AprilTag融合测距的可运行代码最后落在我实际调试中最影响精度的曝光锁定、畸变校正和现场误差验证上。2. OpenMV4单目测距的针孔模型与焦距标定2.1 针孔相机模型像素宽度和物理宽度只差一个比例常数单目测距的基本关系来自相似三角形。假设一个物体的实际宽度为 W毫米它到镜头光心的距离为 Z毫米在传感器上成像后的像素宽度为 w像素镜头等效焦距为 f像素那么物体在空间中的宽度和距离之比等于成像面上的像素宽度和焦距之比W / Z w / f变换一下就能得到两个最常用的公式。已知物体实际宽度 W测出当前帧里的像素宽度 w距离就是Z W × f / w已知距离 Z要反推物体的实际宽度公式变成W w × Z / f这里的 f 不是镜头包装上印的 2.8mm 或 6mm。那个光学焦距是镜头本身的物理参数而公式里要求的是等价的像素焦距它由镜头焦距、感光芯片像元尺寸、图像分辨率共同决定。同一个镜头在 QVGA320×240和 VGA640×480两种分辨率下像素焦距大约是 2 倍关系。换句话说换一次分辨率就要重新标定一次 f直接沿用旧值会让测距结果系统性偏移。还要注意公式的成立条件目标平面要和镜头光轴垂直目标最好位于画面中央附近。目标倾斜超过 15 度时像素宽度会按余弦关系收缩测出来的距离会比实际值大这部分误差在后面测大小方案里同样存在。2.2 为什么 OpenMV4 固件不提供直接的测距函数单目视觉本身存在尺度模糊问题。一张画面里宽度为 100 像素的红色色块可能是距离 50mm、宽度 10mm 的小物体也可能是距离 500mm、宽度 100mm 的大物体。仅凭这一帧图像OpenMV4 无法区分这两种情况因为两者的成像结果完全一样。固件里的find_blobs、find_apriltags只返回像素坐标系下的位置、宽度、面积这些无量纲结果物理尺度必须由外层程序补充。所以正确的理解是OpenMV4 负责“看到”目标并给出稳定的像素数据你负责“告诉”它目标在世界里的一个真实尺寸。这就是为什么测距代码里必然有一个KNOWN_WIDTH_MM常量也是为什么 AprilTag 测距会比颜色测距更省心——AprilTag 的物理边长可以直接在代码里声明不需要临时测量。2.3 用标定板完成焦距标定的步骤与脚本标定焦距不需要特殊设备一张白纸、一把卡尺就能完成。常见的做法是打印一个黑色矩形量出它的实际宽度放在镜头前方某个精确测过的距离处然后在 OpenMV4 IDE 的“工具 → 阈值编辑器”里读出这个矩形的像素宽度代入公式f 像素宽度 × 实际距离 / 实际宽度更省事的办法是让程序自动读取黑块的像素宽度下面这段脚本可以在 QVGA 分辨率下完成标定# focal_calibration.py - 标定当前镜头的等效像素焦距 import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) # 320x240标定和运行必须用同一种分辨率 sensor.skip_frames(time2000) # 已知量黑块实际尺寸和到镜头的距离用尺子实测 KNOWN_WIDTH_MM 80.0 # 标定黑块的物理宽度 DISTANCE_MM 300.0 # 黑块平面到镜头前表面的距离 # 黑色阈值在LAB空间下取一个能覆盖黑块的区间 black_threshold (0, 35, -128, 127, -128, 127) # 在画面中央取一块ROI避免广角边缘畸变影响宽度读数 roi (60, 60, 200, 120) img sensor.snapshot() blobs img.find_blobs([black_threshold], roiroi, pixels_threshold500, mergeTrue) if blobs: # 取面积最大的黑块防止反光点干扰 b max(blobs, keylambda x: x.area()) focal_pixel b.w() * DISTANCE_MM / KNOWN_WIDTH_MM print(pixel_width%d focal_pixel%.1f % (b.w(), focal_pixel)) else: print(no blob found, adjust threshold or roi)参数说明roi(60, 60, 200, 120)在 320×240 画面里框出中央偏上的区域舍掉边缘减少广角畸变对宽度测量的影响pixels_threshold500过滤掉小噪声取max(blobs, keylambda x: x.area())是防止黑块上的白色反光被拆分成多个小 blob最大面积的那个通常是真实目标。标定距离建议在 300~500mm 之间太近对焦不实太远则像素宽度过小1 个像素的量化误差就会带来百分之几的距离偏差。为了得到更稳的结果我一般会取三个距离各测一次取焦距平均值标定参数推荐设置原因帧尺寸QVGA与正式测距代码保持一致严禁标定用 QVGA、测距切到 VGA曝光模式手动固定自动曝光下黑块面积随亮度波动导致标定值漂移距离档位300mm / 400mm / 500mm多档取平均可抵消单次读数的量化误差目标像素宽度大于 60px低于该阈值时 1px 误差占比超过 1.5%标定完成后把focal_pixel写进正式代码并注释掉标定段避免每次开机都重复标定。3. OpenMV4色块测距代码从阈值配置到毫米输出的可运行方案3.1 颜色阈值与 blob 提取参数怎么配置颜色测距的第一步是把目标从背景里分离出来。OpenMV4 的find_blobs默认在 LAB 色彩空间工作阈值列表(L_min, L_max, A_min, A_max, B_min, B_max)六个值依次对应亮度、绿红轴、蓝黄轴的上下界。相比 RGBLAB 把亮度分离到单独通道光照变化时阈值不容易整个失效。以红色物体为例一个能用的初始阈值是red_threshold (25, 80, 30, 127, 20, 127)这个区间表示亮度在 25~80 之间、红轴分量大于 30 的像素会被认为是目标。实际场景里需要打开 IDE 的阈值编辑器对着目标物体微调 A、B 通道边界。要注意的是阈值太宽会把背景里相近颜色的区域一起框进来阈值太窄则目标在光照波动时会被切碎两者都会让 blob 的像素宽度不稳定进而让测距结果产生跳动。find_blobs的几个关键参数直接影响后续测距质量pixels_threshold150过滤掉小于 150 像素的碎片area_threshold150和它作用类似合并时会参考mergeTrue会把相邻的多个 blob 合并成一个整体适合表面有反光或图案的目标但如果场景里有多个相同颜色的物体合并会把它们连成一个宽带测距结果会严重偏大。3.2 完整的颜色测距循环代码下面这段脚本可以在 QVGA 分辨率下锁定一个红色目标并实时输出毫米距离# color_distance.py - 基于色块宽度的单目测距 import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) # 320x240 sensor.skip_frames(time2000) # 固定曝光和白平衡避免blob面积随风光波动 sensor.set_auto_exposure(False, exposure_us20000) sensor.set_auto_whitebal(False, rgb_gain_db(55, 50, 45)) # 标定值焦距来自第二章脚本目标实际宽度来自卡尺 FOCAL_PIXEL 460.0 KNOWN_WIDTH_MM 50.0 red_threshold (25, 80, 30, 127, 20, 127) def calc_distance(pixel_width): if pixel_width 1: return -1.0 return (KNOWN_WIDTH_MM * FOCAL_PIXEL) / pixel_width while True: img sensor.snapshot() blobs img.find_blobs([red_threshold], pixels_threshold150, area_threshold150, mergeTrue) if not blobs: continue # 多目标时只取面积最大的通常是离镜头最近或实际最大的目标 b max(blobs, keylambda x: x.area()) # 像素宽度过小说明目标太远或太小测量不可信 if b.w() 15: continue dist_mm calc_distance(b.w()) img.draw_rectangle(b.rect(), color(255, 0, 0)) img.draw_cross(b.cx(), b.cy(), color(255, 0, 0)) img.draw_string(b.x(), b.y() - 12, %.0fmm % dist_mm, color(255, 0, 0)) print(dist%.1fmm w%dpx % (dist_mm, b.w())) time.sleep_ms(50)代码逻辑分四步find_blobs取出所有候选目标max(blobs, keylambda x: x.area())只保留面积最大的 blob避免多个小色块干扰b.w()拿到目标的像素宽度calc_distance里的除法把像素宽度还原成物理距离。其中if b.w() 15这个过滤很关键目标离得太远时它的像素宽度只有几个像素w的微小波动会被除法放大输出结果会大幅抖动这时与其给一个错误数值不如直接不显示。提示mergeTrue在近距离多目标场景下会把不同物体合并成一个宽带。如果实测发现近处物体输出距离明显偏小把merge改成False并只取最大 blob 即可。3.3 分辨率、曝光和镜头畸变对测距精度的实际影响测距结果稳定不稳定很多时候不是代码问题而是图像参数在变。以下是影响最大的三个因素因素影响表现建议分辨率切换QVGA 与 VGA 的像素焦距不同公式里的FOCAL_PIXEL失效测距分辨率固定不变标定与运行保持一致自动曝光暗处自动拉高增益目标边缘被提亮blob 面积变大距离读数跳变手动固定曝光时间必要时关闭自动增益广角镜头畸变画面边缘物体被拉伸相同物理宽度的 blob 在边缘比中央宽只使用画面中央 60%~70% 区域或用lens_corr校正后再测曝光影响是最隐蔽的。OpenMV4 默认开启自动曝光当镜头对着窗外或亮背景时自动曝光会压低亮度红色目标的暗部直接低于阈值下限blob 的宽度瞬间萎缩测距结果可能从 300mm 跳到 350mm。固定曝光之后这种跳变基本消失代价是画面可能在强光下过曝、暗光下偏暗但只要目标轮廓仍能被阈值完整分割固定曝光永远利大于弊。4. OpenMV4测量物体大小代码三种路径把像素还原成毫米4.1 已知距离时的物体长宽测量代码测距和测大小是同一个公式的两种用法。当目标离镜头的距离已知时物体的实际宽度和高度用下面的循环代码就能得到# object_size.py - 已知距离测量物体实际长宽 import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time2000) FOCAL_PIXEL 460.0 DIST_MM 500.0 # 目标平面到镜头的固定距离用尺子量好后写死 blue_threshold (20, 70, 0, 60, 60, 127) while True: img sensor.snapshot() blobs img.find_blobs([blue_threshold], pixels_threshold200, mergeTrue) for b in blobs: width_mm b.w() * DIST_MM / FOCAL_PIXEL height_mm b.h() * DIST_MM / FOCAL_PIXEL img.draw_rectangle(b.rect(), color(0, 0, 255)) img.draw_string(b.x(), b.y() - 12, %.1fx%.1fmm % (width_mm, height_mm)) print(width%.1fmm height%.1fmm % (width_mm, height_mm))b.w()和b.h()是色块的像素宽高乘以DIST_MM / FOCAL_PIXEL这个比例因子后直接变成毫米。这个比例因子在固定距离下是常量整段代码不需要除法之外的任何复杂运算。必须留意两个误差源一是目标必须正对镜头目标平面相对光轴倾斜 15 度时宽度误差约 3.4%倾斜 30 度时误差接近 14%二是find_blobs返回的是外接矩形宽高如果目标本身有弧度或遮挡b.w()会包含缺口测量值会小于真实包络尺寸。4.2 AprilTag 融合方案一套代码同时输出距离和物体大小AprilTag 的优势在于它的物理边长是已知的。OpenMV4 的find_apriltags返回每个 tag 的像素宽度tag.w()配合标定好的焦距就能算出 tag 到镜头的距离。更实用的是把待测物体放到 tag 旁边同一个平面上算出这个平面的距离后再用同一条公式测量物体大小# apriltag_distance_size.py - AprilTag 测距 同平面物体测大小 import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.GRAYSCALE) # AprilTag 用灰度图识别率更稳定 sensor.set_framesize(sensor.VGA) # 高分辨率下的 tag 像素宽度更精确 sensor.skip_frames(time2000) sensor.set_auto_exposure(False, exposure_us25000) TAG_SIZE_MM 30.0 # AprilTag 的实际边长打印时已确定 FOCAL_PIXEL 900.0 # VGA 分辨率下重新标定过的焦距 while True: img sensor.snapshot() tags img.find_apriltags(familiesimage.TAG36H11) for tag in tags: # 由 tag 的像素宽度反推距离 z_mm (TAG_SIZE_MM * FOCAL_PIXEL) / tag.w() img.draw_rectangle(tag.rect(), color(255, 0, 0)) img.draw_string(tag.cx(), tag.cy() - 15, z%.0fmm % z_mm, color(255, 0, 0)) # 在 tag 同一平面内紧贴 tag 右侧的红色物体 red_threshold (25, 80, 30, 127, 20, 127) search_roi (tag.x() tag.w() 5, tag.y() - 10, img.width() - tag.x() - tag.w() - 5, tag.h() 20) blobs img.find_blobs([red_threshold], roisearch_roi, pixels_threshold100, mergeTrue) for b in blobs: width_mm b.w() * z_mm / FOCAL_PIXEL height_mm b.h() * z_mm / FOCAL_PIXEL img.draw_rectangle(b.rect(), color(0, 255, 0)) img.draw_string(b.x(), b.y() - 12, %.1fx%.1fmm % (width_mm, height_mm), color(0, 255, 0))这个方案的实用价值在于tag 本身提供了尺度基准目标物体只要与 tag 共面就可以直接用同一个z_mm换算尺寸不需要额外测量距离。search_roi把色块搜索范围限制在 tag 右侧的狭长区域避免把其他无关色块也拉进来。tag.w()在距离很近或 tag 被遮挡时会失真实测中如果输出距离明显跳变优先检查 tag 是否完整露出而不是怀疑焦距标定错了。4.3 硬件辅助方案OpenMV4 搭配 ToF 或超声波模块补距离纯单目测距依赖已知目标宽度如果目标物体尺寸不固定最可靠的办法是外接一个测距传感器拿到真实距离视觉只负责测量目标在画面中的像素占比。OpenMV4 通过 I2C 接 VL53L0X 激光测距模块就能在一个循环里同时拿到真实距离和物体像素宽度# tof_vision_size.py - VL53L0X 提供距离视觉提供像素宽度 from machine import I2C import sensor, image, time # VL53L0X 的驱动库放在 openmv 根目录的 vl53l0x.py 里 from vl53l0x import VL53L0X sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time2000) i2c I2C(2) # OpenMV4 的 I2C2 引脚 tof VL53L0X(i2c) FOCAL_PIXEL 460.0 red_threshold (25, 80, 30, 127, 20, 127) while True: img sensor.snapshot() dist_mm tof.read() # 单位毫米量程在 30~1200mm 之间 blobs img.find_blobs([red_threshold], pixels_threshold100, mergeTrue) for b in blobs: width_mm b.w() * dist_mm / FOCAL_PIXEL height_mm b.h() * dist_mm / FOCAL_PIXEL img.draw_string(b.x(), b.y() - 12, %.1fx%.1fmm % (width_mm, height_mm), color(255, 0, 0)) print(dist%dmm size%.1fx%.1fmm % (dist_mm, width_mm, height_mm)) time.sleep_ms(30)这种组合把尺度问题交给硬件解决视觉只负责识别和分割整体可靠性明显高于纯视觉方案。要注意的是 VL53L0X 的测距点是镜头旁边的固定位置不是画面中心目标在画面里左右移动时测到的距离与目标真实距离会有偏差摆放时尽量让 ToF 的激光点和目标在同一个水平面上。5. OpenMV4测距与尺寸测量现场精度调优的5个细节5.1 锁定曝光、白平衡和增益画面参数全部固定自动曝光加上自动白平衡会让阈值分割结果随环境光波动这是测距跳变的头号原因。固定参数有三行代码放在skip_frames之后sensor.set_auto_whitebal(False, rgb_gain_db(55, 50, 45)) sensor.set_auto_exposure(False, exposure_us20000) sensor.set_auto_gain(False, gain_db20)exposure_us20000表示曝光时间 20 毫秒暗光下可以提高到 35 毫秒但帧率会下降gain_db20限制模拟增益上限防止夜间噪点把 blob 边缘打碎。现场环境光照变了重新调一次这三个值不要只调颜色阈值。5.2 用 lens_corr 校正广角镜头边缘畸变OpenMV4 常见的 2.8mm 广角镜头在画面边缘畸变明显同一个物体从画面中央移到边缘测出来的像素宽度会变化。等测距代码跑通后在find_blobs之前加一行img.lens_corr(1.8) # strength 取值 1.5~2.5按镜头实测调整lens_corr的作用是桶形畸变补偿参数1.8是校正强度。标定方法很简单用尺子量一个固定宽度的物体把它在画面里从中央移到边缘观察输出宽度变化如果边缘宽度明显大于中央增大 strength直到两者差异低于 2%。注意lens_corr会重采样图像略微增加运算时间对 QVGA 分辨率影响可忽略。5.3 对测距结果做 5 帧中值滤波而不是平均值单帧的b.w()会有±1~2 像素的抖动换算成距离就是好几毫米。常见的做法是求平均值但平均值对偶发的离群值不够稳比如某帧反光导致 blob 宽度突然增加 20 像素平均值会被拉偏。改用中值滤波保留最近 5 帧的距离做个排序取中间值from stats import median dist_history [] while True: dist_mm calc_distance(b.w()) # 沿用第三章的测距函数 dist_history.append(dist_mm) if len(dist_history) 5: dist_history.pop(0) stable_dist median(dist_history) print(stable%.1fmm raw%.1fmm % (stable_dist, dist_mm))median取中间值即使 5 帧里有一帧完全失真输出也不会被带偏。这个稳定值再送给显示、串口或电机控制逻辑比直接使用dist_mm可靠得多。5.4 现场验证三档距离误差表最直接正式验收之前用一把尺子摆三个距离档位把实测值对比写下来标尺距离代码输出误差备注300mm312mm4.0%目标离镜头近blob 宽度大量化误差占比低500mm508mm1.6%接近标定距离精度最好800mm842mm5.3%像素宽度变小边缘模糊影响阈值分割如果误差随距离整体线性偏大优先怀疑焦距标定值偏小回到第二章重标如果只有远距离误差大检查是否该换更高分辨率并重新标定焦距如果误差分布无规律先固定曝光再看是不是光线让阈值分割不稳定最后才考虑换镜头。这个排查顺序能覆盖现场 90% 的测距精度问题。本文还有配套的精品资源点击获取