圆形鱼眼镜头图像变形为平面图像 [英] Circular Fisheye Image dewarp to flat image

查看:243
本文介绍了圆形鱼眼镜头图像变形为平面图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

2015年11月12日更新

我在Photoshop和Hugin中使用了PanoTools插件,并使用了所有这些参数。最后,我发现满足我最低要求的投影,HFOV和图像输出尺寸的参数。



参数:





处理后的输出:



然后我的问题是如何将所有这些参数和值转换为C#算法编码,以便在提供原始图像时,我会得到校正后的输出图像吗?



非常感谢。






我有一个从圆形鱼眼镜头捕获的正方形图像。尺寸为2650 * 2650像素。



现在,我需要使用C#语言以编程方式将图像扭曲为平面全景图像。
我从Internet到处浏览了



我试图取消变形但没有运气的代码:

 位图sourceImage =(位图)Bitmap.FromFile( circularfisheye.jpg); 
双倍系数= 0.5;
布尔值autoCrop = false;
Color backgroundColor = Color.White;

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

try
{

//检查bpp(每像素位数)是8、24还是32
int Depth = System。 Drawing.Bitmap.GetPixelFormatSize(sourceImage.PixelFormat);
if(Depth!= 8&& Depth!= 24&& Depth!= 32)
{
throw new ArgumentException(仅8、24和32 bpp图像支持的。);
}

//检索颜色分量的计数
int cCount = Depth / 8;

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

//检查是否调整了较小的图像大小并且需要提高质量
// //不生成图像别名
Int32 maxSize = Math.Max(sourceImage.Width,sourceImage。高度);
如果(maxSize< 3000)
{
浮动百分比= 3000F /(float)maxSize;
baseSize = new Size((Int32)((float)sourceImage.Width *百分比),(Int32)((float)sourceImage.Height *百分比));
}

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

//创建绘图对象和白色背景
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();
//锁定源图像并将其复制到字节数组并释放源图像
srcBitmapData = StartImage.LockBits(new Rectangle(0,0,StartImage.Width,StartImage.Height),ImageLockMode。 ReadOnly,StartImage.PixelFormat);
srcPixels =新字节[StartImage.Width * StartImage.Height *(深度/ 8)];
Marshal.Copy(srcBitmapData.Scan0,srcPixels,0,srcPixels.Length);
StartImage.UnlockBits(srcBitmapData);
srcBitmapData = null;

//创建目标图像字节数组
dstPixels = new Byte [srcPixels.Length];

//用选定的背景色填充整个框架
Int32 index =(((1 * StartImage.Width)+ 1)* cCount; // index =((Y * Width)+ X)* cCount
do
{
if(Depth == 32)//对于32 bpp定义Red,Green,Blue和Alpha
{
dstPixels [index ++] = backgroundColor.B;
dstPixels [index ++] = backgroundColor.G;
dstPixels [index ++] = backgroundColor.R;
dstPixels [index ++] = backgroundColor.A; // // a
}
if(Depth == 24)//对于24 bpp定义红色,绿色和蓝色
{
dstPixels [index ++] = backgroundColor.B;
dstPixels [index ++] = backgroundColor.G;
dstPixels [index ++] = backgroundColor.R;
}
if(Depth == 8)
//对于8 bpp定义颜色的值(红色,绿色和蓝色是相同的东西)
{
dstPixels [index ++] = backgroundColor.B;
}

}而(index< srcPixels.Length);
//计算图像的最大可能范围,然后乘以所需因子
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;
安培= a;
}
amp =(amp-2)*(factor< -1?-1:(factor> 1?1:factor));
//定义计算截止点的变量(如果有)
Int32 x1,y1,x2,y2;
x1 = StartImage.Width;
y1 = StartImage.Height;
x2 = 0;
y2 = 0;


//复制新位置的像素
索引=(((1 * StartImage.Width)+ 1)* cCount;
do
{

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

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

//作物
的值if(factor> = 0)
{
if(x == StartImage.Width / 2)
{
if(pt.Y y1 = pt.Y;

如果(pt.Y> y2)
y2 = pt.Y;
}

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

如果(pt.X> x2)
x2 = pt.X;
}
}
否则
{
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;
}
}

//将应用像素的字节索引
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 ++];
}

}而(index< srcPixels.Length);

//根据先前创建的字节数组创建新图像
NewImage = new Bitmap(StartImage.Width,StartImage.Height,StartImage.PixelFormat);
NewImage.SetResolution(StartImage.Horizo​​ntalResolution,StartImage.VerticalResolution);
dstBitmapData = NewImage.LockBits(新矩形(0,0,StartImage.Width,StartImage.Height),ImageLockMode.WriteOnly,StartImage.PixelFormat);
Marshal.Copy(dstPixels,0,dstBitmapData.Scan0,dstPixels.Length);
NewImage.UnlockBits(dstBitmapData);


//生成最终图像以裁剪或调整实际的大小
Bitmap FinalImage = new Bitmap(sourceImage.Width + 1,sourceImage.Height,StartImage.PixelFormat);
NewImage.SetResolution(StartImage.Horizo​​ntalResolution,StartImage.VerticalResolution);

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

//如果启用了自动剪切则执行剪切,并且如果((autoCrop)&&(((x1> 0)||(y1> 0 )||(x2< NewImage.Height)||(y2< NewImage.Height)))
{
Rectangle cropRect = new Rectangle(x1,y1,x2-x1,y2-y1) ;
g1.DrawImage(NewImage,新Rectangle(-1,-1,FinalImage.Width + 1,FinalImage.Height +1),cropRect.X,cropRect.Y,cropRect.Width,cropRect.Height,GraphicsUnit。像素);
}
其他
{
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();
}
最后
{
srcBitmapData = null;
srcPixels = null;
dstPixels = null;
dstBitmapData = null;
}


解决方案

这种扭曲是对称的



在极坐标中,极点位于图像的中心,表示为

  r'= f(r)
Θ'=Θ

,其中引号表示变形的坐标。函数f是未知的,应该通过校准(以常规目标为准)凭经验进行测量。



要校正图像,您需要反转函数f并应用反向转换为图像。实际上,通过校准直接测量g更容易。作为开始的近似值,像

  r = r'+a.r'³

可以做到。



很可能您没有用同一镜头拍摄的网格。最后的方法是使用可调整的参数来实现不失真功能,并通过反复试验对它们进行优化。



还应该可以通过查看






在笛卡尔坐标系中,您可以将校正变换表示为

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

其中 r'=√x'²+y'²


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:

Processed Output:

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.

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

解决方案

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'².

这篇关于圆形鱼眼镜头图像变形为平面图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆