Предположим, у меня есть изображение, которое я хочу деформировать как часть прямой модели системы. В обратной модели мне нужно иметь возможность отменить деформацию. Рассмотрим следующее:
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, он точно инвертирует.
Трехмерное плоское вращение является гомографией, но инверсия H не отменяет поворот, за исключением синхронизации. Если я смогу найти, например, соответствие между четырьмя углами квадрата, тогда я смогу вычислить новую гомографию для обратной проекции.
если инверсия не может инвертировать это, происходит нечто большее. минимальный воспроизводимый пример пожалуйста, такой, чтобы было легко обнаружить сбой в "обходе" данных через преобразование и его обратное. избегать «заговора». это может искажать, и люди часто не осознают, что сюжеты изображений сами по себе не являются изображениями.
Пример довольно минимален, как есть. Я не согласен с вашим педантичным утверждением, что сюжеты изображений «не являются самими изображениями». Может быть некоторое растяжение или подобное от неравных осей, но из-за того, как сделано представление, это будет синфазно и тонко, когда будет показано серьезное различие. Если вы имеете в виду цветовую ось, это тоже обычный режим и, самое большее, отвлечение. Несмотря на это, я добавил к вопросу гомографические матрицы.
Я провел рефакторинг вашего кода, чтобы посмотреть, что происходит.
Во-первых, 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)
.
это омография, да? тогда inv(H) — это отображение, которое отменяет H. Возможно, я упускаю суть