OpenCV drawFrameAxes()函数轴长过长时出现畸变断裂问题
Charuco板位姿估计后
drawFrameAxes轴长过大导致畸变/断裂问题 问题现象
使用OpenCV Contrib 4.10.0及opencv-python 4.7.0.72完成Charuco板检测与位姿估计后,调用cv2.drawFrameAxes()绘制坐标轴时,当轴长设置过大(例如设为10),轴线超出图像范围后会出现以下异常:
- 轴线发生畸变,与预期坐标轴方向不重合
- 出现线条断裂、仅保留轮廓的情况
- 不同轴长下显示的坐标轴形态不一致
该问题在模拟图像与真实采集图像中均存在。
复现代码及相机参数
import os import numpy as np import matplotlib.pyplot as plt import cv2 # 标定后的相机内参 mtx = np.array([[3341.0930071 , 0. , 2020.04757797], [ 0. , 3332.54238818, 1108.10670945], [ 0. , 0. , 1. ]]) # 畸变系数 dist = np.array([-0.10474984, 0.04565679, 0.00948721, 0. , 0. ]) img_rgb = cv2.imread("some img") img_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR) img = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY) # Charuco板配置 dictionary = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_100) board = cv2.aruco.CharucoBoard((7, 5), 0.055, 0.041, dictionary) params = cv2.aruco.DetectorParameters() detector = cv2.aruco.ArucoDetector(dictionary, params) # 检测标记点与Charuco角点 marker_corners, marker_ids, rejectedCandidates = detector.detectMarkers(img) ret, charucoCorners, charucoIds = cv2.aruco.interpolateCornersCharuco(marker_corners, marker_ids, img, board) # 位姿估计 ret, rvec, tvec = cv2.aruco.estimatePoseCharucoBoard(charucoCorners, charucoIds, board, mtx, dist, None, None) # 绘制不同轴长的坐标轴 axis_size = 0.1 result_short = cv2.drawFrameAxes(np.copy(img_rgb), mtx, dist, rvec, tvec, axis_size) axis_size = 5 result_long = cv2.drawFrameAxes(np.copy(img_rgb), mtx, dist, rvec, tvec, axis_size) # 可视化对比 plt.subplot(1,2,1) plt.imshow(result_short) plt.subplot(1,2,2) plt.imshow(result_long) plt.show() # 叠加绘制不同轴长 result_both = cv2.drawFrameAxes(np.copy(img_rgb), mtx, dist, rvec, tvec, 0.2) result_both = cv2.drawFrameAxes(result_both, mtx, dist, rvec, tvec, 2)
原因分析
drawFrameAxes()的核心逻辑是:先在3D空间生成坐标轴端点(原点及三个轴的末端点,长度由axis_size指定),再通过相机内参和畸变参数将这些3D点投影到图像平面。当轴长过大时,部分3D端点会落在相机视锥体之外,投影后的像素坐标会出现异常(如超出图像尺寸范围、产生不符合透视规律的畸变坐标),最终导致绘制的轴线错乱、断裂。
此外,OpenCV的投影函数不会自动裁剪视锥体外的点到图像范围内,而是直接绘制这些异常投影点,进而引发视觉上的形态不一致问题。
解决方案
1. 合理设置轴长
axis_size的单位与Charuco板的方格尺寸单位一致(示例中为米),需根据相机与Charuco板的实际距离设置合理值,确保坐标轴端点投影后落在图像范围内。比如示例中Charuco方格边长为0.055米,轴长设为0.1-0.5米(对应真实世界10-50厘米)是比较合理的,避免设置远大于场景尺度的值(如5、10米)。
2. 手动裁剪投影点后自定义绘制
如果确实需要绘制较长的坐标轴,可以手动生成3D坐标轴点,投影后裁剪到图像范围内,再自行绘制轴线,避免异常点干扰:
def draw_custom_frame_axes(img, mtx, dist, rvec, tvec, axis_length, img_size): # 定义3D坐标轴点:原点 + X/Y/Z轴端点 axis_3d = np.float32([[0,0,0], [axis_length,0,0], [0,axis_length,0], [0,0,axis_length]]).reshape(-1,3) # 将3D点投影到图像平面 axis_2d, _ = cv2.projectPoints(axis_3d, rvec, tvec, mtx, dist) axis_2d = np.int32(axis_2d).reshape(-1,2) # 裁剪坐标到图像有效范围内 h, w = img_size def clip_point(p): return (max(0, min(p[0], w-1)), max(0, min(p[1], h-1))) origin = clip_point(axis_2d[0]) x_end = clip_point(axis_2d[1]) y_end = clip_point(axis_2d[2]) z_end = clip_point(axis_2d[3]) # 绘制轴线(X红、Y绿、Z蓝) cv2.line(img, origin, x_end, (0,0,255), 3) cv2.line(img, origin, y_end, (0,255,0), 3) cv2.line(img, origin, z_end, (255,0,0), 3) return img # 使用示例 h, w = img_rgb.shape[:2] result_custom = draw_custom_frame_axes(np.copy(img_rgb), mtx, dist, rvec, tvec, 5, (h,w)) plt.imshow(cv2.cvtColor(result_custom, cv2.COLOR_BGR2RGB)) plt.show()
3. 升级OpenCV版本
部分旧版本OpenCV的drawFrameAxes存在视锥体外点处理的bug,尝试升级到最新稳定版(如4.15.x或更高),可能已修复该问题。
内容的提问来源于stack exchange,提问作者Gytis Bernotas
相关产品推荐
相关产品推荐

