Гомография в примерах (Homography in examples)

Автор: | 17.03.2021

Tags: Homography examples rectification матрица

Введение 
Матрица гомографии
Вычисление матрицы гомографии и генерация обновленного изображения по заданным точкам
Автоматическая коррекция искажений с использованием угловых детекторов и гомографии
Полезные ссылки

Введение

Гомография — перспективное преобразование плоскости.

Применение:

  • Выравнивание изображений (rectification)

  • Построение панорам (panorama stitching)

  • Определение положения камеры по точкам объекта и их проекциям (camera pose estimation)
  • Двухкамерное сопоставление. 2-view solvers (2d-2d correspondences) 

Матрица гомографии

Гомография — это частный случай модели фундаментальных проективных преобразований, когда объекты сцены находятся в плоскостях (координата z равна 0). Учитывая эту особенность, из композиции матриц фундаментальных проективных преобразований  получаем матрицу гомографии.

Из матричной записи получаем уравнения относительно u и v:   

Результат не изменится, если числитель и знаменатель в обоих уравнениях разделим на h22 (t23). Тогда в уравнениях h22=1, остальные параметры нормализуются по отношению к h22.

После этого из 9 параметров матрицы гомографии остаются только 8  параметров (параметр h22=1).

Для их определения достаточно  задать 4 соответствующие точки в плоскостях и решить систему из 8 уравнений. В OpenCV матрица гомографии вычисляется по вершинам заданного четырехугольника   с помощью функции cv2.findHomography.

Вычисление матрицы гомографии и генерация обновленного изображения по заданным точкам

Пример №1. Исходное  изображение (image1.png) преобразуется матрицей гомографии, которая определяется соответствующими вершинами квадрата и четырехугольника.

import cv2
import numpy as np
img1_square_corners = np.float32([[253,211], [563,211], [563,519],[253,519]])
img2_quad_corners = np.float32([[234,197], [520,169], [715,483], [81,472]])
h, mask = cv2.findHomography(img1_square_corners, img2_quad_corners)
print(' mask:\n',mask)
print(' h:\n',h)
print(' h0:\n',h[0])
print(' h00:\n',h[0,0])
print(' h1:\n',h[1])
im = cv2.imread("image1.png")
out = cv2.warpPerspective(im, h, (800,800))
cv2.imwrite("result.png", out)

Результаты работы программы:

 

Гомография определяется 4 точками — вершинами квадрата img1_square_corners на первом изображении  и вершинами четырехугольника img2_quad_corners на втором изображении.  По 4 угловым точкам фигур вычисляется матрица гомографии с помощью функции cv2.findHomography. Затем используется функция cv2.warpPerspective для генерации обновленного изображения.

Пример №2. На исходном изображении шахматной доски (chess.png) указываете мышкой 4 вершины 4-хугольника, квадрата или  прямоугольника. По ним вычисляется матрица гомографии, на основе которой генерируется исправленное (или искаженное) изображение.

import cv2
import numpy as np

pSrc = [( 98,67),( 331 , 75),( 415 , 469),( 27 , 466)]
pDst = [( 27,67),( 415 , 75),( 415 , 469),( 27 , 466)]

def srcMouse(event, x, y, flags,params):
    global pSrc
    if event == cv2.EVENT_LBUTTONDOWN:
        if len(pSrc) >=4:
            pSrc=[]
        pSrc.append((x,y))
        print (np.array(pSrc,dtype=np.float32))

def dstMouse(event, x, y, flags,params):
    global pDst
    if event == cv2.EVENT_LBUTTONDOWN:
        if len(pDst) >=4:
            pDst=[]
        pDst.append((x,y))

cv2.namedWindow('src')
cv2.setMouseCallback('src', srcMouse, 0)
cv2.namedWindow('dst')
cv2.setMouseCallback('dst', dstMouse, 0)
im = cv2.imread('./data/chess.png')
dst = np.zeros(im.shape,dtype=np.uint8)
while(1):
    imD = im.copy()
    dstD = dst.copy()
    for p in pSrc:
        cv2.circle(imD,p,2,(255,0,0),-1)
    for p in pDst:
        cv2.circle(dstD,p,2,(255,0,0),-1)
    if len(pSrc)==4 and len(pDst)==4:
        H = cv2.findHomography(np.array(pSrc,dtype=np.float32),np.array(pDst,dtype=np.float32),cv2.LMEDS)
        dstD=cv2.warpPerspective(imD,H[0],(dstD.shape[1],dstD.shape[0]))
    cv2.imshow('src',imD)
    cv2.imshow('dst',dstD)
    if cv2.waitKey(1) ==27:
        exit(0)

Результаты работы программы:

На результат влияют и пропорции соответствующего четырехугольника. На последних 2-х рисунках вместо квадрата был задан прямоугольник.

Автоматическая коррекция искажений с использованием угловых детекторов и гомографии

Пример №1. Исходное изображение  четырехугольника (1.jpg) корректируется и обрезается под прямоугольник.

Матрица гомографии определяется через сопоставление углов четырехугольника с соответствующими углами прямоугольника. Углы четырехугольника находятся с помощью углового детектора Shi-Tomasi. Подробное описание программы см. в первоисточнике

import cv2
import numpy as np
import matplotlib.pyplot as plt

def shi_tomashi(image):
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    corners = cv2.goodFeaturesToTrack(gray, 4, 0.01, 100)
    corners = np.int0(corners)
    print('The unsorted points are:')
    print(corners)
    corners = sorted(np.concatenate(corners).tolist())
    corners=np.array(corners)
    print('\nThe sorted points are:')
    print(corners)
    print('\nThe source points are:') 
    im = image.copy()
    for index, c in enumerate(corners):
        x, y = c
        cv2.circle(im, (x, y), 3, 255, -1)
        character = chr(65 + index)
        print(character, ':', c)
        cv2.putText(im, character, tuple(c), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2, cv2.LINE_AA)
    plt.imshow(im)
    plt.title('Corner Detection: Shi-Tomashi')
    plt.show()
    return corners

def get_destination_points(corners):
    w1 = np.sqrt((corners[0][0] - corners[1][0]) ** 2 + (corners[0][1] - corners[1][1]) ** 2)
    w2 = np.sqrt((corners[2][0] - corners[3][0]) ** 2 + (corners[2][1] - corners[3][1]) ** 2)
    w = max(int(w1), int(w2))
    h1 = np.sqrt((corners[0][0] - corners[2][0]) ** 2 + (corners[0][1] - corners[2][1]) ** 2)
    h2 = np.sqrt((corners[1][0] - corners[3][0]) ** 2 + (corners[1][1] - corners[3][1]) ** 2)
    h = max(int(h1), int(h2))
    destination_corners = np.float32([(0, 0), (w - 1, 0), (0, h - 1), (w - 1, h - 1)])
    print('\nThe destination points are:')
    for index, c in enumerate(destination_corners):
        character = chr(65 + index) + "'"
        print(character, ':', c) 
    print('\nThe approximated height and width of the original image is: \n', (h, w))
    return destination_corners, h, w

def unwarp(img, src, dst):
    h, w = img.shape[:2]
    H, _ = cv2.findHomography(src, dst, method=cv2.RANSAC, ransacReprojThreshold=3.0)
    #H, _ = cv2.findHomography(src, dst)
    print('\nThe homography matrix is: \n', H)
    un_warped = cv2.warpPerspective(img, H, (w, h), flags=cv2.INTER_LINEAR)
    # plot
    f, (ax1, ax2) = plt.subplots(1, 2)
    ax1.imshow(img)
    x = [src[0][0], src[2][0], src[3][0], src[1][0], src[0][0]]
    y = [src[0][1], src[2][1], src[3][1], src[1][1], src[0][1]]
    ax1.plot(x, y, color='yellow', linewidth=3)
    ax1.set_ylim([h, 0])
    ax1.set_xlim([0, w])
    ax1.set_title('Targeted Area in Original Image')
    ax2.imshow(un_warped)
    ax2.set_title('Unwarped Image')
    plt.show()
    return un_warped

image = cv2.imread('./images/1.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
src = shi_tomashi(image)
dst, h, w = get_destination_points(src)
un_warped = unwarp(image, src, dst)
cropped = un_warped[0:h, 0:w]
plt.imshow(cropped)
plt.show()

Пример №2. В отличие от предыдущего примера задача решается для реального объекта  (notebook.png).

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

import cv2
import numpy as np
import matplotlib.pyplot as plt

def apply_filter(image):
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    kernel = np.ones((5, 5), np.float32) / 15
    filtered = cv2.filter2D(gray, -1, kernel)
    plt.imshow(cv2.cvtColor(filtered, cv2.COLOR_BGR2RGB))
    plt.title('Filtered Image')
    plt.show()
    return filtered

def apply_threshold(filtered):
    ret, thresh = cv2.threshold(filtered, 254, 255, cv2.THRESH_OTSU)
    plt.imshow(cv2.cvtColor(thresh, cv2.COLOR_BGR2RGB))
    plt.title('After applying OTSU threshold')
    plt.show()
    return thresh

def detect_contour(img, image_shape):
    canvas = np.zeros(image_shape, np.uint8)
    contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    cnt = sorted(contours, key=cv2.contourArea, reverse=True)[0]
    cv2.drawContours(canvas, cnt, -1, (255, 255, 255), 3)
    plt.title('Largest Contour')
    plt.imshow(canvas)
    plt.show()
    return canvas, cnt

def detect_corners_from_contour(canvas, cnt):
    epsilon = 0.02 * cv2.arcLength(cnt, True)
    approx_corners = cv2.approxPolyDP(cnt, epsilon, True)
    cv2.drawContours(canvas, approx_corners, -1, (255, 255, 0), 10)
    approx_corners = sorted(np.concatenate(approx_corners).tolist())
    print('approx_corners', approx_corners)
    # Rearranging the order of the corner points
    # 0, 2, 3, 1    1, 2, 3, 0
    approx_corners = [approx_corners[i] for i in [0, 2, 3, 1]]
    approx_corners=np.array(approx_corners)

    print('\nThe corner points are ...\n')
    for index, c in enumerate(approx_corners):
        character = chr(65 + index)
        print(character, ':', c)
        cv2.putText(canvas, character, tuple(c), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2, cv2.LINE_AA)
    plt.imshow(canvas)
    plt.title('Corner Points')
    plt.show()
    return approx_corners

def get_destination_points(img):
    h, w = img.shape[:2]
    destination_corners = np.float32([(0, 0), (w - 1, 0), (w - 1, h - 1),(0, h - 1), ])
    print('\nThe destination points are:')
    for index, c in enumerate(destination_corners):
        character = chr(65 + index) + "'"
        print(character, ':', c) 
    print('\nThe approximated height and width of the original image is: \n', (h, w))
    return destination_corners, h, w

def unwarp(img, src, dst):
    h, w = img.shape[:2]
    H, _ = cv2.findHomography(src, dst, method=cv2.RANSAC, ransacReprojThreshold=3.0)
    #H, _ = cv2.findHomography(src, dst)
    print('\nThe homography matrix is: \n', H)
    un_warped = cv2.warpPerspective(img, H, (w, h), flags=cv2.INTER_LINEAR)
    # plot
    f, (ax1, ax2) = plt.subplots(1, 2)
    ax1.imshow(img)
    x = [src[0][0], src[1][0], src[2][0], src[3][0], src[0][0]]
    y = [src[0][1], src[1][1], src[2][1], src[3][1], src[0][1]]
    ax1.plot(x, y, color='yellow', linewidth=3)
    ax1.set_ylim([h, 0])
    ax1.set_xlim([0, w])
    ax1.set_title('Targeted Area in Original Image')
    ax2.imshow(un_warped)
    ax2.set_title('Unwarped Image')
    plt.show()
    return un_warped


# chess.png   11.png   51.png   4.png
image = cv2.imread('images/notebook.png')
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
plt.imshow(image)
plt.title('Original Image')
plt.show()
#image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
filtered = apply_filter(image)
thresh = apply_threshold(filtered)
canvas, cnt = detect_contour(thresh, thresh.shape)
approx_corners = detect_corners_from_contour(canvas, cnt)
dst, h, w = get_destination_points(image)
un_warped = unwarp(image, approx_corners, dst)
cropped = un_warped[0:h, 0:w]
plt.imshow(cropped)
plt.show()


 

 

Полезные ссылки: