You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OpenCV projectPoints时Z轴显示过长问题求助

问题原因及解决方案

你的问题出在相机内参设置不合理和坐标轴原点位置错误,加上代码里的两个小bug:

  1. 相机焦距设置过小:你把fx、fy设为1.0,这不符合实际相机参数,导致投影后的坐标缩放完全失真。正常情况下fx、fy应与图像分辨率匹配(比如等于图像宽、高),这样投影比例才合理。
  2. 坐标轴原点在相机光心:tvec设为(0,0,0)意味着世界原点和相机光心重合,旋转后X/Y轴的端点在相机坐标系中的Z坐标会趋近于0,触发透视投影的极端情况(分母接近0),导致这些轴的投影长度异常缩短,而Z轴端点旋转后Z坐标仍保持较大值,投影长度显得更长。
  3. 代码小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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 00:20:56