OpenCV重新映射插值错误?

6

我正在使用opencv remap函数将一张图片映射到另一个坐标系中。 然而,我的初步测试表明插值存在一些问题。 这里,我举一个简单的例子,对于一张在[50,50]位置处为0以外其他地方都是0的图片,进行一个0.1像素的平移。

import cv2
import numpy as np

prvs = np.zeros((100,80), dtype=np.float32)
prvs[50:51, 50:51] = 1.

grid_x, grid_y = np.meshgrid(np.arange(prvs.shape[1]), np.arange(prvs.shape[0]))
grid_y = grid_y.astype(np.float32)
grid_x = grid_x.astype(np.float32) + 0.1

prvs_remapped = cv2.remap(prvs, grid_x, grid_y, interpolation=cv2.INTER_LINEAR)

print(prvs_remapped[50,50])
print(prvs_remapped[50,49])

提供

0.90625
0.09375

然而,基于线性插值方法,我期望得到0.9和0.1。是我的操作有误还是数字计算有问题呢?是否有更精确的重新映射算法可用?

谢谢。

1个回答

11

做得好。我认为你的期望是正确的,就像np.interp所展示的那样,会给出0.10.9的值。

让我们绘制一个金字塔(插值到49:51的像素范围内):

import numpy as np
import cv2
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

prvs = np.zeros((100,80), dtype=np.float32)
prvs[50:51, 50:51] = 1

lin = np.linspace(49,51,200)
grid_x,grid_y = np.meshgrid(lin,lin)
grid_x = grid_x.astype(np.float32)
grid_y = grid_y.astype(np.float32)
prvs_zoommapped = cv2.remap(prvs, grid_x, grid_y, interpolation=cv2.INTER_LINEAR)

fig = plt.figure()
ax = fig.add_subplot(111,projection='3d')
ax.plot_surface(grid_x,grid_y,prvs_zoommapped,cmap='viridis')
plt.show()

result: pyramid with jagged edges

注意到什么不对了吗?使用200x200的绘图网格时,金字塔上有明显的台阶。让我们来看一下结果的横截面:

fig,ax = plt.subplots()
ax.plot(prvs_zoommapped[100,:],'x-')
ax.grid('on')
plt.show()

result: clearly piecewise-constant function

如您所见,结果是分段常数函数,即输出中存在较大的离散化误差。具体来说,我们在结果中看到了0.03125 == 1/32的步长。

我的怀疑是 cv2.remap 并不适用于亚像素操作,而是适用于从一个网格到另一个网格的更大规模的映射。另一种可能是出于性能改进而在内部牺牲了精度。无论哪种情况,你并没有发疯:你应该看到0.10.9作为精确(双)线性插值的结果。

如果您由于其他任务而没有承诺使用openCV,则可以使用各种scipy.interpolate库进行此映射,即2d插值,特别是用于2d插值的其子库。对于您在规则网格上进行线性插值的特殊情况,scipy.interpolate.RegularGridInterpolator 或类似的东西可能更适合。

甚至更好的是(但我还没有使用过这个子模块):scipy.ndimage.map_coordinates看起来正是您需要的:

from scipy import ndimage
ndimage.map_coordinates(prvs, [[50.1, 49.1], [50, 50]], order=1)
# output: array([ 0.89999998,  0.1       ], dtype=float32)

应用于金字塔的例子:

import numpy as np
import cv2
from scipy import ndimage
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

prvs = np.zeros((100,80), dtype=np.float32)
prvs[50:51, 50:51] = 1

lin = np.linspace(49,51,200)
grid_x,grid_y = np.meshgrid(lin,lin)
prvs_zoommapped = ndimage.map_coordinates(prvs, [grid_x, grid_y], order=1)

fig = plt.figure()
ax = fig.add_subplot(111,projection='3d')
ax.plot_surface(grid_x,grid_y,prvs_zoommapped,cmap='viridis')
plt.show()

漂亮平滑的金字塔

好多了。


1
感谢。ndimage.map_coordinates的工作情况符合预期。 插值错误似乎与某些性能优化有关,正如您已经提到的。请参阅http://answers.opencv.org/question/123197/how-to-increase-warpperspective-or-warpaffine-precision/ - Julian S.
@JulianS。我很高兴它有效,并感谢您提供的链接,看起来很正确。 - Andras Deak -- Слава Україні
3
一些追加内容:我重新编译了OpenCV,并在imgproc.hpp中将INTER_BITS从5更改为10(如上面给出的链接所建议的)。现在错误率降至0.00391。错误率似乎是1/2^N,其中N是一个整数。然而,在INTER_BITS = 5的情况下为1/2^4,在INTER_BITS = 10的情况下为1/2^8。因此它不仅仅是1/2^(INTER_BITS - 1)。但是,以防有人想要稍微增加OpenCV的精度并且不能更换到另一个库,我认为这可能会有所帮助。 - Julian S.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接