使用高度图扭曲图像?

时间:2011-03-02 18:33:02

标签: c++ python image-processing opencv unwarp

我有一个图像的高度图,它告诉我每个像素在Z方向上的偏移。我的目标是仅使用它的高度图来展平扭曲的图像。

我该怎么做呢?我知道相机的位置,如果有帮助的话。


为了做到这一点,我在考虑假设每个像素是一个平面上的一个点,然后根据我从高度图得到的Z值以及从那个平移中垂直翻译每个点(想象一下)你正在观察上面的点;这种转变将导致点从你的角度移动。)

从那个预计的移位,我可以提取每个像素的X和Y移位,我可以将其输入cv.Remap()

但我不知道如何使用OpenCV获得点的投影3D偏移,更不用说构建偏移图了。


以下是我正在做的参考图片:

Calibration Image Warped Image

我知道激光的角度(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)

现在变成了一个巨大的图像和代码线程......无论如何,这个代码块花了我7分钟来运行在一个1800万像素的摄像机图像上;那个方式太长了,最后,这种方法对图像没有任何作用(每个像素的偏移量为<< 1)。

有什么想法吗?

3 个答案:

答案 0 :(得分: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函数重新映射每个像素,这似乎有点工作......

答案 1 :(得分:0)

基于与相机的距离的失真仅发生在透视投影上。如果您具有像素的(x,y,z)位置,则可以使用相机的投影矩阵将像素取消投影回世界空间。使用该信息,您可以以正交方式渲染像素。但是,由于原始透视投影,您可能缺少数据。

答案 2 :(得分:0)

按如下方式将场景分开:

  • 您有一个未知的位图图像(x,y) - &gt; (R,G,B)
  • 你有一个已知的身高字段 H (x,y) - &gt; ħ
  • 你有一个相机变换 C (x,y,z) - &gt; (u,v)将场景投影到屏幕平面

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

  • 你有截屏 S (u,v),这是 C 的结果(x,y, H (x,y) ))for x,y in I
  • 你想生成一个截图 S'(u',v'),它是x,y的 C (x,y,0)的结果的 I

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

  1. 光线投射:对于 S 中的每个像素,将光线投射回场景。找出它在高地上的位置;这会在原始图像 I 中显示(x,y),并且屏幕像素会为您提供该点的颜色。一旦你有了那么多你可以恢复,重新转换它以找到 S'

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

  3. 这两种方法都会产生采样问题,这可能会受到超采样或插值的影响;方法1将在图像的遮挡区域留下空白区域,方法2将从第一个表面“投射”。

    修改

    我猜想你的意思是CG风格的高度场,其中S中的每个像素都直接位于S'中相应的位置上方;但这不是页面覆盖表面的方式。页面固定在书脊上并且没有弹性 - 抬起页面的中心会将自由边缘拉向书脊。

    根据您的样本图像,您必须反转此累积拉动 - 检测脊椎中心线的位置和方向,并逐步左右工作,找到每个垂直条页的顶部和底部的高度变化,计算得到的方面缩小和倾斜,并将其反转以重新创建原始平面页面。