使用高度图扭曲图像?

9 投票
3 回答
2448 浏览
提问于 2025-04-16 12:51

我有一张高度图,它告诉我每个像素在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

希望这能帮到你;)


现在,这是我用来去扭曲图像的代码。中间那些奇怪的东西是把3D坐标投影到相机平面上,考虑到相机的位置(以及相机的旋转等):

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)

现在这个话题变成了一大堆图像和代码……总之,这段代码在18MP的相机图像上运行需要7分钟;这实在是太慢了,而且最终这个方法对图像没有任何效果(每个像素的偏移量是<< 1)。

有没有什么想法?

3 个回答

0

基于距离相机的失真只会在透视投影中发生。如果你知道一个像素的(x,y,z)位置,可以使用相机的投影矩阵把这些像素转换回世界坐标系。通过这些信息,你可以以正交的方式来渲染这些像素。不过,由于最初的透视投影,可能会缺少一些数据。

0

把你的场景分成以下几个部分:

  • 你有一张未知的位图图像 I(x,y) -> (r,g,b),也就是每个点(x,y)对应的颜色值(r,g,b)。
  • 你有一个已知的高度场 H(x,y) -> h,表示每个点的高度。
  • 你有一个相机变换 C(x,y,z) -> (u,v),这个变换把场景投影到屏幕上。

需要注意的是,相机变换会丢失一些信息(你不能得到每个屏幕像素的深度值)。在屏幕上,可能会有场景重叠的部分,这种情况下只有最前面的部分会被显示,其他的会被丢弃。所以一般来说,这个过程不是完全可逆的。

  • 你有一个截图 S(u,v),这是通过 C(x,y,H(x,y)) 得到的,x,y来自 I
  • 你想生成一个新的截图 S'(u',v'),这是通过 C(x,y,0) 得到的,x,y同样来自 I

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

  1. 射线投射:对于 S 中的每个像素,向场景中投射一条射线。找出它与高度场的交点;这会给你原始图像 I 中的(x,y),而屏幕像素则给你那个点的颜色。一旦你恢复了尽可能多的 I,再进行变换以找到 S'

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

这两种方法都会遇到采样问题,可以通过超采样或插值来改善;方法1在被遮挡的区域会留下空白,方法2则会从第一个表面“投影”过去。

编辑:

我原以为你指的是计算机图形风格的高度场,其中 S 中的每个像素正好在 S' 中对应的位置上;但这并不是纸张如何在表面上铺展的方式。纸张是固定在脊柱上的,不会拉伸 - 抬起纸张的中心会把自由边缘拉向脊柱。

根据你的示例图像,你需要逆向处理这种累积的拉扯 - 检测脊柱中心线的位置和方向,然后逐步向左和向右工作,找出每个垂直纸条顶部和底部的高度变化,计算出结果的宽度缩小和倾斜,并将其逆转以重新创建原始的平面纸张。

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 函数来重新映射每一个像素,看起来效果还不错...

撰写回答