Reading System.Drawing.Bitmap into OpenCV / Emgu to find contours - c#

My code is divided in three parts: PART 1) Drawing in a bitmap, PART 2) Saving the bitmap as a jpg image, PART 3) Reading the jpg file and find contours using Emgu.
These three parts work separately but I cannot make them work together. Particularly, my problem is how to input the System.Drawing.Bitmap of PART 1 into PART 3 which input is an Image<Bgr, Byte>.
So far I have tried to read the Bitmap "target" in Part 1 directly into Part 3 doing Image<Bgr, Byte> imageFrame = new Image<Bgr, Byte>(target) with no success (it doen't identify any contours)
I have also tried to create an intermediate .jpg file (Part 2) they can share with no success either (it doen't identify any contours either).
The only way that I can make this work is:
i) Run Part 1 and Part 2
ii) Open the resultant jpg image using Paint, hit "Save" and close Paint. I have done this manually.
iii) Run Part 3.
Doing this the contour is identified. However this is not a valid solutions since the step ii) is not automated. However this might help illustrating what the problem is.
Can someone help?
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.Structure;
namespace Contouring
{
class Program
{
static void Main(string[] args)
{
//--------------------------------------PART 1 : DRAWING STUFF IN A BITMAP------------------------------------------------------------------------------------
Pen blackPen = new Pen(Color.FromArgb(255, 0, 0, 0), 1);
Bitmap bmp = new Bitmap(1000, 1000);
Graphics g = Graphics.FromImage(bmp);
//This is just an example using three rectangles for illustration purposes.
//In reality I have a set of arbitrary lines defining complex polygons.
g.DrawRectangle(blackPen, new Rectangle(10, 10, 200, 100)); //rectangle 1
g.DrawRectangle(blackPen, new Rectangle(20, 20, 50, 30)); //rectangle 2
g.DrawRectangle(blackPen, new Rectangle(200, 10, 25, 25)); //rectangle 3
Rectangle r = new Rectangle(10, 10, 250, 250); //bounding box of the 3 rectangles
Rectangle rcrop = new Rectangle(r.X, r.Y, r.Width + 10, r.Height + 10);//This is the cropping rectangle (bonding box adding 10 extra units width and height)
//Crop the model from the bmp
Bitmap src = bmp;
Bitmap target = new Bitmap(r.Width, r.Height);
using (Graphics gs = Graphics.FromImage(target))
{
gs.DrawImage(src, new Rectangle(5, 5, 250, 250), rcrop, GraphicsUnit.Pixel);
gs.Dispose();
}
//--------------------------------------PART 2 : SAVING THE BMP AS JPG------------------------------------------------------------------------------------
target.Save("test.jpg");
//--------------------------------------PART 3 : USING THE SAVED PICTURE AND FIND CONTOURS ----------------------------------------------------------------
Image<Bgr, Byte> imageFrame = new Image<Bgr, Byte>("test.jpg");
//Image<Bgr, Byte> imageFrame = new Image<Bgr, Byte>(target);
//Find contours
Image<Gray, byte> grayFrame = imageFrame.Convert<Gray, byte>();
List<Contour<Point>> result = new List<Contour<Point>>();
using (MemStorage storage = new MemStorage()) //allocate storage for contour approximation
for (Contour<Point> contours = grayFrame.FindContours(Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_LIST, storage); contours != null; contours = contours.HNext)
{
//here i do stuff with the contours and add them to the list result
result.Add(contours);
}
//Write to console
Console.WriteLine(result.Count + " NO. contours have been identified");
}//endmain
}//endprogram
}//endNamespace

Well, I think that trouble is in transparent. EmguCV doesn't understand images with transparent. I updated your code, so, now it work without saving. Does it help or you need transparent images?
//--------------------------------------PART 1 : DRAWING STUFF IN A BITMAP------------------------------------------------------------------------------------
var blackPen = new Pen(Color.FromArgb(255, 0, 0, 0), 1);
var bmp = new Bitmap(1000, 1000, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(bmp))
{
g.Clear(Color.White);
//This is just an example using three rectangles for illustration purposes.
//In reality I have a set of arbitrary lines defining complex polygons.
g.DrawRectangle(blackPen, new Rectangle(10, 10, 200, 100)); //rectangle 1
g.DrawRectangle(blackPen, new Rectangle(20, 20, 50, 30)); //rectangle 2
g.DrawRectangle(blackPen, new Rectangle(200, 10, 25, 25)); //rectangle 3
}
var r = new Rectangle(10, 10, 250, 250); //bounding box of the 3 rectangles
var rcrop = new Rectangle(r.X, r.Y, r.Width + 10, r.Height + 10);//This is the cropping rectangle (bonding box adding 10 extra units width and height)
//Crop the model from the bmp
var src = bmp;
var target = new Bitmap(r.Width, r.Height);
using (var gs = Graphics.FromImage(target))
{
gs.DrawImage(src, new Rectangle(5, 5, 250, 250), rcrop, GraphicsUnit.Pixel);
gs.Dispose();
}
//--------------------------------------PART 3 : USING THE SAVED PICTURE AND FIND CONTOURS ----------------------------------------------------------------
var imageFrame = new Image<Bgr, Byte>(target);
//Find contours
var grayFrame = imageFrame.Convert<Gray, byte>();
var result = new List<Contour<Point>>();
using (var storage = new MemStorage()) //allocate storage for contour approximation
for (var contours = grayFrame.FindContours(CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, RETR_TYPE.CV_RETR_LIST, storage); contours != null; contours = contours.HNext)
{
//here i do stuff with the contours and add them to the list result
result.Add(contours);
}

Related

How to perform image lighting correction with OpenCV?

I have an image which I grab using a camera. Sometimes, the lighting is uneven in them image. There are some dark shades. This causes incorrect optimal thresholding in EMGU as well as Aforge to process the image for OCR.
This is the image:
This is what I get after thresholding:
How do I correct the lighting? I tried adaptive threshold, gives about the same result. Tried gamma correction too using the code below:
ImageAttributes attributes = new ImageAttributes();
attributes.SetGamma(10);
// Draw the image onto the new bitmap
// while applying the new gamma value.
System.Drawing.Point[] points =
{
new System.Drawing.Point(0, 0),
new System.Drawing.Point(image.Width, 0),
new System.Drawing.Point(0, image.Height),
};
Rectangle rect =
new Rectangle(0, 0, image.Width, image.Height);
// Make the result bitmap.
Bitmap bm = new Bitmap(image.Width, image.Height);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.DrawImage(HSICONV.Bitmap, points, rect,
GraphicsUnit.Pixel, attributes);
}
same result. Please help.
UPDATE:
as per Nathancy's suggestion I converted his code to c# for uneven lighting correction and it works:
Image<Gray, byte> smoothedGrayFrame = grayImage.PyrDown();
smoothedGrayFrame = smoothedGrayFrame.PyrUp();
//canny
Image<Gray, byte> cannyFrame = null;
cannyFrame = smoothedGrayFrame.Canny(50, 50);
//smoothing
grayImage = smoothedGrayFrame;
//binarize
Image<Gray, byte> grayout = grayImage.Clone();
CvInvoke.AdaptiveThreshold(grayImage, grayout, 255, AdaptiveThresholdType.GaussianC, ThresholdType.BinaryInv, Convert.ToInt32(numericmainthreshold.Value) + Convert.ToInt32(numericmainthreshold.Value) % 2 + 1, 1.2d);
grayout._Not();
Mat kernelCl = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), new System.Drawing.Point(-1, -1));
CvInvoke.MorphologyEx(grayout, grayout, MorphOp.Close, kernelCl, new System.Drawing.Point(-1, -1), 1, BorderType.Default, new MCvScalar());
Here's an approach:
Convert image to grayscale and Gaussian blur to smooth image
Adaptive threshold to obtain binary image
Perform morphological transformations to smooth image
Dilate to enhance text
Invert image
After converting to grayscale and blurring, we adaptive threshold
There are small holes and imperfections so we perform a morph close to smooth the image
From we here can optionally dilate to enhance the text
Now we invert the image to get our result
I implemented this method in OpenCV and Python but you can adapt the same strategy into C#
import cv2
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, \
cv2.THRESH_BINARY_INV,9,11)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
dilate = cv2.dilate(close, kernel, iterations=1)
result = 255 - dilate
cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('dilate', dilate)
cv2.imshow('result', result)
cv2.waitKey()

Finding contour points in emgucv

I am working with emguCV for finding contours essential points then saving this point in a file and user redraw this shape in future. so, my goal is this image:
example
my solution is this:
1. import image to picturebox
2. edge detection with canny algorithm
3. finding contours and save points
I found a lot of points with below codes but i can't drawing first shape with this point!
using Emgu.CV;
using Emgu.Util;
private void button1_Click(object sender, EventArgs e)
{
Bitmap bmp = new Bitmap(pictureBox1.Image);
Image<Bgr, Byte> img = new Image<Bgr, byte>(bmp);
Image<Gray, Byte> gray = img.Convert<Gray, Byte>().PyrDown().PyrUp();
Gray cannyThreshold = new Gray(80);
Gray cannyThresholdLinking = new Gray(120);
Gray circleAccumulatorThreshold = new Gray(120);
Image<Gray, Byte> cannyEdges = gray.Canny(cannyThreshold, cannyThresholdLinking).Not();
Bitmap color;
Bitmap bgray;
IdentifyContours(cannyEdges.Bitmap, 50, true, out bgray, out color);
pictureBox1.Image = color;
}
public void IdentifyContours(Bitmap colorImage, int thresholdValue, bool invert, out Bitmap processedGray, out Bitmap processedColor)
{
Image<Gray, byte> grayImage = new Image<Gray, byte>(colorImage);
Image<Bgr, byte> color = new Image<Bgr, byte>(colorImage);
grayImage = grayImage.ThresholdBinary(new Gray(thresholdValue), new Gray(255));
if (invert)
{
grayImage._Not();
}
using (MemStorage storage = new MemStorage())
{
for (Contour<Point> contours = grayImage.FindContours(Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_LIST, storage); contours != null; contours = contours.HNext)
{
Contour<Point> currentContour = contours.ApproxPoly(contours.Perimeter * 0.015, storage);
if (currentContour.BoundingRectangle.Width > 20)
{
CvInvoke.cvDrawContours(color, contours, new MCvScalar(255), new MCvScalar(255), -1, 1, Emgu.CV.CvEnum.LINE_TYPE.EIGHT_CONNECTED, new Point(0, 0));
color.Draw(currentContour.BoundingRectangle, new Bgr(0, 255, 0), 1);
}
Point[] pts = currentContour.ToArray();
foreach (Point p in pts)
{
//add points to listbox
listBox1.Items.Add(p);
}
}
}
processedColor = color.ToBitmap();
processedGray = grayImage.ToBitmap();
}
In your code you have added contour approximation operation
Contour<Point> currentContour = contours.ApproxPoly(contours.Perimeter * 0.015, storage);
This contour approximation will approximate your Contour to a nearest polygon & so your actual points got shifted. If you want to reproduce the same image you need not to do any approximation.
Refer this thread.

Overlay bitmap on another bitmap

On a single bitmap I need to display graphs and text values. So what I did is create a bitmap with points and creating a another bitmap with the text and place on the large bitmap.
I tried using the brush to write the text, but I am not able to see the underlying graphics even though trasparency is set.
Instead I thought to set the transparency for the text bitmap, but the bitmap which I have created are 24 bit rgb. So can we set the transparency for the 24 bit map.
Bitmap textBitmap = null;
textBitmap = new Bitmap(10, 10, PixelFormat.Format24bppRgb);
using (Graphics memoryGrahics =
Graphics.FromImage(textBitmap))
{
memoryGrahics.FillRectangle(Brushes.Black, new Rectangle(0, 0, 100, 100));
memoryGrahics.DrawString(result, f, Brushes.White, x, y);
}
//placing the text bitmap on the graphbitmap
using (Graphics g = Graphics.FromImage(GraphBitmap))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
textBitmap.MakeTransparent();
g.DrawImage(textBitmap, 0, 0);
return GraphBitmap;
}
well.. it seems like you are using 2 different Graphical objects... although 1 Graphics objects with 1 bitmap can handle multiple layouts of custom drawings, like so:
int width = 800, height = 600;
var bit = new Bitmap(width, height);
var g = Graphics.FromImage(bit);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
var area = new Rectangle(0, 0, width, height);
g.FillRectangle(new LinearGradientBrush(area, Color.PaleGoldenrod, Color.OrangeRed, 45), area);
g.DrawImage(Image.FromFile(#"your image"), new Point(10, 10));
g.DrawString("sample", new System.Drawing.Font("Tahoma", 56), new SolidBrush(Color.Black), new PointF(50, 50));
pictureBox1.Image = bit;
note that g.DrawImage method can be used to load other bitmaps as well

plotting points on a form

I want to plot points over an image that are a bit transparent. As in I'm able to see over what area are they present. Is there any way on C# .net platform to do so.??
Thanks.
This is one way to do it.
Image bitmap = new Bitmap(100, 100); // sample image, load your real image from file here
using (var g = Graphics.FromImage(bitmap))
{
g.FillRectangle(Brushes.Red, new Rectangle(0, 0, bitmap.Width, bitmap.Height)); // Just to fill the background on the sample image, remove this
var transparentColor = Color.FromArgb(127, Color.Blue); // Create a semitransparent color
using(Brush brush = new SolidBrush(transparentColor))
{
// Create the dot
g.FillEllipse(brush, new Rectangle(10, 10, 25, 25));
// Create another dot
g.FillEllipse(brush, new Rectangle(25, 15, 25, 25));
}
}
myPictureBox.Image = bitmap; // display the image in an Imagebox (optional, you might use your image somewhere else)

C# GDI+ curve drawing issue

I'm trying to draw a series of connected segments, but the curved segments seem to produce an artifact, whereby the outer side of the curve is not smooth at all, but very jagged. This is part of a GIS program I am making.
For these lines, the line itself needs to be quite wide, as this represents the range of data that can be collected on this line for the GIS data. There also has to be an area directly under the line where no data is collected. This also can be wide, but not as wide as the main line.
I have done this using a graphics path, which I then widen and use as a clipping region to block the area directly under the line. I then draw the actual line. The sample code below does this, with made up values for ease of regenerating.
This works fine with straight lines, but with curved lines there are very irregular shapes on the outside of the curves. I have no idea why this happens.
Any ideas would be much appreciated, cheers,
Greg
I made this sample code using a basic form with a picturebox and a button on it, whereby when I clicked the button it would execute this method:
private void drawCurvedLine()
{
//initialise the plot area:
Bitmap image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.BackgroundImage = image;
Graphics g = Graphics.FromImage(image);
//the width of the pen represents the width of a sonar swathe:
Pen widePen = new Pen(new SolidBrush(Color.FromArgb(80, Color.Blue)), 50);
PointF[] points = new PointF[4];
//first straight:
points[0] = new PointF(287.284149F,21.236269F);
points[1] = new PointF(183.638443F,406.936249F);
//second straight:
points[2] = new PointF(130.842773F, 515.574036F);
points[3] = new PointF(-1950.91321F, 3491.868F);
//graphics path for the line:
GraphicsPath gPath = new GraphicsPath();
gPath.AddLine(points[0], points[1]);
gPath.AddArc(new RectangleF(-445.464447F,3.84924316F,640.067444F,640.067444F), -(90 - 105.0412369999982F), 10.8775282F);
gPath.AddArc(new RectangleF(-445.464417F, 3.84915161F, 640.067444F, 640.067444F), -(90 - 115.91811484539707F), 10.8775091F);
gPath.AddLine(points[2], points[3]);
//widen the line to the width equal to what the fish will not be able to see:
gPath.Widen(new Pen(Color.White, 10));
//now exclude that widened line from the main graphics:
g.ExcludeClip(new Region(gPath));
//draw the swathe line:
g.DrawPath(widePen, gPath);
//reset the clipping for the next line:
g.ResetClip();
}
Try to use a separate GraphicsPath for excluded region:
private void drawCurvedLine()
{
//initialise the plot area:
Bitmap image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.BackgroundImage = image;
using(Graphics g = Graphics.FromImage(image))
{
PointF[] points = new PointF[4];
//first straight:
points[0] = new PointF(287.284149F, 21.236269F);
points[1] = new PointF(183.638443F, 406.936249F);
//second straight:
points[2] = new PointF(130.842773F, 515.574036F);
points[3] = new PointF(-1950.91321F, 3491.868F);
//graphics path for the line:
using(GraphicsPath gPath = new GraphicsPath())
{
gPath.AddLine(points[0], points[1]);
gPath.AddArc(new RectangleF(-445.464447F, 3.84924316F, 640.067444F, 640.067444F), -(90 - 105.0412369999982F), 10.8775282F);
gPath.AddArc(new RectangleF(-445.464417F, 3.84915161F, 640.067444F, 640.067444F), -(90 - 115.91811484539707F), 10.8775091F);
gPath.AddLine(points[2], points[3]);
//widen the line to the width equal to what the fish will not be able to see:
using(GraphicsPath innerPath = (GraphicsPath)gPath.Clone())
{
using(Pen pen = new Pen(Color.White, 10))
{
innerPath.Widen(pen);
}
//now exclude that widened line from the main graphics:
using(Region reg = new Region(innerPath))
{
g.ExcludeClip(reg);
//draw the swathe line:
//the width of the pen represents the width of a sonar swathe:
using(Pen widePen = new Pen(new SolidBrush(Color.FromArgb(80, Color.Blue)), 50))
{
g.DrawPath(widePen, gPath);
}
//reset the clipping for the next line:
g.ResetClip();
}
}
}
}
}
Set the smoothing mode properly on your Graphics instance. Take a look here.
Try setting the CompositingQuality, the InterpolationMode and the SmoothingMode properties to increase the quality of your Graphics object:
using(Graphics g = Graphics.FromImage(image))
{
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.AntiAlias;
//...
}

Categories