如何使用 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 src
在 map_x(x, y), map_y(x, y)
然后放入 dst
的 x, 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_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)
那么这里发生了什么?在这种情况下,最容易检查矩阵:
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()
的图像和地面真实单应性
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)
这是 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 justx
values having the typeCV_16SC2
,CV_32FC1
, orCV_32FC2
. SeeconvertMaps()
for details on converting a floating point representation to fixed-point for speed.map2 – The second map of
y
values having the typeCV_16UC1
,CV_32FC1
, or none (empty map ifmap1
is(x,y)
points), respectively.
map1
上的“第一个 地图......”的措辞可能会令人困惑。请记住,这些严格是图像映射位置的坐标 from...这些点被映射 from src
在 map_x(x, y), map_y(x, y)
然后放入 dst
的 x, 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_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)
那么这里发生了什么?在这种情况下,最容易检查矩阵:
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()
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)