我正在使用 OpenCV Contrib 4.10.0 版本检测 charuco 板。一旦我检测并估计了它的姿态,我就会尝试绘制轴;但是,如果我改变它们的长度,即使一切保持不变,我也可以看到不同的轴。我提供了示例图像、相机参数和要重现的代码。也看看附加的图像。
import os
import numpy as np
import matplotlib.pyplot as plt
import cv2
# camera params after calibration
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)
# board info
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)
# detect markers and charuco corners
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, mtx2, dist2, None, None)
# start plotting
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)
# plot
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)
编辑:将示例图片更改为未裁剪的版本。
在使用 OpenCV 的
drawFrameAxes
函数时观察到的行为是由于函数的工作原理以及计算机图形中透视投影的本质造成的。让我解释一下:
-
透视投影: 当使用
drawFrameAxes
在 2D 图像上绘制 3D 轴时,OpenCV 会使用透视投影来创建深度错觉。在这种投影中,远离相机的物体看起来更小,即使它们在 3D 空间中大小相同。 -
drawFrameAxes
的工作原理: 此函数获取相机姿态(旋转和平移)、相机矩阵、畸变系数和 轴长度 作为输入。然后,它沿着提供的相机姿态计算 3D 空间中表示轴端点的点的坐标。接下来,它将这些 3D 点投影到 2D 图像平面,并绘制连接原点和投影点的线。
现在,这就是问题所在:提供的 轴长度 用于在 3D 空间 中定义轴的端点。但是,由于透视投影,在 2D 图像平面 上投影的轴的长度将根据相机姿态和轴相对于相机的位置而变化。
在的示例中,当使用
axis_size = 0.1
时,在 3D 空间中绘制的轴实际上非常小。当投影到 2D 图像平面时,由于透视投影,它们看起来甚至更小,尤其是当相机距离棋盘较远时。
另一方面,当使用
axis_size = 5
时,在 3D 空间中绘制的轴要大得多。当投影到 2D 图像平面时,即使在透视投影之后,它们看起来也更大。
为了获得更一致的结果,可以在 3D 空间(世界坐标)中定义一个固定长度的轴,然后将其投影到图像上。 这可以通过以下方式完成:
import numpy as np
import cv2
# ...(现有的代码)...
# 定义世界坐标中轴的长度
axis_length_world = 0.1
# 定义 3D 空间中的轴端点
axis_ends_world = np.array(
[
[axis_length_world, 0, 0],
[0, axis_length_world, 0],
[0, 0, axis_length_world],
]
)
# 将旋转和平移向量转换为 4x4 变换矩阵
transformation_matrix = np.hstack((cv2.Rodrigues(rvec)[0], tvec))
transformation_matrix = np.vstack((transformation_matrix, np.array([0, 0, 0, 1])))
# 将轴端点从世界坐标转换为相机坐标
axis_ends_camera = transformation_matrix @ np.hstack(
(axis_ends_world, np.ones((3, 1)))
).transpose()
# 将 3D 点投影到 2D 图像平面
projected_points, _ = cv2.projectPoints(
axis_ends_camera[:3, :].transpose(), rvec, tvec, mtx, dist
)
# 在图像上绘制轴
cv2.line(
img_rgb,
tuple(map(int, projected_points[0][0])),
tuple(map(int, projected_points[1][0])),
(255, 0, 0),
2,
)
cv2.line(
img_rgb,
tuple(map(int, projected_points[0][0])),
tuple(map(int, projected_points[2][0])),
(0, 255, 0),
2,
)
cv2.line(
img_rgb,
tuple(map(int, projected_points[0][0])),
tuple(map(int, projected_points[3][0])),
(0, 0, 255),
2,
)
# ...(现有的代码的其余部分)...
这将确保无论相机姿态和位置如何,轴在图像上的长度都保持一致。
标签:python,opencv,computer-vision,camera-calibration,aruco From: 78808361