翘曲图像出现在圆柱投影中

时间:2012-08-18 10:25:41

标签: image 3d geometry computer-vision cylindrical

我想以一种似乎是来自圆柱体的投影的方式扭曲平面图像。

我有一个像这样的平面图像:

I have this flat image

我希望在2D图像中将其显示为:

output image should be something like this

我对几何投影有点过时了。我访问了其他一些问题,如this,但我不明白如何将这些圆柱坐标(theta和rho)表示为笛卡尔(x,y)平面中的x,y坐标。你们能帮我一个详细的例子吗? 我正在为iPhone编码,我没有使用像OpenCV等任何第三方库。

非常感谢。

1 个答案:

答案 0 :(得分:63)

这是两部分的答案,数学和代码

数学

我喜欢这个问题,因为所涉及的投影很有趣,但数学仍然可以手工解决,没有太多困难。首先,重要的是要了解为什么图像完全扭曲它的方式。假设我们有一个平面图像,前面有一个凹圆柱体。

Demo Image 1

第一步是进行正交投影,将图像移动到曲面上。

Demo Image 2

然后将这些点以透视投影回图像平面。请注意,在这种情况下,整个图像会缩小,因为圆柱体的所有部分都具有比图像平面更大的z坐标。在您的情况下,圆柱体在左右边缘处接触图像平面,因此不会发生收缩。当投射回点时,它们不再在图像平面上形成平线,因为圆柱体的z坐标随x变化而存在曲线。

Demo Image 3

第一个技巧是我们实际上想要向后表示这个过程。您可能首先想到要拍摄原始图像中的每个像素并将其移动到新图像中。如果您检查新图像中出现在旧图像中的每个像素并设置其颜色,它实际上会更好。这意味着你需要做3件事。

  1. 设置气缸参数
  2. 从相机投射光线,穿过新图像中的每个点并在圆柱上找到其x,y,z坐标
  3. 使用正交投影将光线移回图像平面(只是意味着删除z组件)
  4. 跟踪一切可能有点棘手,所以我会尝试使用一致的术语。首先,我假设您要保证您的圆柱体在边缘接触您的图像。如果是这样,那么您可以选择的2个自由参数是圆柱半径和焦距。

    zx平面中圆的方程是

    x^2+(z-z0)^2 = r^2
    

    假设圆的中心位于z轴上。如果圆柱的边缘将要触摸宽度为w且焦距为f的图像平面的边缘,则

    omega^2+(f-z0)^2 = r^2 //define omega = width/2, it cleans it up a bit
    z0 = f-sqrt(r^2-omega^2)
    

    现在我们知道了我们移动到第2步的圆柱体的所有参数,从相机投影线,通过xim处的图像平面到xc处的圆柱体。这是术语的快速图表。

    Demo4

    我们知道我们投射的线从原点开始并在xim处穿过图像平面。我们可以把它的等式写成

    x = xim*z/f
    

    因为我们想要x坐标通过圆柱时合成方程式

    xim^2*z^2/f^2 + z^2 - 2*z*z0 +z0^2 - r^2 = 0
    

    您可以使用二次方程求解z,然后插回线方程以获得x。这两个解决方案对应于线接触圆的两个位置,因为我们只关注在图像平面之后发生的那个,并且一个将始终具有更大的x坐标,使用-b + sqrt(...) 。然后

    xc = xim*z/f;
    yc = yim*z/f;
    

    删除正交投影的最后一步很简单,只需删除z组件即可完成。

    代码

    我知道你说你没有使用openCV,但是我将在我的演示中使用它作为图像容器。所有操作都是逐像素完成的,因此您不难将其转换为适用于您正在使用的任何图像容器。首先,我创建了一个函数,它将最终图像中的图像坐标转换为原始图像中的坐标。 OpenCV将其图像原点放在左上角,这就是为什么我开始减去w / 2和h / 2并通过将它们添加回来结束

    cv::Point2f convert_pt(cv::Point2f point,int w,int h)
    {
        //center the point at 0,0
        cv::Point2f pc(point.x-w/2,point.y-h/2);
    
        //these are your free parameters
        float f = w;
        float r = w;
    
        float omega = w/2;
        float z0 = f - sqrt(r*r-omega*omega);
    
        float zc = (2*z0+sqrt(4*z0*z0-4*(pc.x*pc.x/(f*f)+1)*(z0*z0-r*r)))/(2* (pc.x*pc.x/(f*f)+1)); 
        cv::Point2f final_point(pc.x*zc/f,pc.y*zc/f);
        final_point.x += w/2;
        final_point.y += h/2;
        return final_point;
    }
    

    现在剩下的就是在旧图像中对新图像中的每个点进行采样。有很多方法可以做到这一点,我做了我所知道的最简单的方法,双线性插值。此外,这只是设置为灰度工作,使其在颜色上工作很简单,只需将过程应用于所有3个通道。我只是觉得这样会更清楚。

    
    for(int y = 0; y < height; y++)
    {
        for(int x = 0; x < width; x++)
        {
            cv::Point2f current_pos(x,y);
            current_pos = convert_pt(current_pos, width, height);
    
            cv::Point2i top_left((int)current_pos.x,(int)current_pos.y); //top left because of integer rounding
    
            //make sure the point is actually inside the original image
            if(top_left.x < 0 ||
               top_left.x > width-2 ||
               top_left.y < 0 ||
               top_left.y > height-2)
            {
                continue;
            }
    
            //bilinear interpolation
            float dx = current_pos.x-top_left.x;
            float dy = current_pos.y-top_left.y;
    
            float weight_tl = (1.0 - dx) * (1.0 - dy);
            float weight_tr = (dx)       * (1.0 - dy);
            float weight_bl = (1.0 - dx) * (dy);
            float weight_br = (dx)       * (dy);
    
            uchar value =   weight_tl * image.at<uchar>(top_left) +
            weight_tr * image.at<uchar>(top_left.y,top_left.x+1) +
            weight_bl * image.at<uchar>(top_left.y+1,top_left.x) +
            weight_br * image.at<uchar>(top_left.y+1,top_left.x+1);
    
            dest_im.at<uchar>(y,x) = value;
        }
    }
    

    这是f = w / 2和r = w的示例输出。

    enter image description here