Как переназначить или вернуть точку в прежнюю систему координат после того, как warpAffine преобразовал ее? - PullRequest
3 голосов
/ 16 января 2020

Я использую Template Matching (TM), чтобы найти местоположение всех M на изображении (первое изображение слева), но у меня возникли проблемы переназначение местоположения сопоставленной точки (которая относится к местоположению внутри повернутой области интереса) обратно к исходному изображению:

enter image description here

Проблема заключается в том, что я нужно отменить (отменить) преобразование warpAffine в этой точке, и мои вычисления не идеальны, как вы можете видеть на самом правом изображении выше с оранжевыми прямоугольниками.

Я уже посмотрел во все сообщения в SO, относящиеся к этой топике c, но ни одна из них не помогла, поскольку операция, которую я пытаюсь выполнить, немного сложнее:

Простыми словами, что делает это приложение?

  1. Он начинается с загрузки изображений: исходное изображение и шаблон ;
  2. Он создает 8 областей интереса с необходимыми углами поворота. Позже угол поворота используется для исправления ориентации буквы М, чтобы она оставалась горизонтальной, а для ТМ " выглядит довольно ";
  3. A l oop повторяется для каждой области интереса в списке : выбирает область интереса, поворачивает ее, используя rotate_bound(), а затем выполняет TM для нее.;
  4. Когда операция TM успешна и находит букву, она затем пытается переназначить точку, которая определяет местоположение совпадения от повернутых координат ROI до в исходной ROI, которые затем можно использовать для указания правильного местоположения совпадения внутри исходного изображения.

Кажется, основная проблема заключается в отмене всех операций, которые определены в матрице вращения, созданной rotate_bound(). Кстати, если вы никогда не слышали об этой функции, вот вам хороший справочник .

Как я могу исправить вычисление переназначения?

Вот Короткое, Автономное, Правильное (Компилируемое), Пример :

import cv2
import numpy as np

# rotate_bound: helper function that rotates the image adds some padding to avoid cutting off parts of it
# reference: https://www.pyimagesearch.com/2017/01/02/rotate-images-correctly-with-opencv-and-python/
def rotate_bound(image, angle):
    # grab the dimensions of the image and then determine the center
    (h, w) = image.shape[:2]
    (cX, cY) = (w // 2, h // 2)

    # grab the rotation matrix (applying the negative of the angle to rotate clockwise), then grab the sine and cosine
    # (i.e., the rotation components of the matrix)
    M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])

    # compute the new bounding dimensions of the image
    nW = int(np.multiply(h, sin) + np.multiply(w, cos))
    nH = int(np.multiply(h, cos) + np.multiply(w, sin))

    # adjust the rotation matrix to take into account translation
    M[0, 2] += (nW / 2) - cX
    M[1, 2] += (nH / 2) - cY

    # perform rotation and return the image (white background) along with the Rotation Matrix
    return cv2.warpAffine(image, M, (nW, nH), borderValue=(255,255,255)), M


# Step 1 - Load images
input_img = cv2.imread("target.png", cv2.IMREAD_GRAYSCALE)
template_img = cv2.imread("template.png", cv2.IMREAD_GRAYSCALE)
matches_dbg_img = cv2.cvtColor(input_img, cv2.COLOR_GRAY2BGR) # for debugging purposes

# Step 2 - Generate some ROIs
# each ROI contains the x,y,w,h and angle (degree) to rotate the box and make its M appear horizontal
roi_w = 26
roi_h = 26

roi_list = []
roi_list.append((112, 7, roi_w, roi_h, 0))
roi_list.append((192, 36, roi_w, roi_h, -45))
roi_list.append((227, 104, roi_w, roi_h, -90))
roi_list.append((195, 183, roi_w, roi_h, -135))
roi_list.append((118, 216, roi_w, roi_h, -180))
roi_list.append((49, 196, roi_w, roi_h, -225))
roi_list.append((10, 114, roi_w, roi_h, -270))
roi_list.append((36, 41, roi_w, roi_h, -315))

# debug: draw green ROIs
rois_dbg_img = cv2.cvtColor(input_img, cv2.COLOR_GRAY2BGR)
for roi in roi_list:
    x, y, w, h, angle = roi
    x2 = x + w
    y2 = y + h
    cv2.rectangle(rois_dbg_img, (x, y), (x2, y2), (0,255,0), 2)

cv2.imwrite('target_rois.png', rois_dbg_img)
cv2.imshow('ROIs', rois_dbg_img)
cv2.waitKey(0)
cv2.destroyWindow('ROIs')


# Step 3 - Select a ROI, crop and rotate it, then perform Template Matching
for i, roi in enumerate(roi_list):
    x, y, w, h, angle = roi
    roi_cropped = input_img[y:y+h, x:x+w]
    roi_rotated, M = rotate_bound(roi_cropped, angle)

    # debug: display each rotated ROI
    #cv2.imshow('ROIs-cropped-rotated', roi_rotated)
    #cv2.waitKey(0)

    # debug: dump roi to the disk (before/after rotation)
    filename = 'target_roi' + str(i)
    cv2.imwrite(filename + '.png', roi_cropped)
    cv2.imwrite(filename + '_rotated.png', roi_rotated)

    # perform template matching
    res = cv2.matchTemplate(roi_rotated, template_img, cv2.TM_CCOEFF_NORMED)
    (_, score, _, (pos_x, pos_y)) = cv2.minMaxLoc(res)
    print('TM score=', score)

    # Step 4 - When a TM is found, revert the rotation of matched point so that it represents a location in the original image
    # Note: pos_x and pos_y define the location of the matched template in a rotated ROI
    threshold = 0.75
    if (score >= threshold):

        # debug in cropped image
        print('find_k_symbol: FOUND pos_x=', pos_x, 'pos_y=', pos_y, 'w=', template_img.shape[1], 'h=', template_img.shape[0])
        rot_output_roi = cv2.cvtColor(roi_rotated, cv2.COLOR_GRAY2BGR)
        cv2.rectangle(rot_output_roi, (pos_x, pos_y), (pos_x + template_img.shape[1], pos_y + template_img.shape[0]), (0, 165, 255), 2) # orange
        cv2.imshow('rot-matched-template', rot_output_roi)
        cv2.waitKey(0)
        cv2.destroyWindow('rot-matched-template')

        ###
        # How to convert the location of the matched template (pos_x, pos_y) to points in roi_cropped?
        # (which is the ROI before rotation)
        ###

        # extract variables from the rotation matrix
        M_x = M[0][2]
        M_y = M[1][2]
        #print('M_x=', M_x, '\tM_y=', M_y)
        M_cosx = M[0][0]
        M_msinx = M[0][1]
        #print('M_cosx=', M_cosx, '\tM_msinx=', M_msinx)
        M_siny = M[1][0]
        M_cosy = M[1][1]
        #print('M_siny=', M_siny, '\tM_cosy=', M_cosy)

        # undo translation:
        dst1_x = pos_x - M_x
        dst1_y = pos_y - M_y

        # undo rotation:
        # after this operation, (new_pos_x, new_pos_y) should already be a valid point in the original ROI
        new_pos_x =  M_cosx * dst1_x - M_msinx * dst1_y
        new_pos_y = -M_siny * dst1_x + M_cosy  * dst1_y

        # debug: create the bounding rect of the detected symbol in the original input image
        detected_x = x + int(new_pos_x)
        detected_y = y + int(new_pos_y)
        detected_w = template_img.shape[1]
        detected_h = template_img.shape[0]
        detected_rect = (detected_x, detected_y, detected_w, detected_h)

        print('find_k_symbol: detected_x=', detected_x, 'detected_y=', detected_y, 'detected_w=', detected_w, 'detected_h=', detected_h)
        print()

        cv2.rectangle(matches_dbg_img, (detected_x, detected_y), (detected_x + detected_w, detected_y + detected_h), (0, 165, 255), 2) # orange
        cv2.imwrite('target_matches.png', matches_dbg_img)
        cv2.imshow('matches', matches_dbg_img)
        cv2.waitKey(0)

Еще раз, вот изображения, необходимые для запуска приложения: исходное изображение и шаблон изображения .

1 Ответ

3 голосов
/ 16 января 2020

Вы были почти там - все, чего не хватает, - это повернуть прямоугольник ограничительной рамки вокруг его верхнего левого угла на известный угол, а затем нарисовать этот повернутый прямоугольник.

С cv2.rectangle только доктор aws прямоугольники вверх-вправо, нам нужна альтернатива. Одним из вариантов является представление прямоугольника в виде списка его угловых точек (для согласованности, скажем, по часовой стрелке, начиная с верхнего левого угла). Затем мы можем нарисовать его в виде замкнутой ломаной линии, проходящей через эти 4 точки, используя cv2.polylines.


Чтобы повернуть прямоугольник, нам нужно применить преобразование геометрии c на всех его угловых точках. Для этого сначала получаем матрицу преобразования, используя cv2.getRotationMatrix2D.

. Преобразуем угловые точки в однородные координаты и вычисляем скалярное произведение матрицы преобразования с транспонированным массивом координаты.

Для удобства (чтобы каждая точка находилась в одной строке), мы транспонируем результат.

# Rotate rectangle defined by (x,y,w,h) around its top left corner (x,y) by given angle
def rotate_rectangle(x, y, w, h, angle):
    # Generate homogenous coordinates of the corners
    # Start top left, go clockwise
    corners = np.array([
        (x, y, 1)
        , (x + w, y, 1)
        , (x + w, y + h, 1)
        , (x, y + h, 1)
    ], np.int32)
    # Create rotation matrix to transform the coordinates
    m_rot = cv2.getRotationMatrix2D((x, y), angle, 1.0)
    # Apply transformation
    rotated_points = np.dot(m_rot, corners.T).T
    return rotated_points

Теперь вместо вызова cv2.rectangle сначала определим углы повернутой ограничительной рамки:

rot_points = rotate_rectangle(detected_x, detected_y, detected_w, detected_h, angle)

Так как cv2.polylines требует целочисленных координат, мы округляем значения и преобразуем тип данных массива:

rot_points = np.round(rot_points).astype(np.int32)

И, наконец, проведите замкнутую ломаную линию через 4 угловых точки:

cv2.polylines(matches_dbg_img, [rot_points], True, (0, 165, 255), 2)

Result image

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...