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

时间:2017-10-02 05:50:10

标签: python opencv remap

这里是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上的每个人都会输入一个起始点的数组(地图),一个结束点的地图,然后重映射()会将img中的所有值放入新的位置,插入任何空白空间。我这样做,但它不起作用。为什么?大多数示例都适用于C ++。它在python中被破坏了吗?

2 个答案:

答案 0 :(得分:28)

这只是对文档的一个简单误解,我不会责怪你 - 我也花了一些时间来理解它。文档很清楚,但这个功能可能并不像你期望的那样工作;事实上,它的工作方向与相反的方向相同。

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

来自remap() docs

  

map1 - 具有(x,y)x或{{1}类型的CV_16SC2点或CV_32FC1值的第一张地图}。有关将浮点表示转换为定点以获得速度的详细信息,请参阅CV_32FC2

     

map2 - 具有convertMaps()y或无类型的CV_16UC1值的第二张地图(如果CV_32FC1,则为空地图{分别为{1}}分。

此处map1上的verbage与" 的第一个地图..."有点误导。请记住,这些都是图像从 映射的位置的坐标... ...... (x,y)处的 map1正在映射点放在src map_x(x, y), map_y(x, y)。并且它们应该与您想要将它们转换为的图像形状相同。请注意文档中显示的等式:

dst

此处x, ydst(x,y) = src(map_x(x,y),map_y(x,y)) 给出的行和列中查找map_x(x, y)。然后在这些点评估图像。它在map_x中查找x, y的映射坐标,然后将该值分配给x, y中的src。如果你盯着这个足够长的时间,它就会开始变得有意义。在新目标图片中的像素x, y处,我查看dst(0, 0),它告诉我源图像中相应像素的位置,然后我可以指定插值通过查看源中的近值来在目标图像中map_x。这就是map_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)

因此,(0,0)处的目标图像与img处的源图像具有相同的值,第0行和第5列的源图像为153.请注意,目标图像{{1} }。这里没有插值,因为我的地图坐标是精确整数。此外,我还包括一个越界索引(map_y ===== 0 1 2 3 map_x ===== 5 6 7 10 ,它大于图像宽度),并注意到它只是在超出图像宽度时被赋值map_y(0, 0), map_x(0, 0) = 0, 5。界限。

完整用例

这是一个完整的代码示例,使用基础真值单应法,手动扭曲像素位置,然后使用mapped_img[0, 0] = 153从转换后的点映射图像。请注意,我的单应性将map_x[1, 1] = 10 转换为 0。因此,我制作了一组我想要的多个点,然后通过用单应变换来计算这些点在源图像中的位置。然后使用remap()在源图像中查找这些点,并将它们映射到目标图像。

true_dst

Remap for warping

来自Visual Geometry Group at Oxford的图像和基本真实单应性。

答案 1 :(得分:0)

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)

其中transfer函数是

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)