В настоящее время я изучаю модуль openCV 'aruco', уделяя особое внимание оценке маркеров ArUco и AprilTags.
Изучая субпиксельную точность, я столкнулся со странным поведением, которое демонстрирует приведенный ниже код: если я даю «идеальную» калибровку (например, cx / cy равно центру изображения, а искажение установлено на ноль) ) и «идеальный» маркер с известной длиной ребра, cv.detectMarkers даст только правильное значение, если угол поворота составляет 0/90/180 или 270 градусов. Подпиксельная подпрограмма дает (почти) постоянное значение для других ориентаций, но на «сдвинутом» уровне. Ясно, что при заданных c углах 0/90/180/270 градусов пиксель в углу дает резкий переход и, таким образом, может быть обнаружен с высокой точностью. Однако я изо всех сил пытаюсь понять, откуда исходит недооцененная длина во всех других случаях. Это ошибка или следствие какой-то тригонометрии? -> Посмотрите на графики, сгенерированные сценарием ниже: Ошибка в позе является результатом ошибки в определении угла. Таким образом, точность обнаружения будет зависеть от ориентации кода.
Я также проверил маркеры ArUco и различные методы субпикселов. «Пики» остаются, хотя поведение между angular изменится.
Я почти уверен, что это не связано с интерполяцией, связанной с вращением маркера, поскольку я могу наблюдать такое же поведение и в реальных данных (но учтите, что "высота" пиков кажется, как-то зависит от метода интерполяции. Вы можете проверить это, изменив флаг в 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 arbitraty 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)")