我目前正在研究openCV的'aruco'模块,特别关注ArUco标记和AprilTags的poseEstimation。
在研究子像素精度时,我遇到了一种奇怪的行为,如下代码所示:
如果我确实提供了“完美”校准(例如,cx/cy 等于图像中心并且失真设置为零)和具有已知边缘长度的“完美”标记,则 cv.detectMarkers 仅在旋转位于0/90/180 或 270 度。子像素例程为其他方向生成一个(几乎)恒定值,但处于“移位”级别。很明显,在 0/90/180/270 度的特定角度下,角落中的像素会产生急剧的过渡,因此可以高精度检测。然而,我很难找出所有其他情况下被低估的长度的根源。这是一个错误还是由某些三角函数造成的?
--> 查看下面脚本生成的图表: 位姿错误是由角点检测错误造成的。因此,检测精度将取决于代码的方向。
我还检查了 ArUco 标记和不同的子像素化方法。尽管“峰”之间的角度行为会发生变化,但“峰”仍然存在。
我非常确定,这不是由于与标记旋转相关的插值,因为我也可以在实际数据中观察到相同的行为(但请注意,峰值的“高度”似乎在某种程度上取决于插值方法。您可以通过更改 cv.warpAffine 中的标志(例如更改为 cv.INTER_LINEAR)来测试此方法。
我的问题是:
- 峰值是由于错误造成的还是这是预期的行为?
- 如果是后者,你能帮我理解为什么吗?
- 有没有办法消除这种精度的方向依赖性(除了提高系统的分辨率,不需要子像素化)?
编辑:请注意,AprilTag 函数最近才添加到 openCV 中,因此您需要升级到某些标准存储库尚未提供的最新版本。你可以。 G。在 conda-forge 上获取最新版本。 /编辑
# -*- coding: utf-8 -*-
import numpy as np
import cv2 as cv
import pylab as plt
""" generate an "ideal" calibration with zero distortion and perfect alignment
of the main optical axis: """
cam_matrix = np.array([[1.0e+04, 0.00000000e+00, 1.22400000e+03],
[0.00000000e+00, 1.0e+04, 1.02400000e+03],
[0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
dist_coeffs = np.array([[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.]])
# define detection parameters
marker_length = 30.00 # some arbitrary value
marker_length_px = 700
marker_id = 3
dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_APRILTAG_16H5)
para = cv.aruco.DetectorParameters_create()
para.cornerRefinementMethod = cv.aruco.CORNER_REFINE_APRILTAG
para.aprilTagDeglitch = 0
para.aprilTagMinWhiteBlackDiff = 30
para.aprilTagMaxLineFitMse = 20
para.aprilTagCriticalRad = 0.1745329201221466 *6
para.aprilTagMinClusterPixels = 5
para.maxErroneousBitsInBorderRate = 0.35
para.errorCorrectionRate = 1.0
para.minMarkerPerimeterRate = 0.05
para.maxMarkerPerimeterRate = 4
para.polygonalApproxAccuracyRate = 0.05
para.minCornerDistanceRate = 0.05
marker_length_list = []
tvec_z_list = []
# generate pictures (AprilTag ID: 3 centered in image will be rotated by fixed angular steps, e. g. 10 degrees)
degrees_list = np.linspace(0,350,36, dtype=np.int).tolist()
marker = cv.aruco.drawMarker(dictionary, marker_id, marker_length_px)
img = np.zeros((2048, 2448), np.uint8)+255
img[674:1374, 874:1574] = marker
cv.imshow("Original", img)
cv.imwrite("original.png", img)
rows, cols = img.shape
for entry in degrees_list:
# rotate original picture
rot_mat = cv.getRotationMatrix2D((((rows-1)/2),(cols-1)/2), entry, 1)
rot_img = cv.warpAffine(img, rot_mat, (cols, rows), flags=cv.INTER_CUBIC) # interpolation changes the "peak amplitude" e.g. try cv.INTER_LINEAR instead
# detect marker and get pose estimate
corners, ids, rejected = cv.aruco.detectMarkers(rot_img,dictionary,parameters=para)
my_index = ids.tolist().index([marker_id])
fCorners = corners[my_index]
fRvec,fTvec, _obj_points = cv.aruco.estimatePoseSingleMarkers(fCorners, marker_length, cam_matrix, dist_coeffs)
# calculate the respective edge length for each side
L1 = abs(np.sqrt(np.square(fCorners[0][0][0]-fCorners[0][1][0])+np.square(fCorners[0][0][1]-fCorners[0][1][1])))
L2 = abs(np.sqrt(np.square(fCorners[0][0][0]-fCorners[0][3][0])+np.square(fCorners[0][0][1]-fCorners[0][3][1])))
L3 = abs(np.sqrt(np.square(fCorners[0][2][0]-fCorners[0][1][0])+np.square(fCorners[0][2][1]-fCorners[0][1][1])))
L4 = abs(np.sqrt(np.square(fCorners[0][2][0]-fCorners[0][3][0])+np.square(fCorners[0][2][1]-fCorners[0][3][1])))
mean_length = (L1+L2+L3+L4)/4
# append results
marker_length_list. append(mean_length)
tvec_z_list.append(fTvec[0][0][2])
plt.figure("TVEC Z")
plt.plot(degrees_list, tvec_z_list, "go--")
plt.xlabel("marker rotation angle (°)")
plt.ylabel("TVEC Z (units of length)")
plt.figure("Mean marker length (should be 700)")
plt.plot(degrees_list, marker_length_list, "bo--")
plt.xlabel("marker rotation angle (°)")
plt.ylabel("marker length (pixels)")
EDIT2:根据 Christoph Rackwitz 的建议,以下是脚本生成的图片形式的输出:
Marker length: Should ideally be 700:
Marker distance: Should be invariant of rotation: