Что такое аффинное преобразование vs перспективное преобразование в OpenCV?
Аффинное преобразование сохраняет параллельность линий (поворот, масштаб, сдвиг) — матрица 2×3, 3 опорные точки. Перспективное искажает параллельность (проекция камеры) — матрица 3×3, 4 точки. warpAffine vs warpPerspective.
Геометрическая модель
Аффинное преобразование — линейная карта + трансляция. Оно сохраняет параллельность прямых, отношение площадей и коллинеарность точек. Формально: dst = M · [x, y, 1]ᵀ, где M — матрица 2×3. Для вычисления достаточно трёх соответствующих пар точек.
Перспективное (гомографическое) преобразование описывает проекцию плоскости на другую плоскость через точку наблюдения. Параллельные линии сходятся в «точке схода». Матрица — 3×3 (гомография), нормируется на w: [x', y', w'] = H · [x, y, 1]ᵀ, итоговые координаты (x'/w', y'/w'). Требует четыре пары точек.
API в OpenCV
import cv2 as cv
import numpy as np
img = cv.imread("document.jpg")
if img is None:
raise FileNotFoundError("document.jpg not found")
h, w = img.shape[:2]
# --- Аффинное ---
# Три исходные точки → три целевые точки
src_affine = np.float32([[50, 50], [200, 50], [50, 200]])
dst_affine = np.float32([[10, 80], [210, 20], [20, 230]])
M_affine = cv.getAffineTransform(src_affine, dst_affine)
warped_affine = cv.warpAffine(img, M_affine, (w, h))
# M_affine.shape == (2, 3)
print("Affine M shape:", M_affine.shape) # (2, 3)
# --- Перспективное ---
# Четыре угла листа на фото → прямоугольник
src_persp = np.float32([[120, 60], [480, 40], [500, 380], [100, 400]])
dst_persp = np.float32([[0, 0], [400, 0], [400, 300], [0, 300]])
M_persp = cv.getPerspectiveTransform(src_persp, dst_persp)
warped_persp = cv.warpPerspective(img, M_persp, (400, 300))
# M_persp.shape == (3, 3)
print("Perspective M shape:", M_persp.shape) # (3, 3)
# Обратное перспективное преобразование (unwarping)
M_inv = cv.getPerspectiveTransform(dst_persp, src_persp)
unwarped = cv.warpPerspective(warped_persp, M_inv, (w, h))
# Если точек много — используем findHomography с RANSAC
pts_src = np.float32([[120,60],[480,40],[500,380],[100,400],[300,200]])
pts_dst = np.float32([[0,0],[400,0],[400,300],[0,300],[200,150]])
H, mask = cv.findHomography(pts_src, pts_dst, cv.RANSAC, 5.0)
print("Inliers:", mask.sum()) # число точек без выбросов
Когда что выбирать
Аффинное подходит, если камера смотрит почти перпендикулярно, или нужно выровнять изображение после небольшого поворота/масштабирования (аугментация датасета, выравнивание сканов). Параллельность сохраняется — удобно для текста.
Перспективное нужно при съёмке документа под углом, выравнивании дорожных знаков или склейке панорам. Исправляет «трапецию» — классический use-case для OCR-пайплайнов и мобильных сканеров документов.
Для больших облаков точек с выбросами — cv.findHomography(..., cv.RANSAC) автоматически отбрасывает аномалии.
Dtype и shape
Оба warpAffine и warpPerspective возвращают массив той же формы (dsize[1], dsize[0], C) и того же dtype, что и входное изображение. Входной uint8 даёт выходной uint8; float32 — float32. По умолчанию незаполненные пиксели — нули (чёрный); можно передать borderMode и borderValue.
Подводные камни
- Перепутать порядок точек в
getPerspectiveTransform— источник и цель должны идти в одинаковом порядке (например, по часовой стрелке). Иначе получается «бабочка». - Аффинное вместо перспективного для фото документа под углом — объект будет выглядеть искажённым, параллельные края не совпадут.
- Целевой размер
dsize=(w, h)в OpenCV — это(width, height), а не(rows, cols). Перепутать легко, особенно если привык к NumPy. - Точность точек:
getAffineTransformиgetPerspectiveTransformтребуютnp.float32, неfloat64— иначеcv2.error. - Нет RANSAC в
getPerspectiveTransform: если точки получены автоматически (детектор углов, feature matching), используйтеfindHomographyсRANSACилиLMEDS. - Интерполяция по умолчанию —
INTER_LINEAR. Для бинарных масок используйтеINTER_NEAREST, иначе граница размоется. - Обратное преобразование:
warpPerspectiveприменяет обратный маппинг внутри, но если вам нужна явно обратная матрица — вызывайтеnp.linalg.inv(H)или меняйте местамиsrc/dstвgetPerspectiveTransform. - Накопленная ошибка при многократном применении аффинных преобразований: лучше скомпоновать матрицы через умножение, а не применять каждую по очереди.
Common mistakes
- Объяснять
affine vs perspectiveтолько синтаксисом без shape, dtype, состояния или режима выполнения. - Игнорировать leakage, воспроизводимость, пустые входы и скрытые копии данных.
- Не проверять production-симптомы: latency, память, ретраи, дрейф качества и несовпадение версий.
What the interviewer is testing
- Может ли связать
affine vs perspectiveс реальным контрактом входов и выходов. - Упоминает ли тесты, метрики, reproducibility и диагностику ошибок.
- Видит ли различие между demo-кодом в ноутбуке и production-пайплайном.