使用高度图扭曲图像?

9
我有一张高度图像,它告诉我每个像素在Z方向上的偏移量。我的目标是仅使用它的高度图像来平整失真的图像。
我该怎么做?如果有帮助的话,我知道相机的位置。
为了实现这一点,我考虑假设每个像素都是一个平面上的点,然后根据从高度图中获得的Z值垂直地平移每个点,并从该平移中(想象你从上面看这些点;移位会导致点从你的视角移动)提取每个像素的X和Y平移量,这些量可以输入到cv.Remap()中。
但我不知道如何用OpenCV获取点的投影3D偏移量,更不用说构建偏移图了。
以下是我正在处理的参考图像:
我知道激光的角度为45度,从校准图像中,我可以轻松计算出书的高度。
h(x) = sin(theta) * abs(calibration(x) - actual(x))

我使用这种方法对两条线进行线性插值,以生成一个表面。以下是使用Python代码实现的示例(它在一个循环内):

height_grid[x][y] = heights_top[x] * (cv.GetSize(image)[1] - y) + heights_bottom[x] * y

我希望这能帮到您;)


目前,我有以下方法来对图像进行去畸变处理。中间的奇怪内容将一个三维坐标投影到相机平面上,考虑其位置(以及相机的位置、旋转等):

class Point:
  def __init__(self, x = 0, y = 0, z = 0):
    self.x = x
    self.y = y
    self.z = z

mapX = cv.CreateMat(cv.GetSize(image)[1], cv.GetSize(image)[0], cv.CV_32FC1)
mapY = cv.CreateMat(cv.GetSize(image)[1], cv.GetSize(image)[0], cv.CV_32FC1)

c = Point(CAMERA_POSITION[0], CAMERA_POSITION[1], CAMERA_POSITION[2])
theta = Point(CAMERA_ROTATION[0], CAMERA_ROTATION[1], CAMERA_ROTATION[2])
d = Point()
e = Point(0, 0, CAMERA_POSITION[2] + SENSOR_OFFSET)

costx = cos(theta.x)
costy = cos(theta.y)
costz = cos(theta.z)

sintx = sin(theta.x)
sinty = sin(theta.y)
sintz = sin(theta.z)


for x in xrange(cv.GetSize(image)[0]):
  for y in xrange(cv.GetSize(image)[1]):
    
    a = Point(x, y, heights_top[x / 2] * (cv.GetSize(image)[1] - y) + heights_bottom[x / 2] * y)
    b = Point()
    
    d.x = costy * (sintz * (a.y - c.y) + costz * (a.x - c.x)) - sinty * (a.z - c.z)
    d.y = sintx * (costy * (a.z - c.z) + sinty * (sintz * (a.y - c.y) + costz * (a.x - c.x))) + costx * (costz * (a.y - c.y) - sintz * (a.x - c.x))
    d.z = costx * (costy * (a.z - c.z) + sinty * (sintz * (a.y - c.y) + costz * (a.x - c.x))) - sintx * (costz * (a.y - c.y) - sintz * (a.x - c.x))
    
    mapX[y, x] = x + (d.x - e.x) * (e.z / d.z)
    mapY[y, x] = y + (d.y - e.y) * (e.z / d.z)
    

print
print 'Remapping original image using map...'

remapped = cv.CreateImage(cv.GetSize(image), 8, 3)
cv.Remap(image, remapped, mapX, mapY, cv.CV_INTER_LINEAR)

现在这篇文章已经成为了一个大量图片和代码的长串...无论如何,这个代码块要花费7分钟来运行一张1800万像素的相机图像;那太长了,而且最终这种方法对图像没有任何作用(每个像素的偏移量是<< 1)。

有什么想法吗?

3个回答

3

我最终实现了自己的解决方案:

for x in xrange(cv.GetSize(image)[0]):
  for y in xrange(cv.GetSize(image)[1]):

    a = Point(x, y, heights_top[x / 2] * (cv.GetSize(image)[1] - y) + heights_bottom[x / 2] * y)
    b = Point()

    d.x = costy * (sintz * (a.y - c.y) + costz * (a.x - c.x)) - sinty * (a.z - c.z)
    d.y = sintx * (costy * (a.z - c.z) + sinty * (sintz * (a.y - c.y) + costz * (a.x - c.x))) + costx * (costz * (a.y - c.y) - sintz * (a.x - c.x))
    d.z = costx * (costy * (a.z - c.z) + sinty * (sintz * (a.y - c.y) + costz * (a.x - c.x))) - sintx * (costz * (a.y - c.y) - sintz * (a.x - c.x))

    mapX[y, x] = x + 100.0 * (d.x - e.x) * (e.z / d.z)
    mapY[y, x] = y + 100.0 * (d.y - e.y) * (e.z / d.z)


print
print 'Remapping original image using map...'

remapped = cv.CreateImage(cv.GetSize(image), 8, 3)
cv.Remap(image, remapped, mapX, mapY, cv.CV_INTER_LINEAR)

这将使用cv.Remap函数逐像素地重新映射图像,虽然效果有些缓慢,但似乎可以正常工作...


0

仅基于相机距离发生的扭曲只会在透视投影中出现。如果您拥有像素的(x,y,z)位置,则可以使用相机的投影矩阵将像素反向投影回世界空间。有了这些信息,您可以以正交方式渲染像素。但是,由于原始透视投影,您可能会缺少数据。


OpenCV能够将3D映射到2D吗?还是我必须自己想出这个公式?我会尝试实现它,谢谢! - Blender

0

将场景分解如下:

  • 您有一个未知的位图图像I(x,y) -> (r,g,b)
  • 您有一个已知的高度场 H(x,y) -> h
  • 您有一个相机变换 C(x,y,z) -> (u,v),它将场景投影到屏幕平面上

请注意,相机变换会丢失信息(您不会得到每个屏幕像素的深度值)。您还可能在屏幕上有场景重叠的部分,在这种情况下,只显示最前面的部分 - 其余部分被丢弃。因此,一般来说,这不是完全可逆的。

  • 您有一个屏幕截图S(u,v),它是由C(x,y,H(x,y))对于I中的x,y的结果
  • 您想生成一个屏幕截图S'(u',v'),它是由C(x,y,0)对于I中的x,y的结果

有两种明显的方法来解决这个问题;两者都依赖于准确的相机变换值。

  1. 射线投射:对于S中的每个像素,向场景中反射一条射线。找出它在高度场上的位置;这给出了原始图像I中的(x,y),屏幕像素给出了该点的颜色。一旦你恢复了尽可能多的I,重新转换它以找到S'

  2. 双重渲染:对于I中的每个x,y,投影以找到(u,v)和(u',v')。从S(u,v)获取像素颜色并将其复制到S'(u',v')。

这两种方法都会有采样问题,可以通过超采样或插值来解决;方法1会在图像的遮挡区域留下空白,方法2会从第一个表面“穿透”。

编辑:

我原以为您指的是CG风格的高度场,其中S中的每个像素直接位于S'中相应的位置上;但这不是页面覆盖在表面上的方式。页面固定在脊椎处,不可拉伸-抬起页面的中心会将自由边缘向脊椎拉。

根据您的样本图像,您将需要反转这个累积拉动 - 检测脊柱中心线的位置和方向,并逐步向左和右侧进行工作,找到每个垂直页面条顶部和底部高度变化,计算结果狭窄和偏斜,并反转以重新创建原始的平面页面。

我已经相应地编辑了我的答案。我还会包括参考图片,这样你就可以看到我的意思了。 - Blender
是的,样本图像非常有帮助。几个想法:首先,您可以使用长焦镜头并从尽可能远的地方拍摄来使图像近似正交。其次,页面的放置存在一些垂直倾斜 - 将底部边缘靠在平面表面上可以减少或消除这种情况。然后,图像校正最终只需要通过页面入射角的反余弦宽度校正(即非常简单)。 - Hugh Bothwell
我只有一支3倍变焦镜头,所以我必须手动校正切向和径向畸变。您能详细说明一下arccos()方法吗?我还没有完全理解它。 - Blender
考虑页面的一个薄垂直切片。这个切片的表面宽度(由相机视角观察)随页面倾斜角度的余弦值而变化(如果页面是平的,则显示100%的宽度,在45度时显示70.7%的宽度,等等)。因此,如果您知道倾斜角度,请将表面宽度乘以1 / cos(角度)以获取实际宽度。 - Hugh Bothwell
哦,我在考虑页面去畸变的问题;)相机倾斜可以通过OpenCV的相机校准来解决,所以我不太担心它。我尝试实现我所描述的内容,但结果很失败。我的输入/输出图像只相差一个像素... - Blender

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