使用OpenCV projectPoints时Z轴显示过长问题求助
问题原因及解决方案
你的问题出在相机内参设置不合理和坐标轴原点位置错误,加上代码里的两个小bug:
- 相机焦距设置过小:你把fx、fy设为1.0,这不符合实际相机参数,导致投影后的坐标缩放完全失真。正常情况下fx、fy应与图像分辨率匹配(比如等于图像宽、高),这样投影比例才合理。
- 坐标轴原点在相机光心:tvec设为(0,0,0)意味着世界原点和相机光心重合,旋转后X/Y轴的端点在相机坐标系中的Z坐标会趋近于0,触发透视投影的极端情况(分母接近0),导致这些轴的投影长度异常缩短,而Z轴端点旋转后Z坐标仍保持较大值,投影长度显得更长。
- 代码小bug:
createCanvas和draw3DAxis函数带self参数但未作为类方法使用,调用会报错;main里用pi但未导入np.pi,运行会抛出NameError。
修改后的代码
import numpy as np import cv2 import sys def rotByXAxis(angle): return np.array([ [1, 0, 0], [0, np.cos(angle), -np.sin(angle)], [0, np.sin(angle), np.cos(angle)], ], dtype=np.float32) def rotByYAxis(angle): return np.array([ [ np.cos(angle), 0, np.sin(angle)], [ 0, 1, 0], [-np.sin(angle), 0, np.cos(angle)], ], dtype=np.float32) def rotByZAxis(angle): return np.array([ [ np.cos(angle), np.sin(angle), 0], [-np.sin(angle), np.cos(angle), 0], [ 0, 0, 1], ], dtype=np.float32) # 去掉多余的self参数 def createCanvas(height, width): blank_image = np.zeros((height, width, 3), np.uint8) blank_image[:, :] = (255, 255, 255) return blank_image # 去掉多余的self参数 def draw3DAxis(image, rvec, tvec, cameraMatrix, scale=1, dist=None): """ Draw a 6d of axis (XYZ -> RGB) in the given rotation and translation :param image - rgb numpy array :rvec - euler rotations, numpy array of length 3, use cv2.Rodrigues(R)[0] to convert from rotation matrix :tvec - 3d translation vector, in meters (dtype must be float) :cameraMatrix - intrinsic calibration matrix , 3x3 :scale - factor to control the axis lengths :dist - optional distortion coefficients, numpy array of length 4. If None distortion is ignored. """ image = image.astype(np.float32) dist = np.zeros(4, dtype=float) if dist is None else dist if rvec.shape == (3, 3): rvec, _ = cv2.Rodrigues(rvec) points = scale * np.float32([ [1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 0, 0] ]).reshape(-1, 3) axis_points, _ = cv2.projectPoints(points, rvec, tvec, cameraMatrix, dist) image = cv2.arrowedLine( image, tuple(int(e) for e in axis_points[3].ravel()), tuple(int(e) for e in axis_points[0].ravel()), (255, 0, 0), 3, tipLength=0.01 * scale, ) image = cv2.arrowedLine( image, tuple(int(e) for e in axis_points[3].ravel()), tuple(int(e) for e in axis_points[1].ravel()), (0, 255, 0), 3, tipLength=0.01 * scale, ) image = cv2.arrowedLine( image, tuple(int(e) for e in axis_points[3].ravel()), tuple(int(e) for e in axis_points[2].ravel()), (0, 0, 255), 3, tipLength=0.01 * scale, ) return image.astype(np.uint8) # 转回uint8避免显示异常 if __name__ == "__main__": height = 300 width = 400 image = createCanvas(height, width) # 测试绕X轴旋转,替换成rotByYAxis也可以 rvec = rotByXAxis(np.pi/4) # 修改相机内参,设置合理的fx、fy cameraMatrix = np.array([ [width, 0, width/2], [ 0, height, height/2], [ 0, 0, 1.0 ]], dtype=np.float32) image = draw3DAxis( image, rvec=rvec, tvec=np.array([0, 0, 50], dtype=float), # 将坐标轴放在相机前方50单位处 cameraMatrix=cameraMatrix, scale=20, ) cv2.imshow("output", image) key = cv2.waitKey(0) cv2.destroyAllWindows() sys.exit(0)
关键修改说明
- 调整了相机内参的fx和fy为图像宽高,保证投影缩放比例正常。
- 设置
tvec=(0,0,50),让坐标轴在相机前方一定距离,避免Z坐标为0的极端情况。 - 去掉了函数的
self参数,修正了np.pi的引用,添加了图像类型转回uint8的步骤(避免显示时颜色异常)。
这样修改后,旋转X/Y/Z轴时,三个坐标轴的投影长度就会保持一致了。
内容的提问来源于stack exchange,提问作者Ming En
相关产品推荐
相关产品推荐

