如何使用 OpenCV 的重映射功能?

How do I use OpenCV's remap function?

这是 remap() 最简单的可能测试用例:

import cv2
import numpy as np
inimg = np.arange(2*2).reshape(2,2).astype(np.float32)
inmap = np.array([[0,0],[0,1],[1,0],[1,1]]).astype(np.float32)
outmap = np.array([[10,10],[10,20],[20,10],[20,20]]).astype(np.float32)
outimg = cv2.remap(inimg,inmap,outmap,cv2.INTER_LINEAR)
print "inimg:",inimg
print "inmap:",inmap
print "outmap:",outmap
print "outimg:", outimg

这是输出:

inimg: [[ 0.  1.]
 [ 2.  3.]]
inmap: [[ 0.  0.]
 [ 0.  1.]
 [ 1.  0.]
 [ 1.  1.]]
outmap: [[ 10.  10.]
 [ 10.  20.]
 [ 20.  10.]
 [ 20.  20.]]
outimg: [[ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]]

如您所见,outimg 生成 0,0,而且它的形状甚至都不正确。我希望 20x20 或 10x10 的图像具有 0 到 3 范围内的插值。

我已经阅读了所有文档。它和 SO 上的每个人都说你输入了一个起点数组(一个地图),一个终点地图,然后 remap() 会将 img 中的所有值放入它们的新位置,插入任何空 space。我正在这样做,但它不起作用。为什么?大多数示例适用于 C++。 python 坏了吗?

这只是对文档的一个简单误解,我不怪你——我也花了一些时间才理解它。文档很清楚,但是这个函数可能不会按照你期望的方式工作;事实上,它的工作方向与我最初的预期相反

remap()不会做的是获取源图像的​​坐标、变换点,然后进行插值。 remap() 所做的是,对于目标图像中的每个像素,在源图像中查找它来自哪里,然后分配一个插值。它需要以这种方式工作,因为为了进行插值,它需要在每个像素处查看源图像周围的值。让我扩展一下(可能会重复一下,但不要误会)。

来自remap() docs

map1 – The first map of either (x,y) points or just x values having the type CV_16SC2 , CV_32FC1 , or CV_32FC2 . See convertMaps() for details on converting a floating point representation to fixed-point for speed.

map2 – The second map of y values having the type CV_16UC1 , CV_32FC1 , or none (empty map if map1 is (x,y) points), respectively.

map1 上的“第一个 地图......”的措辞可能会令人困惑。请记住,这些严格是图像映射位置的坐标 from...这些点被映射 from srcmap_x(x, y), map_y(x, y) 然后放入 dstx, y。它们应该与您要将它们变形 的图像形状相同。请注意文档中显示的等式:

dst(x,y) =  src(map_x(x,y),map_y(x,y))

此处 map_x(x, y) 正在查找 map_x x, y 给出的行和列。然后在这些像素处评估图像值。它在 src 中查找 x, y 的映射坐标,然后将该值分配给 dst 中的 x, y。如果你盯着它看的时间足够长,它就会开始变得有意义。在新目标图像的像素 (0, 0) 处,我查看 map_xmap_y,它们告诉我源图像中相应像素的位置,然后我可以分配一个内插值(0, 0) 通过查看源中的附近值在目标图像中。这就是 remap() 以这种方式工作的根本原因;它需要知道像素来自哪里才能让相邻像素进行插值。

小的、人为的例子

img = np.uint8(np.random.rand(8, 8)*255)
#array([[230,  45, 153, 233, 172, 153,  46,  29],
#       [172, 209, 186,  30, 197,  30, 251, 200],
#       [175, 253, 207,  71, 252,  60, 155, 124],
#       [114, 154, 121, 153, 159, 224, 146,  61],
#       [  6, 251, 253, 123, 200, 230,  36,  85],
#       [ 10, 215,  38,   5, 119,  87,   8, 249],
#       [  2,   2, 242, 119, 114,  98, 182, 219],
#       [168,  91, 224,  73, 159,  55, 254, 214]], dtype=uint8)

map_y = np.array([[0, 1], [2, 3]], dtype=np.float32)
map_x = np.array([[5, 6], [7, 10]], dtype=np.float32)
mapped_img = cv2.remap(img, map_x, map_y, cv2.INTER_LINEAR)
#array([[153, 251],
#       [124,   0]], dtype=uint8)

那么这里发生了什么?在这种情况下,最容易检查矩阵:

map_y
=====
0  1
2  3

map_x
=====
5  6
7  10

因此 (0, 0) 处的目标图像与 map_y(0, 0), map_x(0, 0) = 0, 5 处的源图像具有相同的值,并且第 0 行和第 5 列的源图像为 153。请注意,在目标图像中 mapped_img[0, 0] = 153。因为我的地图坐标是精确的整数,所以这里没有插值。我还包含了一个越界索引(map_x[1, 1] = 10,它比图像宽度大),并注意到当它越界时它只是被分配了值 0

完整用例示例

这是一个完整的代码示例,使用地面真实单应性,手动扭曲像素位置,然后使用 remap() 从变换点映射图像。注意这里我的单应变换true_dstsrc。因此,我制作了一组我想要的点,然后通过单应性变换计算这些点在源图像中的位置。然后remap()用于查找源图像中的那些点,并将它们映射到目标图像中。

import numpy as np
import cv2

# read images
true_dst = cv2.imread("img1.png")
src = cv2.imread("img2.png")

# ground truth homography from true_dst to src
H = np.array([
    [8.7976964e-01,   3.1245438e-01,  -3.9430589e+01],
    [-1.8389418e-01,   9.3847198e-01,   1.5315784e+02],
    [1.9641425e-04,  -1.6015275e-05,   1.0000000e+00]])

# create indices of the destination image and linearize them
h, w = true_dst.shape[:2]
indy, indx = np.indices((h, w), dtype=np.float32)
lin_homg_ind = np.array([indx.ravel(), indy.ravel(), np.ones_like(indx).ravel()])

# warp the coordinates of src to those of true_dst
map_ind = H.dot(lin_homg_ind)
map_x, map_y = map_ind[:-1]/map_ind[-1]  # ensure homogeneity
map_x = map_x.reshape(h, w).astype(np.float32)
map_y = map_y.reshape(h, w).astype(np.float32)

# remap!
dst = cv2.remap(src, map_x, map_y, cv2.INTER_LINEAR)
blended = cv2.addWeighted(true_dst, 0.5, dst, 0.5, 0)
cv2.imshow('blended.png', blended)
cv2.waitKey()

来自 Visual Geometry Group at Oxford.

的图像和地面真实单应性
warped = cv.warpPerspective(img, H, (width, height))

等同于

idx_pts = np.mgrid[0:width, 0:height].reshape(2, -1).T
map_pts = transform(idx_pts, np.linalg.inv(H))
map_pts = map_pts.reshape(width, height, 2).astype(np.float32)
warped = cv.remap(img, map_pts, None, cv.INTER_CUBIC).transpose(1, 0, 2)

transform 函数所在的位置

def transform(src_pts, H):
    # src = [src_pts 1]
    src = np.pad(src_pts, [(0, 0), (0, 1)], constant_values=1)
    # pts = H * src
    pts = np.dot(H, src.T).T
    # normalize and throw z=1
    pts = (pts / pts[:, 2].reshape(-1, 1))[:, 0:2]
    return pts

src_pts : [[x0, y0], [x1, y1], [x2, y2], ...] (每行一个点)
H, status = cv.findHomography(src_pts, dst_pts)