Fill the holes in emgu cv - c#

How can I fill the holes in binary image in emgu cv?
In Aforge.net it's easy, use Fillholes class.

Thought the question is a little bit old, I'd like to contribute an alternative solution to the problem.
You can obtain the same result as Chris' without memory problem if you use the following:
private Image<Gray,byte> FillHoles(Image<Gray,byte> image)
{
var resultImage = image.CopyBlank();
Gray gray = new Gray(255);
using (var mem = new MemStorage())
{
for (var contour = image.FindContours(
CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
RETR_TYPE.CV_RETR_CCOMP,
mem); contour!= null; contour = contour.HNext)
{
resultImage.Draw(contour, gray, -1);
}
}
return resultImage;
}
The good thing about the method above is that you can selectively fill holes that meets your criteria. For example, you may want to fill holes whose pixel count (count of black pixels inside the blob) is below 50, etc.
private Image<Gray,byte> FillHoles(Image<Gray,byte> image, int minArea, int maxArea)
{
var resultImage = image.CopyBlank();
Gray gray = new Gray(255);
using (var mem = new MemStorage())
{
for (var contour = image.FindContours(
CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
RETR_TYPE.CV_RETR_CCOMP,
mem); contour!= null; contour = contour.HNext)
{
if ( (contour.Area < maxArea) && (contour.Area > minArea) )
resultImage.Draw(contour, gray, -1);
}
}
return resultImage;
}

Yes there is a method but it's a bit messy as its based on cvFloodFill operation. Now all this algorithm is designed to do is fill an area with a colour until it reaches an edge similar to a region growing algorithm. To use this effectively you need to use a little inventive coding but I warn you this code is only to get you started it may require re-factoring to speed things up . As it stands the loop goes through each of your pixels that are less then 255 applies cvFloodFill checks what size the area is and then if it is under a certain area fill it in.
It is important to note that a copy of the image is made of the original image to be supplied to the cvFloodFill operation as a pointer is used. If the direct image is supplied then you will end up with a white image.
OpenFileDialog OpenFile = new OpenFileDialog();
if (OpenFileDialog.ShowDialog() == DialogResult.OK)
{
Image<Bgr, byte> image = new Image<Bgr, byte>(OpenFile.FileName);
for (int i = 0; i < image.Width; i++)
{
for (int j = 0; j < image.Height; j++)
{
if (image.Data[j, i, 0] != 255)
{
Image<Bgr, byte> image_copy = image.Copy();
Image<Gray, byte> mask = new Image<Gray, byte>(image.Width + 2, image.Height + 2);
MCvConnectedComp comp = new MCvConnectedComp();
Point point1 = new Point(i, j);
//CvInvoke.cvFloodFill(
CvInvoke.cvFloodFill(image_copy.Ptr, point1, new MCvScalar(255, 255, 255, 255),
new MCvScalar(0, 0, 0),
new MCvScalar(0, 0, 0), out comp,
Emgu.CV.CvEnum.CONNECTIVITY.EIGHT_CONNECTED,
Emgu.CV.CvEnum.FLOODFILL_FLAG.DEFAULT, mask.Ptr);
if (comp.area < 10000)
{
image = image_copy.Copy();
}
}
}
}
}
The "new MCvScalar(0, 0, 0), new MCvScalar(0, 0, 0)," are not really important in this case as you are only filling in results of a binary image. YOu could play around with other settings to see what results you can achieve. "if (comp.area < 10000)" is the key constant to change is you want to change what size hole the method will fill.
These are the results that you can expect:
Original
Results
The problem with this method is it's extremely memory intensive and it managed to eat up 6GB of ram on a 200x200 image and when I tried 200x300 it ate all 8GB of my RAM and brought everything to a crashing halt. Unless a majority of your image is white and you want to fill in tiny gaps or you can minimise where you apply the method I would avoid it. I would suggest writing you own class to examine each pixel that is not 255 and add the number of pixels surrounding it. You can then record the position of each pixel that was not 255 (in a simple list) and if your count was bellow a threshold set these positions to 255 in your images (by iterating though the list).
I would stick with the Aforge FillHoles class if you do not wish to write your own as it is designed for this purpose.
Cheers
Chris

you can use FillConvexPoly
image.FillConvexPoly(externalContours.ToArray(), new Gray(255));

Related

Crop white space around black image based on color

I using Imaemagick and c# and wondering:
Is it possible to crop image to border without exactly sizes?
From first to second?
First image
Second
To remove the white space around the sudoku square you can loop over the pixels. Since the image is black and white it makes it a lot easier because we can check when any of the R, G or B values drop below a certain white threshold and become black.
In this example, I'm just using an arbitrary 200 value to check.
I'm walking in from the top left and bottom right corners. This will only work if your image is always a perfect square. but you can easily adjust this code to check coordinates more accurately to meet your purposes.
using (var image = new Bitmap(Image.FromFile("firstImage.jpg")))
{
int topX = 0, topY = 0;
int bottomX = image.Width - 1, bottomY = image.Height - 1;
var color = image.GetPixel(topX, topY);
while(color.R > 200)
color = image.GetPixel(++topX, ++topY);
color = image.GetPixel(bottomX, bottomY);
while(color.R > 200)
color = image.GetPixel(--bottomX, --bottomY);
Bitmap croppedImage = new Bitmap(image);
Rectangle cropRect = new Rectangle(topX, topY, bottomX - topX + 1, bottomY - topY + 1);
croppedImage = croppedImage.Clone(cropRect, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
croppedImage.Save("firstImageNoBorder.jpg");
}
Original:
Cropped:

fast way to split 8bit bitmap into eight differet 1bit bitmaps

I need a way to convert 1000+ 8bit bitmaps into eight 1bit bitmaps.
Currently I am running two loops which read each pixel from the main image and assign it a 1bpp image. It takes a very long time to accomplish this, anyway better to do it?
Here is an example of my code (separates only into two images):
Bitmap rawBMP = new Bitmap(path);
Bitmap supportRAW = new Bitmap(rawBMP.Width, rawBMP.Height);
Bitmap modelRAW = new Bitmap(rawBMP.Width, rawBMP.Height);
Color color = new Color();
for (int x = 0; x < rawBMP.Width; x++)
{
for (int y = 0; y < rawBMP.Height; y++)
{
color = rawBMP.GetPixel(x, y);
if (color.R == 166) //model
{
modelRAW.SetPixel(x, y, Color.White);
}
if (color.R == 249) //Support
{
supportRAW.SetPixel(x, y, Color.White);
}
}
}
var supportBMP = supportRAW.Clone(new Rectangle(0, 0, rawBMP.Width, rawBMP.Height), System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
var modelBMP = modelRAW.Clone(new Rectangle(0, 0, rawBMP.Width, rawBMP.Height), System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
If you have to check every pixel then you are going to have to loop through them all at least once, however like TaW suggest there are more efficient ways to access pixels.
SetPixel and GetPixel are much slower then you accessing the data directly, Look at the use of unsafe to get direct access to the data, or marshaling to copy the data back and forth.
( see https://stackoverflow.com/a/1563170 for a more detailed written by notJim)

Detecting one image within another and returning it's value

I have a dictionary of pattern images for many letters and I also have a bitmap that has to be recognized! Heights of "both" images are the same! Some Pattern images have different width.
How to iterate over X axis and recognize letters from pattern?
Right now I'm using this function to Check if X bitmap column has Black pixels in it:
static Boolean GetColumnState(Bitmap bmp, int x)
{
BitmapData pixelData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
Boolean state = false;
unsafe
{
int* pData = (int*)pixelData.Scan0.ToPointer();
pData += x;
for (int i = 0; i < bmp.Height; ++i)
{
pData += bmp.Width;
if (Color.FromArgb(*pData) == Color.FromArgb(255, 0, 0, 0))
{
state = true;
break;
//pixelColumn[i] = Color.FromArgb(*pData);
}
}
}
bmp.UnlockBits(pixelData);
return state;
}
If GetColumnState() returns True, then I crop an image of the same size as pattern image and compare them.
int y = target.Count;
for (int i = 0; i < b1.Width; i++)
{
if (GetColumnState(b1, i + count) == true)
{
int trWidth = target[5].Value.Width;
int trHeight = target[5].Value.Height;
Bitmap bitm = new Bitmap(trWidth, trHeight);
Rectangle section = new Rectangle(new Point(0, b1.Height - trHeight-1), new Size(trWidth, trHeight));
Bitmap cropped = CropImage(b1, section);
cropped.Save(#"C:\111.png");
target[5].Value.Save(#"C:\000.png");
if (CompareMemCmp(cropped, target[5].Value) == true)
{
//count = target[5].Value.Width;
textBox2.AppendText(target[5].Key);
break;
}
else { textBox2.AppendText("noo"); }
//textBox1.Text = "yes!";
}
else
{
//textBox1.Text = "noo";
}
break;
}
Unfortunately, even though Cropped image visually looks the same - it has different size so memcmp (my method of comparison is based on this) returns false..
Bitmap that has to be recognized and Pattern images are all in BlackAndWhite colors.. I'm wondering if there is a more reliable way to compare one image within another Image and return its value through dictionary(OCR)..
Pattern matching based on pixel values is not robust, as you have discovered. If the font is completely predictable and consistently rendered, you can make this work by normalizing the images (align, scale, rotate to match), and computing the mean square difference between the images, then accepting if it's small enough.
In the face of unknown fonts, or varying image sources (screencapture, camera, scan, etc), you need a more robust scheme based on some type of machine learning (ML). Recognizing digits from bitmaps is a classical introductory example to neural networks, but many other ML schemes will work - see Supervised learning#Approaches_and_algorithms on Wikipedia. Note that this will involve training, implying you need a good diverse set of data to train it on.

(Bitmap)image behaves differently than new Bitmap(image)

Here is the test i wrote and that will currently fail:
var unusableColor = Color.FromArgb(13, 19, 20, 19);
var retrievedColor = Color.Empty;
var tempFile = Path.GetTempFileName();
using (var bitmap = new Bitmap(1, 1))
{
bitmap.SetPixel(0, 0, unusableColor);
bitmap.Save(tempFile, ImageFormat.Png);
}
using (var image = Image.FromFile(tempFile))
// This will lead to the error
using (var bitmap = new Bitmap(image))
// But this will work
//using (var bitmap = (Bitmap)image)
{
retrievedColor = bitmap.GetPixel(0, 0);
}
Assert.That(retrievedColor, Is.SameAs(unusableColor));
If you take a look into the retrievedColor you'll find that it will be the same as Color.FromArgb(13, 19, 19, 19). So the difference will be that the green part has changed from 20 to 19.
Any idea why this happens or under which circumstances the constructor of the Bitmap will change a pixel?
Update
Seems to be a deeper nested problem. By replacing the Bitmap constructor by a simple cast of the image variable the problem goes away. This maybe solves the problem, but it doesn't explain it. Further more i was able to reproduce the problem even in Paint.Net by the following procedure:
Open Paint.Net and create a new image (size doesn't matter)
Select all (Ctrl+A)
Remove the selection (Del)
Open the color dialog (F8)
Enter the above values for RGB (19, 20, 19) and at the bottom the transparency (13).
Select the fill tool (F)
Fill the color into the empty image
Select the color selection tool (K)
Click somewhere into your fresh image and watch the color dialog
So it seems it is maybe a deeper problem, not caused by the Bitmap or Image class but maybe by some deeper functionality like GDI+ or something similar.
Update 2
I just wrote a new test to find out all affected colors:
for (int a = 0; a < 256; a++)
{
for (int r = 0; r < 256; r++)
{
for (int g = 0; g < 256; g++)
{
for (int b = 0; b < 256; b++)
{
using (var bitmap = new Bitmap(1, 1))
{
var desiredColor = Color.FromArgb(a, r, g, b);
bitmap.SetPixel(0, 0, desiredColor);
// This will fail in a lot of colors with a low alpha channel value
using (var copiedBitmap = new Bitmap(bitmap))
// This will work, cause the information is entirely copied.
//using (var copiedBitmap = (Bitmap)bitmap.Clone())
{
var retrievedColor = copiedBitmap.GetPixel(0, 0);
if (desiredColor != retrievedColor)
{
Debug.Print(desiredColor + " != " + retrievedColor);
}
}
}
}
}
}
Please don't let it run completely on itself, cause it will take a loonng time to finish and it also finds a looots of differences. But what you can see, if you play around with the transparency (setting to 1 or 10) then you'll see that the RGB values use this as some kind of bit depth.
So the problem occurs if you create a new Bitmap from an existing one that uses low transparency values. The real root cause seems to be far down in GDI, Kernel or somewhere in this area and can't be solved from .Net.
Simply be aware that a color can change by calling the bitmap constructor if the color has a low transparency value. If you really need the original colors to stay alive in a second instance instead use (Bitmap)myBitmap.Clone() or if you load it from disk use (Bitmap)Image.FromFile(filename) cause Image is only an abstract class which will normally instantiated through the Bitmap class.
I checked PNG file saved with your code using Paint.NET and pixel color is exactly unusableColor.
If you change your reading code with this:
using (Bitmap bitmap = (Bitmap)Image.FromFile(tempFile))
{
retrievedColor = bitmap.GetPixel(0, 0);
}
everything works
you can use Clone method:
using (var image = Image.FromFile(tempFile))
{
using (var bitmap = image.Clone() as Bitmap)
{
retrievedColor = bitmap.GetPixel(0, 0);
}
}
Problem is in 'new Bitmap(image)' because it creates new instance. If you look into bitmap's constructor, it creates new transparent image and draws source image. graphics object has smoothing mode property, which is used for drawing quality. default is no antialiasing.
Here is the Bitmap's constructor:
Graphics graphics = null;
try
{
graphics = Graphics.FromImage(this);
graphics.Clear(Color.Transparent);
graphics.DrawImage(original, 0, 0, width, height);
}
finally
{
if (graphics != null)
{
graphics.Dispose();
}
}
So if you just load image from file, or clone, bitmap data is same.

Cannot draw an image using this program?

I am trying to process an image for finding out red color regions in it. I scan pixels and if they are found to be ENOUGH red, they are converted to black, otherwise white.
To speed up the process, I skip certain pixels, and need to draw blocks of black or white at their place. I am using this function but it seems to be wrong somewhere. The image I get in the end is completely blank.
public void ProcessFrame( ref Bitmap image )
{
int skip = 10;
Graphics g = Graphics.FromImage(System.Drawing.Image.FromHbitmap(image.GetHbitmap()));
SolidBrush black = new SolidBrush(Color.Black);
SolidBrush white = new SolidBrush(Color.White);
for (int i = 0; i < image.Width; i=i+skip)
{
for (int j = 0; j < image.Height; j = j + skip)
{
Color cl = image.GetPixel(i, j);
if (cl.R > (cl.G * 2) && cl.R > (cl.B * 2))
{
g.FillRectangle(black, new Rectangle(i, j, skip, skip));
}
else
{
g.FillRectangle(white, new Rectangle(i, j, skip, skip));
}
}
}
}
Can you point out the mistake, OR any other better method to achieve what I aim for?
By blank, do you mean white?
I don't know your image, but if (cl.R > (cl.G * 2) && cl.R > (cl.B * 2)) is not a good test for redness. #010000 passes that test, but is basically black. And, #ffaaaa looks red, but won't pass.
If you had a very dark image, lots of pixels might pass that test that aren't very red. With a light image, lots of red pixels won't pass.
Probably the best way is to convert the color the HSL, and then use values of H(ue) that are in the red zone, but only if S(aturation) and L(uminance) are sufficiently bright and non-gray (over a threshhold to not just be black or gray).
Try to use:
Graphics g = Graphics.FromImage(image);
Then consider other replies to improve your color testing.
You didn't show where image is created, but this code will draw into a bitmap:
var bmp = new Bitmap(200,200);
using (g = Graphics.FromImage(bmp))
{
g.FillEllipse(Brushes.Red, 10, 10, 50, 50);
}
Note that you need to call Graphics.Dispose (or use using) before using the image.
Also note that using GetPixel for image processing will be very slow. Use low-level memory access (Bitmap.LockBits) or try some image processing library for .NET (like AForge).

Categories

Resources