扭曲图像出现在圆柱投影中
computer-vision
image
6
0

我想对平面图像进行扭曲,使其看起来像是来自圆柱体的投影。

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

我有这个平面图像

我想以2D图像的形式显示它:

输出图像应该是这样的

我有点淘汰了几何投影。我参观了像其他一些问题, 可是我不明白我怎么会代表笛卡尔(X,Y)平面这些圆柱坐标(theta和RHO)为x,y坐标。你们能帮我举一个详尽的例子吗?我正在为iPhone编写代码,并且未使用OpenCV等任何第三方库。

谢谢你

参考资料:
Stack Overflow
收藏
评论
共 1 个回答
高赞 时间 活跃

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

数学

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

演示图片1

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

演示图片2

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

演示图3

第一个窍门是我们实际上想向后表示此过程。您可能首先认为您想获取原始图像中的每个像素,然后将其移动到新图像中。如果您检查新图像中出现在旧图像中的每个像素并设置其颜色,则实际上效果会更好。这意味着您需要做三件事。

  1. 设置气缸参数
  2. 投射来自相机的光线,穿过新图像中的每个点,并在圆柱体上找到其x,y,z坐标
  3. 使用正交投影将光线移回图像平面(仅意味着放下z分量)

跟踪所有内容可能会有些棘手,因此我将尝试使用一致的术语。首先,我假设您要确保圆柱体在边缘接触图像。如果是这样,那么您可以选择的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处的圆柱体。这是术语的快速图表。

演示4

我们知道我们要投影的线从原点开始,并在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的示例输出。

在此处输入图片说明

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号