OpenCV Aruco标签检测与三维定位实战:从相机标定到solvePnP位姿估算
2026/9/16 2:44:10 网站建设 项目流程

Aruco标签这几年在机器人、无人机、AR项目里出现频率越来越高,确实是个好用又容易上手的东西。不过网上关于OpenCV下Aruco的教程大部分停留在"检测出框、返回ID"这一步,真到自己动手做测距和三维位置估算时,很多细节就没人讲了——相机标定怎么处理、solvePnP为什么比相似三角形靠谱、旋转向量到底怎么理解、坐标转换怎么落地,这些问题不弄清楚,代码跑通和实际工程能用是两回事。这篇文章就把我从检测到三维定位的完整思路和踩坑记录整理一遍,给正准备用Aruco做定位的兄弟们一个参考。

1. 为什么视觉定位方案里,我更偏向Aruco而不是二维码或AprilTag

先说个实际场景。之前做无人机视觉降落项目,需要在机载相机画面里找到地面目标并估计相对位置。当时候选方案有三套:普通二维码(QR Code)、Aruco、AprilTag。选型时差点直接上QR码——毕竟识别库多、大家熟悉。但仔细对比后还是放弃了QR码,原因后面会说。

1.1 Aruco码的底层原理:每一格都是信息

Aruco码长得有点像简化版的二维码,但工作原理完全不同。它由外部黑色边框和内部编码矩阵组成。外部黑色边框的作用是让检测算法能快速定位出标记的大致区域,内部的黑白格子则负责编码信息。

内部格子的排布方式决定了它能表达多少ID。OpenCV里定义了多种字典(Dictionary),比如DICT_4X4_50意思是4x4内部格子的字典,共50个不重复的码;常用的还有DICT_5X5_100DICT_6X6_250DICT_7X7_1000等。数字越大,可选ID越多,码的信息密度也越高,但对应的物理尺寸也需要更大,否则小格子在小分辨率下难以准确分辨。

Aruco的编码还有一个关键设计——汉明码纠错。它能在单个格子损坏的情况下依然正确解码,同时能检测出多位错误。这意味着轻微的光照不均、打印瑕疵、部分遮挡不严重时,依然能拿到正确的ID。这一点在工业现场尤其重要,我见过太多二维码被污渍遮一个角就完全读不出来的情况。

1.2 和QR码、AprilTag的横向对比

对比项ArucoQR CodeAprilTag
定位速度快,专为视觉定位设计相对较慢,含大量数据位
容错机制汉明码纠错RS纠错,强但开销大有基础纠错
OpenCV原生支持是(contrib模块)是(QRCodeDetector)否,需第三方库
位姿估计便捷度内置solvePnP封装需自行处理内置
最小可检测尺寸相对大(数据密度高)
适用场景定位、测距、位姿估计信息传递、扫码跳转机器人定位、SLAM

从表格能看出,如果目标是"快速感知位置和姿态",Aruco比QR码轻量得多。QR码承载信息能力强,但大量数据格子拖累了检测效率和最小尺寸。AprilTag在学术界也很流行,但OpenCV不原生支持,需要额外编译依赖,工程落地成本高。

1.3 Aruco适合什么项目,不适合什么项目

适合的项目:

  • 移动机器人导航中的地标识别(室内固定位置贴码)
  • 无人机视觉降落、定点悬停
  • 机械臂抓取时的目标粗定位
  • AR场景中的标记物追踪
  • 实验教学里的单目视觉测距

不适合的项目:

  • 需要传递大量结构化数据的场景(选QR码)
  • 超大范围、远距离的地标定位(Aruco在3米外就很考验码的物理尺寸和相机焦段)
  • 有严重遮挡的场景(Aruco对遮挡容忍度有限,超过30%遮挡基本失效)

我个人认为,Aruco的价值在于"它把检测和位姿估计做成了一个接近开箱即用的闭环",这是其他标记方案很难替代的。

2. 动手前的关键一步:相机标定与参数理解

很多教程上来就写检测代码,忽略了最重要的一件事——相机标定。如果只做"检测出框并显示ID",不标定也能跑。但想做测距和三维位置估算,不标定就是空中楼阁,测出来的数据毫无意义。

2.1 相机内参、畸变系数到底是什么

要理解标定的意义,先理清相机成像的完整链路。三维世界中的一个点,要先变换到相机坐标系,再通过针孔模型投影到成像平面,最后转换到像素坐标系。这个映射关系由相机内参决定,包括:

  • 焦距fxfy:以像素为单位,反映镜头焦距和传感器像素尺寸的比值
  • 主点cxcy:光轴与成像平面的交点,通常在图像中心附近

真实镜头还存在畸变——这是由镜头物理结构导致的成像变形。最典型的是径向畸变(桶形畸变、枕形畸变),表现为直线在画面边缘变弯;还有切向畸变,表现为镜头和传感器不完全平行导致的拉伸。OpenCV用k1, k2, k3表示径向畸变,用p1, p2表示切向畸变。

为什么畸变对测距影响巨大?因为Aruco测距依赖图像中标记角点的精确像素坐标。如果角点在边缘区域被畸变拉偏了10个像素,反算出的距离偏差可能是几个厘米甚至十几厘米。广角镜头尤其严重。

2.2 用OpenCV完成标定的完整流程

标定方法很多,最常用的是棋盘格标定法。我用的流程是:

  1. 打印一张棋盘格标定板,注意测量实际格子边长(单位统一用毫米)
  2. 用目标相机拍摄15~20张不同姿态的棋盘格照片,让棋盘格覆盖画面不同区域,倾角越大越好,但不要出现强烈的反光
  3. findChessboardCorners提取角点,再用calibrateCamera计算内参和畸变系数
  4. 将结果保存为npy文件,后续直接加载

核心代码:

import cv2 import numpy as np import glob CHECKERBOARD = (9, 6) # 内角点数 square_size = 25.0 # 每个格子的物理边长,单位mm # 生成对象点坐标 objp = np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) objp *= square_size objpoints = [] # 3D点 imgpoints = [] # 2D像素点 images = glob.glob('calib_images/*.jpg') for fname in images: img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None) if ret: # 亚像素角点精化,提高标定精度 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) objpoints.append(objp) imgpoints.append(corners2) ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) np.savez('calib.npz', mtx=mtx, dist=dist) print("内参矩阵:\n", mtx) print("畸变系数:", dist.ravel())

注意:用Aruco标定板也可以,OpenCV提供了cv2.aruco.calibrateCameraAruco接口,适合项目中正好需要生成Aruco码的场景。但棋盘格通用性更好,不用额外生成标定板图案。

2.3 标定结果验证:重投影误差怎么看

标定完别急着用,先看一个关键指标——重投影误差。calibrateCamera返回的ret就是均方根重投影误差,单位是像素。经验上小于0.5像素说明标定质量不错,如果超过1像素,建议重新采集照片再标定。

采集照片时的几个注意点:

  • 不要让棋盘格在整个画面中占的比例太小,至少占1/4以上
  • 每张照片的棋盘格倾角要有差异,完全正对镜头的照片几乎没有标定贡献
  • 我习惯分远近两组:近距离拍一组(格子占画面1/2),远距离拍一组(格子占画面1/4),组合起来能让fxfy和畸变系数都得到更好约束

3. 检测Aruco标签:从单张图片到实时视频流

标定做完,才进入核心环节。先说环境——OpenCV的Aruco模块不在主包里,而是放在opencv-contrib-python里。新手最常见的问题就是import cv2之后找不到cv2.aruco,多半是装错了包。

3.1 环境准备:opencv-contrib-python版本坑

安装命令:

pip install opencv-contrib-python

注意:opencv-pythonopencv-contrib-python不要同时安装,两个包存在冲突,可能互相覆盖文件导致异常。只装contrib版本就够了,它已经包含了所有主模块功能。

版本方面有个重要变化。OpenCV 4.7之前,Aruco的API主要以cv2.aruco.ArucoDetector之前的旧接口为准,比如cv2.aruco.detectMarkers。4.7版本之后,OpenCV重构了Aruco API,推荐使用cv2.aruco.ArucoDetector类。旧API在新版本里虽然还能用,但会提示DeprecationWarning。我这里统一用新版API写法。

3.2 核心检测流程:字典、参数、检测与绘制

检测Aruco分四步:选择字典、创建检测器、执行检测、绘制结果。

import cv2 import numpy as np # 1. 选择字典 aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_6X6_250) # 2. 设置检测参数并创建检测器 params = cv2.aruco.DetectorParameters() detector = cv2.aruco.ArucoDetector(aruco_dict, params) # 3. 读取图像并检测 img = cv2.imread('test.png') corners, ids, rejected = detector.detectMarkers(img) # 4. 如果检测到,绘制结果 if ids is not None: cv2.aruco.drawDetectedMarkers(img, corners, ids) for i, marker_id in enumerate(ids.flatten()): print(f"检测到ID: {marker_id}") print(f"四个角点像素坐标: {corners[i].reshape(4, 2)}") cv2.imshow('Aruco Detection', img) cv2.waitKey(0) cv2.destroyAllWindows()

detectMarkers返回三个值:corners是每个检测到的标记的四个角点坐标(顺序为左上、右上、右下、左下),ids是对应的ID数组,rejected是"看起来像Aruco但解码失败"的候选轮廓,调试时很有用。

如果要跑摄像头实时检测,加一个循环就行:

cap = cv2.VideoCapture(0) # 建议固定分辨率,避免不同分辨率下标定参数失效 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame = cap.read() if not ret: break corners, ids, rejected = detector.detectMarkers(frame) if ids is not None: # 这里可以加测距和位姿估计逻辑 cv2.aruco.drawDetectedMarkers(frame, corners, ids) cv2.putText(frame, f"Markers: {len(ids)}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow('Aruco Real-time', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

3.3 可以调节的检测参数和漏检处理

DetectorParameters里有不少可调项,我实际调过并且有明显效果的有三个:

  • adaptiveThreshWinSizeMin/adaptiveThreshWinSizeMax/adaptiveThreshWinSizeStep:控制自适应阈值窗口大小范围。光照不均时,默认参数可能漏检,适当扩大窗口范围能提高召回率
  • minMarkerPerimeterRate:标记最小周长占图像短边的比例。相机距离远、标记看起来小的时候,这个值要调小,否则小标记会被直接过滤掉
  • polygonalApproxAccuracyRate:多边形逼近精度。太严格会漏检,太宽松会引入误检,一般保持默认

调试建议:先统计rejected的数量和轮廓特征,如果某个标记始终进入rejected而不进入corners,说明解码环节出了问题,优先检查打印质量、是否有反光、标记尺寸是否过小。

经验:检测到但解码失败的标记,rejected返回的轮廓中心点也可以作为"粗略定位"的信号,尤其在做视觉伺服时,可以对候选区域做一次ROI放大再检测,往往能救回来。

4. 测距实现:相似三角形与solvePnP两条路线

检测到Aruco只是第一步,能算出它离相机多远才是工程落地的关键。这部分有两种实现路线,我强烈建议直接走第二条。

4.1 为什么直接相似三角形不够用

网上最常见的单目测距公式是:

距离 = (真实尺寸 × 焦距) / 像素尺寸

原理是针孔相机模型下的相似三角形。以Aruco为例,可以取标记的边长作为真实尺寸,通过四个角点坐标算标记在像素平面上的边长,然后套公式。

这个方案在标记完全正对相机(标记平面与成像平面平行)时勉强能用,但实际场景几乎不可能保证这种关系。一旦标记倾斜(比如无人机降落时从斜上方看地面码),透视变形导致像素边长不断变化,测出的距离会来回跳。而且这个公式只能测"距离",完全无法给出偏航角、俯仰角这类姿态信息。

所以直接相似三角形的适用场景非常有限:固定角度、固定姿态、精度要求低。

4.2 基于solvePnP的测距原理:PnP问题本质

准确的方案是求解PnP(Perspective-n-Point)问题。n=4,因为我们已知Aruco四个角点在标记坐标系下的三维坐标(这个可以精确构造出来),又通过检测得到了它们在图像中的四个二维像素坐标。求解PnP就是要找到一个旋转矩阵和平移向量,使得这组3D点投影到图像后,与观测到的2D点尽量重合。

一旦求出了平移向量tvec,相机光心到标记坐标系原点的距离就是tvec的模长:

distance = sqrt(tx^2 + ty^2 + tz^2)

这个距离对姿态不敏感——标记无论怎么倾斜,只要角点提取准确,测距结果都稳定。这正是它优于相似三角形的地方。而且tvec的三个分量本身就包含了标记在相机坐标系下的三维位置,测距只是附带的副产品。

标准流程:

  1. 构造Aruco四个角点的3D坐标(以标记中心为原点)
  2. 输入相机内参和畸变系数
  3. 调用solvePnP或OpenCV封装好的estimatePoseSingleMarkers
  4. 用旋转向量和平移向量计算距离和姿态

4.3 完整测距代码与精度验证

import cv2 import numpy as np # 加载相机标定结果 calib_data = np.load('calib.npz') mtx = calib_data['mtx'] dist = calib_data['dist'] # Aruco标记物理尺寸(单位:毫米) MARKER_SIZE_MM = 50.0 # 标记四个角点在标记坐标系下的3D坐标(Z轴垂直标记平面) marker_points_3d = np.array([ [-MARKER_SIZE_MM / 2, MARKER_SIZE_MM / 2, 0], [ MARKER_SIZE_MM / 2, MARKER_SIZE_MM / 2, 0], [ MARKER_SIZE_MM / 2, -MARKER_SIZE_MM / 2, 0], [-MARKER_SIZE_MM / 2, -MARKER_SIZE_MM / 2, 0] ], dtype=np.float32) aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_6X6_250) params = cv2.aruco.DetectorParameters() detector = cv2.aruco.ArucoDetector(aruco_dict, params) cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame = cap.read() if not ret: break corners, ids, rejected = detector.detectMarkers(frame) if ids is not None: for i, marker_id in enumerate(ids.flatten()): # corners[i] 形状是 (1, 4, 2) corners_2d = corners[i].reshape(4, 2).astype(np.float32) # 求解PnP success, rvec, tvec = cv2.solvePnP( marker_points_3d, corners_2d, mtx, dist) if success: # 距离(毫米)转为米更直观 distance_m = np.linalg.norm(tvec) / 1000.0 # 标记中心在相机坐标系下的位置 cx, cy, cz = tvec.flatten() text = f"ID:{marker_id} dist:{distance_m:.3f}m" cv2.putText(frame, text, (int(corners_2d[0][0]), int(corners_2d[0][1] - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 绘制坐标轴,直观看到姿态 cv2.drawFrameAxes(frame, mtx, dist, rvec, tvec, MARKER_SIZE_MM / 2) cv2.imshow('Aruco Distance', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

实测精度方面,我拿50mm的Aruco码,用固定焦距的工业相机在0.3m到2.5m范围内做了测试,均匀光照下测距误差大概在1到3厘米之间。距离越远、像素占比越小,误差会快速增大。到了4米开外,50mm的码在720p分辨率下只有几十个像素的边长,角点检测的量化误差就会被直接放大到距离误差上。

提示:如果你的项目要求毫米级精度,单目视觉基本做不到,别纠结,考虑换双目或深度相机。Aruco单目方案的优势是"够用且便宜",不是"极致精度"。

5. 三维位置估算:从相机坐标到世界坐标的完整推导

测距只给了"距离",但很多场景需要的是完整的三维位置——比如机械臂要知道目标在相机坐标系下的(x, y, z)坐标。这部分就是把solvePnP解出来的数据用到位。

5.1 旋转向量与平移向量:位姿的四要素

solvePnP返回的rvec是旋转向量(罗德里格斯向量),tvec是平移向量。两者合起来表达了"标记坐标系相对于相机坐标系的刚体变换"。

旋转向量用"旋转轴方向+旋转角大小"编码三维旋转:向量方向是旋转轴,向量模长是旋转角度(弧度)。想把旋转向量转成更直观的旋转矩阵用cv2.Rodrigues

R, _ = cv2.Rodrigues(rvec)

平移向量则直接是标记坐标系原点(即标记中心)在相机坐标系下的三维坐标,单位与构造3D点时一致。这就是标记中心即使距离相机偏移,也能准确得到它在空间中的位置的核心原因。

5.2 欧拉角转换与坐标系映射

旋转矩阵适合计算,但不适合人看。实际项目中通常转成欧拉角(俯仰角pitch、偏航角yaw、滚转角roll)。转换时要注意旋转顺序,不同顺序得到的角度含义完全不同。机器人领域常用ZYX内旋顺序:

def rvec_to_euler(rvec): R, _ = cv2.Rodrigues(rvec) sy = np.sqrt(R[0, 0]**2 + R[1, 0]**2) if sy > 1e-6: x = np.arctan2(R[2, 1], R[2, 2]) # roll y = np.arctan2(-R[2, 0], sy) # pitch z = np.arctan2(R[1, 0], R[0, 0]) # yaw else: x = np.arctan2(-R[1, 2], R[1, 1]) y = np.arctan2(-R[2, 0], sy) z = 0 return np.degrees([x, y, z])

拿到欧拉角后,"相机相对标记的俯仰角是15度"这样的描述就能直接用于控制逻辑。比如无人机降落时,如果俯仰角太大说明机头下压过多,需要调整姿态再确认降落路径。

5.3 多标签场景下的位置融合

很多时候场景里不止一个Aruco码。比如AGV导航走廊里贴了多个地标,或无人机降落场铺了一排码。如果每个码都单独计算位姿,那么当机器移动时,不同码算出的位置可能是断断续续切换的。

我的做法是:把检测到的所有标记都当作观测,根据每个标记的检测置信度(可以用标记的像素面积或角点重投影误差衡量)加权融合出最终位置。核心思路:

def fuse_marker_poses(corners, ids, mtx, dist, marker_size_mm): total_weight = 0.0 fused_tvec = np.zeros(3) for i, marker_id in enumerate(ids.flatten()): corners_2d = corners[i].reshape(4, 2).astype(np.float32) marker_points_3d = build_marker_points(marker_size_mm) success, rvec, tvec = cv2.solvePnP( marker_points_3d, corners_2d, mtx, dist) if success: # 用像素面积作为置信度权重 area = cv2.contourArea(corners_2d) weight = max(area, 1.0) R, _ = cv2.Rodrigues(rvec) fused_tvec += weight * tvec.flatten() total_weight += weight if total_weight > 0: fused_tvec /= total_weight return fused_tvec return None

多标签融合有一个隐含前提:所有标记都在同一个平面坐标系下,并且已知每个标记之间的相对位姿。如果标记是散布贴在墙上的,还需要额外标定每个标记相对于"基准标记"的变换关系,否则不同标记给出的「相机到标记」的变换是相对各自坐标系的,不能直接平均。

6. 实战中的坑与调优经验

代码能跑通只是开始,实际环境里各种问题才是工程落地真正的拦路虎。这里集中记录我踩过印象最深的几个坑。

6.1 光照、反光和遮挡

Aruco的检测依赖黑白格子的对比度,光照不足或反光会让格子灰度分不清边界。我踩过最典型的坑是:室内顶灯直射在标签表面产生反光,局部高光导致有几个格子被误判成白色,汉明码校验没过,标记直接进rejected。后来换了哑光覆膜的打印纸,情况立刻好转。

户外场景则是另一个极端——强日光下相机自动曝光会让黑色格子的灰度抬升,黑白对比度反而下降。解决思路:

  • 固定相机曝光和增益,不要用自动曝光
  • 在标签周围加哑光黑色边框,减少环境光干扰
  • 条件允许时,选DICT_4X4_50这类格子少的字典,每个格子占的像素面积更大,抗噪声能力更强

遮挡问题记一条规律:Aruco对内部格子的遮挡容忍度约20%到30%。外部黑框被挡住检测不到区域,内部又有部分格子被挡,基本必挂。

6.2 字典大小与误检

字典选择不只是ID数量问题,直接影响检测鲁棒性。DICT_4X4_50的码很简单,误检概率相对低——因为候选码空间小。但DICT_7X7_1000的码复杂,内部格子多,每个格子更小,同样物理尺寸下检测距离反而更近。

我的选择习惯:只有几十个ID需求就用DICT_4X4_50DICT_5X5_100,宁可码大一点、简单一点。ID需求上百个才考虑DICT_6X6_250。在纯粹追求检测率的项目里,DICT_4X4_50的表现在我的实测中最好。

6.3 相机分辨率与检测距离的平衡

提高检测距离有两个方向:提高相机分辨率或者增大码的物理尺寸,但两者都有代价。分辨率提高会降低帧率,而且标定参数在分辨率改变后要重新标定;增大码的尺寸会占用实际空间,不适合小场景。

实际操作中,可以先做一次摸底测试:把你的码放在预期最远距离处,拍一张照片,如果码的短边像素长度小于80到100像素,基本可以准备换方案了。这个值再往下会明显触发角点提取和亚像素精度的极限。

6.4 marker尺寸单位与坐标系一致性陷阱

这个坑特别隐蔽。构造3D角点坐标时用毫米作单位,计算结果tvec就是毫米。如果后续做坐标转换时和其他模块的米制单位混用,误差会瞬间放大1000倍。我见过有同事拿毫米制tvec去和米制坐标系的点做矩阵运算,结果整个定位轨迹漂到天边去。

我的约定:全项目统一用米作为长度单位。

构造角点:

MARKER_SIZE_M = 0.05 marker_points_3d = np.array([ [-MARKER_SIZE_M / 2, MARKER_SIZE_M / 2, 0], [ MARKER_SIZE_M / 2, MARKER_SIZE_M / 2, 0], [ MARKER_SIZE_M / 2, -MARKER_SIZE_M / 2, 0], [-MARKER_SIZE_M / 2, -MARKER_SIZE_M / 2, 0] ], dtype=np.float32)

还有坐标系方向问题。Aruco的3D点约定Z轴垂直标记平面向外(面向相机方向),Y轴向下,X轴向右。这与ROS的相机坐标系(Z轴朝前、Y轴朝下)不一致,在不同框架间搬运数据时要先做坐标轴映射,不能直接照搬。

6.5 角点亚像素精化到底有没有用

solvePnP对2D角点坐标的精度非常敏感。理论上,marker在图像中占的像素越少,角点坐标误差对位姿的影响越大。OpenCV的cornerSubPix可以把角点定位到亚像素精度。但Aruco的角点本身是黑白格子的交点,对比度足够时cornerSubPix确实能带来肉眼可见的稳定性提升。

老版本OpenCV在estimatePoseSingleMarkers内部不做亚像素精化,所以建议在送入solvePnP之前先做一次:

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_refined = cv2.cornerSubPix(gray, corners_2d, (5, 5), (-1, -1), criteria)

实测能减少边角处几毫米的距离抖动。代价是每帧多一次cornerSubPix,CPU占用略增,实时性要求高的项目需要衡量一下。

最后再分享一个调试技巧:在画面上叠加drawFrameAxes画出的坐标轴,能立刻看出位姿估计是否合理。坐标轴的长度用marker尺寸的一半比较合适,太短看不清旋转趋势,太长会在小marker上产生视觉干扰。如果在静止画面里坐标轴还在不停抖动,优先检查角点提取稳定性和标定参数是否匹配当前分辨率。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询