1

UPDATE as on 12 Nov 2015

I used PanoTools plugin with Photoshop and Hugin and played with all those parameters. End up i found the parameters for projection, HFOV and image output size that fulfill my lowest requirement.

Parameteres:

Parameters Value

Processed Output: enter image description here

My question is then how can i convert all these parameters and values into C# algorithm coding so that when I provide the original image, i will get the corrected output image?

Thanks a lot.


I have a square image captured from a circular fisheye camera. The size is 2650 * 2650 pixels.

Now, i will need to programmatically dewarp the image to a flat panorama image using C# language. I had look around from internet with different algorithm example from Link for code below , Link1 and Link2 but just can't make it success. My maths sincerely sucks and can't help me with that. Hopefully someone able to guide me through this. Thanks a lot.

Example of image output from the camera:

--Image grabbed from Wikipedia Fisheye Lens & size modified to fit my sample pixel. enter image description here

The code i tried to dewarp it but no luck:

        Bitmap sourceImage = (Bitmap)Bitmap.FromFile("circularfisheye.jpg");
        double factor = 0.5;
        Boolean autoCrop = false;
        Color backgroundColor = Color.White;

        Bitmap StartImage = null;
        BitmapData srcBitmapData = null;
        Byte[] srcPixels = null;
        Byte[] dstPixels = null;
        Bitmap NewImage = null;
        BitmapData dstBitmapData = null;

        try
        {

            // Checks whether bpp ​​( Bits Per Pixel ) is 8 , 24, or 32
            int Depth = System.Drawing.Bitmap.GetPixelFormatSize(sourceImage.PixelFormat);
            if (Depth != 8 && Depth != 24 && Depth != 32)
            {
                throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
            }

            // Retrieves the count of the color components
            int cCount = Depth / 8;

            Size baseSize = new Size(sourceImage.Width, sourceImage.Height);

            // check if a low image resize and need to improve the quality
            // and not generate image aliasing
            Int32 maxSize = Math.Max(sourceImage.Width, sourceImage.Height);
            if (maxSize < 3000)
            {
                float percent = 3000F / (float)maxSize;
                baseSize = new Size((Int32)((float)sourceImage.Width * percent), (Int32)((float)sourceImage.Height * percent));
            }

            StartImage = new Bitmap(baseSize.Width, baseSize.Height, sourceImage.PixelFormat);
            StartImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);

            // Create the drawing object and white background
            Graphics g = Graphics.FromImage(StartImage);
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.DrawImage(sourceImage, new Rectangle(-1, -1, baseSize.Width + 1, baseSize.Height + 1), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel);
            g.Dispose();
            // Locks the source image and copies it to the byte array and releases the source image
            srcBitmapData = StartImage.LockBits(new Rectangle(0, 0, StartImage.Width, StartImage.Height), ImageLockMode.ReadOnly, StartImage.PixelFormat);
            srcPixels = new byte[StartImage.Width * StartImage.Height * (Depth / 8)];
            Marshal.Copy(srcBitmapData.Scan0, srcPixels, 0, srcPixels.Length);
            StartImage.UnlockBits(srcBitmapData);
            srcBitmapData = null;

            // Create the target image byte array
            dstPixels = new Byte[srcPixels.Length];

            // Fill the entire frame with the selected background color
            Int32 index = ((1 * StartImage.Width) + 1) * cCount; //index = ((Y * Width) + X) * cCount
            do
            {
                if (Depth == 32) //For 32 bpp defines Red , Green, Blue and Alpha
                {
                    dstPixels[index++] = backgroundColor.B;
                    dstPixels[index++] = backgroundColor.G;
                    dstPixels[index++] = backgroundColor.R;
                    dstPixels[index++] = backgroundColor.A; // a
                }
                if (Depth == 24) //For 24 bpp defines Red , Green and Blue
                {
                    dstPixels[index++] = backgroundColor.B;
                    dstPixels[index++] = backgroundColor.G;
                    dstPixels[index++] = backgroundColor.R;
                }
                if (Depth == 8)
                // For 8 bpp defines the value of color ( Red , Green and Blue to be the same thing)
                {
                    dstPixels[index++] = backgroundColor.B;
                }

            } while (index < srcPixels.Length);
            // Calculate the maximum possible extent for the image and multiply by the desired factor
            double amp = 0;
            double ang = Math.PI * 0.5;
            for (Int32 a = 0; a < StartImage.Height; a++)
            {
                int y = (int)((StartImage.Height / 2) - amp * Math.Sin(ang));
                if ((y < 0) || (y > StartImage.Height))
                    break;
                amp = a;
            }
            amp = (amp - 2) * (factor < -1 ? -1 : (factor > 1 ? 1 : factor));
            // Define variables that calculates the cutoff points (if any)
            Int32 x1, y1, x2, y2;
            x1 = StartImage.Width;
            y1 = StartImage.Height;
            x2 = 0;
            y2 = 0;


            // Copy pixel by pixel for the new positions
            index = ((1 * StartImage.Width) + 1) * cCount;
            do
            {

                Int32 y = (Int32)((index / cCount) / StartImage.Width);
                Int32 x = (index / cCount) - (y * StartImage.Width);

                Point pt = NewPoint(new Point(x, y), StartImage.Width, StartImage.Height, amp, factor < 0);

                //Values ​​for crop
                if (factor >= 0)
                {
                    if (x == StartImage.Width / 2)
                    {
                        if (pt.Y < y1)
                            y1 = pt.Y;

                        if (pt.Y > y2)
                            y2 = pt.Y;
                    }

                    if (y == StartImage.Height / 2)
                    {
                        if (pt.X < x1)
                            x1 = pt.X;

                        if (pt.X > x2)
                            x2 = pt.X;
                    }
                }
                else
                {
                    if ((x == 1) && (y == 1))
                    {
                        y1 = pt.Y;
                        x1 = pt.X;
                    }

                    if ((x == StartImage.Width - 1) && (y == StartImage.Height - 1))
                    {
                        y2 = pt.Y;
                        x2 = pt.X;
                    }
                }

                //Bytes Index which will apply the pixel
                Int32 dstIndex = ((pt.Y * StartImage.Width) + pt.X) * cCount;

                if (Depth == 32)
                {
                    dstPixels[dstIndex] = srcPixels[index++];
                    dstPixels[dstIndex + 1] = srcPixels[index++];
                    dstPixels[dstIndex + 2] = srcPixels[index++];
                    dstPixels[dstIndex + 3] = srcPixels[index++]; // a
                }
                if (Depth == 24)
                {
                    dstPixels[dstIndex] = srcPixels[index++];
                    dstPixels[dstIndex + 1] = srcPixels[index++];
                    dstPixels[dstIndex + 2] = srcPixels[index++];
                }
                if (Depth == 8)
                {
                    dstPixels[dstIndex] = srcPixels[index++];
                }

            } while (index < srcPixels.Length);

            //Creates a new image based on the byte array previously created
            NewImage = new Bitmap(StartImage.Width, StartImage.Height, StartImage.PixelFormat);
            NewImage.SetResolution(StartImage.HorizontalResolution, StartImage.VerticalResolution);
            dstBitmapData = NewImage.LockBits(new Rectangle(0, 0, StartImage.Width, StartImage.Height), ImageLockMode.WriteOnly, StartImage.PixelFormat);
            Marshal.Copy(dstPixels, 0, dstBitmapData.Scan0, dstPixels.Length);
            NewImage.UnlockBits(dstBitmapData);


            //Generates the final image to crop or resize the real coo
            Bitmap FinalImage = new Bitmap(sourceImage.Width + 1, sourceImage.Height, StartImage.PixelFormat);
            NewImage.SetResolution(StartImage.HorizontalResolution, StartImage.VerticalResolution);

            Graphics g1 = Graphics.FromImage(FinalImage);
            g1.SmoothingMode = SmoothingMode.AntiAlias;
            g1.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g1.PixelOffsetMode = PixelOffsetMode.HighQuality;

            //Performs the cut if enabled automatic cutting and there is need to cut
            if ((autoCrop) && ((x1 > 0) || (y1 > 0) || (x2 < NewImage.Height) || (y2 < NewImage.Height)))
            {
                Rectangle cropRect = new Rectangle(x1, y1, x2 - x1, y2 - y1);
                g1.DrawImage(NewImage, new Rectangle(-1, -1, FinalImage.Width + 1, FinalImage.Height + 1), cropRect.X, cropRect.Y, cropRect.Width, cropRect.Height, GraphicsUnit.Pixel);
            }
            else
            {
                g1.DrawImage(NewImage, new Rectangle(-1, -1, FinalImage.Width + 1, FinalImage.Height + 1), 0, 0, NewImage.Width, NewImage.Height, GraphicsUnit.Pixel);
            }

            g1.Dispose();
            g1 = null;

            NewImage = null;
            FinalImage.Save("output.jpg");
            FinalImage.Dispose();
        }
        finally
        {
            srcBitmapData = null;
            srcPixels = null;
            dstPixels = null;
            dstBitmapData = null;
        }
Community
  • 1
  • 1
Fei Hap Lee
  • 201
  • 1
  • 7
  • 17
  • Please add technical description of correction method used. I couldn't see something like barrel distortion formulas in this code. http://mipav.cit.nih.gov/pubwiki/index.php/Barrel_Distortion_Correction – MBo Nov 06 '15 at 06:50
  • good day. The code i was referred to http://www.helviojunior.com.br/fotografia/barrel-and-pincushion-distortion/ .It's however in Portugese. I can't tell if any barrel distortion formulas in there. – Fei Hap Lee Nov 06 '15 at 08:56
  • 1
    And what is wrong with your code? Have you considered using OpenCV or another image library? – MBo Nov 06 '15 at 10:20
  • Hi MBo, i can try any library or method as long as there is a solution. I wish to try OpenCV but don't know where to start – Fei Hap Lee Nov 09 '15 at 00:25
  • http://opencv.org/ and dotnet wrapper http://www.emgu.com/wiki/index.php/Main_Page – MBo Nov 10 '15 at 06:46

3 Answers3

3

Such a distortion as a symmetry of revolution.

In polar coordinates, with the pole at the center of the image, it is expressed as

r' = f(r)
Θ' = Θ

where the quote indicates the distorted coordinates. The function f is unknown and should be measured empirically, by calibration (looking at a regular target).

To correct the image, you need to invert the function f and apply the reverse transform to the image. In fact, it is easier to measure g directly by calibration. As a starting approximation, a simple model like

r = r' + a.r'³ 

can do.

Most probably you don't have a picture of a grid taken with the same lens. Your last resort is to implement the undistortion function with adjustable parameters, and optimize these by trial and error.

It should also be possible to derive the calibration curve by looking at the deformation of straight lines, but this is more "technical".


In Cartesian coordinates, you can express the correction transform as

x = g(r').x'/r'
y = g(r').y'/r'

where r' = √x'²+y'².

  • Hi Sir, i did trial and error to get those parameters and the result is positive. Now it's the C# code to translate those parameters. Can you kindly help me with that please? – Fei Hap Lee Nov 12 '15 at 09:43
  • How's the output image ? –  Nov 12 '15 at 09:47
  • Not perfect but at least met my requirement though. Can't really post an output image here as it's something private and confidential. Sorry about that. Is an output image needed for your reference? – Fei Hap Lee Nov 12 '15 at 09:50
  • Why don't you process the image in your post ? –  Nov 12 '15 at 09:53
  • No idea what it wrong. I don't intend to reverse engineer you code. You can contact me privately through my web site (profile) if you want. –  Nov 12 '15 at 10:12
1

Use the algorithm from here:

http://www.helviojunior.com.br/fotografia/barrel-and-pincushion-distortion/

It worked for me

Tarek.Mh
  • 638
  • 10
  • 8
  • This link is awesome! Small note: just change "percent" calculation into a fixed value of `1`. That'll go through the weird output. – Dinosaure Feb 08 '22 at 16:41
0

I've made some revamp to the HelvioJunior's library (that was linked by @Tarek.Mh), I think this may suit your need:

BarrelDistortion(bmp, 1 / 2.5f, true)

Below, the code:

using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using static System.Math;

namespace HelvioJunior
{
    //https://www.helviojunior.com.br/fotografia/barrel-and-pincushion-distortion/
    public class Program
    {
        private static void Main(string[] args)
        {
            Bitmap source = (Bitmap)Image.FromFile(@"JpwX0.png");
            Bitmap bmp = BarrelDistortion(source, 4/10f, true);
            bmp.Save(@"test.png");
            bmp.Dispose();
            source.Dispose();
        }

        static public Bitmap BarrelDistortion(Bitmap sourceImage, double factor = 0, bool autoCrop = true, uint previewRectangleWidth = 0, Color? fillerColor = null)
        {
            int sourceRight = sourceImage.Width - 1, sourceBottom = sourceImage.Height - 1;

            // Vertical amplitude is half the height times factor
            // Horizontal amplitude is missing ; vertical amplitude's applied to both directions
            double amp = sourceBottom / 2f * factor;

            // Inner shrinking area points
            RePoint[] lPts;
            bool inverse = factor < 0;

            // Shrinking area coordinates (center point is considered always available)
            double x1 = sourceRight / 2f,
                y1 = sourceBottom / 2f,
                x2 = sourceRight / 2f,
                y2 = sourceBottom / 2f;

            if (inverse)
            {
                lPts = new RePoint[]
                {
                    new RePoint(0, 0),
                    new RePoint(0, sourceBottom),
                    new RePoint(sourceRight, sourceBottom),
                    new RePoint(sourceRight, 0)
                };
            }
            else
            {
                lPts = new RePoint[]
                {
                    new RePoint(sourceRight * 1 / 2f, 0),
                    new RePoint(0, sourceBottom * 1 / 2f),
                    new RePoint(sourceRight, sourceBottom * 1 / 2f),
                    new RePoint(sourceRight * 1 / 2f, sourceBottom)
                };
            }

            foreach (var pN in lPts.Select(pt => NewPoint(pt, sourceImage.Width, sourceImage.Height, amp, inverse)))
            {
                if (pN.Y < y1) y1 = pN.Y;
                if (pN.Y > y2) y2 = pN.Y;
                if (pN.X < x1) x1 = pN.X;
                if (pN.X > x2) x2 = pN.X;
            }

            // Bytes per color from bit per pixel (bpp) format
            int bpcCount = Image.GetPixelFormatSize(sourceImage.PixelFormat) / 8;

            Rectangle sourceRectangle = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
            int srcLength = sourceImage.Width * sourceImage.Height * bpcCount;

            // Gets sourceImage byte array as srcpixels
            BitmapData srcBitmapData = sourceImage.LockBits(sourceRectangle, ImageLockMode.ReadOnly, sourceImage.PixelFormat);
            byte[] srcPixels = new byte[srcLength];
            Marshal.Copy(srcBitmapData.Scan0, srcPixels, 0, srcLength);
            sourceImage.UnlockBits(srcBitmapData);
            srcBitmapData = null;

            // Destination byte array preparation as dstPixels
            byte[] dstPixels = new byte[srcLength];
            int dstIndex = 0;

            // Filler color preparation
            Color fillColor = fillerColor ?? Color.Transparent;
            if (!autoCrop)
            {
                if (bpcCount <= 4) // Depth > 32bpp may not work as expected, filler color's not applied for bit safety reason
                    do
                    {
                        dstPixels[dstIndex++] = fillColor.B;
                        if (bpcCount > 1)
                        {
                            dstPixels[dstIndex++] = fillColor.G;
                            dstPixels[dstIndex++] = fillColor.R;
                            if (bpcCount > 3)
                                dstPixels[dstIndex++] = fillColor.A; // a
                        }
                    } while (dstIndex < srcLength);
            }

            // Byte-to-byte copy (incl. Point transformation)
            int index = 0, srcBpcLength = srcLength - bpcCount;
            do
            {
                int comp = index / bpcCount; // comp yields the current "pixel" position
                int y = comp / sourceImage.Width; // Each line is sourceImage.Width bytes wide
                int x = comp - (y * sourceImage.Width); // Remaining (comp - lines) bytes is target column (ranges from 0 to width - 1)

                // Destination "pixel"
                RePoint pt = NewPoint(new RePoint(x, y), sourceImage.Width, sourceImage.Height, amp, inverse);
                dstIndex = (((int)pt.Y * sourceImage.Width) + (int)pt.X) * bpcCount; // dstIndex++ overflows when |amp| >= 2

                if (dstIndex >= 0 && dstIndex <= srcBpcLength)
                    for (int i = 0; i++ < bpcCount;)
                        dstPixels[dstIndex++] = srcPixels[index++];
                else
                    index += bpcCount;
            } while (index < srcLength);
            srcPixels = null;

            // Destination bytes application
            BitmapData dstBitmapData = sourceImage.LockBits(sourceRectangle, ImageLockMode.WriteOnly, sourceImage.PixelFormat);
            Marshal.Copy(dstPixels, 0, dstBitmapData.Scan0, srcLength);
            sourceImage.UnlockBits(dstBitmapData);
            dstBitmapData = null;
            dstPixels = null;

            // Final Image area
            Rectangle cropRect = new Rectangle((int)Ceiling(x1), (int)Ceiling(y1), (int)Ceiling(x2 - x1), (int)Ceiling(y2 - y1));
            Rectangle destRectangle = autoCrop ? cropRect : sourceRectangle;

            // Final image preparation
            Bitmap FinalImage = new Bitmap(destRectangle.Width, destRectangle.Height, sourceImage.PixelFormat);
            FinalImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);

            Graphics g1 = Graphics.FromImage(FinalImage);
            g1.DrawImage(sourceImage, -destRectangle.X, -destRectangle.Y);

            // Previsualization rectangle
            if (previewRectangleWidth > 0)
                g1.DrawRectangle(new Pen(Color.Red, previewRectangleWidth), cropRect.X - 1, cropRect.Y - 1, cropRect.Width + previewRectangleWidth, cropRect.Height + previewRectangleWidth);

            g1.Dispose();
            g1 = null;

            return FinalImage;
        }

        private static RePoint NewPoint(RePoint aP, double Width, double Height, double Amplitude, bool inverse)
        {
            double h = aP.Y / (Height - 1);
            double w = aP.X / (Width - 1);

            // Works ok for [0/2] to [1/2]
            // Floating point error(s) here, in the range of ]1/2] to [2/2] (No workaround found)
            double sinX = Round(Sin(PI * w), 15); // Range of [0] to [1] * PI ; result ranges from 0 (far from center) to 1 (at center)
            double sinY = Round(Sin(PI * h), 15);

            double caX = Amplitude * (1 - 2 * w);
            double caY = Amplitude * (1 - 2 * h);

            double aY = 0, aX = 0;
            if (inverse)
            {
                aX = -caX;
                aY = -caY;
            }


            double pY = aP.Y + aY + caY * sinX;
            double pX = aP.X + aX + caX * sinY;

            return new RePoint(pX, pY);
        }

        private struct RePoint
        {
            public double X;
            public double Y;

            public RePoint(double x, double y)
            {
                X = x;
                Y = y;
            }
        }
    }
}
Dinosaure
  • 125
  • 10