Random AccessViolationExceptions From Working With Bitmaps - c#

I'm trying to make a program that, using pointers, detects lines in an image and removes those lines. Currently, the detecting lines part is working really well, and for the most part the removing the lines part is working as well. However, after around 150-200 images, the program will throw random AccessViolationExceptions in places that have nothing to do with the unsafe bits of the code.
This is the bit that does the line removal:
static unsafe Bitmap RemoveLines(Bitmap input, int[] horizontalLines, int[] verticalLines)
{
Bitmap output;
if (input.PixelFormat == PixelFormat.Format24bppRgb)
{
output = (Bitmap) input.Clone();
}
else
{
output = ConvertTo24bpp((Bitmap)input.Clone());
}
BitmapData bitmapData = output.LockBits(new Rectangle(0, 0, output.Width, output.Height), ImageLockMode.ReadWrite, output.PixelFormat);
int w = output.Width;
int h = output.Height;
int bpp = 3;
int s = bitmapData.Stride;
byte* p = (byte*) bitmapData.Scan0;
for (int r = 0; r < h; r++)
{
for (int c = 0; c < h; c++)
{
if (horizontalLines.Contains(r) || verticalLines.Contains(c))
{
int i = (r * s) + c * bpp;
p[i + 0] = 255;
p[i + 1] = 255;
p[i + 2] = 255;
}
}
}
output.UnlockBits(bitmapData);
return output;
}
After this code, I save the resulting Bitmap as well as embedding it in another Bitmap for comparison purposes:
// ... Detect lines and such
Bitmap export = new Bitmap(bitmap.Width * 3, bitmap.Height, PixelFormat.Format24bppRgb);
Graphics fg = Graphics.FromImage(export);
fg.DrawImage(bitmap, 0, 0); // Draw the original input bitmap
fg.DrawImage(edited, bitmap.Width, 0); // Draw the input after processing (Line Detection)
try
{
Bitmap lineRemoved = RemoveLines(bitmap, horizontalLines.ToArray(), verticalLines.ToArray()); // Remove lines based on earlier detection
lineRemoved.Save(cellDirectory + "\\Lines\\cell_lr_" + i.ToString("D2") + j.ToString("D2") + ".gif", ImageFormat.Gif); // Save image after removal
fg.DrawImage(lineRemoved, bitmap.Width * 2, 0); // Add image to composite for comparison; This line is what throws the error most of the time
lineRemoved.Dispose();
export.Save(cellDirectory + "\\Lines\\cell" + i.ToString("D2") + j.ToString("D2") + ".gif", ImageFormat.Gif);
}
catch (Exception ex)
{ }
The DrawImage call is what throws errors, and it is always an AccessViolationException followed by an InvalidOperationException. Looking at lineRemoved during the error shows that most of its members have "threw exception of type 'InvalidOperationException'" rather than actual values, even though one line before the same Bitmap saved just fine on its own. The input bitmap remains unchanged throughout the code, and is always Cloned or drawn to a different bitmap when I need to alter it in any way.
I've tried commenting out the lines after saving lineRemoved, but then the same error pops up later in the code. What's more, that try/catch doesn't actually catch the Exception - it always says unhandled. It's got to be something to do with the pointers, but otherwise I am completely lost as to what is causing this.

Your code contains a subtle one-character bug. The line that reads
for (int c = 0; c < h; c++)
should be
for (int c = 0; c < w; c++)
If the image is in landscape orientation, your bug would cause the right part of the image not being processed.
If the image is in protrait orientation, it would cause the buffer to overflow, leading to an access violation exception (if you're lucky) or memory corruption (if you're not).
That being said, your algorithm is not very efficient. For example, you are doing the calculation
int i = (r * s) + c * bpp;
for every pixel you're drawing, while obviously (r * s) doesn't change in the inner loop, and c * bpp can be replaced by something like currentPixel += bpp.
In fact it would probably be more efficient to loop over horizontalLines and verticalLines.

Related

C# How do I convert my get GetPixel / SetPixel color processing to Lockbits?

EDIT: I deeply appreciate the replies. What I need more than anything here is sample code for what I do with the few lines of code in the nested loop, since that's what works right in GetPixel/SetPixel, but also what I can't get to work right using Lockbits. Thank you
I'm trying to convert my image processing filters from GetPixel / SetPixel to Lockbits, to improve processing time. I have seen Lockbits tutorials here on Stack Overflow, MSDN, and other sites as well, but I'm doing something wrong. I'm starting with an exceedingly simple filter, which simply reduces green to create a red and purple effect. Here's my code:
private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Get bitmap from picturebox
Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();
// search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.
for (int y = 0; y < bmpMain.Height; y++)
for (int x = 0; x < bmpMain.Width; x++)
{
bmpMain.GetPixel(x, y);
Color c = bmpMain.GetPixel(x, y);
int myRed = c.R, myGreen = c.G, myBlue = c.B;
myGreen -= 128;
if (myGreen < 0) myGreen = 0;
bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
}
// assign the new bitmap to the picturebox
pictureBoxMain.Image = (Bitmap)bmpMain;
// Save a copy to the HD for undo / redo.
string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
}
So that GetPixel / SetPixel code works fine, but it's slow. So I tried this:
private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Get bitmap from picturebox
Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();
Rectangle rect = new Rectangle(Point.Empty, bmpMain.Size);
BitmapData bmpData = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
// search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.
for (int y = 0; y < bmpMain.Height; y++)
for (int x = 0; x < bmpMain.Width; x++)
{
bmpMain.GetPixel(x, y);
Color c = new Color();
int myRed = c.R, myGreen = c.G, myBlue = c.B;
myGreen -= 128;
if (myGreen < 0) myGreen = 0;
bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
}
bmpMain.UnlockBits(bmpData);
// assign the new bitmap to the picturebox
pictureBoxMain.Image = (Bitmap)bmpMain;
// Save a copy to the HD for undo / redo.
string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
}
Which throws the error "An unhandled exception of type 'System.InvalidOperationException' occurred in System.Drawing.dll Additional information: Bitmap region is already locked" when it reaches the first line of the nested loop.
I realize this has to be a beginner's error, I'd appreciate if someone could demonstrate the correct way to convert this very simple filter to Lockbits. Thank you very much
The array returned by scan0 is in this format BGRA BGRA BGRA BGRA ... and so on,
where B = Blue, G = Green, R = Red, A = Alpha.
Example of a very small bitmap 4 pixels wide and 3 pixels height.
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
stride = width*bytesPerPixel = 4*4 = 16 bytes
height = 3
maxLenght = stride*height= 16*3 = 48 bytes
To reach a certain pixel in the image (x, y) use this formula
int certainPixel = bytesPerPixel*x + stride * y;
B = scan0[certainPixel + 0];
G = scan0[certainPixel + 1];
R = scan0[certainPixel + 2];
A = scan0[certainPixel + 3];
public unsafe void Test(Bitmap bmp)
{
int width = bmp.Width;
int height = bmp.Height;
//TODO determine bytes per pixel
int bytesPerPixel = 4; // we assume that image is Format32bppArgb
int maxPointerLenght = width * height * bytesPerPixel;
int stride = width * bytesPerPixel;
byte R, G, B, A;
BitmapData bData = bmp.LockBits(
new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadWrite, bmp.PixelFormat);
byte* scan0 = (byte*)bData.Scan0.ToPointer();
for (int i = 0; i < maxPointerLenght; i += 4)
{
B = scan0[i + 0];
G = scan0[i + 1];
R = scan0[i + 2];
A = scan0[i + 3];
// do anything with the colors
// Set the green component to 0
G = 0;
// do something with red
R = R < 54 ? (byte)(R + 127) : R;
R = R > 255 ? 255 : R;
}
bmp.UnlockBits(bData);
}
You can test is yourself. Create a very small bitmap ( few pixels wide/height) in paint or any other program and put a breakpoint at the begining of the method.
Additional information: Bitmap region is already locked"
You now know why GetPixel() is slow, it also uses Un/LockBits under the hood. But does so for each individual pixel, the overhead steals cpu cycles. A bitmap can be locked only once, that's why you got the exception. Also the basic reason that you can't access a bitmap in multiple threads simultaneously.
The point of LockBits is that you can access the memory occupied by the bitmap pixels directly. The BitmapData.Scan0 member gives you the memory address. Directly addressing the memory is very fast. You'll however have to work with an IntPtr, the type of Scan0, that requires using a pointer or Marshal.Copy(). Using a pointer is the optimal way, there are many existing examples on how to do this, I won't repeat it here.
... = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
The last argument you pass is very, very important. It selects the pixel format of the data and that affects the code you write. Using bmpMain.PixelFormat is the fastest way to lock but it is also very inconvenient. Since that now requires you to adapt your code to the specific pixel format. There are many, take a good look at the PixelFormat enum. They differ in the number of bytes taken for each pixel and how the colors are encoded in the bits.
The only convenient pixel format is Format32bppArgb, every pixel takes 4 bytes, the color/alpha is encoded in a single byte and you can very easily and quickly address the pixels with an uint*. You can still deal with Format24bppRgb but you now need a byte*, that's a lot slower. The ones that have a P in the name are pre-multiplied formats, very fast to display but exceedingly awkward to deal with. You may thus be well ahead by taking the perf hit of forcing LockBits() to convert the pixel format. Paying attention to the pixel format up front is important to avoid this kind of lossage.

Parallel.For statement return "System.InvalidOperationException" with a Bitmap Processing

Well, I have a code to apply a Rain Bow filter in "x" image, I have to do in two ways: Sequential & parallel, my sequential code is working without problems, but the parallel section doesn't work. And I have no idea, why?.
Code
public static Bitmap RainbowFilterParallel(Bitmap bmp)
{
Bitmap temp = new Bitmap(bmp.Width, bmp.Height);
int raz = bmp.Height / 4;
Parallel.For(0, bmp.Width, i =>
{
Parallel.For(0, bmp.Height, x =>
{
if (i < (raz))
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R / 5, bmp.GetPixel(i, x).G, bmp.GetPixel(i, x).B));
}
else if (i < (raz * 2))
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R, bmp.GetPixel(i, x).G / 5, bmp.GetPixel(i, x).B));
}
else if (i < (raz * 3))
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R, bmp.GetPixel(i, x).G, bmp.GetPixel(i, x).B / 5));
}
else if (i < (raz * 4))
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R / 5, bmp.GetPixel(i, x).G, bmp.GetPixel(i, x).B / 5));
}
else
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R / 5, bmp.GetPixel(i, x).G / 5, bmp.GetPixel(i, x).B / 5));
}
});
});
return temp;
}
Besides, In a moments the program return the same error but says "The object is already in use".
PS. I'm beginner with c#, and I Searched this topic in another post and I found nothing.
Thank you very much in advance
As commenter Ron Beyer points out, using the SetPixel() and GetPixel() methods is very slow. Each call to one of those methods involves a lot of overhead in the transition between your managed code down to the actual binary buffer that the Bitmap object represents. There are a lot of layers there, and the video driver typically gets involved which requires transitions between user and kernel level execution.
But besides being slow, these methods also make the object "busy", and so if an attempt to use the bitmap (including calling one of those methods) is made between the time one of those methods is called and when it returns (i.e. while the call is in progress), an error occurs with the exception you saw.
Since the only way that parallelizing your current code would be helpful is if these method calls could occur concurrently, and since they simply cannot, this approach isn't going to work.
On the other hand, using the LockBits() method is not only guaranteed to work, there's a very good chance that you will find the performance is so much better using LockBits() that you don't even need to parallelize the algorithm. But should you decide you do, because of the way LockBits() works — you gain access to a raw buffer of bytes that represents the bitmap image — you can easily parallelize the algorithm and take advantage of multiple CPU cores (if present).
Note that when using LockBits() you will be working with the Bitmap object at a level that you might not be accustomed to. If you are not already knowledgeable with how bitmaps really work "under the hood", you will have to familiarize yourself with the way that bitmaps are actually stored in memory. This includes understanding what the different pixel formats mean, how to interpret and modify pixels for a given format, and how a bitmap is laid out in memory (e.g. the order of rows, which can vary depending on the bitmap, as well as the "stride" of the bitmap).
These things are not terribly hard to learn, but it will require patience. It is well worth the effort though, if performance is your goal.
Parallel is hard on the singular mind. And mixing it with legacy GDI+ code can lead to strange results..
Your code has numerous issues:
You call GetPixel three times per pixel instead of once
You are accessing the pixels not horizontally as you should
You call y x and x i; the machine won't mind but us people do
You are using way too much parallelization. No use to have much more of it than you have cores. It creates overhead that is bound to eat up any gains, unless your inner loop has a really hard job to do, like millions of calculations..
But the exception you get has nothing to do with these issues. And one mistake you don't make is to access the same pixel in parallel... So why the crash?
After cleaning up the code I found that the error in the stack trace pointed to SetPixel and there to System.Drawing.Image.get_Width(). The former is obvious, the latter not part of our code..!?
So I dug into the source code at referencesource.microsoft.com and found this:
/// <include file='doc\Bitmap.uex' path='docs/doc[#for="Bitmap.SetPixel"]/*' />
/// <devdoc>
/// <para>
/// Sets the color of the specified pixel in this <see cref='System.Drawing.Bitmap'/> .
/// </para>
/// </devdoc>
public void SetPixel(int x, int y, Color color) {
if ((PixelFormat & PixelFormat.Indexed) != 0) {
throw new InvalidOperationException(SR.GetString(SR.GdiplusCannotSetPixelFromIndexedPixelFormat));
}
if (x < 0 || x >= Width) {
throw new ArgumentOutOfRangeException("x", SR.GetString(SR.ValidRangeX));
}
if (y < 0 || y >= Height) {
throw new ArgumentOutOfRangeException("y", SR.GetString(SR.ValidRangeY));
}
int status = SafeNativeMethods.Gdip.GdipBitmapSetPixel(new HandleRef(this, nativeImage), x, y, color.ToArgb());
if (status != SafeNativeMethods.Gdip.Ok)
throw SafeNativeMethods.Gdip.StatusException(status);
}
The real work is done by SafeNativeMethods.Gdip.GdipBitmapSetPixel but before that the method does a bounds check on the Bitmap's Width and Height. And while these in our case of course never change the system still won't allow accessing them in parallel and hence crashes when at some point the checks are happening interwoven. Totally uncesessary, of course, but there you go..
So GetPixel (which has the same behaviour) and SetPixel can't safely be used in a parallel processing.
Two ways out of it:
We can add locks to the code and thus make sure the checks won't happen at the 'same' time:
public static Bitmap RainbowFilterParallel(Bitmap bmp)
{
Bitmap temp = new Bitmap(bmp);
int raz = bmp.Height / 4;
int height = bmp.Height;
int width = bmp.Width;
// set a limit to parallesim
int maxCore = 7;
int blockH = height / maxCore + 1;
//lock (temp)
Parallel.For(0, maxCore, cor =>
{
//Parallel.For(0, bmp.Height, x =>
for (int yb = 0; yb < blockH; yb++)
{
int i = cor * blockH + yb;
if (i >= height) continue;
for (int x = 0; x < width; x++)
{
{
Color c;
// lock the Bitmap just for the GetPixel:
lock (temp) c = temp.GetPixel(x, i);
byte R = c.R;
byte G = c.G;
byte B = c.B;
if (i < (raz)) { R = (byte)(c.R / 5); }
else if (i < raz + raz) { G = (byte)(c.G / 5); }
else if (i < raz * 3) { B = (byte)(c.B / 5); }
else if (i < raz * 4) { R = (byte)(c.R / 5); B = (byte)(c.B / 5); }
else { G = (byte)(c.G / 5); R = (byte)(c.R / 5); }
// lock the Bitmap just for the SetPixel:
lock (temp) temp.SetPixel(x, i, Color.FromArgb(R,G,B));
};
}
};
});
return temp;
}
Note that limiting parallism is so important there is even a member in the ParallelOptions class and a parameter inParallel.For to control it! I have set the maximum core numer to 7, but this would be better:
int degreeOfParallelism = Environment.ProcessorCount - 1;
So this should save us some overhead. But still: I'd expect that to be slower than a corrected sequential method!
Instead going for a LockBits as Peter and Ron have suggested method makes things really fast (1ox) and adding parallelism potentially even faster still..
So finally to finish up this length answer, here is a Lockbits plus Limited-Parallel solution:
public static Bitmap RainbowFilterParallelLockbits(Bitmap bmp)
{
Bitmap temp = null;
temp = new Bitmap(bmp);
int raz = bmp.Height / 4;
int height = bmp.Height;
int width = bmp.Width;
Rectangle rect = new Rectangle(Point.Empty, bmp.Size);
BitmapData bmpData = temp.LockBits(rect,ImageLockMode.ReadOnly, temp.PixelFormat);
int bpp = (temp.PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3;
int size = bmpData.Stride * bmpData.Height;
byte[] data = new byte[size];
System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, data, 0, size);
var options = new ParallelOptions();
int maxCore = Environment.ProcessorCount - 1;
options.MaxDegreeOfParallelism = maxCore > 0 ? maxCore : 1;
Parallel.For(0, height, options, y =>
{
for (int x = 0; x < width; x++)
{
{
int index = y * bmpData.Stride + x * bpp;
if (y < (raz)) data[index + 2] = (byte) (data[index + 2] / 5);
else if (y < (raz * 2)) data[index + 1] = (byte)(data[index + 1] / 5);
else if (y < (raz * 3)) data[index ] = (byte)(data[index ] / 5);
else if (y < (raz * 4))
{ data[index + 2] = (byte)(data[index + 2] / 5);
data[index] = (byte)(data[index] / 5); }
else
{ data[index + 2] = (byte)(data[index + 2] / 5);
data[index + 1] = (byte)(data[index + 1] / 5);
data[index] = (byte)(data[index] / 5); }
};
};
});
System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
temp.UnlockBits(bmpData);
return temp;
}
While not strictly relevant I wanted to post a better faster version than any of the one's that I see given in the answers. This is the fastest way I know of to iterate through a bitmap and save the results in C#. In my work we need to go through millions of large images, this is just me grabbing the red channel and saving it for my own purposes but it should give you the idea of how to work
//Parallel Unsafe, Corrected Channel, Corrected Standard div 5x faster
private void TakeApart_Much_Faster(Bitmap processedBitmap)
{
_RedMin = byte.MaxValue;
_RedMax = byte.MinValue;
_arr = new byte[BMP.Width, BMP.Height];
long Sum = 0,
SumSq = 0;
BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
//this is a much more useful datastructure than the array but it's slower to fill.
points = new ConcurrentDictionary<Point, byte>();
unsafe
{
int bytesPerPixel = Image.GetPixelFormatSize(bitmapData.PixelFormat) / 8;
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
_RedMin = byte.MaxValue;
_RedMax = byte.MinValue;
byte* PtrFirstPixel = (byte*)bitmapData.Scan0;
Parallel.For(0, heightInPixels, y =>
{
//pointer to the first pixel so we don't lose track of where we are
byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
//0+2 is red channel
byte redPixel = currentLine[x + 2];
Interlocked.Add(ref Sum, redPixel);
Interlocked.Add(ref SumSq, redPixel * redPixel);
//divide by three since we are skipping ahead 3 at a time.
_arr[x/3, y] = redPixel;
_RedMin = redPixel < _RedMin ? _RedMin : redPixel;
_RedMax = redPixel > RedMax ? RedMax : redPixel;
}
});
_RedMean = Sum / TotalPixels;
_RedStDev = Math.Sqrt((SumSq / TotalPixels) - (_RedMean * _RedMean));
processedBitmap.UnlockBits(bitmapData);
}
}

Search for Pixel on screen

I want to find a specific pixel coordinates from screen. Here's my code (I'm mega-super-newbie, I just started today with C#:
static string GetPixel(int X, int Y)
{
Point position = new Point(X, Y);
var bitmap = new Bitmap(1, 1);
var graphics = Graphics.FromImage(bitmap);
graphics.CopyFromScreen(position, new Point(0, 0), new Size(1, 1));
var _Pixel = bitmap.GetPixel(0, 0);
return "0x" + _Pixel.ToArgb().ToString("x").ToUpper().Remove(0, 2);
//it returns a pixel color in a form of "0xFFFFFF" hex code
//I had NO idea how to convert it to hex code so I did that :P
}
static void Main()
{
// for x = 1 to screen width...
for (int x = 1; x <= Screen.PrimaryScreen.Bounds.Bottom; x++)
{
// for x = 1 and y = 1 to screen height...
for (int y = 1; y <= Screen.PrimaryScreen.Bounds.Height; y++)
{
string pixel = GetPixel(x, y);
if (pixel == "0x007ACC") //blue color
{
MessageBox.Show("Found 0x007ACC at: (" + x + "," + y + ")");
break; //exit loop
}
}
}
}
edit:
Here's an error which appears when I run this script:
An unhandled exception of type 'System.ArgumentOutOfRangeException'
occurred in mscorlib.dll
Additional information: Index and length must refer to a location
within the string
I have experience in AutoIt, it's my first day with C# ^^
Regards
Welcome to SO.
Most coordinates and other things are 0-based, just as in arrays.
That being said, it would be best to use the Bounds' X/Y/Width and Height properties for the loops:
var bounds = Screen.PrimaryScreen.Bounds;
for (int x = bounds.X; x < bounds.Width; x++) {
for(int y = bounds.Y; y < bounds.Height; y++) {
..
And the proper way to convert the ARGB value to hex is to use the string.Format() method:
string hex = string.Format("0x{0:8x}", argb);
EDIT: Apparently Graphics.CopyFromScreen leaks handles like there's no tomorrow, which causes strange exceptions to be thrown when no more handles are available (source)
A quick workaround for your scenario could be to capture the whole screen once and then search in the bitmap, i.e. Graphics.CopyFromScreen(new Position(0, 0), new Position(0, 0), new Size(bounds.Width, bounds.Height));
Unfortunately this didn't get fixed in .Net 4.0 (don't know about 4.5), so the only proper solution seems to be to P/Invoke the native GDI functions, as described here.

Recognizing image within image in C#

I'd like to find an image (needle) within an image (haystack).
To keep things simple I take two screenshots of my desktop. One full size (haystack) and a tiny one (needle). I then loop through the haystack image and try to find the needle image.
capture needle and haystack screenshot
loop through haystack, looking out for haystack[i] == first pixel of needle
[if 2. is true:] loop through the 2nd to last pixel of needle and compare it to haystack[i]
Expected result: the needle image is found at the correct location.
I already got it working for some coordinates/widths/heights (A).
But sometimes bits seem to be "off" and therefore no match is found (B).
What could I be doing wrong? Any suggestions are welcome. Thanks.
var needle_height = 25;
var needle_width = 25;
var haystack_height = 400;
var haystack_width = 500;
A. example input - match
var needle = screenshot(5, 3, needle_width, needle_height);
var haystack = screenshot(0, 0, haystack_width, haystack_height);
var result = findmatch(haystack, needle);
B. example input - NO match
var needle = screenshot(5, 5, needle_width, needle_height);
var haystack = screenshot(0, 0, haystack_width, haystack_height);
var result = findmatch(haystack, needle);
1. capture needle and haystack image
private int[] screenshot(int x, int y, int width, int height)
{
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics.FromImage(bmp).CopyFromScreen(x, y, 0, 0, bmp.Size);
var bmd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly, bmp.PixelFormat);
var ptr = bmd.Scan0;
var bytes = bmd.Stride * bmp.Height / 4;
var result = new int[bytes];
Marshal.Copy(ptr, result, 0, bytes);
bmp.UnlockBits(bmd);
return result;
}
2. try to find a match
public Point findmatch(int[] haystack, int[] needle)
{
var firstpixel = needle[0];
for (int i = 0; i < haystack.Length; i++)
{
if (haystack[i] == firstpixel)
{
var y = i / haystack_height;
var x = i % haystack_width;
var matched = checkmatch(haystack, needle, x, y);
if (matched)
return (new Point(x,y));
}
}
return new Point();
}
3. verify full match
public bool checkmatch(int[] haystack, int[] needle, int startx, int starty)
{
for (int y = starty; y < starty + needle_height; y++)
{
for (int x = startx; x < startx + needle_width; x++)
{
int haystack_index = y * haystack_width + x;
int needle_index = (y - starty) * needle_width + x - startx;
if (haystack[haystack_index] != needle[needle_index])
return false;
}
}
return true;
}
Instead of making two screenshots of your desktop with a time interval between them, I would take a screenshot once and cut "needle" and "haystack" from those same bitmap source. Otherwise you have the risk of a change of your desktop contents between the two moments where the screenshots are taken.
EDIT: And when your problem still occurs after that, I would try to save the image to a file and try again with that file using your debugger, giving you a reproducible situation.
First, there is a problem with the findmatch loop. You shouldn't just use the haystack image as an array, because you need to subtract needle's width and height from right and bottom respectively:
public Point? findmatch(int[] haystack, int[] needle)
{
var firstpixel = needle[0];
for (int y = 0; y < haystack_height - needle_height; y++)
for (int x = 0; x < haystack_width - needle_width; x++)
{
if (haystack[y * haystack_width + x] == firstpixel)
{
var matched = checkmatch(haystack, needle, x, y);
if (matched)
return (new Point(x, y));
}
}
return null;
}
That should probably solve the problem. Also, keep in mind that there might be multiple matches. For example, if "needle" is a completely white rectangle portion of a window, there will most likely be many matches in the entire screen. If this is a possibility, modify your findmatch method to continue searching for results after the first one is found:
public IEnumerable<Point> FindMatches(int[] haystack, int[] needle)
{
var firstpixel = needle[0];
for (int y = 0; y < haystack_height - needle_height; y++)
for (int x = 0; x < haystack_width - needle_width; x++)
{
if (haystack[y * haystack_width + x] == firstpixel)
{
if (checkmatch(haystack, needle, x, y))
yield return (new Point(x, y));
}
}
}
Next, you need to keep a habit of manually disposing all objects which implement IDisposable, which you have created yourself. Bitmap and Graphics are such objects, meaning that your screenshot method needs to be modified to wrap those objects in using statements:
private int[] screenshot(int x, int y, int width, int height)
{
// dispose 'bmp' after use
using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
// dispose 'g' after use
using (var g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(x, y, 0, 0, bmp.Size);
var bmd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
bmp.PixelFormat);
var ptr = bmd.Scan0;
// as David pointed out, "bytes" might be
// a bit misleading name for a length of
// a 32-bit int array (so I've changed it to "len")
var len = bmd.Stride * bmp.Height / 4;
var result = new int[len];
Marshal.Copy(ptr, result, 0, len);
bmp.UnlockBits(bmd);
return result;
}
}
}
The rest of the code seems ok, with the remark that it won't be very efficient for certain inputs. For example, you might have a large solid color as your desktop's background, which might result in many checkmatch calls.
If performance is of interest to you, you might want to check different ways to speed up the search (something like a modified Rabin-Karp comes to mind, but I am sure there are some existing algorithms which ensure that invalid candidates are skipped immediately).
I don't think your equations for haystack_index or needle_index are correct. It looks like you take the Scan0 offset into account when you copy the bitmap data, but you need to use the bitmap's Stride when calculating the byte position.
Also, the Format32bppArgb format uses 4 bytes per pixel. It looks like you are assuming 1 byte per pixel.
Here's the site I used to help with those equations: https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx
Format32BppArgb: Given X and Y coordinates, the address of the first element in the
pixel is Scan0+(y * stride)+(x*4). This Points to the blue byte. The
following three bytes contain the green, red and alpha bytes.
Here are the class reference with example code that works great for my C# application for finding needle in haystack for each frame from a USB camera, in year 2018... I believe Accord is mostly a bunch of C# wrappers for fast C++ code.
Also check out the C# wrapper for Microsoft C++ DirectShow that I use to search for needle within each frame from a USB camera

C# Attempted to read or write protected memory error

Addendum: it seems to run correctly when I uncheck "optimize code" which leads me to believe it is some quirky configuration problem
Firstly I am trying to run unmanaged code. I have "allow unsafe code" checked. It is pointing to this line of code here where I am trying to read a bitmap without using the relatively slow getpixel:
byte[] buff = { scanline[xo], scanline[xo + 1], scanline[xo + 2], 0xff };
Entire snippet is below. How can I correct this problem?
private const int PIXELSIZE = 4; // Number of bytes in a pixel
BitmapData mainImageData = mainImage.LockBits(new Rectangle(0, 0, mainImage.Width, mainImage.Height), ImageLockMode.ReadOnly, mainImage.PixelFormat);
List<Point> results = new List<Point>();
foundRects = new List<Rectangle>();
for (int y = 0; y < mainImageData.Height
{
byte* scanline = (byte*)mainImageData.Scan0 + (y * mainImageData.Stride);
for (int x = 0; x < mainImageData.Width; x++)
{
int xo = x * PIXELSIZE;
byte[] buff = { scanline[xo], scanline[xo + 1],
scanline[xo + 2], 0xff };
int val = BitConverter.ToInt32(buff, 0);
// Pixle value from subimage in desktop image
if (pixels.ContainsKey(val) && NotFound(x, y))
{
Point loc = (Point)pixels[val];
int sx = x - loc.X;
int sy = y - loc.Y;
// Subimage occurs in desktop image
if (ImageThere(mainImageData, subImage, sx, sy))
{
Point p = new Point(x - loc.X, y - loc.Y);
results.Add(p);
foundRects.Add(new Rectangle(x, y, subImage.Width,
subImage.Height));
}
}
}
It's hard to say with the limited information we have, but I see a couple of obvious problems, one of which addresses your issue directly:
You are not checking the pixel format, but are assuming that it is 32bppRGB. It is likely 24bppRGB, and that would explain the error.
You are reading the RGB values incorrectly; Windows internally stores bitmaps in BGR order.
You are not calling UnlockBits at the end of the method.
What are the values xo + 1 and xo + 2 when x is mainImageData.Width - 1? They are surely walking off the reservation.
The most likely reason is that the image isn't 32bpp. it could be 24bpp, or possibly even 8 or 16 bit.
You should be getting PIXELSIZE from PixelFormat Rather than hard-coding it to 4.
Check to see that the value you are getting for Stride agrees with the Width.

Categories

Resources