How can I convert extracted Mat Contours to Bitmap in Opencvsharp? - c#

After I applied the Threshold on the Destination Image
I get a Mat as Reference
then I called the function FindContours()
to extract all existed Contours on the target Image
finally I tried to convert the extracted contours one at a time to Bitmap and here at this point am getting an Error:
System.ArgumentException: "Number of channels must be 1, 3 or 4. Parametername: src" by converting Mat to Bitmap
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog getImag = new OpenFileDialog();
getImag.Filter = "PNG,JPG|*.png;*.jpg";
DialogResult result = getImag.ShowDialog();
string Source_Logo_Link = string.Empty;
if (result == DialogResult.OK)
{
Source_Logo_Link = getImag.FileName;
System.Drawing.Image image = System.Drawing.Image.FromFile(Source_Logo_Link);
Mat src = new OpenCvSharp.Mat(Source_Logo_Link);
OpenCvSharp.Mat gray = src.CvtColor(ColorConversionCodes.BGR2GRAY);
OpenCvSharp.Mat binary = gray.Threshold(0, 255, ThresholdTypes.Otsu);
OpenCvSharp.Mat[] contoursQuery;
OutputArray hierarchyQ = InputOutputArray.Create(new List<Vec4i>());
binary.FindContours(out contoursQuery, hierarchyQ, RetrievalModes.CComp, ContourApproximationModes.ApproxTC89KCOS);
List<Bitmap> images = new List<Bitmap>();
for (int i = 0; i <= contoursQuery.Length; i++)
images.add(contoursQuery[i].toBitmap());
}
}

sorry this comes a bit late, but I hope this still helps though. Apart from a few other issues (like the counter of the for loop), I guess that your major problem is, that you miss-understood the result of FindContours.
It really just gives you a list of all contour points, which it found in the image, not complete images.
Have a look at the documentation FindContours, it clearly states for the contour parameter:
Each contour is stored as a vector of points
So that means if you wand them "one at a time to Bitmap" you'll need to create these bitmaps first, and then draw the contours into it. To make drawing a bit more easy (and faster) there is the DrawContours command, which you could use for the drawing part.
Over all, in the end, it should look somehow like this:
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog getImag = new OpenFileDialog();
getImag.Filter = "PNG,JPG|*.png;*.jpg";
DialogResult result = getImag.ShowDialog();
string Source_Logo_Link = string.Empty;
if (result == DialogResult.OK)
{
Source_Logo_Link = getImag.FileName;
System.Drawing.Image image = System.Drawing.Image.FromFile(Source_Logo_Link);
Mat src = new OpenCvSharp.Mat(Source_Logo_Link);
OpenCvSharp.Mat gray = src.CvtColor(ColorConversionCodes.BGR2GRAY);
OpenCvSharp.Mat binary = gray.Threshold(0, 255, ThresholdTypes.Otsu);
// I'd really prefer to work with point lists and HierarchyIndex because
// it's much more descriptive that way
OpenCvSharp.Point[][] contours;
OpenCvSharp.HierarchyIndex[] hierarchyQ;
binary.FindContours(out contours,out hierarchyQ, RetrievalModes.List, ContourApproximationModes.ApproxTC89KCOS);
List<Bitmap> images = new List<Bitmap>();
for (int i = 0; i < contoursQuery.Length; i++){
var singleContour = new OpenCvSharp.Mat(src.Size(), MatType.CV_8U,0);
singleContour.DrawContours(contours,i,Scalar.White);
images.Add(singleContour.ToBitmap());
}
}
}
I also changed the RetrievalMode to "List", since you don't really seem to care about the hierarchical relationships.
Hope that helps ;-)

Related

Trying to read pixel color from PictureBox in C# WinForms

I am having an odd issue whereby when I then try to iterate over the pixels of the PictureBox or save the bmp it is just all black.
The intention of the tool is that you select a font, size, and style, and then it loops over the ASCII chars of that font and shows each character in the picture box, and converts the pixel data into a HEX array so I can use them on LCD displays.
The main part of the tool works whereby it is correctly looping through the ASCII chars and displaying them in the picture box but after each char is drawn to the picture box and I then try to iterate over the pixels of the PictureBox every pixel is returned as 0,0,0 RGB "black" and if I save the bmp which was drawn to the PictureBox that too is all black, but again I can see the bmp of that char correct drawn in the PictureBox yet the PictureBox data and bmp data does not match what I see in the PictureBox itself, I am truly lost as to why I am unable to correctly iterate or save the bmp or PictureBox.
I have tried not using async functions which is not ideal as I want the UI to be free, and I have tried various means to read the pixels and save the bmp but the result is the same. I hope to ask if anyone knows why I am getting this odd behavior and the solution to the issue.
Regards from Ed.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace generateFonts
{
public partial class Form1 : Form
{
string font = "";
float fontSize = 0;
FontStyle fontStyle = FontStyle.Regular;
public Form1()
{
InitializeComponent();
comboBox1.SelectedIndex = 0;
comboBox2.SelectedIndex = 0;
comboBox3.SelectedIndex = 0;
font = comboBox1.SelectedItem.ToString();
fontSize = Convert.ToInt32(comboBox2.SelectedItem);
fontStyle = FontStyle.Regular;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
font = comboBox1.SelectedItem.ToString();
}
private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
{
fontSize = Convert.ToInt32(comboBox2.SelectedItem);
}
private void comboBox3_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox3.SelectedIndex == 0)
fontStyle = FontStyle.Regular;
else if (comboBox3.SelectedIndex == 1)
fontStyle = FontStyle.Italic;
else if(comboBox3.SelectedIndex == 2)
fontStyle = FontStyle.Bold;
}
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => StartProcess(1));
}
private void StartProcess(int runs)
{
// Font
Font myFont = new Font(font, fontSize, fontStyle);
List<string> bytes = new List<string>();
string[] bits = { "0", "0", "0", "0", "0", "0", "0", "0" };
byte bitPos = 0;
for (byte i = 32; i < 126; i++)
{
//Create a Image-Object on which we can paint
Image bmp = new Bitmap(200, 200);
//Create the Graphics-Object to paint on the Bitmap
Graphics g = Graphics.FromImage(bmp);
string c = Convert.ToChar(i).ToString();
//Get the perfect Image-Size so that Image-Size = String-Size
SizeF size = g.MeasureString(c, myFont);
//Use this to become better Text-Quality on Bitmap.
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
//Here we draw the string on the Bitmap
g.DrawString(c, myFont, new SolidBrush(Color.Black), 0, 0);
if (!Directory.Exists("FontBmps"))
Directory.CreateDirectory("FontBmps");
this.Invoke((MethodInvoker)delegate ()
{
pictureBox2.Width = Convert.ToInt32(size.Width);
pictureBox2.Height = Convert.ToInt32(size.Height);
pictureBox2.Image = bmp; // <--- this is working and the picturebox shows the bmp correctly
bmp.Save("FontBmps/" + i + "_" + font + "_" + fontSize + "px_" + fontStyle + ".bmp", ImageFormat.Bmp); // <--- error here: this saves a black square instead of the bmp i see displayed in the picturebox GUI ??
// Even if i save the picturebox itself that too is just a black square instead of the correct image shown in the GUI ??
// now convert the bmp to a HEX array of pixels
for (int h = 0; h < pictureBox2.Height; h++)
{
for (int w = 0; w < pictureBox2.Width; w++)
{
Color colour = (pictureBox2.Image as Bitmap).GetPixel(w, h);
if (colour.R == 0 && colour.G == 0 && colour.B == 0)
{
bits[bitPos] = "1";
}
else
{
bits[bitPos] = "0"; // <-- never hits me, again for some reason the bmp or picturebox is all black pixels data but i see it correctly show in the picturebox GUI ??
}
if (bitPos < 7)
bitPos++;
else
{
//string bitStr = bits.ToString();
//string hex = Convert.ToByte(bitStr, 2).ToString("x2");
//bytes.Add(hex);
bitPos = 0;
for(byte n = 0; n < 8; n++)
{
bits[n] = "0";
}
}
}
}
if (bitPos != 0)
{
// TO DO...
}
});
Thread.Sleep(500); // <--- i have this just to see it displaying the chars but i have removed it with no effect to the issue
}
// now add List to a file in the correct format
foreach (string str in bytes)
{
Console.WriteLine(str);
// TO DO...
}
}
}
}
I believe the image is black, but some parts have transparency. That is, you would need to check Alpha (Color.A). What you see in the picture box, would be the background color of the picture box where it is transparent.
You won't see the transparency in the saved file, given that the ImageFormat.Bmp does not support transparency. Try Png instead, which supports transparency (and has lossless compression).
Alternatively, you can use Graphics.Clear to have the image be the color you want for background (white, I guess) before drawing to it.
Aside from that, I'll suggest to use bmp instead of (pictureBox2.Image as Bitmap), and use Bitmap.LockBits. That would improve performance.
This might be useful for reference: Converting a bitmap to monochrome. See also C# - Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App.

linear image stitching using emgu.cv in c#

I have a device that rotates an object and takes a picture of a portion of the object at regular intervals. Currently, I have 30 pictures. To stitch the images into a flat image, I am taking a slice right out of the center of each picture of a fixed width (between 50 and 75 pixels). I am trying to stitch these slices together into a flat image of the original picture using the EMGU CV Stitching library with the sample stitching code that comes with EMGU. I am testing with between 5 and 10 slices at a time. Sometimes, I am getting an error that says "Error, need more images". When I do get a result, it looks terrible with weird curvatures. I don't need any spatial adjustments. I just want to stitch them in a linear fashion from left to right. Any ideas, either using EMGU or other?
Here are a few slices and the result:
Why is the resulting image not the same height as the 4 slices? What must be done just to stitch these together in a linear fashion so that the text is continuous?
Here is the code I am using:
private void selectImagesButton_Click(object sender, EventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.CheckFileExists = true;
dlg.Multiselect = true;
if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
sourceImageDataGridView.Rows.Clear();
Image<Bgr, byte>[] sourceImages = new Image<Bgr, byte>[dlg.FileNames.Length];
for (int i = 0; i < sourceImages.Length; i++)
{
sourceImages[i] = new Image<Bgr, byte>(dlg.FileNames[i]);
using (Image<Bgr, byte> thumbnail = sourceImages[i].Resize(200, 200, Emgu.CV.CvEnum.Inter.Cubic, true))
{
DataGridViewRow row = sourceImageDataGridView.Rows[sourceImageDataGridView.Rows.Add()];
row.Cells["FileNameColumn"].Value = dlg.FileNames[i];
row.Cells["ThumbnailColumn"].Value = thumbnail.ToBitmap();
row.Height = 200;
}
}
try
{
//only use GPU if you have build the native binary from code and enabled "NON_FREE"
using (Stitcher stitcher = new Stitcher(false))
{
using (AKAZEFeaturesFinder finder = new AKAZEFeaturesFinder())
{
stitcher.SetFeaturesFinder(finder);
using (VectorOfMat vm = new VectorOfMat())
{
Mat result = new Mat();
vm.Push(sourceImages);
Stopwatch watch = Stopwatch.StartNew();
this.Text = "Stitching";
Stitcher.Status stitchStatus = stitcher.Stitch(vm, result);
watch.Stop();
if (stitchStatus == Stitcher.Status.Ok)
{
resultImageBox.Image = result;
this.Text = String.Format("Stitched in {0} milliseconds.", watch.ElapsedMilliseconds);
}
else
{
MessageBox.Show(this, String.Format("Stiching Error: {0}", stitchStatus));
resultImageBox.Image = null;
}
}
}
}
}
finally
{
foreach (Image<Bgr, Byte> img in sourceImages)
{
img.Dispose();
}
}
}
}

How to show a specific frame of a GIF image in C#?

I want to show a frame of a gif image. I searched and found that the following code should work, but it doesn't work. it detects the number of frames correctly but it shows the whole frames of gif instead of the specified frame. thanks everybody.
Image[] frames = new Image[36];
Image GG = Image.FromFile(#"C:\Users\Administrator\TEST C#\TEST2frame2\chef.gif");
FrameDimension dimension = new FrameDimension(GG.FrameDimensionsList[0]);
// Number of frames
int frameCount = GG.GetFrameCount(dimension);
label1.Text = frameCount.ToString();
// Return an Image at a certain index
GG.SelectActiveFrame(dimension, 1);
frames[1] = ((Image)GG.Clone());
pictureBox1.Image = frames[1];
Use your own code up until the call of SelectActiveFrame() and after that change to this lines:
frames[0] = new Bitmap(GG);
pictureBox1.Image = frame[0];
This should do the trick. Please do not forget do dispose the created Images.
Oh it works, but not as you expect it to.
When you set an active frame of a gif image it actually restarts its animation from that frame. You have to stop it when you change a frame, by setting the pictureBox.IsEnabled to false, for instance. Try the following code
private Image img;
public Form1()
{
InitializeComponent();
img = Image.FromFile(#"C:\Users\Administrator\TEST C#\TEST2frame2\chef.gif");
pictureBox1.Image = img;
}
private void button1_Click(object sender, EventArgs e)
{
var dim = new FrameDimension(img.FrameDimensionsList[0]);
img.SelectActiveFrame(dim, 1);
pictureBox1.Enabled = false;
}
Try pressing the button in different moments and you will see that the active image frame will change.

Tracking Blobs with Microsoft Kinect

I am working on a people counter. For this I have the Microsoft Kinect installed over the door.
I am working with C# and EmguCV. I have extracted the heads of the people, so that they appear as white blobs on a black image. Then I have created a bounding box around the heads. That works fine. So I now how many blobs I have per frame and I also now their position. This works fine. But now I want to track the blobs because I want to count how much people come in and go out, but I don't know how to do this. Can anyone help me? The problem is that every frame, new blobs can appear and old blobs can disappear. Can anyone give me an algorithm or maybe some code? or a paper.
Thanks a lot!
Sure. This is the code for the blobs:
using (MemStorage stor = new MemStorage())
{
Contour<System.Drawing.Point> contours = head_image.FindContours(Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_EXTERNAL, stor);
for (int i = 0; contours != null; contours = contours.HNext)
{
i++;
//if ((contours.Area > Math.Pow(sliderMinSize.Value, 2)) && (contours.Area < Math.Pow(sliderMaxSize.Value, 2)))
{
MCvBox2D box = contours.GetMinAreaRect();
blobCount++;
contour_image.Draw(box, new Bgr(System.Drawing.Color.Red), 1);
new_position = new System.Drawing.Point((int)(box.center.X), (int)(box.center.Y));
new_x = box.center.X;
new_y = box.center.Y;
}
}
}
Please see Emgu CV Blob Detection for more information. Assuming you are using Emgu CV 2.1 or higher, then the answer will work. If you are using version 1.5 or higher, see this thread on how to easily detect blobs. Or look at the code below
Capture capture = new Capture();
ImageViewer viewer = new ImageViewer();
BlobTrackerAutoParam param = new BlobTrackerAutoParam();
param.ForgroundDetector = new ForgroundDetector(Emgu.CV.CvEnum.FORGROUND_DETECTOR_TYPE.FGD);
param.FGTrainFrames = 10;
BlobTrackerAuto tracker = new BlobTrackerAuto(param);
Application.Idle += new EventHandler(delegate(object sender, EventArgs e)
{
tracker.Process(capture.QuerySmallFrame().PyrUp());
Image<Gray, Byte> img = tracker.GetForgroundMask();
//viewer.Image = tracker.GetForgroundMask();
MCvFont font = new MCvFont(Emgu.CV.CvEnum.FONT.CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0);
foreach (MCvBlob blob in tracker)
{
img.Draw(Rectangle.Round(blob), new Gray(255.0), 2);
img.Draw(blob.ID.ToString(), ref font, Point.Round(blob.Center), new Gray(255.0));
}
viewer.Image = img;
});
viewer.ShowDialog();
Hope this helps!
EDIT
I think you should use this code every ten frames or so (~3 times a second) and do something like this:
Capture capture = new Capture();
ImageViewer viewer = new ImageViewer();
BlobTrackerAutoParam param = new BlobTrackerAutoParam();
param.ForgroundDetector = new ForgroundDetector(Emgu.CV.CvEnum.FORGROUND_DETECTOR_TYPE.FGD);
param.FGTrainFrames = 10;
BlobTrackerAuto tracker = new BlobTrackerAuto(param);
int frames = 0;
Application.Idle += new EventHandler(delegate(object sender, EventArgs e)
{
frames++;//Add to number of frames
if (frames == 10)
{
frames = 0;//if it is after 10 frames, do processing and reset frames to 0
tracker.Process(capture.QuerySmallFrame().PyrUp());
Image<Gray, Byte> img = tracker.GetForgroundMask();
//viewer.Image = tracker.GetForgroundMask();
int blobs = 0;
MCvFont font = new MCvFont(Emgu.CV.CvEnum.FONT.CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0);
foreach (MCvBlob blob in tracker)
{
//img.Draw(Rectangle.Round(blob), new Gray(255.0), 2);
//img.Draw(blob.ID.ToString(), ref font, Point.Round(blob.Center), new Gray(255.0));
//Only uncomment these if you want to draw a rectangle around the blob and add text
blobs++;//count each blob
}
blobs = /*your counter here*/;
blobs = 0; //reset
viewer.Image = img;//get next frame
});
viewer.ShowDialog();
EDIT 2
It sounds like you just want to identify the blobs, it sounds like you want McvBlob.ID. This is the ID of the blob and you can check which ID's are still there and which are not. I would still do this every ten frames to not slow it down as much. You just need a simple algorithm that can observe what the ID's are, and if they have changed. I would store the IDs in a List<string> and check that list for changes every few frames. Example:
List<string> LastFrameIDs, CurrentFrameIDs;
Capture capture = new Capture();
ImageViewer viewer = new ImageViewer();
BlobTrackerAutoParam param = new BlobTrackerAutoParam();
param.ForgroundDetector = new ForgroundDetector(Emgu.CV.CvEnum.FORGROUND_DETECTOR_TYPE.FGD);
param.FGTrainFrames = 10;
BlobTrackerAuto tracker = new BlobTrackerAuto(param);
int frames = 0;
Application.Idle += new EventHandler(delegate(object sender, EventArgs e)
{
frames++;//Add to number of frames
if (frames == 10)
{
frames = 0;//if it is after 10 frames, do processing and reset frames to 0
tracker.Process(capture.QuerySmallFrame().PyrUp());
Image<Gray, Byte> img = tracker.GetForgroundMask();
//viewer.Image = tracker.GetForgroundMask();
int blobs = 0, i = 0;
MCvFont font = new MCvFont(Emgu.CV.CvEnum.FONT.CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0);
foreach (MCvBlob blob in tracker)
{
i++;
//img.Draw(Rectangle.Round(blob), new Gray(255.0), 2);
//img.Draw(blob.ID.ToString(), ref font, Point.Round(blob.Center), new Gray(255.0));
//Only uncomment these if you want to draw a rectangle around the blob and add text
CurrentFrameIDs.Add(blob.ID.ToString());
if (CurrentFrameIDs[i] == LastFrameIDs[i])
img.Draw(Rectangle.Round(blob), new Gray(0,0), 2);//mark the new/changed blob
blobs++;//count each blob
}
blobs = /*your counter here*/;
blobs = 0; //reset
i = 0;
LastFrameIDs = CurrentFrameIDs;
CurrentFrameIDs = null;
viewer.Image = img;//get next frame
});
viewer.ShowDialog();

watershed function provided by EmguCv

I want to use watershed function provided by emgucv.I used the following code but all I get is a white picture.Please help me and correct this code.Thanks.
Image im;
Bitmap bm;
Bitmap bmF;
private void button1_Click(object sender, EventArgs e)//setting the background image
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
im = Image.FromFile(openFileDialog1.FileName);
bm = new Bitmap(im);
}
panel1.BackgroundImage = im;
panel1.Width = im.Width;
panel1.Height = im.Height;
panel1.Visible = true;
}
private void button2_Click(object sender, EventArgs e)
{
watershed(bm);
}
private void watershed(Bitmap bm)
{
Image<Bgr, Byte> imWa = new Image<Bgr, byte>(bm);
Image<Gray, Int32> imgr = new Image<Gray, int>(imWa.Width, imWa.Height);
Rectangle rec = imWa.ROI;
imgr.Draw(new CircleF(new PointF(rec.Left + rec.Width / 2.0f, rec.Top + rec.Height / 2.0f), (float)(Math.Min(imWa.Width, imWa.Height) / 4.0f)), new Gray(255), 0);
CvInvoke.cvWatershed(imWa, imgr);
bmF=new Bitmap(bm.Width,bm.Height);
bmF= imgr.ToBitmap();
panel1.BackgroundImage = (Image)bmF;
panel1.Invalidate();
}
You need to better prepare your mask file for the watershed (i.e. imgr).
For this purpose you need to set all to zero first. You can do that by calling:
CvInvoke.cvZero(imgr);
Then you should introduce at least a second "class". Hence you could draw a second circle with different coordinates (something belonging to the background). To be on the safe side use a different greyvalue for the first cirle (e.g. new Gray(100)) than for the second one (e.g. new Gray(200)).
You will get your result in your mask file imgr at the end, with the two classes showing in different greyvalues.
I am not really sure you need the ROI bit...
Instead of:
bmF= imgr.ToBitmap();
Try this:
bmF= imgr.Convert<Gray,byte>().ToBitmap();

Categories