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

Posted

技术标签:

【中文标题】如何使用 OpenCV 的重映射功能?【英文标题】:How do I use OpenCV's remap function? 【发布时间】:2018-03-13 04:57:15 【问题描述】:

这是 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 中的所有值放入它们的新位置,插入任何空白空间。我正在这样做,但它不起作用。为什么?大多数示例都是针对 C++ 的。它在python中被破坏了吗?

【问题讨论】:

我已经给出了答案,但是如果您回答我的问题,我可以通过一个更简单的示例对其进行扩展……您期望的输出是什么?显然,首先输出形状为(2, 2),但您期望的值是多少? 我期望输出形状为 20x20,因为这是最大值。大多数其他点都是插值的。 我期望输出 [[0,0,0,0,0,0,0,0,0,0,.1,.2,.3,.4,. 5,.6,.7,.8,.9,1] .... [0,0,0,0,0,0,0,0,0,2,2.1,2.2,2.3,2.4,2.5 ,2.6,2.7,2.8,2.9,3]] 【参考方案1】:

这只是对文档的简单误解,我不怪你——我也摸索了几次才理解它。文档很清楚,但是此功能可能无法按您期望的方式工作;事实上,它的工作方向与我最初的预期相反。

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

来自remap() docs:

ma​​p1(x,y) 点或仅具有 CV_16SC2CV_32FC1CV_32FC2 类型的 x 值的第一张地图。有关将浮点表示转换为定点以提高速度的详细信息,请参阅convertMaps()

ma​​p2y 值的第二个映射,其类型分别为 CV_16UC1CV_32FC1 或无(如果 map1(x,y) 点则为空映射)。

map1 上的措辞带有“...的 first 映射”可能会令人困惑。请记住,这些严格来说是您的图像被映射的位置的坐标...这些点被映射srcmap_x(x, y), map_y(x, y),然后放入@987654342 @x, y。并且它们应该与您想要将它们变形的图像的形状相同。请注意文档中显示的方程式:

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

这里map_x(x, y)x, y 给出的行和列中查找map_x。然后在这些像素处评估图像值。它在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_dst 转换为 src。因此,我制作了一组我想要的任意多的点,然后通过使用单应性变换来计算这些点在源图像中的位置。然后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 的图像和地面实况单应性。

【讨论】:

这里有很多东西要消化。我很欣赏它的思想和深度。你的单应性例子正是我想要做的,只是有不同的图像和挑战。重点是我需要知道如何使用 remap() 在您的代码中,“map_ind = H.dot(lin_homg_ind) map_x, map_y = map_ind[:-1]/map_ind[-1]”(抱歉,不允许cmets 格式) - 这就是我需要了解的内容。 src(map_x(x,y),map_y(x,y)) - 这告诉我 map_x 提供了从 src 获取并放入 dst[x,y] 的点 - 这正是我正在尝试做的事情。 ? @johnktejik “这告诉我 map_x 提供了从 src 获取的点并放入 dst[x,y] - 这正是我想要做的。”是的,确切地说。 map_x[1, 0] 保存来自src 的坐标,该坐标被放入dst[1, 0]。顺便说一句,您可以使用反引号来执行内联代码; `this` 产生this。请随时给我发送电子邮件(在我个人资料中的网站上),我很乐意帮助您解决实施中的具体问题。这些功能我用过很多次。 @johnktejik 添加了我们通过电子邮件讨论过的简短代码示例。干杯! @johnktejik 我很好地扩展了它,因为我还没有看到另一篇文章详细介绍此功能,所以我认为当人们尝试使用 remap() 时,它会在未来有所帮助.无论如何,更大的代码示例已经在一个文件中。 :)【参考方案2】:
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)

【讨论】:

以上是关于如何使用 OpenCV 的重映射功能?的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV 例程300篇249. 图像的重映射(cv2.remap)

详解OpenCV的坐标重映射函数remap()的两种使用方法并附使用它得到图像的水平镜像和垂直镜像的示例代码

仿射变换

如何使用 React Router 的重定向功能?

如何使用AWS Lambda和SNS事件触发Spring Cloud功能的重试

如何在OpenCV中使用双重类型的地图重新映射