6

I have a large bulk of photographs taken with a fisheye lens. As I want to do some image-processing (e.g. edge detection) on the photos I want to remove the barrel distortion which effects my results heavily.

After some research and lots of read articles I found this page: They describe an algorithm (and some formulas) to solve this problem.

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

rsrc = distance of a pixel from the center of the source image
rcorr = distance of a pixel from the center in the corrected image
a,b,c = distortion of image d = linear scaling of image

I used these formulas and tried to implement this in a Java application. Unfortunately it doesn't work and I failed to make it work. "Corrected" image look nothing like the original photograph and instead show some mysterious circles in the middle. Look here:

http://imageshack.us/f/844/barreldistortioncorrect.jpg/ (this used to be a photograph of a white cow in front a blue wall)

Here is my code:

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;
}

My questions are:
1) Is this formula correct?
2) Do I have made a mistake turning that formula into a piece of software?
3) There are other algorithms out there (e.g. How to simulate fisheye lens effect by openCV? or wiki/Distortion_(optics)), are they better?

Thanks for your help!

Community
  • 1
  • 1
Lucas
  • 95
  • 1
  • 2
  • 6
  • The square grid of pixels near the edge say a lot about what the problem likely is. Whether or not your algorithm works for any photo, I have no idea. One likely reason it isn't working is that you may be overcorrecting the distortion. – AJMansfield Sep 27 '12 at 11:19
  • As I mentioned below I tried setting b to an infinitely small value. It gives a different result (no spherical correction anymore) but still does not display the same image. See here: http://imageshack.us/f/191/barreldistortioncorrect.jpg/ – Lucas Sep 27 '12 at 11:31
  • Might an infinately small b value be overcorrecting in the _other_ direction? – AJMansfield Sep 27 '12 at 15:14
  • Try making an animation of what happens to the image as you slide the parameter values from one extreme to the other; that could illuminate your problem. If you have access to tools like Wolfram Mathematica, it would be pretty simple to do, but even without that, you could just make it generate a bajillion images for different parameter values and stich them together into an animation. – AJMansfield Sep 27 '12 at 15:18
  • Also, try going over all the math with pencil on paper, to see what actually happens to the image pixels, to make sure your math is correct. – AJMansfield Sep 27 '12 at 15:21
  • One more thing - you ought to compress the computation down to improve readability. Doing it one step at a time like that really isn't nesasary, and actually reduces readability. If you feel you must keep it spread out, at least compress the part that shifts the coordinate system so the origin is at the center of the image. – AJMansfield Sep 27 '12 at 15:27
  • For those who are trying to click on "Page" link and receiving 404, the link is now moved here >> http://mipav.cit.nih.gov/pubwiki/index.php/Barrel_Distortion_Correction – streak Oct 15 '14 at 05:36

4 Answers4

10

The main bug you have is that the algorithm specifies that r_corr and r_src are in units of min((xDim-1)/2, (yDim-1)/2). That needs to be done to normalise the calculation so that the parameter values are not dependent on the size of the source image. With the code as it is you'll need to use much smaller values for paramB, e.g. it worked ok for me with paramB = 0.00000002 (for an image with dimensions 2272 x 1704).

You also have a bug in calculating the difference from the center that causes the resulting image to be rotated 180 degree compared to the source image.

Fixing both these bugs should give you something like this:

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;
}

With this version you can use parameter values from existing lens databases like LensFun (though you'll need to flip the sign of each parameter). The page describing the algorithm can now be found at http://mipav.cit.nih.gov/pubwiki/index.php/Barrel_Distortion_Correction

SteveH
  • 301
  • 5
  • 6
  • I have been working on 360 panorama using fisheye lenses. I have used ptiGui as a refernece for image undistortion and and stitching. but the problem is that when i put the a b c parameters which ptgui provides for undistortion in your code the results are very different. Infact the effect of a is almost opposite in Ptgui from your code. what do you think could be the problem?? – Ahmed_Faraz Dec 21 '16 at 10:37
  • Hi @SteveH, great solution! Do you know of a way to take the new coordinates you create and convert them back to an image or matrix format in R? I tried your solution but am having trouble with your last line: pixels[dstPos] = pixelsCopy[srcY * width + srcX]. I don't think this works the same in Java as in R. – SqueakyBeak Mar 14 '19 at 16:02
2

I think your circles are caused by this line:

double srcYd = centerY + (diffX * factor);

which I'm guessing should be:

double srcYd = centerY + (diffY * factor);
Tom Makin
  • 3,203
  • 23
  • 23
0

Probably your radial distortion parameters are too large, and the image became packed on a sphere. Try to put smaller values in a,b,c and d.

Andrey Rubshtein
  • 20,795
  • 11
  • 69
  • 104
  • Setting an infinitely small value for b (and leaving a = c = 0), there is no sphere anymore but still all pixels in the image seem to be mixed up. See here: http://imageshack.us/f/191/barreldistortioncorrect.jpg/ What makes me think that there must be a problem with my code and not the algorithm. If I set a = b = c = 0 and d = 1 then everything works fine and the image is left unchanged. – Lucas Sep 27 '12 at 11:29
0

Your values are very extreme, so you see extreme results.

Try a=0, b=0, c=1. That describes no correction at all, if your program is correct you should see the original image. Then gradually change c and b. Changing in increments of 0.1 is a good start.

Bernie Sumption
  • 444
  • 6
  • 11