0

I managed to make License Plate Recognition example from emgucv worked. However I didn't get the desired OCR recognition to recognize the license plate number of vehicle at my place.

this is the code

public class LicensePlateDetector : DisposableObject
{
    private Tesseract _ocr;

    /// <summary>
    /// Create a license plate detector
    /// </summary>
    public LicensePlateDetector()
    {
        //create OCR
        _ocr = new Tesseract();

        _ocr.SetVariable("tessedit_char_whitelist", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
        _ocr.Init(@"D:\tessdata", "eng", false);            
    }

    public List<String> DetectLicensePlate(
     Image<Bgr, byte> img,
     List<Image<Gray, Byte>> licensePlateImagesList,
     List<Image<Gray, Byte>> filteredLicensePlateImagesList,
     List<MCvBox2D> detectedLicensePlateRegionList)
    {
        List<String> licenses = new List<String>();
        using (Image<Gray, byte> gray = img.Convert<Gray, Byte>())
        //using (Image<Gray, byte> gray = GetWhitePixelMask(img))
        using (Image<Gray, Byte> canny = new Image<Gray, byte>(gray.Size))  
        using (MemStorage stor = new MemStorage())
        {
            canny.ThresholdBinary(new Gray(50), new Gray(255));

            CvInvoke.cvCanny(gray, canny, 100, 50, 3);

            canny.Dilate(20);
            
            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, licensePlateImagesList, filteredLicensePlateImagesList, detectedLicensePlateRegionList, licenses);
        }
        return licenses;
    }

    private void FindLicensePlate(
     Contour<Point> contours, Image<Gray, Byte> gray, Image<Gray, Byte> canny,
     List<Image<Gray, Byte>> licensePlateImagesList, List<Image<Gray, Byte>> filteredLicensePlateImagesList, List<MCvBox2D> detectedLicensePlateRegionList,
     List<String> licenses)
    {
        for (; contours != null; contours = contours.HNext)
        {
            //int numberOfChildren = GetNumberOfChildren(contours);
            //if it does not contains any children (charactor), it is not a license plate region
            //if (numberOfChildren == 0) continue;
            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, licensePlateImagesList, filteredLicensePlateImagesList, detectedLicensePlateRegionList, licenses);
                    continue;
                }

                MCvBox2D box = contours.GetMinAreaRect();
                if (box.angle < -45.0)
                {
                    float tmp = box.size.Width;
                    box.size.Width = box.size.Height;
                    box.size.Height = tmp;
                    box.angle += 90.0f;
                }
                else if (box.angle > 45.0)
                {
                    float tmp = box.size.Width;
                    box.size.Width = box.size.Height;
                    box.size.Height = tmp;
                    box.angle -= 90.0f;
                }

                double whRatio = (double)box.size.Width / box.size.Height;
                if (!(3.0 < whRatio && whRatio < 8.0))
                //if (!(1.0 < whRatio && whRatio < 2.0))
                {  //if the width height ratio is not in the specific range,it is not a license plate 
                    //However we should search the children of this contour to see if any of them is a license plate
                    Contour<Point> child = contours.VNext;
                    if (child != null)
                        FindLicensePlate(child, gray, canny, licensePlateImagesList, filteredLicensePlateImagesList, detectedLicensePlateRegionList, licenses);
                    continue;
                }

                using (Image<Gray, Byte> tmp1 = gray.Copy(box))
                //resize the license plate such that the front is ~ 10-12. This size of front results in better accuracy from tesseract
                using (Image<Gray, Byte> tmp2 = tmp1.Resize(240, 180, Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC, true))
                {
                    //removes some pixels from the edge
                    int edgePixelSize = 2;
                    tmp2.ROI = new Rectangle(new Point(edgePixelSize, edgePixelSize), tmp2.Size - new Size(2 * edgePixelSize, 2 * edgePixelSize));
                    Image<Gray, Byte> plate = tmp2.Copy();

                    Image<Gray, Byte> filteredPlate = FilterPlate(plate);

                    
                    //Tesseract.Charactor[] words;
                    List<Word> words;
                    StringBuilder strBuilder = new StringBuilder();
                    using (Bitmap tmp = filteredPlate.Bitmap)
                    {
                        //_ocr.Recognize(tmp);
                        words = _ocr.DoOCR(tmp, filteredPlate.ROI);
                        
                        if (words.Count == 0) continue;

                        for (int i = 0; i < words.Count; i++)
                        {
                            strBuilder.Append(words[i].Text);
                        }
                    }

                    licenses.Add(strBuilder.ToString());
                    licensePlateImagesList.Add(plate);
                    filteredLicensePlateImagesList.Add(filteredPlate);
                    detectedLicensePlateRegionList.Add(box);

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

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

    private static int GetNumberOfChildren(Contour<Point> contours)
    {
        Contour<Point> child = contours.VNext;
        if (child == null) return 0;
        int count = 0;
        while (child != null)
        {
            count++;
            child = child.HNext;
        }
        return count;
    }
    protected override void DisposeObject()
    {
        _ocr.Dispose();
    }
}

this is the picture of the local car with standard license plate number. I used openfiledialog to obtain it.

nissan plate recognition

However, my program failed to recognize any character add all. the closest that I got was for this picture. but this not kind of picture I want for recognition

Bmw car plate recognition

was it due to image size or perhaps i need to do further image pre-processing. Thanks in advance

Community
  • 1
  • 1
  • One more thing which is about the width-height ratio. This code is for European license plate. I actually have no idea on how to find about this width-height ratio for my country. I have gone to the transportation ministry website and couldn't find anything regard to this. – Muadzir Aziz Apr 21 '15 at 10:39
  • 1
    Are those the images you feed to tesseract? Or is this the original which you feed to the Licence detector? What are we supposed to do with the long listing? You are giving a whitelist to tesseract with uppercase and lowercase letters, but the plates contain only uppercase letters. And numbers seem to be missing. For a case like this a -psm=7 option would make sense. – tobltobs Apr 22 '15 at 12:46

0 Answers0