I'm working on a screen sharing app, which runs a loop and grab fast screenshots using GDI methods . example here
Of course I also use a flood fill algorithm to find the changes areas between 2 images (previous screenshot and current).
I use another small trick - I downscale the snapshot resolution in 10, because processing 1920*1080=2073600 pixels very constantly is not very efficient.
However when I find the rectangle bounds - I apply it on the original full size bitmap and I just multiply by 10 the dimension (including top, left, width, height).
This is the scanning code:
unsafe bool ArePixelsEqual(byte* p1, byte* p2, int bytesPerPixel)
{
for (int i = 0; i < bytesPerPixel; ++i)
if (p1[i] != p2[i])
return false;
return true;
}
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
var bmData1 = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
var bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
int bytesPerPixel = 4;
IntPtr scan01 = bmData1.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride1 = bmData1.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
bool[] visited = new bool[nWidth * nHeight];
byte* base1 = (byte*)scan01.ToPointer();
byte* base2 = (byte*)scan02.ToPointer();
for (int y = 0; y < nHeight; y ++)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; ++x)
{
if (!ArePixelsEqual(p1, p2, bytesPerPixel) && !(visited[x + nWidth * y]))
{
// fill the different area
int minX = x;
int maxX = x;
int minY = y;
int maxY = y;
var pt = new Point(x, y);
Stack<Point> toBeProcessed = new Stack<Point>();
visited[x + nWidth * y] = true;
toBeProcessed.Push(pt);
while (toBeProcessed.Count > 0)
{
var process = toBeProcessed.Pop();
var ptr1 = (byte*)scan01.ToPointer() + process.Y * stride1 + process.X * bytesPerPixel;
var ptr2 = (byte*)scan02.ToPointer() + process.Y * stride2 + process.X * bytesPerPixel;
//Check pixel equality
if (ArePixelsEqual(ptr1, ptr2, bytesPerPixel))
continue;
//This pixel is different
//Update the rectangle
if (process.X < minX) minX = process.X;
if (process.X > maxX) maxX = process.X;
if (process.Y < minY) minY = process.Y;
if (process.Y > maxY) maxY = process.Y;
Point n; int idx;
//Put neighbors in stack
if (process.X - 1 >= 0)
{
n = new Point(process.X - 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.X + 1 < nWidth)
{
n = new Point(process.X + 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y - 1 >= 0)
{
n = new Point(process.X, process.Y - 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y + 1 < nHeight)
{
n = new Point(process.X, process.Y + 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
}
//finaly set a rectangle.
Rectangle r = new Rectangle(minX * 10, minY * 10, (maxX - minX + 1) * 10, (maxY - minY + 1) * 10);
rec.Add(r);
//got the rectangle now i'll do whatever i want with that.
//notify i scaled everything by x10 becuse i want to apply the changes on the originl 1920x1080 image.
}
p1 += bytesPerPixel;
p2 += bytesPerPixel;
}
base1 += stride1;
base2 += stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
This is my call:
private void Start()
{
full1 = GetDesktopImage();//the first,intial screen.
while (true)
{
full2 = GetDesktopImage();
a = new Bitmap(full1, 192, 108);//resizing for faster processing the images.
b = new Bitmap(full2, 192, 108); // resizing for faster processing the images.
CodeImage(a, b);
count++; // counter for the performance.
full1 = full2; // assign old to current bitmap.
}
}
However, after all the tricks and techniques I used, the algorithm runs quite slow... on my machine - Intel i5 4670k 3.4ghz - it runs only 20 times (at the maximum! It might get lower)! It maybe sounds fast (don't forget I have to send each changed area over the network after), but I'm looking to achieve more processed image per second. I think the main bottleneck is in the resizing of the 2 images - but I just thought it would be even faster after resizing - because it would have to loop through less pixels... 192*108=200,000 only..
I would appreciate any help, any improvement. Thanks.
Related
I'm currently in the development phase of a photoconverter program and in the process of developing a blur filter. At the initial stages of prototyping this feature, i devised a algorithm in which I had an accumulator for each color channel and add all the pixels in a radius of the target pixel. Afterwards the program would divide the accum by the amount of pixels read(not counting those offscreen). At first I thought this would be fine but when it started to work, I had the problem of this filter taking an hour to render with this being the result at the lowest setting. So I opted to utilize parallel processing in C# to make this process much easier and faster to run. With the boost of speed came the cost of the image becoming very glitched out. Here's the image before, and Here's the image afterwards
This is the code I wrote for the filter
public static DirectBitmap NewBlur (DirectBitmap image, int radius)
{
int sectorDiam = 128;
DirectBitmap newimage = image;
List<Rectangle> renderSectors = new List<Rectangle>();
Rectangle rect;
for (int x = 0; x < (image.Width / sectorDiam); x++)
{
int xwidth = sectorDiam;
for (int y = 0; y < (image.Height / sectorDiam); y++)
{
int yheight = sectorDiam;
rect = new Rectangle(x * sectorDiam, y * sectorDiam, xwidth, yheight);
renderSectors.Add(rect);
}
}
var Picrect = new Rectangle(0, 0, image.Width, image.Height);
var data = image.Bitmap.LockBits(Picrect, ImageLockMode.ReadWrite, image.Bitmap.PixelFormat);
var depth = Bitmap.GetPixelFormatSize(data.PixelFormat) / 8; //bytes per pixel
var buffer = new byte[data.Width * data.Height * depth];
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
Parallel.ForEach(renderSectors, sector =>
{
BlurSection(buffer, sector, Picrect, radius, image.Width, image.Height, depth);
}
);
Marshal.Copy(buffer, 0, data.Scan0, buffer.Length);
image.Bitmap.UnlockBits(data);
return image;
}
And here's the method for each section of the image to be blurred.
public static void BlurSection(byte[] buffer, Rectangle blurSector, Rectangle bitmaprect, int radius, int width, int height, int depth)
{
int[] Accum = new int[4];
for (int x = blurSector.X; x < blurSector.Width+ blurSector.X; x++)
{
for (int y = blurSector.Y; y < blurSector.Height + blurSector.Y; y++)
{
Accum[0] = 0;
Accum[1] = 0;
Accum[2] = 0;
Accum[3] = 0;
for (int i = -radius; i <= radius; i++)
{
for (int j = -radius; j <= radius; j++)
{
var offset = 0;
offset = (((y+j) * width) + (x+i)) * depth;
if (bitmaprect.Contains(new Point(x + i, y + j))){
Accum[0] += buffer[offset + 0];
Accum[1] += buffer[offset + 1];
Accum[2] += buffer[offset + 2];
Accum[3]++;
}
}
}
Accum[0] = Accum[0] / Accum[3];
if (Accum[0] > 255)
{
Accum[0] = 255;
}
Accum[1] = Accum[1] / Accum[3];
if (Accum[1] > 255)
{
Accum[1] = 255;
}
Accum[2] = Accum[2] / Accum[3];
if (Accum[2] > 255)
{
Accum[2] = 255;
}
var newoffset = ((y * width) + (x * depth*2));
buffer[newoffset + 0] = (byte)Accum[0];
buffer[newoffset + 1] = (byte)Accum[1];
buffer[newoffset + 2] = (byte)Accum[2];
}
}
}
It's also worth noting that I'm using a Bitmap class to make access to pixel data much easier, the "DirectBitmap" you can find here: https://stackoverflow.com/a/34801225/15473435. Is there anything that I'm missing or not aware of that's causing this algorithm not to function?
I'm using the following code to remove whitespace around an image.
static Bitmap TrimBitmap(Bitmap source)
{
Rectangle srcRect = default(Rectangle);
BitmapData data = null;
try
{
data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] buffer = new byte[data.Height * data.Stride];
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
int xMin = int.MaxValue,
xMax = int.MinValue,
yMin = int.MaxValue,
yMax = int.MinValue;
bool foundPixel = false;
// Find xMin
for (int x = 0; x < data.Width; x++)
{
bool stop = false;
for (int y = 0; y < data.Height; y++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
xMin = x;
stop = true;
foundPixel = true;
break;
}
}
if (stop)
break;
}
// Image is empty...
if (!foundPixel)
return null;
// Find yMin
for (int y = 0; y < data.Height; y++)
{
bool stop = false;
for (int x = xMin; x < data.Width; x++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
yMin = y;
stop = true;
break;
}
}
if (stop)
break;
}
// Find xMax
for (int x = data.Width - 1; x >= xMin; x--)
{
bool stop = false;
for (int y = yMin; y < data.Height; y++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
xMax = x;
stop = true;
break;
}
}
if (stop)
break;
}
// Find yMax
for (int y = data.Height - 1; y >= yMin; y--)
{
bool stop = false;
for (int x = xMin; x <= xMax; x++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
yMax = y;
stop = true;
break;
}
}
if (stop)
break;
}
srcRect = Rectangle.FromLTRB(xMin, yMin, xMax , yMax);
}
finally
{
if (data != null)
source.UnlockBits(data);
}
Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
using (Graphics graphics = Graphics.FromImage(dest))
{
graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel);
}
return dest;
}
I'm trying to trim a Bitmap with Text Drawn on it.The Proper Image should look like this after trimming
But after trimming i get the following result ..the bottom portion clipped off
What i'm i doing wrong? Please advice..
This is actually a problem with Rectangle.FromLTRB !
Looking closer at the images you will find that you have actually lost only one row of pixels. (The strong magnification had me fooled for a while..)
The algorithm to determine the Height (and Width) of the rectangle is basically right, but off by one.
If you use this
srcRect = Rectangle.FromLTRB(xMin, yMin, xMax + 1 , yMax + 1);
or this:
srcRect = new Rectangle(xMin, yMin, xMax - xMin + 1 , yMax - yMin + 1);
it should work.
You can test with pen and paper: Say: 1st pixel row with a color = 4, first from bottom on a 10 pixel square: 8, that makes 5 not 4 net data: 4,5,6,7,8.
Do note that this issue is inherent in FromLTRB:
Rectangle myRectangle = Rectangle.FromLTRB(0, 0, 10, 10);
..results in a Rectangle with Height=10 even though 0..10 should cover 11 pixels rows! So the Right-Bottom coordinate is actually excluded from the result!!
I think the whole issue with rectangles being off by one stems from legacy ways to use a Pen with its alignment. When using the same rectangles to fill with a Brush all works as expected.
The following routine is to sharpen an 8 bit indexed grayscale only.
This code seems to have no effect on the input image. That is, what is going in, the same is coming out.
If I increase the value of strength the image seems to be getting darker, but, never filtered.
What could be possibly going wrong?
I am using the following kernel,
double[,] _numericalKernel = new double[,]
{ { -1, -1, -1, },
{ -1, 9, -1, },
{ -1, -1, -1, }, };
The following is my source code for sharpening,
public static Bitmap NonfftSharpen(Bitmap image, double[,] mask, double strength)
{
Bitmap bitmap = (Bitmap)image.Clone();
if (bitmap != null)
{
int width = bitmap.Width;
int height = bitmap.Height;
if (mask.GetLength(0) != mask.GetLength(1))
{
throw new Exception("_numericalKernel dimensions must be same");
}
// Create sharpening filter.
int filterSize = mask.GetLength(0);
double[,] filter = (double[,])mask.Clone();
int channels = sizeof(byte);
double bias = 1.0 - strength;
double factor = strength / 16.0;
int halfOfFilerSize = filterSize / 2;
byte[,] result = new byte[bitmap.Width, bitmap.Height];
// Lock image bits for read/write.
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.ReadWrite,
PixelFormat.Format8bppIndexed);
// Declare an array to hold the bytes of the bitmap.
int memorySize = bitmapData.Stride * height;
byte[] memory = new byte[memorySize];
// Copy the RGB values into the local array.
Marshal.Copy(bitmapData.Scan0, memory, 0, memorySize);
int rgb;
// Fill the color array with the new sharpened color values.
for (int y = halfOfFilerSize; y < height - halfOfFilerSize; y++)
{
for (int x = halfOfFilerSize; x < width - halfOfFilerSize; x++)
{
for (int filterY = 0; filterY < filterSize; filterY++)
{
double grayShade = 0.0;
for (int filterX = 0; filterX < filterSize; filterX++)
{
int imageX = (x - halfOfFilerSize + filterX + width) % width;
int imageY = (y - halfOfFilerSize + filterY + height) % height;
rgb = imageY * bitmapData.Stride + channels * imageX;
grayShade += memory[rgb + 0] * filter[filterX, filterY];
}
rgb = y * bitmapData.Stride + channels * x;
int b = Math.Min(Math.Max((int)(factor * grayShade + (bias * memory[rgb + 0])), 0), 255);
result[x, y] = (byte)b;
}
}
}
// Update the image with the sharpened pixels.
for (int x = halfOfFilerSize; x < width - halfOfFilerSize; x++)
{
for (int y = halfOfFilerSize; y < height - halfOfFilerSize; y++)
{
rgb = y * bitmapData.Stride + channels * x;
memory[rgb + 0] = result[x, y];
}
}
// Copy the RGB values back to the bitmap.
Marshal.Copy(memory, 0, bitmapData.Scan0, memorySize);
// Release image bits.
bitmap.UnlockBits(bitmapData);
return bitmap;
}
else
{
throw new Exception("input image can't be null");
}
}
I've changed your function a bit to make it work.
Take care that the strength parameter has no effect in my function. You can play with the bias and factor values to get different results in brightness and so on.
public static Bitmap NonfftSharpen(Bitmap image, double[,] mask, double strength)
{
Bitmap bitmap = (Bitmap)image.Clone();
if (bitmap != null)
{
int width = bitmap.Width;
int height = bitmap.Height;
if (mask.GetLength(0) != mask.GetLength(1))
{
throw new Exception("_numericalKernel dimensions must be same");
}
// Create sharpening filter.
int filterSize = mask.GetLength(0);
double[,] filter = (double[,])mask.Clone();
int channels = sizeof(byte);
double bias = 0.0; // 1.0 - strength;
double factor = 1.0; // strength / 16.0;
byte[,] result = new byte[bitmap.Width, bitmap.Height];
// Lock image bits for read/write.
BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, width, height),
ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
// Declare an array to hold the bytes of the bitmap.
int memorySize = bitmapData.Stride * height;
byte[] memory = new byte[memorySize];
// Copy the RGB values into the local array.
Marshal.Copy(bitmapData.Scan0, memory, 0, memorySize);
int pixel;
// Fill the color array with the new sharpened color values.
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
double grayShade = 0.0;
for (int filterY = 0; filterY < filterSize; filterY++)
{
for (int filterX = 0; filterX < filterSize; filterX++)
{
int imageX = (x - filterSize / 2 + filterX + width) % width;
int imageY = (y - filterSize / 2 + filterY + height) % height;
pixel = imageY * bitmapData.Stride + channels * imageX;
grayShade += memory[pixel] * filter[filterX, filterY];
}
int newPixel = Math.Min(Math.Max((int)(factor * grayShade + bias), 0), 255);
result[x, y] = (byte)newPixel;
}
}
}
// Update the image with the sharpened pixels.
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
pixel = y * bitmapData.Stride + channels * x;
memory[pixel] = result[x, y];
}
}
// Copy the values back to the bitmap.
Marshal.Copy(memory, 0, bitmapData.Scan0, memorySize);
// Release image bits.
bitmap.UnlockBits(bitmapData);
return bitmap;
}
else
{
throw new Exception("input image can't be null");
}
}
I hope this gets you going :)
Regards
I'm trying to implement a cropping method myself,using the unsafe code and pointer to speed up the whole process.
This is my code:
private unsafe void Cut(Bitmap bmp, Rectangle r) {
Bitmap result = new Bitmap(r.Width, r.Height, bmp.PixelFormat);
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
BitmapData bmData2 = result.LockBits(new Rectangle(0, 0, result.Width, result.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, result.PixelFormat);
IntPtr scan0 = bmData.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride = bmData.Stride;
int stride2 = bmData2.Stride;
int x = r.X;
int y = r.Y;
int width = r.Width;
int height = r.Height;
for (; y < height; y++) {
byte * p = (byte * ) scan0.ToPointer();
p += y * stride;
byte * p2 = (byte * ) scan02.ToPointer();
p2 += y * stride2;
for (; x < width; x++) {
p2[0] = p[0];
p2[1] = p[1];
p2[2] = p[2];
p2[3]=p[3];
p += 4;
p2 += 4;
}
}
result.Save("a.png");
}
And the call to this method:
Bitmap b = (Bitmap)Bitmap.FromFile(#"C:\Users\itapi\Desktop\1.png");
Rectangle r = new Rectangle(200, 500, 300, 450);
Cut(b, r);
When i run the code,i just get a black rectangle as result...not the pixels i wanted to copy from the intial image.
The image from the example above is in32bpprgb format
I'm not sure what i'm doing wrong..i'll appreciate any help.
Thanks.
On the result, you chose "System.Drawing.Imaging.ImageLockMode.ReadOnly" I think you want ReadWrite or WriteOnly.
When I ran through debugger, this statement: for (; y < height; y++) condition failed immediately on my rather large image. So your loop logic is incorrect for running through the lines... Use a debugger :)
EDIT I ran it through the debugger, and your Y and X logic is wrong. I did a quick fix on the Y logic, and got it too crop. You'll have to do something similar to the X to get the correct crop point.
Try this, it cropped and saved a file:
int x = r.X;
int y = r.Y;
int width = r.Width;
int height = r.Height;
int newY = 0;
for (y = r.Y; y < height+r.Y; y++) //For each line in the old image
{
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
byte* p2 = (byte*)scan02.ToPointer();
p2 += newY * stride2;
for (x=r.X; x < width+r.X; x++)
{
p2[0] = p[0];
p2[1] = p[1];
p2[2] = p[2];
p2[3] = p[3];
p += 4;
p2 += 4;
}
newY++;
}
result.Save("\\a.png");
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'².
Use the algorithm from here:
http://www.helviojunior.com.br/fotografia/barrel-and-pincushion-distortion/
It worked for me
I've made some revamp to the HelvioJunior's library (that was linked by #Tarek.Mh), I think this may suit your need:
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;
}
}
}
}