Понимание детального определения / определения маркера openCV aruco: субпиксельная точность - PullRequest
0 голосов
/ 18 февраля 2020

В настоящее время я изучаю модуль openCV 'aruco', уделяя особое внимание оценке маркеров ArUco и AprilTags.

Изучая субпиксельную точность, я столкнулся со странным поведением, которое демонстрирует приведенный ниже код: если я даю «идеальную» калибровку (например, cx / cy равно центру изображения, а искажение установлено на ноль) ) и «идеальный» маркер с известной длиной ребра, cv.detectMarkers даст только правильное значение, если угол поворота составляет 0/90/180 или 270 градусов. Подпиксельная подпрограмма дает (почти) постоянное значение для других ориентаций, но на «сдвинутом» уровне. Ясно, что при заданных c углах 0/90/180/270 градусов пиксель в углу дает резкий переход и, таким образом, может быть обнаружен с высокой точностью. Однако я изо всех сил пытаюсь понять, откуда исходит недооцененная длина во всех других случаях. Это ошибка или следствие какой-то тригонометрии? -> Посмотрите на графики, сгенерированные сценарием ниже: Ошибка в позе является результатом ошибки в определении угла. Таким образом, точность обнаружения будет зависеть от ориентации кода.

Я также проверил маркеры ArUco и различные методы субпикселов. «Пики» остаются, хотя поведение между angular изменится.

Я почти уверен, что это не связано с интерполяцией, связанной с вращением маркера, поскольку я могу наблюдать такое же поведение и в реальных данных (но учтите, что "высота" пиков кажется, как-то зависит от метода интерполяции. Вы можете проверить это, изменив флаг в cv.warpAffine, например, на cv.INTER_LINEAR).

Тогда мои вопросы будут такими:

  1. Являются ли пики из-за ошибки или это ожидаемое поведение?
  2. Если последнее, не могли бы вы помочь мне понять, почему?
  3. Есть ли способ устранить эту зависимость точности ориентации от точности (кроме увеличения разрешения системы таким образом, что не требуется субпиксель)?

РЕДАКТИРОВАТЬ: обратите внимание, что функции 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)")
...