我目前正在研究openCV的'aruco'模块,特别关注ArUco标记和AprilTags的poseEstimation。
查看亚像素精度,我遇到了一个奇怪的行为,下面的代码证明了这一点:如果我确实提供了“完美”校准(例如 cx/cy 等于图像中心并且失真设置为零)和“完美”具有已知边长的标记,如果旋转为 0/90/180 或 270 度,cv.detectMarkers 只会产生正确的值。子像素例程为其他方向产生(几乎)恒定值,但处于“偏移”水平。很明显,在 0/90/180/270 度的特定角度,角落中的像素会产生急剧的过渡,因此可以高精度检测。然而,我很难看出在所有其他情况下被低估的长度是从哪里来的。这是一个错误还是由某些三角函数引起的?--> 看下面脚本生成的图表:姿态误差是由角点检测误差引起的。因此,检测精度将取决于代码的方向。
我还检查了 ArUco 标记和不同的亚像素化方法。尽管两者之间的角度行为会改变,但“峰值”仍然存在。
我很确定,这不是由于与标记旋转相关的插值,因为我也可以在实际数据中观察到相同的行为(但请注意,峰的“高度”似乎在某种程度上取决于插值方法。您可以通过将 cv.warpAffine 中的标志更改为 cv.INTER_LINEAR 来进行测试。
我的问题是:
- 峰值是由于错误还是这是预期的行为?
- 如果是后者,你能帮我理解为什么吗?
- 有没有办法消除精度的这种方向依赖性(除了增加系统的分辨率,不需要亚像素)?
编辑:请注意,AprilTag 功能最近才添加到 openCV,因此您需要升级到某些标准存储库中尚不可用的最新版本。例如,您可以在 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 an 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)")