License Plate Recognition in CSharp: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
mNo edit summary |
||
Line 1: | Line 1: | ||
<font color=green>''' This project is part of the Emgu.CV.Example solution of [[Version_History#Emgu.CV-2.0.0. | <font color=green>''' This project is part of the Emgu.CV.Example solution of [[Version_History#Emgu.CV-2.0.0.0_Alpha|Version 2.0.0.0 Alpha]] release '''</font> | ||
== System Requirement == | == System Requirement == | ||
Line 5: | Line 5: | ||
!Component || Requirement || Detail | !Component || Requirement || Detail | ||
|- | |- | ||
|Emgu CV || [[Version_History#Emgu.CV-2.0.0. | |Emgu CV || [[Version_History#Emgu.CV-2.0.0.0_Alpha|Version 2.0.0.0 Alpha]] || | ||
|- | |- | ||
|Operation System || Windows Only || The OCR engine tessnet2 is written in unmanaged C++ <br/> Not compatible with Linux | |Operation System || Windows Only || The OCR engine tessnet2 is written in unmanaged C++ <br/> Not compatible with Linux |
Revision as of 14:46, 14 July 2009
This project is part of the Emgu.CV.Example solution of Version 2.0.0.0 Alpha release
System Requirement
Component | Requirement | Detail |
---|---|---|
Emgu CV | Version 2.0.0.0 Alpha | |
Operation System | Windows Only | The OCR engine tessnet2 is written in unmanaged C++ Not compatible with Linux |
License Plate Recognition
According to wikipedia
- Automatic number plate recognition (ANPR; see also other names below) is a mass surveillance method that uses optical character recognition on images to read the license plates on vehicles. As of 2006, systems can scan number plates at around one per second on cars traveling up to 100 mph (160 km/h).[citation needed] They can use existing closed-circuit television or road-rule enforcement cameras, or ones specifically designed for the task. They are used by various police forces and as a method of electronic toll collection on pay-per-use roads and monitoring traffic activity, such as red light adherence in an intersection.
- ANPR can be used to store the images captured by the cameras as well as the text from the license plate, with some configurable to store a photograph of the driver. Systems commonly use infrared lighting to allow the camera to take the picture at any time of the day. A powerful flash is included in at least one version of the intersection-monitoring cameras, serving both to illuminate the picture and to make the offender aware of his or her mistake. ANPR technology tends to be region-specific, owing to plate variation from place to place.
This tutorial's approach to ANPR is divided into two stage
- In the first stage, we perform license plate detection
- In the second stage, we perform OCR on the license plate to recover the license number
Complete Source code
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Emgu.Util;
using Emgu.CV;
using Emgu.CV.Structure;
using tessnet2;
using System.Diagnostics;
namespace LicensePlateRecognition
{
/// <summary>
/// A license plate detector
/// </summary>
public class LicensePlateDetector : DisposableObject
{
private Tesseract _ocr;
/// <summary>
/// Create a license plate detector
/// </summary>
public LicensePlateDetector()
{
//create OCR
_ocr = new Tesseract();
//You can download more language definition data from
//http://code.google.com/p/tesseract-ocr/downloads/list
//Languages supported includes:
//Dutch, Spanish, German, Italian, French and English
_ocr.Init("eng", false);
}
/// <summary>
/// Detect license plate from the given image
/// </summary>
/// <param name="img">The image to search license plate from</param>
/// <param name="licensePlateList">A list of images where the detected license plate region is stored</param>
/// <param name="filteredLicensePlateList">A list of images where the detected license plate region with noise removed is stored</param>
/// <param name="boxList">A list where the region of license plate, defined by an MCvBox2D is stored</param>
/// <returns>The list of words for each license plate</returns>
public List<List<Word>> DetectLicensePlate(Image<Bgr, byte> img, List<Image<Gray, Byte>> licensePlateList, List<Image<Gray, Byte>> filteredLicensePlateList, List<MCvBox2D> boxList)
{
//Stopwatch w = Stopwatch.StartNew();
List<List<Word>> licenses = new List<List<Word>>();
using (Image<Gray, byte> gray = img.Convert<Gray, Byte>())
using (Image<Gray, Byte> canny = new Image<Gray, byte>(gray.Size))
using (MemStorage stor = new MemStorage())
{
CvInvoke.cvCanny(gray, canny, 100, 50, 3);
Contour<Point> contours = canny.FindContours(
Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_TREE,
stor);
FindLicensePlate(contours, gray, canny, licensePlateList, filteredLicensePlateList, boxList, licenses);
}
//w.Stop();
return licenses;
}
private void FindLicensePlate(
Contour<Point> contours, Image<Gray, Byte> gray, Image<Gray, Byte> canny,
List<Image<Gray, Byte>> licensePlateList, List<Image<Gray, Byte>> filteredLicensePlateList, List<MCvBox2D> boxList,
List<List<Word>> licenses)
{
for (; contours != null; contours = contours.HNext)
{
Contour<Point> approxContour = contours.ApproxPoly(contours.Perimeter * 0.05, contours.Storage);
if (approxContour.Area > 100 && approxContour.Total == 4)
{
//img.Draw(contours, new Bgr(Color.Red), 1);
if (!IsParallelogram(approxContour.ToArray()))
{
Contour<Point> child = contours.VNext;
if (child != null)
FindLicensePlate(child, gray, canny, licensePlateList, filteredLicensePlateList, boxList, licenses);
continue;
}
MCvBox2D box = approxContour.GetMinAreaRect();
double whRatio = (double)box.size.Width / box.size.Height;
if (!(3.0 < whRatio && whRatio < 8.0))
{
Contour<Point> child = contours.VNext;
if (child != null)
FindLicensePlate(child, gray, canny, licensePlateList, filteredLicensePlateList, boxList, licenses);
continue;
}
Image<Gray, Byte> plate = gray.Copy(box);
Image<Gray, Byte> filteredPlate = FilterPlate(plate);
List<Word> words;
using (Bitmap bmp = filteredPlate.Bitmap)
words = _ocr.DoOCR(bmp, filteredPlate.ROI);
licenses.Add(words);
licensePlateList.Add(plate);
filteredLicensePlateList.Add(filteredPlate);
boxList.Add(box);
}
}
}
/// <summary>
/// Check if the four points forms a parallelogram
/// </summary>
/// <param name="pts">The four points that defines a polygon</param>
/// <returns>True if the four points defines a parallelogram</returns>
private static bool IsParallelogram(Point[] pts)
{
LineSegment2D[] edges = PointCollection.PolyLine(pts, true);
double diff1 = Math.Abs(edges[0].Length - edges[2].Length);
double diff2 = Math.Abs(edges[1].Length - edges[3].Length);
if (diff1 / edges[0].Length <= 0.05 && diff1 / edges[2].Length <= 0.05
&& diff2 / edges[1].Length <= 0.05 && diff2 / edges[3].Length <= 0.05)
{
return true;
}
return false;
}
/// <summary>
/// Filter the license plate to remove noise
/// </summary>
/// <param name="plate">The license plate image</param>
/// <returns>License plate image without the noise</returns>
private static Image<Gray, Byte> FilterPlate(Image<Gray, Byte> plate)
{
Image<Gray, Byte> thresh = plate.ThresholdBinaryInv(new Gray(120), new Gray(255));
using (Image<Gray, Byte> plateMask = new Image<Gray, byte>(plate.Size))
using (Image<Gray, Byte> plateCanny = plate.Canny(new Gray(100), new Gray(50)))
using (MemStorage stor = new MemStorage())
{
plateMask.SetValue(255.0);
for (
Contour<Point> contours = plateCanny.FindContours(
Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_EXTERNAL,
stor);
contours != null; contours = contours.HNext)
{
Rectangle rect = contours.BoundingRectangle;
if (rect.Height > (plate.Height >> 1))
{
rect.X -= 1; rect.Y -= 1; rect.Width += 2; rect.Height += 2;
rect.Intersect(plate.ROI);
plateMask.Draw(rect, new Gray(0.0), -1);
}
}
thresh.SetValue(0, plateMask);
}
thresh._Erode(1);
thresh._Dilate(1);
return thresh;
}
protected override void DisposeObject()
{
_ocr.Dispose();
}
}
}
Result
