5.23. Корекція перспективи

Попередження

Довільна матриця transform 3 на 3 підтримується лише на OpenMV Cam N6 – на будь-якій іншій платі ключове слово мовчки ігнорується. Програми, яким потрібно запускатися деінде, повинні використовувати стандартний метод rotation_corr() (у формі corners=) або попередньо обчислювати виправлене зображення поза платою.

Стандартний метод rotation_corr() реалізує певну родину перспективних деформацій за допомогою невеликого набору параметрів і працює на кожній підтримуваній платі. Деяким програмам потрібна деформація, яка не вписується у цю форму: довільне проєктивне перевідображення з одного чотирикутника в інший, відкалібрована корекція для відомого кріплення, що вже була розрахована заздалегідь, матриця деформації, готова до використання від якогось попереднього алгоритму. Для таких випадків draw_image() – разом із copy(), crop() та scale() – приймає ключове слово transform, яке бере вручну побудовану матрицю 3 на 3, що безпосередньо описує деформацію.

5.23.1. Афінні та проєктивні перетворення

Геометричні деформації виражаються в однорідних координатах: позиція пікселя (x, y) з доданою 1, помножена на матрицю 3 на 3.

Форма афінного перетворення є відправною точкою. Його нижній рядок фіксований на \((0, 0, 1)\):

\[\begin{split}\begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}\end{split}\]

Виписавши, кожна вихідна координата є лінійною комбінацією вхідних координат плюс константа:

\[x' = a x + b y + c, \qquad y' = d x + e y + f\]

що охоплює масштабування, обертання, зсув і трансляцію у будь-якому поєднанні – і при всіх цих перетвореннях паралельні лінії залишаються паралельними.

Форма проєктивного (перспективного) перетворення звільняє нижній рядок:

\[\begin{split}\begin{bmatrix} x'' \\ y'' \\ w' \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ g & h & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}, \qquad (x', y') = \left( \frac{x''}{w'}, \; \frac{y''}{w'} \right)\end{split}\]

Виписавши:

\[x' = \frac{a x + b y + c}{g x + h y + 1}, \qquad y' = \frac{d x + e y + f}{g x + h y + 1}\]

Ділення на \(w' = g x + h y + 1\) робить перетворення проєктивним, а не просто афінним. Коли \(g\) та \(h\) обидва рівні нулю, \(w'\) залишається рівним одиниці і ділення нічого не змінює – це знову афінна форма. Коли будь-яке з них ненульове, \(w'\) змінюється залежно від вхідної позиції і пікселі в різних позиціях отримують різний ступінь перспективного стиснення, що вже не зберігає паралельності прямих ліній – це і є ефект трапецієподібного спотворення при погляді на плоску поверхню під косим кутом. Проєктивне перетворення є найбільш загальним геометричним викривленням, що переводить прямі лінії у прямі лінії; масштабування, відзеркалення, транспонування, обертання та корекція чотирьох кутів – усе це окремі випадки одного перетворення.

Іменовані перетворення безпосередньо випливають з афінної форми. Тотожне перетворення – це одинична матриця, а:

\[\begin{split}\underbrace{\begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{bmatrix}}_{\text{translate by } (t_x, \; t_y)} \qquad \underbrace{\begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{bmatrix}}_{\text{scale by } (s_x, \; s_y)} \qquad \underbrace{\begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}}_{\text{rotate by } \theta}\end{split}\]

Для більшості вручну побудованих перетворень програма починає з однієї з цих матриць як основи і множить на подальші матриці для кожної додаткової операції, завершуючи єдиною матрицею 3 на 3, що описує складену деформацію. Матриці застосовуються справа наліво: \(M = T R S\) спочатку виконує масштабування, потім обертання, а потім трансляцію. Складена матриця, яка потрібна в кінцевому підсумку всім, – це обертання навколо центру зображення: проста матриця обертання обертає зображення навколо початку координат пікселів у верхньому лівому куті, тому відцентрована версія переміщує центр \((c_x, c_y)\) до початку координат, виконує обертання і переміщує назад:

\[\begin{split}M = \underbrace{\begin{bmatrix} 1 & 0 & c_x \\ 0 & 1 & c_y \\ 0 & 0 & 1 \end{bmatrix}}_{\text{move centre back}} \underbrace{\begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}}_{\text{rotate}} \underbrace{\begin{bmatrix} 1 & 0 & -c_x \\ 0 & 1 & -c_y \\ 0 & 0 & 1 \end{bmatrix}}_{\text{move centre to origin}}\end{split}\]

5.23.2. Ключове слово transform

Матриця передається через ключове слово transform, що задається як масив ulab.numpy.ndarray розміром 3 на 3. Метод, який слід використовувати, – draw_image(), що деформує джерело через матрицю під час малювання на цільове зображення – результат потрапляє до буфера, яким керує програма, а деформація поєднується з усім іншим у виклику: масштабуванням, альфа-змішуванням, маскуванням.

import ulab.numpy as np

M = np.array([[1.2,  0.0, -20.0],
              [0.0,  1.2, -15.0],
              [0.0,  0.0,   1.0]])

canvas.draw_image(img, transform=M)

Приклад деформує img на canvas з масштабуванням 1.2 у кожному напрямку та зсувом ліворуч і вгору на 20 та 15 пікселів відповідно – афінна деформація, побудована безпосередньо з елементів матриці, описаних вище. Те саме ключове слово для copy(), crop() та scale() застосовує деформацію до самого зображення.