鱼眼镜头的桶形畸变矫正算法——在Java中无法实现

6

我有一大批用鱼眼镜头拍摄的照片。由于我想对这些照片进行图像处理(例如边缘检测),因此我想消除桶形畸变,这会严重影响我的结果。

经过一些研究和阅读文章后,我发现了这个网页:他们描述了一种算法(和一些公式)来解决这个问题。

M = a *rcorr^3 + b * rcorr^2 + c * rcorr + d
rsrc = (a * rcorr^3 + b * rcorr^2 + c * rcorr + d) * rcorr

rsrc = 图像像素距离源图像中心的距离
rcorr = 图像像素距离校正后图像中心的距离
a,b,c = 图像失真 d = 图像线性缩放

我使用了这些公式,并尝试在Java应用程序中实现它。不幸的是,它没有起作用,我未能使其起作用。“校正”图像看起来与原始照片完全不同,而是显示一些神秘的圆圈在中间。看这里:

http://imageshack.us/f/844/barreldistortioncorrect.jpg/(这曾经是一张白色奶牛在蓝墙前的照片)

这是我的代码:

protected int[] correction(int[] pixels) {

    //
    int[] pixelsCopy = pixels.clone();

    // parameters for correction
    double paramA = 0.0; // affects only the outermost pixels of the image
    double paramB = -0.02; // most cases only require b optimization
    double paramC = 0.0; // most uniform correction
    double paramD = 1.0 - paramA - paramB - paramC; // describes the linear scaling of the image

    //
    for(int x = 0; x < dstView.getImgWidth(); x++) {
        for(int y = 0; y < dstView.getImgHeight(); y++) {

            int dstX = x;
            int dstY = y;

            // center of dst image
            double centerX = (dstView.getImgWidth() - 1) / 2.0;
            double centerY = (dstView.getImgHeight() - 1) / 2.0;

            // difference between center and point
            double diffX = centerX - dstX;
            double diffY = centerY - dstY;
            // distance or radius of dst image
            double dstR = Math.sqrt(diffX * diffX + diffY * diffY);

            // distance or radius of src image (with formula)
            double srcR = (paramA * dstR * dstR * dstR + paramB * dstR * dstR + paramC * dstR + paramD) * dstR;

            // comparing old and new distance to get factor
            double factor = Math.abs(dstR / srcR);
            // coordinates in source image
            double srcXd = centerX + (diffX * factor);
            double srcYd = centerY + (diffX * factor);

            // no interpolation yet (just nearest point)
            int srcX = (int)srcXd;
            int srcY = (int)srcYd;

            if(srcX >= 0 && srcY >= 0 && srcX < dstView.getImgWidth() && srcY < dstView.getImgHeight()) {

                int dstPos = dstY * dstView.getImgWidth() + dstX;
                pixels[dstPos] = pixelsCopy[srcY * dstView.getImgWidth() + srcX];
            }
        }
    }

    return pixels;
}

我的问题如下:
1)这个公式是否正确?
2)我将这个公式转换成软件时是否犯了错误?
3)还有其他算法可供选择(例如如何通过openCV模拟鱼眼镜头效果?或wiki/Distortion_(optics)),它们更好吗?
感谢您的帮助!

靠近边缘的像素方格可以很清楚地表明问题的可能性。至于你的算法是否适用于任何照片,我不知道。它无法工作的一个可能原因是你可能过度纠正了失真。 - AJMansfield
正如我在下面提到的,我尝试将b设置为无限小的值。这给出了不同的结果(不再有球形矫正),但仍然不能显示相同的图像。请参见此处:http://imageshack.us/f/191/barreldistortioncorrect.jpg/ - Lucas
一个无限小的 b 值可能会在“另一个”方向上进行过度校正吗? - AJMansfield
尝试制作一个动画,展示在将参数值从一个极端滑动到另一个极端时图像发生的变化;这可以帮助你理解问题所在。如果你有像 Wolfram Mathematica 这样的工具,那么做起来会非常简单,但即使没有这个工具,你也可以让它为不同的参数值生成大量图像,并将它们拼接成动画。 - AJMansfield
对于那些试图点击“页面”链接并收到404错误的人,该链接现已移至此处>> http://mipav.cit.nih.gov/pubwiki/index.php/Barrel_Distortion_Correction - streak
显示剩余2条评论
4个回答

10

你主要存在的问题是算法规定r_corr和r_src的单位是min((xDim-1)/2, (yDim-1)/2)。这需要进行规范化计算,以便参数值不会依赖于源图像的大小。由于代码是这样的,你需要使用更小的paramB值,例如对于一个尺寸为2272 x 1704的图像,我的paramB值为0.00000002就可以正常工作。

你还有一个错误是在计算与中心的差异时导致生成的图像相对于源图像旋转了180度。

修复这两个错误应该让你得到像这样的结果:

protected static int[] correction2(int[] pixels, int width, int height) {
    int[] pixelsCopy = pixels.clone();

    // parameters for correction
    double paramA = -0.007715; // affects only the outermost pixels of the image
    double paramB = 0.026731; // most cases only require b optimization
    double paramC = 0.0; // most uniform correction
    double paramD = 1.0 - paramA - paramB - paramC; // describes the linear scaling of the image

    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            int d = Math.min(width, height) / 2;    // radius of the circle

            // center of dst image
            double centerX = (width - 1) / 2.0;
            double centerY = (height - 1) / 2.0;

            // cartesian coordinates of the destination point (relative to the centre of the image)
            double deltaX = (x - centerX) / d;
            double deltaY = (y - centerY) / d;

            // distance or radius of dst image
            double dstR = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

            // distance or radius of src image (with formula)
            double srcR = (paramA * dstR * dstR * dstR + paramB * dstR * dstR + paramC * dstR + paramD) * dstR;

            // comparing old and new distance to get factor
            double factor = Math.abs(dstR / srcR);

            // coordinates in source image
            double srcXd = centerX + (deltaX * factor * d);
            double srcYd = centerY + (deltaY * factor * d);

            // no interpolation yet (just nearest point)
            int srcX = (int) srcXd;
            int srcY = (int) srcYd;

            if (srcX >= 0 && srcY >= 0 && srcX < width && srcY < height) {
                int dstPos = y * width + x;
                pixels[dstPos] = pixelsCopy[srcY * width + srcX];
            }
        }
    }

    return pixels;
}

使用此版本,您可以使用现有镜头数据库(如LensFun)中的参数值(尽管您需要翻转每个参数的符号)。该算法的描述页面现在可以在http://mipav.cit.nih.gov/pubwiki/index.php/Barrel_Distortion_Correction找到。


我一直在使用鱼眼镜头制作360全景图。我使用ptiGui作为图像去畸变和拼接的参考。但问题是,当我将ptgui提供的去畸变的a b c参数放入你的代码中时,结果却非常不同。实际上,在Ptgui中,a的效果与你的代码几乎相反。你认为可能出了什么问题? - Ahmed_Faraz
嗨@SteveH,非常好的解决方案!您知道将创建的新坐标转换回R中的图像或矩阵格式的方法吗?我尝试了您的解决方案,但在最后一行遇到了麻烦:pixels [dstPos] = pixelsCopy [srcY * width + srcX]。我认为这在Java和R中不起作用。 - SqueakyBeak

2
我认为您的圆圈是由以下代码引起的:

double srcYd = centerY + (diffX * factor);

我猜这句话的意思是:
double srcYd = centerY + (diffY * factor);

0

你的数值非常极端,因此你看到了极端的结果。

尝试设置a=0,b=0,c=1。这将不会进行任何纠正操作,如果你的程序正确,那么你应该看到原始图像。然后逐渐更改c和b。以0.1的增量进行更改是一个不错的开始。


0

可能你的径向畸变参数太大了,导致图像在球体上变形。尝试将abcd的值设小。


将b设置为无限小的值(并且保持a = c = 0),则不再有球体,但图像中的所有像素似乎仍然混在一起。请参见此处:http://imageshack.us/f/191/barreldistortioncorrect.jpg/ 这让我认为我的代码肯定有问题,而不是算法。如果我将a = b = c = 0和d = 1,则一切正常,图像保持不变。 - Lucas

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