Software takes an image as input (bmp, jpeg, whatever) with a white background and a black grid on it, I need to recognize grid propertys like Heigth, Width and cell size
This would be an easy job if the pixel color were sharp, but as we know due to compression it's not like that
I tried some normalizing algorithm in order to work with a pure black and white image but they are not 100% accurate, this the result using this simple function:
public static Bitmap BlackWhite(Bitmap bmp)
{
Bitmap bw = bmp.Clone(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), PixelFormat.Format1bppIndexed);
return bw;
}
before and after
as you can see outline is not correct
I get better results using a slow home made function:
public static Bitmap ColorSharp(Bitmap bitmap)
{
int i = 0;
int y = 0;
for(i = 0; i < bitmap.Width; i++)
{
for (y = 0; y < bitmap.Height; y++)
{
Color color = bitmap.GetPixel(i, y);
if (color.R <= 145 && color.G <= 145 && color.B <= 145)
bitmap.SetPixel(i, y, Color.Black);
else
bitmap.SetPixel(i, y, Color.White);
}
}
return bitmap;
}
but still not perfect
i know it's probabily impossible to have a 100% accuracy, expecially with high compressed image, but do you have any idea how to deal with this kind of problem?
You can try to apply Skeletonization or Thinning morphological operations to bitmap matrix. There are standard functions in OpenCV which will work fast.
Related
I use a black and white pictures as a mask to generate nice contours after applying a rectangle. Unfortunately to get rid of the black color I use the MakeTransparent method, unfortunately it is very slow, in my code I have to perform such a procedure twice, which in the case of 20 images takes about 5 seconds. Is there any other solution to speed up this procedure?
Bitmap contour = new Bitmap(
imageMaskCountour.Width, imageMaskCountour.Height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(contour))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.FillRectangle(ContourBrush, 0, 0, contour.Width, contour.Height);
g.DrawImage(
imageMaskCountour,
new Rectangle(0, 0, contour.Width, contour.Height),
new Rectangle(0, 0, imageMaskCountour.Width, imageMaskCountour.Height),
GraphicsUnit.Pixel);
}
contour.MakeTransparent(Color.Black);
Edit:
I try add LockBitmap and add the following method:
public void MakeTransparent(Color color)
{
for (int y = 0; y < this.Height; y++)
{
for (int x = 0; x < this.Width; x++)
{
if (this.GetPixel(x, y) == color)
{
this.SetPixel(x, y, Color.Transparent);
}
}
}
}
But it is much slower.
Every operation you are doing on the Bitmap requires locking and unlocking of the bits. That makes it very slow. See this answer how to access the bitmap data directly by locking once, manipulating all data and finally unlocking it.
The following code is an extension method for a regular Bitmap using the direct access just mentioned. It implements the logic of your method. Note that I save the Height and Width to local variables. Iam doing that because I recognized that it is much slower if used multiple times like in a loop condition.
Caution:
The underlying code only works for PixelFormat.Format32bppArgb, see PixelFormat reference.
The code gives you an idea how to access the pixel data directly and can be adapted to other pixel formats too.
The following points must be respected if adapting to other pixel formats.
Byte size of one destination pixel to correctly write the color value.
The correct row addressing through the Bitmap.Stride property, see Stride for reference. In case of PixelFormat.Format32bppArgb the stride matches the width by: Bitmap.Stride == Bitmap.Width * 4 and therefore no row address adjustments are needed.
public static void FastMakeTransparent(this Bitmap bitmap, Color color)
{
BitmapData bitmapData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
int* pixelPointer = (int*)bitmapData.Scan0;
int bitmapHeight = bitmap.Height;
int bitmapWidth = bitmap.Width;
int colorInt = color.ToArgb();
int transparentInt = Color.Transparent.ToArgb();
for (int i = 0; i < bitmapHeight; ++i)
{
for (int j = 0; j < bitmapWidth; ++j)
{
if (*pixelPointer == colorInt)
*pixelPointer = transparentInt;
++pixelPointer;
}
}
}
bitmap.UnlockBits(bitmapData);
}
Benchmark with Stopwatch class on my Intel Core2Duo P8400 (2.26 GHz) CPU.
Bitmap size 1000x1000 random filled accumulated time of 100 runs
Target AnyCPU .NetFramework 4.5.2
Release build
MakeTransparent Ticks: 24224801 | Time: 2422 ms
FastMakeTransparent Ticks: 1332730 | Time: 133 ms
Debug build
MakeTransparent Ticks: 24681178 | Time: 2468 ms
FastMakeTransparent Ticks: 5976810 | Time: 597 ms
I have an image and I would like to create a GraphicsPath representing a selection on the image based on some sort of thresholding on the pixels making up the image. For instance a color range algorithm that takes into account pixels with color values close to a given color.
My problem at the moment is not on the algorithm to select pixels, but on the GraphicsPath which with my current code becomes far too complicated (too many points) and hence time-expensive to create it and especially to further manage it.
At the moment I'm simply using the following code (in this example I simply take opaque pixels) where I add a Rectangle for each taken pixel.
BitmapData bitmapData = sourceBitmap.LockBits(new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, sourceBitmap.PixelFormat);
GraphicsPath gp = new GraphicsPath();
unsafe
{
byte* imagePointer = (byte*)bitmapData.Scan0;
for (int x = 0; x < bitmapData.Width; x++)
{
for (int y = 0; y < bitmapData.Height; y++)
{
if (imagePointer[3] != 0)
gp.AddRectangle(new Rectangle(x, y, 1, 1));
imagePointer += 4;
}
}
}
How can I simplify this process or the resulting GraphicsPath to obtain a much simpler path, ideally with only contours?
EDIT
I will then use to path to: display its contours on screen (running ants); use it as a mask for other images (cancel what is out from the path); do hit tests (whether a point is inside the path). As an example:
lets say i have a image
i would like to find the black rectangle bounds(right,left,width,height) in the image(lets say there's no other black pixels in this image).
my code so far is:
private unsafe Bitmap GetDiffBitmap(Bitmap bmp)
{
bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
for(int y = 0; y < nHeight; y++)
{
//define the pointers inside the first loop for parallelizing
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
for (int x = 0; x < nWidth; x++)
{
//always get the complete pixel when differences are found
if(p[0]==0 && p[1]==0 && p[2]==0)
{
// p[0] = 255;
// p[1] = 255;
// p[2] =255;
right = nWidth;//geting the position of the lastest black pixel;
}
p += 4;
}
}
bmp.UnlockBits(bmData);
return bmp;
}
its seems like my nwidth is also as the image width-so its not working.. i got acces to these pixels and i can change them but i dont know why i can count them and find a proper bounds of the black rectangle... if anyone could help me i would really apperciate it,
There are a few issues with this kind of image analysis:
1 replace
if(p[0]==0 && p[1]==0 && p[2]==0)
{
right = nWidth;//geting the position of the lastest black pixel;
}
with
if(p[0]==0 && p[1]==0 && p[2]==0)
{
right = x; //geting the position of the lastest black pixel;
}
Your x-iteration variable already counts which pixel you are on.
2 The code you provided only works for 32bpp pixel formats. If this is on purpose you should check if the bitmap you are analysing is in a compatible format.
3 With most compressed image formats you often won't get an exactly 0 on all 3 color channels for black, you should do a "less than something dark" check instead of a zero.
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.
The user provides my app an image, from which the app needs to make a mask:
The mask contains a red pixel for each transparent pixel in the original image.
I tried the following:
Bitmap OrgImg = Image.FromFile(FilePath);
Bitmap NewImg = new Bitmap(OrgImg.Width, OrgImg.Height);
for (int y = 0; y <= OrgImg.Height - 1; y++) {
for (int x = 0; x <= OrgImg.Width - 1; x++) {
if (OrgImg.GetPixel(x, y).A != 255) {
NewImg.SetPixel(x, y, Color.FromArgb(255 - OrgImg.GetPixel(x, y).A, 255, 0, 0));
}
}
}
OrgImg.Dispose();
PictureBox1.Image = NewImg;
I am worried about the performance on slow PCs. Is there a better approach to do this?
It is perfectly acceptable to use GetPixel() if it is only used sporadicly, e.g. on loading one image. However, if you want to do a more serious image processing, it is better to work directly with BitmapData. A small example:
//Load the bitmap
Bitmap image = (Bitmap)Image.FromFile("image.png");
//Get the bitmap data
var bitmapData = image.LockBits (
new Rectangle (0, 0, image.Width, image.Height),
ImageLockMode.ReadWrite,
image.PixelFormat
);
//Initialize an array for all the image data
byte[] imageBytes = new byte[bitmapData.Stride * image.Height];
//Copy the bitmap data to the local array
Marshal.Copy(bitmapData.Scan0,imageBytes,0,imageBytes.Length);
//Unlock the bitmap
image.UnlockBits(bitmapData);
//Find pixelsize
int pixelSize = Image.GetPixelFormatSize(image.PixelFormat);
// An example on how to use the pixels, lets make a copy
int x = 0;
int y = 0;
var bitmap = new Bitmap (image.Width, image.Height);
//Loop pixels
for(int i=0;i<imageBytes.Length;i+=pixelSize/8)
{
//Copy the bits into a local array
var pixelData = new byte[3];
Array.Copy(imageBytes,i,pixelData,0,3);
//Get the color of a pixel
var color = Color.FromArgb (pixelData [0], pixelData [1], pixelData [2]);
//Set the color of a pixel
bitmap.SetPixel (x,y,color);
//Map the 1D array to (x,y)
x++;
if( x >= bitmap.Width)
{
x=0;
y++;
}
}
//Save the duplicate
bitmap.Save ("image_copy.png");
This approach is indeed slow. A better approach would be using Lockbits and access the underlying matrix directly. Take a look at https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx or http://www.mfranc.com/programming/operacje-na-bitmapkach-net-1/ or https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.lockbits or other articles about lockbits in StackOverflow.
It's a tiny bit more complex since you'll have to work with bytes directly (4 per pixel if you're working with RGBA), but the performance boost is significant and is well worth it.
Another note - OrgImg.GetPixel(x, y) is slow, if you're sticking with this (and not lockbits) make sure you only use it once (it may be already optimized, just check if there's a difference).