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

如何根据3D圆盘的2D投影计算其pitch(俯仰)和yaw(偏航)旋转参数

3D圆盘旋转参数估计算法开发与问题说明

我想要编写算法计算3D圆盘/圆形相对于图像平面的旋转参数,计划开发一款应用,采用相关专业论文所述方法识别真实世界圆形物体的旋转,论文使用高等数学对相关问题做了阐释。
为测试应用,我参考了相关技术帖子的代码生成旋转后圆形的合成图像。
我使用OpenCV的fitEllipse函数,在30-45°小角度场景下可满足需求:仅施加pitch或仅施加yaw旋转时,我可通过椭圆长短轴比值的反余弦函数较为轻松、相对准确地计算角度,但当两种旋转同时施加时,该方法就会失效。我不清楚椭圆长短轴与其旋转参数之间的数学关系,不确定是合成图像存在误差,还是我对pitch角度的计算有误。

测试代码

import numpy as np
import cv2


def get_3d_rotation_matrix(width, height, theta, phi, gamma, dx, dy, dz):
    w, h = width, height
    d = np.sqrt(w ** 2 + h ** 2)
    # theta = np.deg2rad(theta)
    # phi = np.deg2rad(phi)
    # gamma = np.deg2rad(gamma)
    focal = f = d / (2 * np.sin(gamma) if np.sin(gamma) != 0 else 1)
    dz = focal

    # Projection 2D -> 3D matrix
    A1 = np.array([[1, 0, -w / 2],
                   [0, 1, -h / 2],
                   [0, 0, 1],
                   [0, 0, 1]])

    # Rotation matrices around the X, Y, and Z axis
    RX = np.array([[1, 0, 0, 0],
                   [0, np.cos(theta), -np.sin(theta), 0],
                   [0, np.sin(theta), np.cos(theta), 0],
                   [0, 0, 0, 1]])

    RY = np.array([[np.cos(phi), 0, -np.sin(phi), 0],
                   [0, 1, 0, 0],
                   [np.sin(phi), 0, np.cos(phi), 0],
                   [0, 0, 0, 1]])

    RZ = np.array([[np.cos(gamma), -np.sin(gamma), 0, 0],
                   [np.sin(gamma), np.cos(gamma), 0, 0],
                   [0, 0, 1, 0],
                   [0, 0, 0, 1]])

    # Composed rotation matrix with (RX, RY, RZ)
    R = np.dot(np.dot(RX, RY), RZ)

    # Translation matrix
    T = np.array([[1, 0, 0, dx],
                  [0, 1, 0, dy],
                  [0, 0, 1, dz],
                  [0, 0, 0, 1]])

    # Projection 3D -> 2D matrix
    A2 = np.array([[f, 0, w / 2, 0],
                   [0, f, h / 2, 0],
                   [0, 0, 1, 0]])

    # Final transformation matrix
    return np.dot(A2, np.dot(T, np.dot(R, A1)))

def get_image_3d_rotated(image, theta, phi, gamma, dx, dy, dz):
    height, width = image.shape
    rtheta, rphi, rgamma = np.deg2rad(theta), np.deg2rad(phi), np.deg2rad(gamma)
    mat = get_3d_rotation_matrix(width, height, rtheta, rphi, rgamma, dx, dy, dz)

    return cv2.warpPerspective(image.copy(), mat, (width, height))


def callback(x):
    pass

alpha = 0
beta = 15
gamma = 0
canvas2D = np.zeros((600, 800), dtype=np.uint8)
#canvas2D = cv2.circle(canvas2D, (canvas2D.shape[1]//2, canvas2D.shape[0]//2), 50, 255)
cv2.namedWindow('circleImg')
cv2.createTrackbar('pitch', 'circleImg', 0, 90, callback)
cv2.createTrackbar('yaw', 'circleImg', 0, 90, callback)
cv2.createTrackbar('roll', 'circleImg', 0, 90, callback)
cv2.createTrackbar('radius', 'circleImg', 50, 100, callback)
cv2.setTrackbarPos('pitch', 'circleImg', 0)
cv2.setTrackbarPos('yaw', 'circleImg', 0)
cv2.setTrackbarPos('roll', 'circleImg', 0)

while True:
    canvas2D = np.zeros((1000, 1000), dtype = np.uint8)

    pitch  = cv2.getTrackbarPos('pitch', 'circleImg')
    yaw    = cv2.getTrackbarPos('yaw', 'circleImg')
    roll   = cv2.getTrackbarPos('roll', 'circleImg')
    radius = cv2.getTrackbarPos('radius', 'circleImg')

    cv2.circle(canvas2D, (canvas2D.shape[1] // 2, canvas2D.shape[0] // 2), radius, 255)
    
    rotated_img = get_image_3d_rotated(canvas2D, pitch, yaw, roll, 0, 0, 0)
    
    cv2.imshow('circleImg', rotated_img)
    
    contours, _ = cv2.findContours(rotated_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if len(contours) > 0:
        if len(contours[0]) > 4:
            ellipse = cv2.fitEllipse(contours[0])
            if ellipse[1][0] > 0 and ellipse[1][1] > 0:
                THETA = np.arccos(ellipse[1][0] / ellipse[1][1])
                FI = ellipse[2] * np.pi / 180
                my_len = ellipse[1][1] / np.cos(FI)
                my_angle = np.arccos(my_len / ellipse[1][1]) * np.pi / 180
                print(my_angle)

                img_contours = np.zeros((rotated_img.shape[0], rotated_img.shape[1], 3), dtype=np.uint8)

                #calculate the offset from the center of the ellipse
                offset_minor_x = ellipse[1][0] / 2 * np.cos((90-ellipse[2]) * np.pi / 180)
                offset_minor_y = ellipse[1][0] / 2 * np.cos(ellipse[2] * np.pi / 180)
                offset_major_x = ellipse[1][1] / 2 * np.cos(ellipse[2] * np.pi / 180)
                offset_major_y = ellipse[1][1] / 2 * np.sin(ellipse[2] * np.pi / 180)

                #start and endpoints of the major and minor axes
                minor_end_x   = int(ellipse[0][0] + offset_minor_x)
                minor_end_y   = int(ellipse[0][0] + offset_minor_y)
                minor_start_x = int(ellipse[0][0] - offset_minor_x)
                minor_start_y = int(ellipse[0][0] - offset_minor_y)

                major_end_x   = int(ellipse[0][1] + offset_major_x)
                major_end_y   = int(ellipse[0][1] - offset_major_y)
                major_start_x = int(ellipse[0][1] - offset_major_x)
                major_start_y = int(ellipse[0][1] + offset_major_y)

                #start and endpoints of major and minor axes
                cv2.circle(img_contours, (minor_start_y, minor_start_x), 2, (0, 255, 0), -1)
                cv2.circle(img_contours, (minor_end_y,   minor_end_x),   2, (0, 0, 255), -1)
                cv2.circle(img_contours, (major_start_y, major_start_x), 2, (0, 255, 0), -1)
                cv2.circle(img_contours, (major_end_y,   major_end_x),   2, (0, 0, 255), -1)

                #draw major and minor axes
                cv2.line(img_contours, [minor_start_y, minor_start_x], [minor_end_y, minor_end_x], (0, 0, 255))
                cv2.line(img_contours, [major_start_y, major_start_x], [major_end_y, major_end_x], (0, 255, 0))

                cv2.ellipse(img_contours, ellipse, (255, 0, 255))

                cv2.putText(img_contours, 'THETA:{:.2f}'.format(THETA*180/np.pi), (100, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)

                cv2.imshow('circleImg', img_contours)

    if cv2.waitKey(5) & 0xFF == 27:
        cv2.destroyAllWindows()
        break

内容的提问来源于stack exchange,提问作者Valentine

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 10:54:03