mlq移动最小二乘方法
Image Deformation Using Moving Least Squares
- 移动最小二乘的移动是指, 所有traindata 对 每个grid point的影响(weight)是不一样的。
- 文中介绍三种变换: 仿射,相似,刚体变换
- 对应的代码: https://github.com/Jarvis73/Moving-Least-Squares
https://github.com/search?q=Moving+Least+Squares
- 问题:
当有一些点聚在一起,会产生大的影响,即使 偏移很小。?
因此点应该均匀?
-
对于起始点固定的情况比较友好,可以提前计算出A,然后每次目标变化的时候A也是不变的。
-
注意边界
clip 应该到 h-1, w-1更好,而不是设为0
其次不取整,采用cv2.remap更好:下面通过光流warp 图像
def flow_warp(im1, im2, flow): """ :param im1: pre image :param im2: next image :param flow: optical flow ,im1 to im2, h, w, 2, 0:(x),1:(y) :return: """ h, w, c = im1.shape hh = np.arange(0, h) ww = np.arange(0, w) xx, yy = np.meshgrid(ww, hh) # tx = xx - flow[..., 0] # ty = yy - flow[..., 1] tx = flow[..., 0] ty = flow[..., 1] mask = ((tx < 0) + (ty < 0) + (tx > w - 1) + (ty > h - 1)) > 0 # print(mask.shape, mask.dtype, np.sum(mask)) # out = interp_linear(image, tx, ty) # warped im1, should compare with im2 out = cv2.remap(im1, tx.astype(np.float32), ty.astype(np.float32), interpolation=cv2.INTER_LINEAR) out[mask] = 0 return out
-
画网格:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from matplotlib.collections import LineCollection
def plot_grid(x,y, ax=None, **kwargs):
ax = ax or plt.gca()
segs1 = np.stack((x,y), axis=2)
segs2 = segs1.transpose(1,0,2)
ax.add_collection(LineCollection(segs1, **kwargs))
ax.add_collection(LineCollection(segs2, **kwargs))
ax.autoscale()
ax.invert_yaxis() # y轴反向
if __name__ == "__main__":
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
image = np.array(Image.open("images/toy.jpg"))
plt.figure()
ax = plt.subplot(121)
ax.imshow(cv2.resize(image, (100, 100)))
# h, w, c = 100, 100, 3
# hh, ww = np.arange(h), np.arange(w)
# xx, yy = np.meshgrid(ww, hh)
f = lambda x, y: (x + 6 * np.exp(-x ** 2 - y ** 2), y)
grid_x, grid_y = np.meshgrid(np.linspace(0, 100, 40), np.linspace(0, 100, 40))
plot_grid(grid_x, grid_y, color="lightgrey")
distx, disty = f(grid_x, grid_y)
plot_grid(distx, disty, color="C0")
ax.invert_yaxis() # y轴反向
plt.show()
- affine 我的实现
import time
import cv2
import numpy as np
import torch
from PIL import Image
from matplotlib import pyplot as plt
from torchvision.utils import flow_to_image
from img_utils import mls_affine_deformation
from plot_grid import plot_grid
def mls_affine():
image = np.array(Image.open("images/toy.jpg"))
height, width, _ = image.shape
gridX = np.arange(width, dtype=np.int16)
gridY = np.arange(height, dtype=np.int16)
xx, yy = np.meshgrid(gridX, gridY)
# print(xx[:4,:4], yy[:4,:4])
# affine = mls_affine_deformation(vy, vx, p, q, alpha=1)
eps = 1e-8
alpha = 2
q = np.array([
[155, 30], [155, 125], [155, 225],
[235, 100], [235, 160], [295, 85], [293, 180]
])
p = np.array([
[211, 42], [155, 125], [100, 235],
[235, 80], [235, 140], [295, 85], [295, 180]
])
n = len(p)
q = q.astype(np.float32).reshape(n, 1, 1, 2)
p = p.astype(np.float32).reshape(n, 1, 1, 2)
v = np.vstack((yy.reshape(1, height, width), xx.reshape(1, height, width))).astype(np.float32)
# v = np.dstack((yy, xx)).transpose(2, 0, 1) # python速度提升的方法, 第一个是对输入明确类型, 第二个是尽量不要用dstack
v = v.transpose(1, 2, 0).reshape(-1, height, width, 2)
print(p.shape, v.shape)
w = 1 / (np.sum((p.reshape(-1, 1, 1, 2) - v) ** alpha, axis=-1) + eps) # n, h, w
print(w.shape)
w_norm = w / np.sum(w, axis=0, keepdims=True) # n, h, w
p_star = np.sum(p.reshape(-1, 1, 1, 2) * w_norm.reshape(-1, height, width, 1), axis=0) # 1, h, w, 2
q_star = np.sum(q.reshape(-1, 1, 1, 2) * w_norm.reshape(-1, height, width, 1), axis=0) # 1, h, w, 2
p_hat = p.reshape(-1, 1, 1, 2) - p_star # n, h, w, 2
q_hat = q.reshape(-1, 1, 1, 2) - q_star # n, h, w, 2
A_1row = np.sum(p_hat[..., 0][..., None] * p_hat * w_norm[..., None], axis=0, keepdims=True) # 1, h, w, 2
A_2row = np.sum(p_hat[..., 1][..., None] * p_hat * w_norm[..., None], axis=0, keepdims=True) # 1, h, w, 2
b_1row = np.sum(p_hat[..., 0][..., None] * q_hat * w_norm[..., None], axis=0, keepdims=True) # 1, h, w, 2
b_2row = np.sum(p_hat[..., 1][..., None] * q_hat * w_norm[..., None], axis=0, keepdims=True) # 1, h, w, 2
A = np.transpose(np.hstack((A_1row, A_2row)), [1, 2, 0, 3]).reshape(height, width, 2, 2)
b = np.transpose(np.hstack((b_1row, b_2row)), [1, 2, 0, 3]).reshape(height, width, 2, 2)
print(A.shape, b.shape, A[:1,:1, :, :])
# a11 = A[..., 0, 0]
# a12 = A[..., 0, 1]
# a21 = A[..., 1, 0]
# a22 = A[..., 1, 1]
# A_det = a11 * a22 - a12 * a21
# A_inv = np.dstack((a22, -a12, -a21, a11)) / A_det[..., None]
# A_inv = A_inv.reshape(height, width, 2, 2)
A_inv = np.linalg.inv(A)
M = np.einsum("ijmk,ijkn->ijmn", A_inv, b)
x = v.reshape(height, width, 1, 2) - p_star.reshape(height, width, 1, 2)
flow = np.einsum("ijmk,ijkn->ijmn", x, M) + q_star.reshape(height, width, 1, 2)
flow = np.transpose(flow.reshape(height, width, 2), (2, 0, 1))
flow[flow < 0] = 0
flow[0][flow[0] > height - 1] = height - 1
flow[1][flow[1] > width - 1] = width - 1
return flow
def mls_affine2(xx, yy, p, q):
start = time.time()
height, width = xx.shape
eps = 1e-8
alpha = 2
# start = time.time()
n = len(p)
q = q.astype(np.float32).reshape(n, 2, 1, 1)
p = p.astype(np.float32).reshape(n, 2, 1, 1)
v = np.vstack((yy.reshape(1, height, width), xx.reshape(1, height, width))).astype(np.float32)
# v = np.dstack((yy, xx)).transpose(2, 0, 1) # python速度提升的方法, 第一个是对输入明确类型, 第二个是尽量不要用dstack
p = p.transpose(0, 2, 3, 1)
q = q.transpose(0, 2, 3, 1)
v = v.transpose(1, 2, 0).reshape(-1, height, width, 2)
print(p.shape, v.shape)
w = 1 / (np.sum((p.reshape(-1, 1, 1, 2) - v) ** alpha, axis=-1) + eps) # n, h, w
print(w.shape)
end = time.time()
print('run time:', end-start)
w_norm = w / np.sum(w, axis=0, keepdims=True) # n, h, w
p_star = np.sum(p.reshape(-1, 1, 1, 2) * w_norm.reshape(-1, height, width, 1), axis=0) # 1, h, w, 2
q_star = np.sum(q.reshape(-1, 1, 1, 2) * w_norm.reshape(-1, height, width, 1), axis=0) # 1, h, w, 2
p_hat = p.reshape(-1, 1, 1, 2) - p_star # n, h, w, 2
q_hat = q.reshape(-1, 1, 1, 2) - q_star # n, h, w, 2
end = time.time()
print('run time1:', end - start)
A_1row = np.sum(p_hat[..., 0][..., None] * p_hat * w_norm[..., None], axis=0, keepdims=True) # 1, h, w, 2
A_2row = np.sum(p_hat[..., 1][..., None] * p_hat * w_norm[..., None], axis=0, keepdims=True) # 1, h, w, 2
b_1row = np.sum(p_hat[..., 0][..., None] * q_hat * w_norm[..., None], axis=0, keepdims=True) # 1, h, w, 2
b_2row = np.sum(p_hat[..., 1][..., None] * q_hat * w_norm[..., None], axis=0, keepdims=True) # 1, h, w, 2
end = time.time()
print('run time2:', end - start)
A = np.transpose(np.hstack((A_1row, A_2row)), [1, 2, 0, 3]).reshape(height, width, 2, 2)
b = np.transpose(np.hstack((b_1row, b_2row)), [1, 2, 0, 3]).reshape(height, width, 2, 2)
print(A.shape, b.shape, A[:1,:1, :, :])
end = time.time()
print('run time3:', end - start)
# a11 = A[..., 0, 0]
# a12 = A[..., 0, 1]
# a21 = A[..., 1, 0]
# a22 = A[..., 1, 1]
# A_det = a11 * a22 - a12 * a21
# A_inv = np.dstack((a22, -a12, -a21, a11)) / A_det[..., None]
# A_inv = A_inv.reshape(height, width, 2, 2)
A_inv = np.linalg.inv(A)
M = np.einsum("ijmk,ijkn->ijmn", A_inv, b)
x = v.reshape(height, width, 1, 2) - p_star.reshape(height, width, 1, 2)
flow = np.einsum("ijmk,ijkn->ijmn", x, M) + q_star.reshape(height, width, 1, 2)
end = time.time()
print('run time4:', end - start)
flow = np.transpose(flow.reshape(height, width, 2), (2, 0, 1))
flow[flow < 0] = 0
flow[0][flow[0] > height - 1] = height - 1
flow[1][flow[1] > width - 1] = width - 1
end = time.time()
print('run time5:', end - start)
return flow.astype(np.int16)
def flow_warp(im1, im2, flow):
"""
:param im1: pre image
:param im2: next image
:param flow: optical flow ,im1 to im2, h, w, 2, 0:(x),1:(y)
:return:
"""
h, w, c = im1.shape
hh = np.arange(0, h)
ww = np.arange(0, w)
xx, yy = np.meshgrid(ww, hh)
# tx = xx - flow[..., 0]
# ty = yy - flow[..., 1]
tx = flow[..., 0]
ty = flow[..., 1]
mask = ((tx < 0) + (ty < 0) + (tx > w - 1) + (ty > h - 1)) > 0
# print(mask.shape, mask.dtype, np.sum(mask))
# out = interp_linear(image, tx, ty)
# warped im1, should compare with im2
out = cv2.remap(im1, tx.astype(np.float32), ty.astype(np.float32), interpolation=cv2.INTER_LINEAR)
out[mask] = 0
return out
def mls_affine_my(image, p, q):
'''
其实给定p和 q, 完整的flow就已经确定
Parameters
----------
image: h, w, c
p : n*2, (x, y)
q : n*2, (x, y)
Returns
-------
return flow:p->q, shape like image, h*w*2: (x, y)
'''
eps = 1e-8
height, width, _ = image.shape
gridX = np.arange(width, dtype=np.int16)
gridY = np.arange(height, dtype=np.int16)
xx, yy = np.meshgrid(gridX, gridY)
n = len(p)
q = q.astype(np.float32).reshape(n, 1, 1, 2)
p = p.astype(np.float32).reshape(n, 1, 1, 2)
v = np.dstack((xx, yy))
w = 1.0 / (np.sum((p - v) ** 2, axis=-1) + eps) ** 1
w_norm = w / np.sum(w, axis=0, keepdims=True)
p_star = np.sum(p * w_norm.reshape(-1, height, width, 1), axis=0) # 1, h, w, 2
q_star = np.sum(q * w_norm.reshape(-1, height, width, 1), axis=0) # 1, h, w, 2
p_hat = p - p_star # n, h, w, 2
q_hat = q - q_star # n, h, w, 2
print(p_hat.shape, q_hat.shape, w_norm.shape)
A_1row = np.sum(p_hat[..., 0][..., None] * p_hat * w_norm[..., None], axis=0) # 1, h, w, 2
A_2row = np.sum(p_hat[..., 1][..., None] * p_hat * w_norm[..., None], axis=0) # 1, h, w, 2
b_1row = np.sum(p_hat[..., 0][..., None] * q_hat * w_norm[..., None], axis=0) # 1, h, w, 2
b_2row = np.sum(p_hat[..., 1][..., None] * q_hat * w_norm[..., None], axis=0) # 1, h, w, 2
A = np.dstack((A_1row, A_2row)).reshape(height, width, 2, 2)
b = np.dstack((b_1row, b_2row)).reshape(height, width, 2, 2)
print(A.shape, b.shape, A[:1, :1, :, :])
# a11 = A[..., 0, 0]
# a12 = A[..., 0, 1]
# a21 = A[..., 1, 0]
# a22 = A[..., 1, 1]
# A_det = a11 * a22 - a12 * a21
# A_inv = np.dstack((a22, -a12, -a21, a11)) / A_det[..., None]
# A_inv = A_inv.reshape(height, width, 2, 2)
A_inv = np.linalg.inv(A)
M = np.einsum("ijmk,ijkn->ijmn", A_inv, b)
x = v.reshape(height, width, 1, 2) - p_star.reshape(height, width, 1, 2)
y = np.einsum("ijmk,ijkn->ijmn", x, M) + q_star.reshape(height, width, 1, 2)
flow = y.reshape(height, width, 2) - v
print(flow.shape, flow.dtype)
return flow
def load_flow_to_numpy(path):
with open(path, 'rb') as f:
magic = np.fromfile(f, np.float32, count=1)
assert (202021.25 == magic), 'Magic number incorrect. Invalid .flo file'
w = np.fromfile(f, np.int32, count=1)[0]
h = np.fromfile(f, np.int32, count=1)[0]
data = np.fromfile(f, np.float32, count=2 * w * h)
data2D = np.reshape(data, (h, w, 2))
# print(data2D[:10,:10,0])
return data2D
def flow_to_image_torch(flow):
flow = torch.from_numpy(np.transpose(flow, [2, 0, 1]).astype(np.float32))
flow_im = flow_to_image(flow)
img = np.transpose(flow_im.numpy(), [1, 2, 0])
#print(img.shape)
return img
"""
箭头图
"""
def draw_flow(im, flow, step=40, norm=1):
# 在间隔分开的像素采样点处绘制光流
h, w = im.shape[:2]
y, x = np.mgrid[step/2:h:step, step/2:w:step].reshape(2, -1).astype(int)
if norm:
fx, fy = flow[y, x].T / flow[y, x].max() * step // 2.00001
else:
fx, fy = flow[y, x].T # / flow[y, x].max() * step // 2
# 创建线的终点
lines = np.vstack([x, y, x+fx, y+fy]).T.reshape(-1, 2, 2)
lines = np.int32(lines)
# 创建图像并绘制
vis = im #cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)
for (x1, y1), (x2, y2) in lines:
cv2.line(vis, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.circle(vis, (x1, y1), 2, (0, 0, 255), -1)
return vis
if __name__ == "__main__":
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
image = np.array(Image.open("images/toy.jpg"))
#
# flow = mls_affine()
# c, h, w = flow.shape
# flow = np.transpose(flow, (1, 2, 0))[..., ::-1]
# out = flow_warp(image, image, flow)
# plt.figure()
# plt.imshow(np.hstack((image, out)))
# plt.show()
# print('dd')
q = np.array([
[155, 30], [155, 125], [155, 225],
[235, 100], [235, 160], [295, 85], [293, 180]
])
p = np.array([
[211, 42], [155, 125], [100, 235],
[235, 80], [235, 140], [295, 85], [295, 180]
])
q = q.reshape(-1, 2)[..., ::-1]
p = p.reshape(-1, 2)[..., ::-1]
h, w, c = image.shape
hh = range(h)
ww = range(w)
xx, yy = np.meshgrid(ww, hh)
flow = mls_affine_my(image, p, q)
flow_im = flow_to_image_torch(flow)
flow_im2 = draw_flow(image, flow, step=20, norm=1)
plt.figure()
plt.subplot(121)
plt.imshow(flow_im)
plt.subplot(122)
plt.imshow(flow_im2)
plt.show()
xy = np.dstack((xx, yy)).astype(np.float32)
newxy = xy + flow
out = flow_warp(image, image, newxy)
plt.figure()
plt.imshow(np.hstack((image, out)))
plt.show()
plt.figure()
xx = xx[::20, ::20]
yy = yy[::20, ::20]
plot_grid(xx, yy, color="lightgrey")
newxy = xy - flow
xx = newxy[..., 0]
yy = newxy[..., 1]
xx = xx[::20, ::20]
yy = yy[::20, ::20]
plot_grid(xx, yy, color="C0")
plt.show()
# aug2 = np.ones_like(image)
# aug2[yy, xx] = image[tuple(flow[..., ::-1])]
# plt.figure()
# plt.imshow(np.hstack((image, aug2)))
# plt.show()
# 我的版本 和 他不一样?
xx, yy = np.meshgrid(range(5), range(5))
p1 = np.array([[1, 1], [3, 0.9], [3, 1.1], [3, 1], [3.1, 1], [2.9, 1], [3,1], [3,1], [3,1]])
p2 = np.array([[1, 1], [4, 0.9], [4, 1.1], [4, 1], [4.1, 1], [3.9, 1], [4,1], [4,1], [4,1]]) # 0.5
# p1 = np.array([[1, 1], [3, 0.9], [3, 1.1], [3, 1]])
# p2 = np.array([[1, 1], [4, 0.9], [4, 1.1], [4, 1]]) # 0.5
# p1 = np.array([[1, 1], [3, 0.9], [3, 1.1]]) # 0.5
# p2 = np.array([[1, 1], [4, 0.9], [4, 1.1]])
# p1 = np.array([[1, 1], [3.0, 0.9]])
# p2 = np.array([[1, 1], [4.0, 1.1]]) # -1.04504478
flow = mls_affine_my(xx[..., None], p1, p2)
print(flow[1, 2, 0], flow[..., 0])
flow = mls_affine_deformation(xx, yy, p2[...,::-1], p1[...,::-1])# 2,h,w (r,c)
print(flow.shape)
flow = np.transpose(flow, (1,2,0))[..., ::-1] - np.dstack((xx, yy))
print('ss2:', flow[1, 2, 0], flow[..., 0])
正确性待验证,包括仓库里的代码正确性也不保证。
-
关于weight 是否有更好的方式,来应对其他应用
更好的weight计算方法,比如考虑图像内容差异。双边weight等。
-
就是一些特殊情况,边界情况,求逆有问题的情况等需要考虑
-
最好阅读原文Image Deformation Using Moving Least Squares,其他一些blog也可以参考
https://blog.csdn.net/u011426016/article/details/125243631
https://blog.csdn.net/DIAJEY/article/details/114322764 -
可以用在曲面拟合上, 3Dlut拟合, 光流图,深度图拟合优化等
比如光流:
import numpy as np
import scipy.optimize
import torch
from scipy import sparse
from matplotlib import pyplot as plt
from scipy.optimize import minimize
from scipy.sparse import csc_matrix
from torchvision.utils import flow_to_image
from tqdm import tqdm
from movelq import mls_affine_deformation
def generate_data(pic=False):
s = np.random.uniform(0, 1, [24, 2])
t = np.random.uniform(0, 1, [24])
print(s)
print(t, t* np.arange(24))
t = t* np.arange(24)
monotonic_e = False
if monotonic_e:
s = np.sort(s, axis=0)
t = np.sort(t, axis=0)
t[:6] = t[6:12][::-1]
# 拟合spline
z1 = np.polyfit(s[..., 0], t, 4)
p1 = np.poly1d(z1)
z2 = np.polyfit(s[..., 1], t, 4)
p2 = np.poly1d(z2)
xx = np.linspace(0, 1, 1000)
z1 = p1(xx)
z2 = p2(xx)
if pic:
# 画散点图
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(s[..., 0], s[..., 1], t, label="random points")
ax.plot(xx, np.zeros_like(xx), z1, label=' x spline')
ax.plot(np.zeros_like(xx), xx, z2, label=' y spline')
ax.set_xlabel('X label') # 画出坐标轴
ax.set_ylabel('Y label')
ax.set_zlabel('Z label')
ax.legend()
plt.show()
# merge spline
h = xx
w = xx
ww, hh = np.meshgrid(w, h)
z = p1(ww) + p2(hh)
z = 100 * (z - z.min()) / z.max()
if pic:
# 画散点图
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(s[..., 0], s[..., 1], t, label="random points")
ax.plot(xx, np.zeros_like(xx), z1 , label=' x spline')
ax.plot(np.zeros_like(xx), xx, z2 , label=' y spline')
ax.plot_surface(ww, hh, z, cmap='rainbow')
ax.set_xlabel('X label') # 画出坐标轴
ax.set_ylabel('Y label')
ax.set_zlabel('Z label')
ax.legend()
plt.show()
return ww, hh, z
def flow_to_image_torch(flow):
flow = torch.from_numpy(np.transpose(flow, [2, 0, 1]).astype(np.float32))
flow_im = flow_to_image(flow)
img = np.transpose(flow_im.numpy(), [1, 2, 0])
#print(img.shape)
return img
def generate_flow():
def load_flow_to_numpy(path):
with open(path, 'rb') as f:
magic = np.fromfile(f, np.float32, count=1)
assert (202021.25 == magic), 'Magic number incorrect. Invalid .flo file'
w = np.fromfile(f, np.int32, count=1)[0]
h = np.fromfile(f, np.int32, count=1)[0]
data = np.fromfile(f, np.float32, count=2 * w * h)
data2D = np.reshape(data, (h, w, 2))
# print(data2D[:10,:10,0])
return data2D
file = r'C:\Users\rp\source\repos\dis_flow\dis_flow\frame_0001.flo'
flow_gt = load_flow_to_numpy(file)
return flow_gt
if __name__ == "__main__":
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
flow_gt = generate_flow()
print(flow_gt.shape, flow_gt.dtype, flow_gt.min(), flow_gt.max())
flow_gt_im = flow_to_image_torch(flow_gt)
plt.figure()
plt.imshow(flow_gt_im)
#plt.show()
# 计算 match point
h, w, c = flow_gt.shape
hh = np.arange(0, h)
ww = np.arange(0, w)
xx, yy = np.meshgrid(ww, hh)
tx = xx + flow_gt[..., 0]
ty = yy + flow_gt[..., 1]
pp = np.hstack((yy.reshape(-1, 1), xx.reshape(-1, 1)))
qq = np.hstack((ty.reshape(-1, 1), tx.reshape(-1, 1)))
print(pp[100:105, :], qq[100:105, :])
train_index = np.random.randint(0, len(pp), 2127)
p = pp[train_index]
q = qq[train_index]
ret = mls_affine_deformation(xx, yy, p, q, alpha=1.0, eps=1e-8) # 2, h, w
ret = ret.transpose(1, 2, 0) # h, w, 2. 先r后c
flow = np.dstack((yy, xx)) - ret
flow = flow[..., ::-1]
print('dd', flow_gt[100:105,100:105, 0], flow[100:105,100:105, 0])
print(flow_gt[100:105, 100:105, 1], flow[100:105, 100:105, 1])
pic = 1
if pic:
# 画散点图
fig = plt.figure()
ax1 = fig.gca(projection='3d')
ax1.scatter(p[..., 0], p[..., 1], q[..., 0]-p[..., 0], )
ax1.scatter(p[..., 0], p[..., 1], q[..., 1]-p[..., 1], )
z = ret.reshape(-1, 2)[train_index]
ax2 = fig.gca(projection='3d')
ax2.scatter(p[..., 0], p[..., 1], p[..., 0] - z[..., 0], marker='+', c='r')
ax2.scatter(p[..., 0], p[..., 1], p[..., 1] - z[..., 1], marker='+', c='k')
plt.show()
flow_im = flow_to_image_torch(flow.astype(np.float32))
plt.figure()
plt.imshow(flow_im)
plt.show()