Преобразование изображения в вид сверху

В этой заметке будет рассмотрено преобразование изображения в вид сверху с помощью матрицы гомографии. В жаргоне компьютерного зрения вид сверху называется вид с высоты птичьего полета (bird eye view).

Что такое матрица гомографии ?

Работа с планарными (плоскими) объектами — часто возникающая задача в компьютерном зрении. Преобразования для планарных объектов называются гомографией.

К примеру есть плоский объект, расположенный в одной плоскости, который имеет точки \(q1\), \(q2\), \(q3\), \(q4\). Мы снимаем объект в одном положении с фиксированной камерой, после чего снимаем этот же объект в другом положении с той же камеры. Точка \(q1 \) представляет одну и ту же точку на двух разных изображениях, также как и точки \(q2\), \(q3\) и \(q4\). В компьютерном зрении такие точки называются соответствующими точками. Также можно говорить, если мы снимаем неподвижный объект камерой с одной позиции, после чего с другой.

Оказывается, что координаты точек на первом и втором изображении будут связаны преобразованием гомографии:

\[x_i’ = \frac{h_{11}x_i + h_{12}y_i + h_{13}}{h_{31}x_i + h_{32}y_i + h_{33}} \\
y_i’ = \frac{h_{21}x_i + h_{22}y_i + h_{23}}{h_{31}x_i + h_{32}y_i + h_{33}} \]

Выведем это преобразование:

Рассмотрим проекцию на первом изображении и проекцию на втором изображении. Во втором случае у нас другая проекционная матрица, потому что поза другая, соответственно точка тоже будет другая. Мы хотим найти преобразование между пикселями на изображении. Идея получения этого преобразования состоит в следующем: так как объект плоский, то мы можем выбрать такую систему координат, в которой начало координат расположено где-то на этой плоскости, ось \(Z\) будет направлена по нормали к этой плоскости. И как-то направлены оси \(X\), \(Y\) . В этой системе координат будут новые проекционные матрицы, которые всегда можно пересчитать. Главное, что координаты нашего объекта теперь вместо \((X,Y,Z,1)\) будут \((X, Y, 0, 1)\). При умножении такого вектора \((X, Y, 0, 1)\) на проекционную матрицу размерности \(3×4\), то третий столбец будет все время умножаться на ноль. Поэтому на плоскости в качестве проекционной матрицы мы можем использовать матрицу \(P\) без третьего столбца и умножать ее на вектор \((X, Y, 1)\). Тоже самое верно для второго изображения. Теперь новые проекционные матрицы \(P1′\) и \(P2′\) имеют размерность \(3×3\), дальнейшее просто. Мы можем слева умножить на обратную матрицу, получим, что

\[ \begin{bmatrix} w_i x_i′ \\ w_i y_i′ \\ w_i \end{bmatrix} = H \begin{bmatrix} x_i \\ y_i \\ 1 \end{bmatrix}, \]

где \(H\) матрица гомографии размерностью \(3×3\)

Таким образом, если мы знаем координаты первого изображения на плоскости, то существует такая матрица размером \(3×3\), с помощью которой можно найти координаты на втором изображении, используя формулу приведенную выше. Результатом такого преобразования получается вектор в однородных координатах. Для перехода от однородных координат к мировым нужно результирующий вектор поделить на \(w_i\).

Выравнивание изображения с помощью матрицы гомографии

Рассмотрим 2 изображения, показанных на рисунке 1. На данном рисунке можно увидеть 4 соответствующие точки, помеченные разными цветами: синим, зеленым, черным и красным. Обычно соответствующие точки для выравнивания изображения обнаруживаются автоматически путем сопоставления между изображениями таких функций, как SIFT или SURF, но в данном разделе такие точки мы выделяли вручную.

Рассмотрим код на языке Python с использованием библиотек OpenCV и Numpy в котором указаны исходные и конечные точки на двух изображениях.

import cv2
import numpy as np

# Read source image.
im_src = cv2.imread('book1.jpg')
# Four corners of the book in source image
pts_src = np.float32([[1016, 1856], [1944, 1927], [1917, 2148], [998, 2081]])

# Read destination image.
im_dst = cv2.imread('book2.jpg')
# Four corners of the book in destination image.
pts_dst = np.float32([[1410, 1437], [2004, 2009], [1846, 2149], [1268, 1572]])<br />

Рисунок 1: Два различных изображения одной и той же книги

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

Для расчета матрицы гомографии будем использовать библиотеку OpenCV, которая надежно оценит гомографию изображения, используя 4 начальные
и 4 конечные точки.

# Calculate the homography
M = cv2.getPerspectiveTransform(pts_src, pts_dst)

# Warp source image to destination
im_out = cv2.warpPerspective(im_src, M, (im_src.shape[1], im_src.shape[0])

# Show output
cv2.imshow("Warped Source Image", im_out)
cv2.imwrite("book_bird_eye_view.jpg", im_out)

cv2.waitKey(0)

 

Рисунок 2: Выравнивание изображений с помощью матрицы гомографии

Вид сверху

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

Для удобства будем задавать точки кликом мыши на изображении. Выделять точки стоит с левого верхнего угла. Исходя из разрешения исходного изображения, размер книги возьмем равным 1500×2000 пикселей. Вы можете изменить размер объекта на ваше усмотрение.

def mouse_handler(event, x, y, flags, data):
    if event == cv2.EVENT_LBUTTONDOWN:
        cv2.circle(data['im'], (x, y), 25, (0, 0, 255), -1)
        cv2.imshow("Image", data['im'])
        if len(data['points']) < 4:
            data['points'].append([x, y])


def get_four_points(im):
    data = {}
    data['im'] = im.copy()
    data['points'] = []

    cv2.imshow("Image", im)
    cv2.setMouseCallback("Image", mouse_handler, data)
    cv2.waitKey(0)

    points = np.float32(data['points'])
    return points


if __name__ == '__main__':

    # Read in the image.
    im_src = cv2.imread('book1.jpg')

    # Show image and wait for 4 clicks.
    pts_src = get_four_points(im_src)

    # Book size
    size = (1500, 2000)

    # Destination coordinates located in the center of the image
    pts_dst = np.float32(
        [                    
            [im.shape[1]/2 - size[0]/2, im.shape[0]/2 - size[1]/2],
            [im.shape[1]/2 + size[0]/2, im.shape[0]/2 - size[1]/2],
            [im.shape[1]/2 + size[0]/2, im.shape[0]/2 + size[1]/2],
            [im.shape[1]/2 - size[0]/2, im.shape[0]/2 + size[1]/2]
         ]
    )

 

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

Рисунок 3: Преобразования изображения в вид сверху

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

Еще одним из самых интересных применений гомографии является создание панорам (сшивание двух изображений).

Полный код программы на языке Python

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

import cv2
import numpy as np


def mouse_handler(event, x, y, flags, data):
    if event == cv2.EVENT_LBUTTONDOWN:
        cv2.circle(data['im'], (x, y), 25, (0, 0, 255), -1)
        cv2.imshow("Image", data['im'])
        if len(data['points']) < 4:
            data['points'].append([x, y])

def get_four_points(im):
    data = {}
    data['im'] = im.copy()
    data['points'] = []

    cv2.imshow("Image", im)
    cv2.setMouseCallback("Image", mouse_handler, data)
    cv2.waitKey(0)

    points = np.float32(data['points'])
    return points

if __name__ == '__main__':

    # Read in the image.
    im_src = cv2.imread('book1.jpg')

    # Show image and wait for 4 clicks.
    pts_src = get_four_points(im_src)

    # Book size
    size = (1500, 2000)

    # Destination coordinates located in the center of the image
    pts_dst = np.float32(
        [
            [im_src.shape[1]/2 - size[0]/2, im_src.shape[0]/2 - size[1]/2],
            [im_src.shape[1]/2 + size[0]/2, im_src.shape[0]/2 - size[1]/2],
            [im_src.shape[1]/2 + size[0]/2, im_src.shape[0]/2 + size[1]/2],
            [im_src.shape[1]/2 - size[0]/2, im_src.shape[0]/2 + size[1]/2]
         ]
    )

    # Calculate the homography
    M = cv2.getPerspectiveTransform(pts_src, pts_dst)

    # Warp source image to destination
    im_out = cv2.warpPerspective(im_src, M, (im_src.shape[1], im_src.shape[0])

    # Show output
    cv2.imshow("Warped Source Image", im_out)
    cv2.imwrite("book_bird_eye_view.jpg", im_out)

    cv2.waitKey(0)