How to perform image lighting correction with OpenCV? - c#

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()

Related

Rotate a Gmap marker (bitmap) by any degree in C#

I'm using GMaps for using google maps on c#. I write latitude and langitude values and press the load button. Then the code put a marker(like arrow) that point. I want to rotate that marker for any degree like google maps'. I don't have any sensors so I can write the degree in a textbox and press a rotate button. How can i do that? This code shows how I create markers and put them into my map. I know bitmap a little but not too much and sorry for my bad english. I hope you'll understand what I want.
`double lat = Convert.ToDouble(txtLat.Text);
double lng = Convert.ToDouble(txtLong.Text);
map.Position = new PointLatLng(lat, lng);
//custom marker
Bitmap bmpMarker = (Bitmap)Image.FromFile("img/arrow.png");
PointLatLng point = new PointLatLng(lat, lng);
GMap.NET.WindowsForms.GMapMarker marker = new GMarkerGoogle(point, bmpMarker);
//1. Create a Overlay
GMapOverlay markers = new GMapOverlay("markers");
map.ZoomAndCenterMarkers("markers");
//2. Add all available markers to that Overlay
markers.Markers.Add(marker);
//3. Cover map with Overlay
map.Overlays.Add(markers);
//RotateImage(bmpMarker, 180.0f);
marker.ToolTipText = map.Position.ToString();`
Try to set the Bitmap image again with the rotation because it is not a reference type:
GMap.NET.WindowsForms.GMapMarker marker = new GMarkerGoogle(point, RotateImg(bmpMarker,45));
Suggestion to rotate the bitmap:
public Bitmap RotateImg(Bitmap bmpimage, float angle)
{
int w = bmpimage.Width;
int h = bmpimage.Height;
PixelFormat pf;
pf = bmpimage.PixelFormat;
Bitmap tempImg = new Bitmap(w, h, pf);
Graphics g = Graphics.FromImage(tempImg);
g.DrawImageUnscaled(bmpimage, 1, 1);
g.Dispose();
GraphicsPath path = new GraphicsPath();
path.AddRectangle(new RectangleF(0.0F, 0.0F, w, h));
Matrix mtrx = new Matrix();
mtrx.Rotate(angle);
RectangleF rct = path.GetBounds(mtrx);
Bitmap newImg = new Bitmap(Convert.ToInt32(rct.Width), Convert.ToInt32(rct.Height), pf);
g = Graphics.FromImage(newImg);
g.TranslateTransform(-rct.X, -rct.Y);
g.RotateTransform(angle);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImageUnscaled(tempImg, 0, 0);
g.Dispose();
tempImg.Dispose();
return newImg;
}

Cropping Image(From Screen)

I have read many question about this. But my question is little bit different. What i need to do crop image from screen.
There is my codes
Bitmap photo = new Bitmap(Screen.PrimaryScreen.Bounds.Width,Screen.primaryScreen.Bounds.Height)
Graphics gr = Graphics.FromImage(photo);
gr.CopyFromScreen(0,0,0,0 new size(foto.Width,foto.Height));
picturebox1.Image = photo;
And there my crop codes
Rectangle cropRec = new Rectangle(1,1,1,1);
Bitmap target = new Bitmap(cropRec.Width,cropRec.Height);
using(Graphics g = Graphics.FromImage(target))
{
g.DrawImage(photo,new Rentangle(0,0,target.Width,target.Height),cropRec,GraphicsUnit.Pixel);
}
I want to crop middle part of this photo and compare with itself.
Thanks n advance
To symmetrically crop your image as shown in the following sketch, try creating a Bitmap with margins in the X and Y axis and then cropping the screenshot image by using those margins in CopyFromScreen():
Bitmap printscreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width - 2 * xmargin, Screen.PrimaryScreen.Bounds.Height - 2 * ymargin);
Graphics graphics = Graphics.FromImage(printscreen as Image);
graphics.CopyFromScreen(xmargin, ymargin, 0, 0, printscreen.Size);

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

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);
}

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.

c# write text on bitmap

I have following problem. I want to make some graphics in c# windows form.
I want to read bitmap to my program and after it write some text on this bitmap. In the end I want this picture load to pictureBox. And it's my question. How can I do it?
example, how must it work:
Bitmap a = new Bitmap(#"path\picture.bmp");
a.makeTransparent();
// ? a.writeText("some text", positionX, positionY);
pictuteBox1.Image = a;
Is it possible do to?
Bitmap bmp = new Bitmap("filename.bmp");
RectangleF rectf = new RectangleF(70, 90, 90, 50);
Graphics g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawString("yourText", new Font("Tahoma",8), Brushes.Black, rectf);
g.Flush();
image.Image=bmp;
Very old question, but just had to build this for an app today and found the settings shown in other answers do not result in a clean image (possibly as new options were added in later .Net versions).
Assuming you want the text in the centre of the bitmap, you can do this:
// Load the original image
Bitmap bmp = new Bitmap("filename.bmp");
// Create a rectangle for the entire bitmap
RectangleF rectf = new RectangleF(0, 0, bmp.Width, bmp.Height);
// Create graphic object that will draw onto the bitmap
Graphics g = Graphics.FromImage(bmp);
// ------------------------------------------
// Ensure the best possible quality rendering
// ------------------------------------------
// The smoothing mode specifies whether lines, curves, and the edges of filled areas use smoothing (also called antialiasing).
// One exception is that path gradient brushes do not obey the smoothing mode.
// Areas filled using a PathGradientBrush are rendered the same way (aliased) regardless of the SmoothingMode property.
g.SmoothingMode = SmoothingMode.AntiAlias;
// The interpolation mode determines how intermediate values between two endpoints are calculated.
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Use this property to specify either higher quality, slower rendering, or lower quality, faster rendering of the contents of this Graphics object.
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
// This one is important
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
// Create string formatting options (used for alignment)
StringFormat format = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
// Draw the text onto the image
g.DrawString("yourText", new Font("Tahoma",8), Brushes.Black, rectf, format);
// Flush all graphics changes to the bitmap
g.Flush();
// Now save or use the bitmap
image.Image = bmp;
References
https://msdn.microsoft.com/en-us/library/system.drawing.graphics.smoothingmode(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.interpolationmode(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.drawing.graphics.pixeloffsetmode(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.drawing.graphics.textrenderinghint(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.drawing.stringformat(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/21kdfbzs(v=vs.110).aspx
You need to use the Graphics class in order to write on the bitmap.
Specifically, one of the DrawString methods.
Bitmap a = new Bitmap(#"path\picture.bmp");
using(Graphics g = Graphics.FromImage(a))
{
g.DrawString(....); // requires font, brush etc
}
pictuteBox1.Image = a;
var bmp = new Bitmap(#"path\picture.bmp");
using( Graphics g = Graphics.FromImage( bmp ) )
{
g.DrawString( ... );
}
picturebox1.Image = bmp;
If you want wrap your text, then you should draw your text in a rectangle:
RectangleF rectF1 = new RectangleF(30, 10, 100, 122);
e.Graphics.DrawString(text1, font1, Brushes.Blue, rectF1);
See: https://msdn.microsoft.com/en-us/library/baw6k39s(v=vs.110).aspx

Categories