Crop/Remove Unwanted Space at the edges of image - c#

I have search a lot to remove the unwanted space but could not found. I only found links which can used to remove black and white background space. But my background images can be anything. So, If I have these images,
How I can extract the part of image which I required. For example,

Here's my solution for your question :
I have declared a method which gets the Original Image then It looks for the background Color by checking the corners of the provided Image , if at least 3 corners have similar Color (10% offset at most) then we've found the background color then It tries to find the bounds of those shapes in the Image which of course have different color than Background Color
after Finding the Bounds The Function Crops the Image and Return the new Cropped Area as a new Bitmap !
This is the Demo File : Download
Complete Solution : Download
Here's the results for :
Image 1 :
Image 2 :
Image 3 :
here's the Function inside ImageProcessingTools class
Simplified,
public class ImageHelper
{
#region CropUnwantedBackground
public static Bitmap CropUnwantedBackground(Bitmap bmp)
{
var backColor = GetMatchedBackColor(bmp);
if (backColor.HasValue)
{
var bounds = GetImageBounds(bmp, backColor);
var diffX = bounds[1].X - bounds[0].X + 1;
var diffY = bounds[1].Y - bounds[0].Y + 1;
var croppedBmp = new Bitmap(diffX, diffY);
var g = Graphics.FromImage(croppedBmp);
var destRect = new Rectangle(0, 0, croppedBmp.Width, croppedBmp.Height);
var srcRect = new Rectangle(bounds[0].X, bounds[0].Y, diffX, diffY);
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
bmp.Dispose();
return croppedBmp;
}
else
{
bmp.Dispose();
return null;
}
}
#endregion
#region Private Methods
#region GetImageBounds
private static Point[] GetImageBounds(Bitmap bmp, Color? backColor)
{
//--------------------------------------------------------------------
// Finding the Bounds of Crop Area bu using Unsafe Code and Image Proccesing
Color c;
int width = bmp.Width, height = bmp.Height;
bool upperLeftPointFounded = false;
var bounds = new Point[2];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
c = bmp.GetPixel(x, y);
bool sameAsBackColor = ((c.R <= backColor.Value.R * 1.1 && c.R >= backColor.Value.R * 0.9) &&
(c.G <= backColor.Value.G * 1.1 && c.G >= backColor.Value.G * 0.9) &&
(c.B <= backColor.Value.B * 1.1 && c.B >= backColor.Value.B * 0.9));
if (!sameAsBackColor)
{
if (!upperLeftPointFounded)
{
bounds[0] = new Point(x, y);
bounds[1] = new Point(x, y);
upperLeftPointFounded = true;
}
else
{
if (x > bounds[1].X)
bounds[1].X = x;
else if (x < bounds[0].X)
bounds[0].X = x;
if (y >= bounds[1].Y)
bounds[1].Y = y;
}
}
}
}
return bounds;
}
#endregion
#region GetMatchedBackColor
private static Color? GetMatchedBackColor(Bitmap bmp)
{
// Getting The Background Color by checking Corners of Original Image
var corners = new Point[]{
new Point(0, 0),
new Point(0, bmp.Height - 1),
new Point(bmp.Width - 1, 0),
new Point(bmp.Width - 1, bmp.Height - 1)
}; // four corners (Top, Left), (Top, Right), (Bottom, Left), (Bottom, Right)
for (int i = 0; i < 4; i++)
{
var cornerMatched = 0;
var backColor = bmp.GetPixel(corners[i].X, corners[i].Y);
for (int j = 0; j < 4; j++)
{
var cornerColor = bmp.GetPixel(corners[j].X, corners[j].Y);// Check RGB with some offset
if ((cornerColor.R <= backColor.R * 1.1 && cornerColor.R >= backColor.R * 0.9) &&
(cornerColor.G <= backColor.G * 1.1 && cornerColor.G >= backColor.G * 0.9) &&
(cornerColor.B <= backColor.B * 1.1 && cornerColor.B >= backColor.B * 0.9))
{
cornerMatched++;
}
}
if (cornerMatched > 2)
{
return backColor;
}
}
return null;
}
#endregion
#endregion
}
and here is a simple one usage in ASP.NET,
if (IsPostBack && Request.Files.Count > 0)
{
var file = Request.Files[0];
var bmp = new Bitmap(file.InputStream);
var croppedBmp = ImageHelper.CropUnwantedBackground(bmp);
Response.ContentType = file.ContentType;
croppedBmp.Save(Response.OutputStream, ImageFormat.Jpeg);
Response.End();
}
And Finally I should mention that , these Fantastic Tutorials have Helped me a lot in Image Processing :
Image Processing for Dummies with C# and GDI+
Image Processing using C#
Hope it Helps :)

Here's a more reliable approach that uses a Sobel energy filter and a fast bounding box detection routine (extracted from the WhitespaceTrimmer plugin for ImageResizer).
namespace ImageResizer.Plugins.WhitespaceTrimmer {
public class BoundingBoxFinder {
/// <summary>
/// Returns a rectangle inside 'lookInside' that bounds any energy greater than 'threshold'.
/// </summary>
/// <param name="image"></param>
/// <param name="lookInside">A rectangle of 'image' to look inside. </param>
/// <param name="threshold">1-255, the energy threshold to detect activity. 80-150 is a good range.</param>
/// <returns></returns>
public Rectangle FindBoxSobel(Bitmap originalImage, Rectangle lookInside, byte threshold) {
Bitmap image = originalImage;
try {
//Convert if needed (makes an extra copy)
if (image.PixelFormat != PixelFormat.Format24bppRgb &&
image.PixelFormat != PixelFormat.Format32bppArgb &&
image.PixelFormat != PixelFormat.Format32bppRgb) {
image = AForge.Imaging.Image.Clone(image, PixelFormat.Format24bppRgb);
}
//Crop if needed (makes an extra copy unless we converted too, then only 1 extra copy)
if (!lookInside.Equals(new Rectangle(0, 0, image.Width, image.Height))) {
Bitmap oldImage = image;
try {
image = new Crop(lookInside).Apply(image);
} finally {
if (oldImage != originalImage) oldImage.Dispose(); //Dispose the cloned
}
}
//Makes 1 more copy at 1/3rd the size, in grayscale
Rectangle result = FindBoxSobel(image, threshold);
return new Rectangle(lookInside.X + result.X, lookInside.Y + result.Y, result.Width, result.Height);
} finally {
if (image != originalImage) image.Dispose();
}
}
/// <summary>
/// Requires 24 bit or 32 bit (A) RGB image.
/// </summary>
/// <param name="rgb"></param>
/// <param name="threshold"></param>
/// <returns></returns>
public Rectangle FindBoxSobel(Bitmap rgb, byte threshold) {
using (Bitmap gray = Grayscale.CommonAlgorithms.Y.Apply(rgb)) {
//Apply sobel operator to grayscale image
new SobelEdgeDetector().ApplyInPlace(gray);
//Threshold into black and white.
new Threshold(threshold).ApplyInPlace(gray);
//Trim only exact black pixels
// lock source bitmap data
BitmapData data = gray.LockBits(new Rectangle(0, 0, gray.Width, gray.Height), ImageLockMode.ReadOnly, gray.PixelFormat);
try {
return FindBoxExactGrayscale(data, 0);
} finally {
gray.UnlockBits(data);
}
}
}
/// <summary>
/// Returns a bounding box that only excludes the specified color.
/// Only works on 8-bit images.
/// </summary>
/// <param name="sourceData"></param>
/// <param name="colorToRemove">The palette index to remove.</param>
/// <returns></returns>
public Rectangle FindBoxExactGrayscale(BitmapData sourceData, byte indexToRemove) {
if (sourceData.PixelFormat != PixelFormat.Format8bppIndexed) throw new ArgumentOutOfRangeException("FindBoxExact only operates on 8-bit grayscale images");
// get source image size
int width = sourceData.Width;
int height = sourceData.Height;
int offset = sourceData.Stride - width;
int minX = width;
int minY = height;
int maxX = 0;
int maxY = 0;
// find rectangle which contains something except color to remove
unsafe {
byte* src = (byte*)sourceData.Scan0;
for (int y = 0; y < height; y++) {
if (y > 0) src += offset; //Don't adjust for offset until after first row
for (int x = 0; x < width; x++) {
if (x > 0 || y > 0) src++; //Don't increment until after the first pixel.
if (*src != indexToRemove) {
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
if (y > maxY)
maxY = y;
}
}
}
}
// check
if ((minX == width) && (minY == height) && (maxX == 0) && (maxY == 0)) {
minX = minY = 0;
}
return new Rectangle(minX,minY,maxX - minX + 1, maxY - minY + 1);
}
}
}

Related

Draw angled rectangle on bitmap

I have a picture containing text :
I made a method to detect text rows. This method return the 4 corners for the text zone (always sorted) :
I want to modify the bitmap to draw a rectangle (with transparence) from theses 4 corners. Something like this :
I have my image in gray scale. I created a function to draw a rectangle, but I only achieve to draw a right rectangle :
public static void SaveDrawRectangle(int width, int height, Byte[] matrix, int dpi, System.Drawing.Point[] corners, string path)
{
System.Windows.Media.Imaging.WriteableBitmap wbm = new System.Windows.Media.Imaging.WriteableBitmap(width, height, dpi, dpi, System.Windows.Media.PixelFormats.Bgra32, null);
uint[] pixels = new uint[width * height];
for (int Y = 0; Y < height; Y++)
{
for (int X = 0; X < width; X++)
{
byte pixel = matrix[Y * width + X];
int red = pixel;
int green = pixel;
int blue = pixel;
int alpha = 255;
if (X >= corners[0].X && X <= corners[1].X &&
Y >= corners[0].Y && Y <= corners[3].Y)
{
red = 255;
alpha = 255;
}
pixels[Y * width + X] = (uint)((alpha << 24) + (red << 16) + (green << 8) + blue);
}
}
wbm.WritePixels(new System.Windows.Int32Rect(0, 0, width, height), pixels, width * 4, 0);
using (FileStream stream5 = new FileStream(path, FileMode.Create))
{
PngBitmapEncoder encoder5 = new PngBitmapEncoder();
encoder5.Frames.Add(BitmapFrame.Create(wbm));
encoder5.Save(stream5);
}
}
How can I draw a rectangle from 4 corners ?
I modify my condition by replacing with that code:
public static void SaveDrawRectangle(int width, int height, Byte[] matrix, int dpi, List<Point> corners, string path)
{
System.Windows.Media.Imaging.WriteableBitmap wbm = new System.Windows.Media.Imaging.WriteableBitmap(width, height, dpi, dpi, System.Windows.Media.PixelFormats.Bgra32, null);
uint[] pixels = new uint[width * height];
for (int Y = 0; Y < height; Y++)
{
for (int X = 0; X < width; X++)
{
byte pixel = matrix[Y * width + X];
int red = pixel;
int green = pixel;
int blue = pixel;
int alpha = 255;
if (IsInRectangle(X, Y, corners))
{
red = 255;
}
pixels[Y * width + X] = (uint)((alpha << 24) + (red << 16) + (green << 8) + blue);
}
}
wbm.WritePixels(new System.Windows.Int32Rect(0, 0, width, height), pixels, width * 4, 0);
using (FileStream stream5 = new FileStream(path, FileMode.Create))
{
PngBitmapEncoder encoder5 = new PngBitmapEncoder();
encoder5.Frames.Add(BitmapFrame.Create(wbm));
encoder5.Save(stream5);
}
}
public static bool IsInRectangle(int X, int Y, List<Point> corners)
{
Point p1, p2;
bool inside = false;
if (corners.Count < 3)
{
return inside;
}
var oldPoint = new Point(
corners[corners.Count - 1].X, corners[corners.Count - 1].Y);
for (int i = 0; i < corners.Count; i++)
{
var newPoint = new Point(corners[i].X, corners[i].Y);
if (newPoint.X > oldPoint.X)
{
p1 = oldPoint;
p2 = newPoint;
}
else
{
p1 = newPoint;
p2 = oldPoint;
}
if ((newPoint.X < X) == (X <= oldPoint.X)
&& (Y - (long)p1.Y) * (p2.X - p1.X)
< (p2.Y - (long)p1.Y) * (X - p1.X))
{
inside = !inside;
}
oldPoint = newPoint;
}
return inside;
}
It works but have 2 failings :
generated images are very big (base image take 6 Mo and after drawing 25 Mo)
generation take several time (my images are 5000x7000 pixels, process take 10 seconds)
There is probably a better way, but this way is working good.

How to scan two images for differences?

I'm trying to scan 2 images (32bppArgb format), identify when there is a difference and store the difference block's bounds in a list of rectangles.
Suppose these are the images:
second:
I want to get the different rectangle bounds (the opened directory window in our case).
This is what I've done:
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
bmData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, 1920, 1080), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, 1920, 1080), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
IntPtr scan0 = bmData.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride = bmData.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
int minX = int.MaxValue;;
int minY = int.MaxValue;
int maxX = 0;
bool found = false;
for (int y = 0; y < nHeight; y++)
{
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
byte* p2 = (byte*)scan02.ToPointer();
p2 += y * stride2;
for (int x = 0; x < nWidth; x++)
{
if (p[0] != p2[0] || p[1] != p2[1] || p[2] != p2[2] || p[3] != p2[3]) //found differences-began to store positions.
{
found = true;
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
}
else
{
if (found)
{
int height = getBlockHeight(stride, scan0, maxX, minY, scan02, stride2);
found = false;
Rectangle temp = new Rectangle(minX, minY, maxX - minX, height);
rec.Add(temp);
//x += minX;
y += height;
minX = int.MaxValue;
minY = int.MaxValue;
maxX = 0;
}
}
p += 4;
p2 += 4;
}
}
return rec;
}
public unsafe int getBlockHeight(int stride, IntPtr scan, int x, int y1, IntPtr scan02, int stride2) //a function to get an existing block height.
{
int height = 0;;
for (int y = y1; y < 1080; y++) //only for example- in our case its 1080 height.
{
byte* p = (byte*)scan.ToPointer();
p += (y * stride) + (x * 4); //set the pointer to a specific potential point.
byte* p2 = (byte*)scan02.ToPointer();
p2 += (y * stride2) + (x * 4); //set the pointer to a specific potential point.
if (p[0] != p2[0] || p[1] != p2[1] || p[2] != p2[2] || p[3] != p2[3]) //still change on the height in the increasing **y** of the block.
height++;
}
return height;
}
This is actually how I call the method:
Bitmap a = Image.FromFile(#"C:\Users\itapi\Desktop\1.png") as Bitmap;//generates a 32bppRgba bitmap;
Bitmap b = Image.FromFile(#"C:\Users\itapi\Desktop\2.png") as Bitmap;//
List<Rectangle> l1 = CodeImage(a, b);
int i = 0;
foreach (Rectangle rec in l1)
{
i++;
Bitmap tmp = b.Clone(rec, a.PixelFormat);
tmp.Save(i.ToString() + ".png");
}
But I'm not getting the exact rectangle.. I'm getting only half of that and sometimes even worse. I think something in the code's logic is wrong.
Code for #nico
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 = 3;
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 += 5)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; x += 5)
{
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);
}
}
}
if (((maxX - minX + 1) > 5) & ((maxY - minY + 1) > 5))
rec.Add(new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1));
}
p1 += 5 * bytesPerPixel;
p2 += 5 * bytesPerPixel;
}
base1 += 5 * stride1;
base2 += 5 * stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
I see a couple of problems with your code. If I understand it correctly, you
find a pixel that's different between the two images.
then you continue to scan from there to the right, until you find a position where both images are identical again.
then you scan from the last "different" pixel to the bottom, until you find a position where both images are identical again.
then you store that rectangle and start at the next line below it
Am I right so far?
Two obvious things can go wrong here:
If two rectangles have overlapping y-ranges, you're in trouble: You'll find the first rectangle fine, then skip to the bottom Y-coordinate, ignoring all the pixels left or right of the rectangle you just found.
Even if there is only one rectangle, you assume that every pixel on the rectangle's border is different, and all the other pixels are identical. If that assumption isn't valid, you'll stop searching too early, and only find parts of rectangles.
If your images come from a scanner or digital camera, or if they contain lossy compression (jpeg) artifacts, the second assumption will almost certainly be wrong. To illustrate this, here's what I get when I mark every identical pixel the two jpg images you linked black, and every different pixel white:
What you see is not a rectangle. Instead, a lot of pixels around the rectangles you're looking for are different:
That's because of jpeg compression artifacts. But even if you used lossless source images, pixels at the borders might not form perfect rectangles, because of antialiasing or because the background just happens to have a similar color in that region.
You could try to improve your algorithm, but if you look at that border, you will find all kinds of ugly counterexamples to any geometric assumptions you'll make.
It would probably be better to implement this "the right way". Meaning:
Either implement a flood fill algorithm that erases different pixels (e.g. by setting them to identical or by storing a flag in a separate mask), then recursively checks if the 4 neighbor pixels.
Or implement a connected component labeling algorithm, that marks each different pixel with a temporary integer label, using clever data structures to keep track which temporary labels are connected. If you're only interested in a bounding box, you don't even have to merge the temporary labels, just merge the bounding boxes of adjacent labeled areas.
Connected component labeling is in general a bit faster, but is a bit trickier to get right than flood fill.
One last advice: I would rethink your "no 3rd party libraries" policy if I were you. Even if your final product will contain no 3rd party libraries, development might by a lot faster if you used well-documented, well-tested, useful building blocks from a library, then replaced them one by one with your own code. (And who knows, you might even find an open source library with a suitable license that's so much faster than your own code that you'll stick with it in the end...)
ADD: In case you want to rethink your "no libraries" position: Here's a quick and simple implementation using AForge (which has a more permissive library than emgucv):
private static void ProcessImages()
{
(* load images *)
var img1 = AForge.Imaging.Image.FromFile(#"compare1.jpg");
var img2 = AForge.Imaging.Image.FromFile(#"compare2.jpg");
(* calculate absolute difference *)
var difference = new AForge.Imaging.Filters.ThresholdedDifference(15)
{OverlayImage = img1}
.Apply(img2);
(* create and initialize the blob counter *)
var bc = new AForge.Imaging.BlobCounter();
bc.FilterBlobs = true;
bc.MinWidth = 5;
bc.MinHeight = 5;
(* find blobs *)
bc.ProcessImage(difference);
(* draw result *)
BitmapData data = img2.LockBits(
new Rectangle(0, 0, img2.Width, img2.Height),
ImageLockMode.ReadWrite, img2.PixelFormat);
foreach (var rc in bc.GetObjectsRectangles())
AForge.Imaging.Drawing.FillRectangle(data, rc, Color.FromArgb(128,Color.Red));
img2.UnlockBits(data);
img2.Save(#"compareResult.jpg");
}
The actual difference + blob detection part (without loading and result display) takes about 43ms, for the second run (this first time takes longer of course, due to JITting, cache, etc.)
Result (the rectangle is larger due to jpeg artifacts):
Here is a flood-fill based version of your code. It checks every pixel for difference. If it finds a different pixel, it runs an exploration to find the entire different area.
The code is only meant as an illustration. There are certainly some points that could be improved.
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 static unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
if (bmp.PixelFormat != bmp2.PixelFormat || bmp.Width != bmp2.Width || bmp.Height != bmp2.Height)
throw new ArgumentException();
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 = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
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); }
}
}
rec.Add(new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1));
}
p1 += bytesPerPixel;
p2 += bytesPerPixel;
}
base1 += stride1;
base2 += stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
You can achieve this easily using a flood fill segmentation algorithm.
First an utility class to make fast bitmap access easier. This will help to encapsulate the complex pointer-logic and make the code more readable:
class BitmapWithAccess
{
public Bitmap Bitmap { get; private set; }
public System.Drawing.Imaging.BitmapData BitmapData { get; private set; }
public BitmapWithAccess(Bitmap bitmap, System.Drawing.Imaging.ImageLockMode lockMode)
{
Bitmap = bitmap;
BitmapData = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), lockMode, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
}
public Color GetPixel(int x, int y)
{
unsafe
{
byte* dataPointer = MovePointer((byte*)BitmapData.Scan0, x, y);
return Color.FromArgb(dataPointer[3], dataPointer[2], dataPointer[1], dataPointer[0]);
}
}
public void SetPixel(int x, int y, Color color)
{
unsafe
{
byte* dataPointer = MovePointer((byte*)BitmapData.Scan0, x, y);
dataPointer[3] = color.A;
dataPointer[2] = color.R;
dataPointer[1] = color.G;
dataPointer[0] = color.B;
}
}
public void Release()
{
Bitmap.UnlockBits(BitmapData);
BitmapData = null;
}
private unsafe byte* MovePointer(byte* pointer, int x, int y)
{
return pointer + x * 4 + y * BitmapData.Stride;
}
}
Then a class representing a rectangle containing different pixels, to mark them in the resulting image. In general this class can also contain a list of Point instances (or a byte[,] map) to make indicating individual pixels in the resulting image possible:
class Segment
{
public int Left { get; set; }
public int Top { get; set; }
public int Right { get; set; }
public int Bottom { get; set; }
public Bitmap Bitmap { get; set; }
public Segment()
{
Left = int.MaxValue;
Right = int.MinValue;
Top = int.MaxValue;
Bottom = int.MinValue;
}
};
Then the steps of a simple algorithm are as follows:
find different pixels
use a flood-fill algorithm to find segments on the difference image
draw bounding rectangles for the segments found
The first step is the easiest one:
static Bitmap FindDifferentPixels(Bitmap i1, Bitmap i2)
{
var result = new Bitmap(i1.Width, i2.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
var ia1 = new BitmapWithAccess(i1, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var ia2 = new BitmapWithAccess(i2, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var ra = new BitmapWithAccess(result, System.Drawing.Imaging.ImageLockMode.ReadWrite);
for (int x = 0; x < i1.Width; ++x)
for (int y = 0; y < i1.Height; ++y)
{
var different = ia1.GetPixel(x, y) != ia2.GetPixel(x, y);
ra.SetPixel(x, y, different ? Color.White : Color.FromArgb(0, 0, 0, 0));
}
ia1.Release();
ia2.Release();
ra.Release();
return result;
}
And the second and the third steps are covered with the following three functions:
static List<Segment> Segmentize(Bitmap blackAndWhite)
{
var bawa = new BitmapWithAccess(blackAndWhite, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var result = new List<Segment>();
HashSet<Point> queue = new HashSet<Point>();
bool[,] visitedPoints = new bool[blackAndWhite.Width, blackAndWhite.Height];
for (int x = 0;x < blackAndWhite.Width;++x)
for (int y = 0;y < blackAndWhite.Height;++y)
{
if (bawa.GetPixel(x, y).A != 0
&& !visitedPoints[x, y])
{
result.Add(BuildSegment(new Point(x, y), bawa, visitedPoints));
}
}
bawa.Release();
return result;
}
static Segment BuildSegment(Point startingPoint, BitmapWithAccess bawa, bool[,] visitedPoints)
{
var result = new Segment();
List<Point> toProcess = new List<Point>();
toProcess.Add(startingPoint);
while (toProcess.Count > 0)
{
Point p = toProcess.First();
toProcess.RemoveAt(0);
ProcessPoint(result, p, bawa, toProcess, visitedPoints);
}
return result;
}
static void ProcessPoint(Segment segment, Point point, BitmapWithAccess bawa, List<Point> toProcess, bool[,] visitedPoints)
{
for (int i = -1; i <= 1; ++i)
{
for (int j = -1; j <= 1; ++j)
{
int x = point.X + i;
int y = point.Y + j;
if (x < 0 || y < 0 || x >= bawa.Bitmap.Width || y >= bawa.Bitmap.Height)
continue;
if (bawa.GetPixel(x, y).A != 0 && !visitedPoints[x, y])
{
segment.Left = Math.Min(segment.Left, x);
segment.Right = Math.Max(segment.Right, x);
segment.Top = Math.Min(segment.Top, y);
segment.Bottom = Math.Max(segment.Bottom, y);
toProcess.Add(new Point(x, y));
visitedPoints[x, y] = true;
}
}
}
}
And the following program given your two images as arguments:
static void Main(string[] args)
{
Image ai1 = Image.FromFile(args[0]);
Image ai2 = Image.FromFile(args[1]);
Bitmap i1 = new Bitmap(ai1.Width, ai1.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Bitmap i2 = new Bitmap(ai2.Width, ai2.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var g1 = Graphics.FromImage(i1))
using (var g2 = Graphics.FromImage(i2))
{
g1.DrawImage(ai1, Point.Empty);
g2.DrawImage(ai2, Point.Empty);
}
var difference = FindDifferentPixels(i1, i2);
var segments = Segmentize(difference);
using (var g1 = Graphics.FromImage(i1))
{
foreach (var segment in segments)
{
g1.DrawRectangle(Pens.Red, new Rectangle(segment.Left, segment.Top, segment.Right - segment.Left, segment.Bottom - segment.Top));
}
}
i1.Save("result.png");
Console.WriteLine("Done.");
Console.ReadKey();
}
produces the following result:
As you can see there are more differences between the given images. You can filter the resulting segments with regard to their size for example to drop the small artefacts. Also there is of course much work to do in terms of error checking, design and performance.
One idea is to proceed as follows:
1) Rescale images to a smaller size (downsample)
2) Run the above algorithm on smaller images
3) Run the above algorithm on original images, but restricting yourself only to rectangles found in step 2)
This can be of course extended to a multi-level hierarchical approach (using more different image sizes, increasing accuracy with each step).
Ah an algorithm challenge. Like! :-)
There are other answers here using f.ex. floodfill that will work just fine. I just noticed that you wanted something fast, so let me propose a different idea. Unlike the other people, I haven't tested it; it shouldn't be too hard and should be quite fast, but I simply don't have the time at the moment to test it myself. If you do, please share the results. Also, note that it's not a standard algorithm, so there are probably some bugs here and there in my explanation (and no patents).
My idea is derived from the idea of mean adaptive thresholding but with a lot of important differences. I cannot find the link from wikipedia anymore or my code, so I'll do this from the top of my mind. Basically you create a new (64-bit) buffer for both images and fill it with:
f(x,y) = colorvalue + f(x-1, y) + f(x, y-1) - f(x-1, y-1)
f(x,0) = colorvalue + f(x-1, 0)
f(0,y) = colorvalue + f(0, y-1)
The main trick is that you can calculate the sum value of a portion of the image fast, namely by:
g(x1,y1,x2,y2) = f(x2,y2)-f(x1-1,y2)-f(x2,y1-1)+f(x1-1,y1-1)
In other words, this will give the same result as:
result = 0;
for (x=x1; x<=x2; ++x)
for (y=y1; y<=y2; ++y)
result += f(x,y)
In our case this means that with only 4 integer operations this will get you some unique number of the block in question. I'd say that's pretty awesome.
Now, in our case, we don't really care about the average value; we just care about some sort-of unique number. If the image changes, it should change - simple as that. As for colorvalue, usually some gray scale number is used for thresholding - instead, we'll be using the complete 24-bit RGB value. Because there are only so few compares, we can simply scan until we find a block that doesn't match.
The basic algorithm that I propose works as follows:
for (y=0; y<height;++y)
for (x=0; x<width; ++x)
if (src[x,y] != dst[x,y])
if (!IntersectsWith(x, y, foundBlocks))
FindBlock(foundBlocks);
Now, IntersectsWith can be something like a quad tree of if there are only a few blocks, you can simply iterate through the blocks and check if they are within the bounds of the block. You can also update the x variable accordingly (I would). You can even balance things by re-building the buffer for f(x,y) if you have too many blocks (more precise: merge found blocks back from dst into src, then rebuild the buffer).
FindBlocks is where it gets interesting. Using the formula for g that's now pretty easy:
int x1 = x-1; int y1 = y-1; int x2 = x; int y2 = y;
while (changes)
{
while (g(srcimage,x1-1,y1,x1,y2) == g(dstimage,x1-1,y1,x1,y2)) { --x1; }
while (g(srcimage,x1,y1-1,x1,y2) == g(dstimage,x1,y1-1,x1,y2)) { --y1; }
while (g(srcimage,x1,y1,x1+1,y2) == g(dstimage,x1,y1,x1+1,y2)) { ++x1; }
while (g(srcimage,x1,y1,x1,y2+1) == g(dstimage,x1,y1,x1,y2+1)) { ++y1; }
}
That's it. Note that the complexity of the FindBlocks algorithm is O(x + y), which is pretty awesome for finding a 2D block IMO. :-)
As I said, let me know how it turns out.

Set white and black pixel depends on brightness

I'm trying to find the needle from these or similar pictures:
My solution is take average brightness of picture and set black and white pixels depends on it. The result is something like this:
I dont need to see numbers and staf there.. only needle but its not big problem when its there. But im using set, get pixel functions and its realy slow i reed something about LockBits metod, i use it for get brightness but i dont know how to use it correct for setpixel. Can anyone help me with my code?..thnx
BitmapData imageData = imgPristroj.LockBits(new Rectangle(0, 0, imgPristroj.Width, imgPristroj.Height), ImageLockMode.ReadOnly, imgPristroj.PixelFormat);
float brightness = 0;
float average;
unsafe
{
try
{
UnmanagedImage Unimg = new UnmanagedImage(imageData);
int pixelSize = (Unimg.PixelFormat == PixelFormat.Format24bppRgb) ? 3 : 4;
byte* p = (byte*)Unimg.ImageData.ToPointer();
for (int y = 0; y < Unimg.Height; y++)
{
for (int x = 0; x < Unimg.Width; x++, p += pixelSize)
{
brightness += Unimg.GetPixel(x, y).GetBrightness();
}
}
average = brightness / (Unimg.Width * Unimg.Height);
}
finally
{
imgPristroj.UnlockBits(imageData); //Unlock
}
}
img19 = (Bitmap)imgPristroj.Clone();
for (int y = 0; y < img19.Height; y++)
{
for (int x = 0; x < img19.Width; x++)
{
if (img19.GetPixel(x, y).GetBrightness() > average)
{
img19.SetPixel(x, y, Color.White);
}
else
{
img19.SetPixel(x, y, Color.Black);
}
}
}
Edit2: This is my whole code..
float brightness = 0;
float average = 0;
PixelUtil pixelUtil2 = new PixelUtil((Bitmap)imgPristroj.Clone());
pixelUtil2.LockBits();
for (int y = 0; y < imgPristroj.Height; y++)
{
// for each pixel
for (int x = 0; x < imgPristroj.Width; x++)
{
brightness += pixelUtil2.GetPixel(x, y).GetBrightness();
}
}
average = brightness / (imgPristroj.Width * imgPristroj.Height);
pixelUtil2.UnlockBits();
img19 = (Bitmap)imgPristroj.Clone();
Crop cfilter1 = new Crop(new Rectangle(0, (int)(pix * 1.6), img19.Width, (int)(pix * 3)));
img19 = cfilter1.Apply(img19);
PixelUtil pixelUtil = new PixelUtil(img19);
pixelUtil.LockBits();
for (int y = 0; y < img19.Height; y++)
{
for (int x = 0; x < img19.Width; x++)
{
if (pixelUtil.GetPixel(x, y).GetBrightness() >= average)
{
pixelUtil.SetPixel(x, y, Color.White);
}
else
{
pixelUtil.SetPixel(x, y, Color.Black);
}
}
}
pixelUtil.UnlockBits();
string filepath = Environment.CurrentDirectory;
string fileName = System.IO.Path.Combine(filepath, #"img" + ".bmp");
img19.Save(fileName);
I get this bitmap with color pixels, can you tell me why?
And when i use red color.. not black... (pixelUtil.SetPixel(x, y, Color.Red);) i have got this funny pic. (differnt size is OK..)
You can try using marshaling: It's quite fast. (You just need to copy-paste this)
public class PixelUtil
{
Bitmap source = null;
IntPtr Iptr = IntPtr.Zero;
BitmapData bitmapData = null;
public byte[] Pixels { get; set; }
public int Depth { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
/// <summary>
/// Pixel marshaling class, use this to get and set pixels rapidly.
/// </summary>
/// <param name="source">The Bitmap to work with</param>
public PixelUtil(Bitmap source)
{
this.source = source;
}
/// <summary>
/// Lock bitmap data
/// </summary>
public void LockBits()
{
try
{
// Get width and height of bitmap
Width = source.Width;
Height = source.Height;
// get total locked pixels count
int PixelCount = Width * Height;
// Create rectangle to lock
var rect = new Rectangle(0, 0, Width, Height);
// get source bitmap pixel format size
Depth = System.Drawing.Image.GetPixelFormatSize(source.PixelFormat);
// Check if bpp (Bits Per Pixel) is 8, 24, or 32
if (Depth != 8 && Depth != 24 && Depth != 32)
{
throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
}
// Lock bitmap and return bitmap data
bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
source.PixelFormat);
// create byte array to copy pixel values
int step = Depth / 8;
Pixels = new byte[PixelCount * step];
Iptr = bitmapData.Scan0;
// Copy data from pointer to array
Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Unlock bitmap data
/// </summary>
public void UnlockBits()
{
try
{
// Copy data from byte array to pointer
Marshal.Copy(Pixels, 0, Iptr, Pixels.Length);
// Unlock bitmap data
source.UnlockBits(bitmapData);
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Get the color of the specified pixel
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Color GetPixel(int x, int y)
{
Color clr = Color.Empty;
// Get color components count
int cCount = Depth / 8;
// Get start index of the specified pixel
int i = ((y * Width) + x) * cCount;
if (i > Pixels.Length - cCount)
throw new IndexOutOfRangeException();
if (Depth == 32) //For 32 bpp get Red, Green, Blue and Alpha
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
byte a = Pixels[i + 3]; // a
clr = Color.FromArgb(a, r, g, b);
}
if (Depth == 24) //For 24 bpp get Red, Green and Blue
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
clr = Color.FromArgb(r, g, b);
}
if (Depth == 8) //For 8 bpp get color value (Red, Green and Blue values are the same)
{
byte c = Pixels[i];
clr = Color.FromArgb(c, c, c);
}
return clr;
}
/// <summary>
/// Set the color of the specified pixel
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="color"></param>
public void SetPixel(int x, int y, Color color)
{
//Get color components count
int cCount = Depth / 8;
//Get start index of the specified pixel
int i = ((y * Width) + x) * cCount;
if (Depth == 32) //For 32 bpp set Red, Green, Blue and Alpha
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
Pixels[i + 3] = color.A;
}
if (Depth == 24) //For 24 bpp set Red, Green and Blue
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
}
if (Depth == 8) //For 8 bpp set color value (Red, Green and Blue values are the same)
{
Pixels[i] = color.B;
}
}
}
You can call like this:
public void Example(Bitmap image)
{
PixelUtil pixelUtil = new PixelUtil(image);
pixelUtil.LockBits();
Color firstPixel = pixelUtil.GetPixel(0, 0);
pixelUtil.SetPixel(0, 0, Color.White);
//Don't forget to unlock!
pixelUtil.UnlockBits();
}
EDIT:
Not sure how are you saving the image, but it Works fine for me:
public Form1()
{
InitializeComponent();
Bitmap image1 = new Bitmap(#"C:\Users\Nicke Manarin\Desktop\YEeJO.png");
BlackWhite(image1);
}
public void BlackWhite(Bitmap image)
{
PixelUtil pixelUtil = new PixelUtil(image);
pixelUtil.LockBits();
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
if (pixelUtil.GetPixel(x, y).GetBrightness() > 0.5)
{
pixelUtil.SetPixel(x, y, Color.White);
}
else
{
pixelUtil.SetPixel(x, y, Color.Black);
}
}
}
pixelUtil.UnlockBits();
pictureBox1.Image = image;
image.Save(#"C:\Users\Nicke Manarin\Desktop\YEeJO2.png");
}
I use this code and now its working good.... thnx again !!!!
public class PointBitmap
{
Bitmap source = null;
IntPtr Iptr = IntPtr.Zero;
BitmapData bitmapData = null;
public int Depth { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
public PointBitmap(Bitmap source)
{
this.source = source;
}
public void LockBits()
{
try
{
// Get width and height of bitmap
Width = source.Width;
Height = source.Height;
// get total locked pixels count
int PixelCount = Width * Height;
// Create rectangle to lock
Rectangle rect = new Rectangle(0, 0, Width, Height);
// get source bitmap pixel format size
Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
// Check if bpp (Bits Per Pixel) is 8, 24, or 32
if (Depth != 8 && Depth != 24 && Depth != 32)
{
throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
}
// Lock bitmap and return bitmap data
bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
source.PixelFormat);
unsafe
{
Iptr = bitmapData.Scan0;
}
}
catch (Exception ex)
{
throw ex;
}
}
public void UnlockBits()
{
try
{
source.UnlockBits(bitmapData);
}
catch (Exception ex)
{
throw ex;
}
}
public Color GetPixel(int x, int y)
{
unsafe
{
byte* ptr = (byte*)Iptr;
ptr = ptr + bitmapData.Stride * y;
ptr += Depth * x / 8;
Color c = Color.Empty;
if (Depth == 32)
{
int a = ptr[3];
int r = ptr[2];
int g = ptr[1];
int b = ptr[0];
c = Color.FromArgb(a, r, g, b);
}
else if (Depth == 24)
{
int r = ptr[2];
int g = ptr[1];
int b = ptr[0];
c = Color.FromArgb(r, g, b);
}
else if (Depth == 8)
{
int r = ptr[0];
c = Color.FromArgb(r, r, r);
}
return c;
}
}
public void SetPixel(int x, int y, Color c)
{
unsafe
{
byte* ptr = (byte*)Iptr;
ptr = ptr + bitmapData.Stride * y;
ptr += Depth * x / 8;
if (Depth == 32)
{
ptr[3] = c.A;
ptr[2] = c.R;
ptr[1] = c.G;
ptr[0] = c.B;
}
else if (Depth == 24)
{
ptr[2] = c.R;
ptr[1] = c.G;
ptr[0] = c.B;
}
else if (Depth == 8)
{
ptr[2] = c.R;
ptr[1] = c.G;
ptr[0] = c.B;
}
}
}
}
i think only difference here is Unsafe modifier

Index was outside of bounds of the array while comparing images

I am trying to figure this one out. I am comparing images in my program. For the most part when I try and compare the image I have no issues. Although when I use a few different combinations I get this error. I originally thought it was because the image formats were different. Although that is not the case.
If I take each image and compare them against something else it works fine. Just not when compared against eachother. I have marked below where the error appears.
public Color this[int x, int y]
{
get
{
int index = (x + (y * image.Width)) * 4;
return Color.FromArgb(rgbValues[index + 3], rgbValues[index + 2], rgbValues[index + 1], rgbValues[index]); //This line is where the index out of bounds of the array error happens.
}
set
{
int index = (x + (y * image.Width)) * 4;
rgbValues[index] = value.B;
rgbValues[index + 1] = value.G;
rgbValues[index + 2] = value.R;
rgbValues[index + 3] = value.A;
}
}
/// <summary>
/// Width of the image.
/// </summary>
public int Width
{
get
{
return image.Width;
}
}
/// <summary>
/// Height of the image.
/// </summary>
public int Height
{
get
{
return image.Height;
}
}
/// <summary>
/// Returns the modified Bitmap.
/// </summary>
I would appreciate any help. I am so confused to why it only happens when the images are used in combination.
In case you were wondering how I load the image I do it in vb.net. The C# code is in a dll. Here is my vb.net code to load the bitmap to the memory. Also the image size is: 431x253
Dim bm As Bitmap = Bitmap.FromFile(Label1.Text)
Dim bm2 As Bitmap = Bitmap.FromFile(Label2.Text)
' Dim pnt As Point = ImageFinder.Contains(bm, bm2)
Dim ir As New ImageChecker(bm, bm2)
Dim imageContains As Boolean = ir.findimageboolean()
MessageBox.Show(imageContains)
EDIT: Here is the full code of the DLL
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
namespace ImageRecognition
{
public class LockedFastImage
{
private Bitmap image;
private byte[] rgbValues;
private System.Drawing.Imaging.BitmapData bmpData;
private IntPtr ptr;
private int bytes;
public LockedFastImage(Bitmap image)
{
this.image = image;
Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);
bmpData = image.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, image.PixelFormat);
ptr = bmpData.Scan0;
bytes = Math.Abs(bmpData.Stride) * image.Height;
rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
}
~LockedFastImage()
{
// Try to unlock the bits. Who cares if it dont work...
try
{
image.UnlockBits(bmpData);
}
catch { }
}
/// <summary>
/// Returns or sets a pixel of the image.
/// </summary>
/// <param name="x">x parameter of the pixel</param>
/// <param name="y">y parameter of the pixel</param>
public Color this[int x, int y]
{
get
{
int index = (x + (y * image.Width)) * 4;
return Color.FromArgb(rgbValues[index + 3], rgbValues[index + 2], rgbValues[index + 1], rgbValues[index]);
}
set
{
int index = (x + (y * image.Width)) * 4;
rgbValues[index] = value.B;
rgbValues[index + 1] = value.G;
rgbValues[index + 2] = value.R;
rgbValues[index + 3] = value.A;
}
}
/// <summary>
/// Width of the image.
/// </summary>
public int Width
{
get
{
return image.Width;
}
}
/// <summary>
/// Height of the image.
/// </summary>
public int Height
{
get
{
return image.Height;
}
}
/// <summary>
/// Returns the modified Bitmap.
/// </summary>
public Bitmap asBitmap()
{
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
return image;
}
}
public class ImageChecker
{
private LockedFastImage big_image;
private LockedFastImage small_image;
/// <summary>
/// The time needed for last operation.
/// </summary>
public TimeSpan time_needed = new TimeSpan();
/// <summary>
/// Error return value.
/// </summary>
static public Point CHECKFAILED = new Point(-1, -1);
/// <summary>
/// Constructor of the ImageChecker
/// </summary>
/// <param name="big_image">The image containing the small image.</param>
/// <param name="small_image">The image located in the big image.</param>
public ImageChecker(Bitmap big_image, Bitmap small_image)
{
this.big_image = new LockedFastImage(big_image);
this.small_image = new LockedFastImage(small_image);
}
/// <summary>
/// Returns the location of the small image in the big image. Returns CHECKFAILED if not found.
/// </summary>
/// <param name="x_speedUp">speeding up at x achsis.</param>
/// <param name="y_speedUp">speeding up at y achsis.</param>
/// <param name="begin_percent_x">Reduces the search rect. 0 - 100</param>
/// <param name="end_percent_x">Reduces the search rect. 0 - 100</param>
/// <param name="begin_percent_x">Reduces the search rect. 0 - 100</param>
/// <param name="end_percent_y">Reduces the search rect. 0 - 100</param>
public Point bigContainsSmall(int x_speedUp = 4, int y_speedUp = 4, int begin_percent_x = 0, int end_percent_x = 100, int begin_percent_y = 0, int end_percent_y = 100)
{
/*
* SPEEDUP PARAMETER
* It might be enough to check each second or third pixel in the small picture.
* However... In most cases it would be enough to check 4 pixels of the small image for diablo porposes.
* */
/*
* BEGIN, END PARAMETER
* In most cases we know where the image is located, for this we have the begin and end paramenters.
* */
DateTime begin = DateTime.Now;
if (x_speedUp < 1) x_speedUp = 1;
if (y_speedUp < 1) y_speedUp = 1;
if (begin_percent_x < 0 || begin_percent_x > 100) begin_percent_x = 0;
if (begin_percent_y < 0 || begin_percent_y > 100) begin_percent_y = 0;
if (end_percent_x < 0 || end_percent_x > 100) end_percent_x = 100;
if (end_percent_y < 0 || end_percent_y > 100) end_percent_y = 100;
int x_start = (int)((double)big_image.Width * ((double)begin_percent_x / 100.0));
int x_end = (int)((double)big_image.Width * ((double)end_percent_x / 100.0));
int y_start = (int)((double)big_image.Height * ((double)begin_percent_y / 100.0));
int y_end = (int)((double)big_image.Height * ((double)end_percent_y / 100.0));
/*
* We cant speed up the big picture, because then we have to check pixels in the small picture equal to the speeded up size
* for each pixel in the big picture.
* Would give no speed improvement.
* */
//+ 1 because first pixel is in picture. - small because image have to be fully in the other image
for (int x = x_start; x < x_end - small_image.Width + 1; x++)
for (int y = y_start; y < y_end - small_image.Height + 1; y++)
{
//now we check if all pixels matches
for (int sx = 0; sx < small_image.Width; sx += x_speedUp)
for (int sy = 0; sy < small_image.Height; sy += y_speedUp)
{
if (small_image[sx, sy] != big_image[x + sx, y + sy])
goto CheckFailed;
}
//check ok
time_needed = DateTime.Now - begin;
return new Point(x, y);
CheckFailed: ;
}
time_needed = DateTime.Now - begin;
return CHECKFAILED;
}
public Boolean findimageboolean(int x_speedUp = 1, int y_speedUp = 1, int begin_percent_x = 0, int end_percent_x = 100, int begin_percent_y = 0, int end_percent_y = 100)
{
/*
* SPEEDUP PARAMETER
* It might be enough to check each second or third pixel in the small picture.
* However... In most cases it would be enough to check 4 pixels of the small image for diablo porposes.
* */
/*
* BEGIN, END PARAMETER
* In most cases we know where the image is located, for this we have the begin and end paramenters.
* */
DateTime begin = DateTime.Now;
if (x_speedUp < 1) x_speedUp = 1;
if (y_speedUp < 1) y_speedUp = 1;
if (begin_percent_x < 0 || begin_percent_x > 100) begin_percent_x = 0;
if (begin_percent_y < 0 || begin_percent_y > 100) begin_percent_y = 0;
if (end_percent_x < 0 || end_percent_x > 100) end_percent_x = 100;
if (end_percent_y < 0 || end_percent_y > 100) end_percent_y = 100;
int x_start = (int)((double)big_image.Width * ((double)begin_percent_x / 100.0));
int x_end = (int)((double)big_image.Width * ((double)end_percent_x / 100.0));
int y_start = (int)((double)big_image.Height * ((double)begin_percent_y / 100.0));
int y_end = (int)((double)big_image.Height * ((double)end_percent_y / 100.0));
/*
* We cant speed up the big picture, because then we have to check pixels in the small picture equal to the speeded up size
* for each pixel in the big picture.
* Would give no speed improvement.
* */
//+ 1 because first pixel is in picture. - small because image have to be fully in the other image
for (int x = x_start; x < x_end - small_image.Width + 1; x++)
for (int y = y_start; y < y_end - small_image.Height + 1; y++)
{
//now we check if all pixels matches
for (int sx = 0; sx < small_image.Width; sx += x_speedUp)
for (int sy = 0; sy < small_image.Height; sy += y_speedUp)
{
if (small_image[sx, sy] != big_image[x + sx, y + sy])
goto CheckFailed;
}
//check ok
time_needed = DateTime.Now - begin;
return true;
CheckFailed: ;
}
time_needed = DateTime.Now - begin;
return false;
}
}
}
If Stride is negative you need to copy image bytes in a different way, so I made some modifications to your constructor:
public LockedFastImage(Bitmap image)
{
this.image = image;
Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);
bmpData = image.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
ptr = bmpData.Scan0;
bytes = Math.Abs(bmpData.Stride) * image.Height;
rgbValues = new byte[bytes];
if (bmpData.Stride < 0)
{
int lines, pos, BytesPerLine = Math.Abs(bmpData.Stride);
for (lines = pos = 0; lines < image.Height; lines++, pos += BytesPerLine)
{
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, pos, BytesPerLine);
ptr = (IntPtr)(ptr.ToInt64() + bmpData.Stride);
}
}
else
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
}
If you are storing pixel data as ARGB, you need to lock pixels using the corresponding PixelFormat.
I also modified your accessors:
public Color this[int x, int y]
{
get
{
int index = y * (image.Width << 2) + x;
return Color.FromArgb(rgbValues[index + 3], rgbValues[index + 2], rgbValues[index + 1], rgbValues[index]);
}
set
{
int index = y * (image.Width << 2) + x;
rgbValues[index] = value.B;
rgbValues[index + 1] = value.G;
rgbValues[index + 2] = value.R;
rgbValues[index + 3] = value.A;
}
}
By the way, I haven't tested this code!
Strictly speaking, you should save BytesPerLine as a private field and then use it to calculate index in your accessors. So you should change this (image.Width << 2) to BytesPerLine.
Also you can try changing your get accessor to the following (not sure if it's faster, but you can try it if you wish):
get
{
return Color.FromArgb(BitConverter.ToInt32(rgbValues, y * (image.Width << 2) + x));
}

Automatically trim a bitmap to minimum size?

Suppose I have a System.Drawing.Bitmap in 32bpp ARGB mode. It's a large bitmap, but it's mostly fully transparent pixels with a relatively small image somewhere in the middle.
What is a fast algorithm to detect the borders of the "real" image, so I can crop away all the transparent pixels from around it?
Alternatively, is there a function already in .Net that I can use for this?
The basic idea is to check every pixel of the image to find the top, left, right and bottom bounds of the image. To do this efficiently, don't use the GetPixel method, which is pretty slow. Use LockBits instead.
Here's the implementation I came up with:
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;
int xMax = 0;
int yMin = int.MaxValue;
int yMax = 0;
for (int y = 0; y < data.Height; y++)
{
for (int x = 0; x < data.Width; x++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
if (x < xMin) xMin = x;
if (x > xMax) xMax = x;
if (y < yMin) yMin = y;
if (y > yMax) yMax = y;
}
}
}
if (xMax < xMin || yMax < yMin)
{
// Image is empty...
return null;
}
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;
}
It can probably be optimized, but I'm not a GDI+ expert, so it's the best I can do without further research...
EDIT: actually, there's a simple way to optimize it, by not scanning some parts of the image :
scan left to right until you find a non-transparent pixel; store (x, y) into (xMin, yMin)
scan top to bottom until you find a non-transparent pixel (only for x >= xMin); store y into yMin
scan right to left until you find a non-transparent pixel (only for y >= yMin); store x into xMax
scan bottom to top until you find a non-transparent pixel (only for xMin <= x <= xMax); store y into yMax
EDIT2: here's an implementation of the approach above:
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;
}
There won't be a significant gain if the non-transparent part is small of course, since it will still scan most of the pixels. But if it's big, only the rectangles around the non-transparent part will be scanned.
I would like to suggest a divide & conquer approach:
split the image in the middle (e.g. vertically)
check if there are non-transparent pixels on the cut line (if so, remember min/max for bounding box)
split left half again vertically
if cut line contains non-transparent pixels -> update bounding box
if not, you can probably discard the leftmost half (I don't know the pictures)
continue with the left-right half (you stated that the image is somewhere in the middle) until you find the leftmost bound of the image
do the same for the right half

Categories

Resources