Обратное или обратимое отображение ndimage.map_coordinates на основе планарной гомографии

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

import numpy as np

from scipy.ndimage import map_coordinates

from matplotlib import pyplot as plt

def make_rotation_matrix(abg, radians=False):
    ABG = np.zeros(3)
    ABG[:len(abg)] = abg
    abg = ABG
    if not radians:
        abg = np.radians(abg)

    alpha, beta, gamma = abg
    cos1 = np.cos(alpha)
    cos2 = np.cos(beta)
    cos3 = np.cos(gamma)
    sin1 = np.sin(alpha)
    sin2 = np.sin(beta)
    sin3 = np.sin(gamma)
    Rx = np.asarray([
        [1,    0,  0   ],  # NOQA
        [0, cos1, -sin1],
        [0, sin1,  cos1]
    ])
    Ry = np.asarray([
        [cos2,  0, sin2],
        [    0, 1,    0],  # NOQA
        [-sin2, 0, cos2],
    ])
    Rz = np.asarray([
        [cos3, -sin3, 0],
        [sin3,  cos3, 0],
        [0,        0, 1],
    ])
    m = Rz@Ry@Rx
    return m


# draw a square in an image
sfe = np.zeros((128,128), dtype=float)
c=64
w=32
sfe[c-w:c+w,c-w:c+w] = 1

# compute the coordinates, translate to the origin, rotate, translate back
xin = np.arange(128)
yin = np.arange(128)
xin, yin = np.meshgrid(xin,yin)

rot = make_rotation_matrix((0,45,0))

ox, oy = 127/2, 127/2
tin = np.eye(4)
tin[0,-1] = -ox
tin[1,-1] = -oy

tout = np.eye(4)
tout[0,-1] = ox
tout[1,-1] = oy

rot2 = np.zeros((4,4), dtype=float)
rot2[:3,:3] = rot
rot2[-1,-1] = 1

M = tout@(rot2@tin)
Mi = np.linalg.inv(M)

points = np.zeros((xin.size, 4), dtype=float)
points[:,0] = xin.ravel()
points[:,1] = yin.ravel()
points[:,2] = 0  # z=0
points[:,3] = 1 # lambda coordinate for homography

out = np.dot(Mi, points.T)

xout = out[0].reshape(xin.shape)
yout = out[1].reshape(yin.shape)
zout = out[2].reshape(xin.shape)
hout = out[3].reshape(xin.shape)

# do I need to do something differently here?
points2 = points.copy()
out2 = np.dot(M, points2.T)

xout2 = out2[0].reshape(xin.shape)
yout2 = out2[1].reshape(yin.shape)
zout2 = out2[2].reshape(xin.shape)
hout2 = out2[3].reshape(xin.shape)

mapped = map_coordinates(sfe, (yout,xout))
unmapped = map_coordinates(mapped, (yout2,xout2))
neighbors = np.hstack((sfe, mapped, unmapped))
plt.imshow(neighbors)

Если я выполняю тактовое вращение вместо вращения вне плоскости, я получаю ожидаемое поведение:

Я понимаю, что по построению я предполагаю, что изображение представляет собой вид с высоты птичьего полета или плоскую гомографию, что нормально. Что мне не хватает? Некоторые Google, связанные с деформацией изображений, находят загадочные ответы Matlab, но я не понимаю, что делает «пространственная привязка».

Обновлено: пример гомографии, инверсия которой фактически не отменяет преобразование с помощью map_coordinates:

H = np.array([[ 0.063, -0.011,  0.761],
       [ 0.011,  0.063, -0.639],
       [-0.   , -0.   ,  0.063]])

Просто рисуя квадрат с plot.scatter, он точно инвертирует.

это омография, да? тогда inv(H) — это отображение, которое отменяет H. Возможно, я упускаю суть

Christoph Rackwitz 17.04.2023 15:42

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

Brandon Dube 17.04.2023 17:54

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

Christoph Rackwitz 17.04.2023 23:28

Пример довольно минимален, как есть. Я не согласен с вашим педантичным утверждением, что сюжеты изображений «не являются самими изображениями». Может быть некоторое растяжение или подобное от неравных осей, но из-за того, как сделано представление, это будет синфазно и тонко, когда будет показано серьезное различие. Если вы имеете в виду цветовую ось, это тоже обычный режим и, самое большее, отвлечение. Несмотря на это, я добавил к вопросу гомографические матрицы.

Brandon Dube 22.04.2023 00:09
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я провел рефакторинг вашего кода, чтобы посмотреть, что происходит.

Во-первых, map_coordinates() выполняет «вытягивание», то есть втягивает исходные пиксели в результирующую сетку, используя предоставленные вами индексы. Эти индексы должны быть сгенерированы с использованием обычной сетки (для результата) и обратного преобразования (для исходного кадра). Вот почему кажется, что ваш квадрат расширяется, а не сжимается.

Затем... удаление Z имеет значение, и где вы отбрасываете "Z" (входы/выходы для преобразования 4x4), особенно при инвертировании преобразования.

Учитывая вращение вне плоскости, скажем вокруг Y, вы получите что-то вроде этого:

[[ 0.70711  0.       0.70711  0.     ]
 [ 0.       1.       0.       0.     ]
 [-0.70711  0.       0.70711  0.     ]
 [ 0.       0.       0.       1.     ]]

Обратное этому:

[[ 0.70711  0.      -0.70711  0.     ]
 [ 0.       1.       0.       0.     ]
 [ 0.70711  0.       0.70711  0.     ]
 [ 0.       0.       0.       1.     ]]

Если вы поместите Z в оба (и примените это к 2D-данным, которые у вас есть), вы теперь получите пару преобразований, которые оба сжимают изображение:

[[0.70711 0.      0.     ]
 [0.      1.      0.     ]
 [0.      0.      1.     ]]

[[0.70711 0.      0.     ]
 [0.      1.      0.     ]
 [0.      0.      1.     ]]

(В вашем случае это каждый раз вызывает расширение из-за map_coordinates() и его операции «вытягивания»)

Сжатие — это появление вращения точек в плоскости, но это не вращение. Удаление Z не поддерживает inv(M) @ M == I.

Повернутые точки, вывернутые из своей плоскости, имеют ненулевое Z, что важно, когда вы хотите повернуть их дальше (например, повернуть назад). Удаление Z означает, что у вас больше нет этой информации. Вы должны принять их положение в пространстве, а трансформация в 2D вместо этого должна сжиматься или растягиваться, в зависимости от того, из какой плоскости вы предполагаете, что они пришли и куда им нужно идти.

Вы должны сначала перенести Z в M (4x4), а затем инвертировать полученную матрицу 3x3. Теперь у вас есть правильная инверсия, которая расширяет изображение, что приводит к тождественному преобразованию.

[[0.70711 0.      0.     ]
 [0.      1.      0.     ]
 [0.      0.      1.     ]]

[[1.41421 0.      0.     ]
 [0.      1.      0.     ]
 [0.      0.      1.     ]]

Теперь немного кода:

def translate4(tx=0, ty=0, tz=0):
    T = np.eye(4)
    T[0:3, 3] = (tx, ty, tz)
    return T

def rotate4(rx=0, ry=0, rz=0):
    R = np.eye(4)
    R[0:3, 0:3] = make_rotation_matrix((rx, ry, rz))
    return R

def dropZ(T4):
    "assumes that XYZW inputs have Z=0 and that the result's Z will be ignored"
    tmp = T4[[0,1,3], :]
    tmp = tmp[:, [0,1,3]]
    return tmp
# input data. don't mind the use of OpenCV. I wasn't in the mood to come up with random() calls to give the square some texture.
im_source = cv.imread(cv.samples.findFile("lena.jpg"), cv.IMREAD_GRAYSCALE)
height, width = im_source.shape[:2]
# transformation: rotate around center
cx, cy = (width-1)/2, (height-1)/2
Tin = translate4(-cx, -cy)
Tout = translate4(+cx, +cy)
R = rotate4(ry=45, rz=30) # with a little in-plane rotation
M = Tout @ R @ Tin
M3 = dropZ(M)
Mi3 = inv(M3)
#print(M3)
#print(Mi3)
# coordinates grid
xin = np.arange(width)
yin = np.arange(height)
xin, yin = np.meshgrid(xin, yin)
zin = np.zeros_like(xin)
win = np.ones_like(xin)

points4 = np.vstack((xin.flatten(), yin.flatten(), zin.flatten(), win.flatten()))
print(points4)

points3 = np.vstack((xin.flatten(), yin.flatten(), win.flatten()))
print(points3)
# always: transform inverted because map_coords is backwards/pull
# can't invert right at the map_coords() call because we've already warped the grid by then

points_warped = inv(M3) @ points3 # apply M3 to identity grid, for input image
print("warped:")
print(points_warped)

points_identity = M3 @ points_warped # apply inv(M3) to warped grid, giving identity grid
# it's equal to M3 @ inv(M3) @ points3
# which is I (identity) @ points3
print("unwarped: (identity grid)")
print(points_identity)

points_unwarping = M3 @ points3 # apply inv(M3) to identity grid, suitable for unwarping *warped* image

# map_coordinates() wants indices, so Y,X or I,J
coords_warped = points_warped.reshape((3, height, width))[[1,0]]
coords_identity = points_identity.reshape((3, height, width))[[1,0]]
coords_unwarping = points_unwarping.reshape((3, height, width))[[1,0]]

im_warped = map_coordinates(im_source, coords_warped)
im_identity = map_coordinates(im_source, coords_identity)
im_unwarped = map_coordinates(im_warped, coords_unwarping)

neighbors = np.hstack((im_source, im_warped, im_identity, im_unwarped))
#neighbors = np.hstack((im1, im2, im3))
plt.figure(figsize=(20,20))
plt.imshow(neighbors, cmap='gray')
plt.show()

К счастью, это все линейно (не нелинейно) и inv(M) @ M == I == M @ inv(M).

Другие вопросы по теме