///'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' public PossiblePlate extractPlate(Mat imgOriginal, List <PossibleChar> listOfMatchingChars) { PossiblePlate possiblePlate = new PossiblePlate(); //this will be the return value //sort chars from left to right based on x position listOfMatchingChars.Sort((firstChar, secondChar) => firstChar.intCenterX.CompareTo(secondChar.intCenterX)); //calculate the center point of the plate double dblPlateCenterX = Convert.ToDouble(listOfMatchingChars[0].intCenterX + listOfMatchingChars[listOfMatchingChars.Count - 1].intCenterX) / 2.0; double dblPlateCenterY = Convert.ToDouble(listOfMatchingChars[0].intCenterY + listOfMatchingChars[listOfMatchingChars.Count - 1].intCenterY) / 2.0; PointF ptfPlateCenter = new PointF(Convert.ToSingle(dblPlateCenterX), Convert.ToSingle(dblPlateCenterY)); //calculate plate width and height int intPlateWidth = Convert.ToInt32(Convert.ToDouble(listOfMatchingChars[listOfMatchingChars.Count - 1].boundingRect.X + listOfMatchingChars[listOfMatchingChars.Count - 1].boundingRect.Width - listOfMatchingChars[0].boundingRect.X) * PLATE_WIDTH_PADDING_FACTOR); int intTotalOfCharHeights = 0; foreach (PossibleChar matchingChar in listOfMatchingChars) { intTotalOfCharHeights = intTotalOfCharHeights + matchingChar.boundingRect.Height; } double dblAverageCharHeight = Convert.ToDouble(intTotalOfCharHeights) / Convert.ToDouble(listOfMatchingChars.Count); object intPlateHeight = Convert.ToInt32(dblAverageCharHeight * PLATE_HEIGHT_PADDING_FACTOR); //calculate correction angle of plate region double dblOpposite = listOfMatchingChars[listOfMatchingChars.Count - 1].intCenterY - listOfMatchingChars[0].intCenterY; double dblHypotenuse = detectChars.distanceBetweenChars(listOfMatchingChars[0], listOfMatchingChars[listOfMatchingChars.Count - 1]); double dblCorrectionAngleInRad = Math.Asin(dblOpposite / dblHypotenuse); double dblCorrectionAngleInDeg = dblCorrectionAngleInRad * (180.0 / Math.PI); //assign rotated rect member variable of possible plate possiblePlate.rrLocationOfPlateInScene = new RotatedRect(ptfPlateCenter, new SizeF(Convert.ToSingle(intPlateWidth), Convert.ToSingle(intPlateHeight)), Convert.ToSingle(dblCorrectionAngleInDeg)); Mat rotationMatrix = new Mat(); //final steps are to perform the actual rotation Mat imgRotated = new Mat(); Mat imgCropped = new Mat(); CvInvoke.GetRotationMatrix2D(ptfPlateCenter, dblCorrectionAngleInDeg, 1.0, rotationMatrix); //get the rotation matrix for our calculated correction angle CvInvoke.WarpAffine(imgOriginal, imgRotated, rotationMatrix, imgOriginal.Size); //rotate the entire image //crop out the actual plate portion of the rotated image CvInvoke.GetRectSubPix(imgRotated, possiblePlate.rrLocationOfPlateInScene.MinAreaRect().Size, possiblePlate.rrLocationOfPlateInScene.Center, imgCropped); possiblePlate.imgPlate = imgCropped; //copy the cropped plate image into the applicable member variable of the possible plate return(possiblePlate); }
///'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' public void writeLicensePlateCharsOnImage(ref Mat imgOriginalScene, PossiblePlate licPlate) { Point ptCenterOfTextArea = new Point(); //this will be the center of the area the text will be written to Point ptLowerLeftTextOrigin = new Point(); //this will be the bottom left of the area that the text will be written to FontFace fontFace = FontFace.HersheySimplex; //choose a plain jane font double dblFontScale = licPlate.imgPlate.Height / 30; //base font scale on height of plate area int intFontThickness = Convert.ToInt32(dblFontScale * 1.5); //base font thickness on font scale Size textSize = new Size(); //to get the text size, we should use the OpenCV function getTextSize, but for some reason Emgu CV does not include this //we can instead estimate the test size based on the font scale, this will not be especially accurate but is good enough for our purposes here textSize.Width = Convert.ToInt32(dblFontScale * 18.5 * licPlate.strChars.Length); textSize.Height = Convert.ToInt32(dblFontScale * 25); ptCenterOfTextArea.X = Convert.ToInt32(licPlate.rrLocationOfPlateInScene.Center.X); //the horizontal location of the text area is the same as the plate //if the license plate is in the upper 3/4 of the image, we will write the chars in below the plate if ((licPlate.rrLocationOfPlateInScene.Center.Y < (imgOriginalScene.Height * 0.75))) { ptCenterOfTextArea.Y = Convert.ToInt32(licPlate.rrLocationOfPlateInScene.Center.Y + Convert.ToInt32(Convert.ToDouble(licPlate.rrLocationOfPlateInScene.MinAreaRect().Height) * 1.6)); //else if the license plate is in the lower 1/4 of the image, we will write the chars in above the plate } else { ptCenterOfTextArea.Y = Convert.ToInt32(licPlate.rrLocationOfPlateInScene.Center.Y - Convert.ToInt32(Convert.ToDouble(licPlate.rrLocationOfPlateInScene.MinAreaRect().Height) * 1.6)); } ptLowerLeftTextOrigin.X = Convert.ToInt32(ptCenterOfTextArea.X - (textSize.Width / 2)); //calculate the lower left origin of the text area ptLowerLeftTextOrigin.Y = Convert.ToInt32(ptCenterOfTextArea.Y + (textSize.Height / 2)); //based on the text area center, width, and height CvInvoke.PutText(imgOriginalScene, licPlate.strChars, ptLowerLeftTextOrigin, fontFace, dblFontScale, SCALAR_YELLOW, intFontThickness); //write the text on the image }
///'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' public void drawRedRectangleAroundPlate(Mat imgOriginalScene, PossiblePlate licPlate) { PointF[] ptfRectPoints = new PointF[5]; //declare array of 4 points, floating point type ptfRectPoints = licPlate.rrLocationOfPlateInScene.GetVertices(); //get 4 vertices of rotated rect Point pt0 = new Point(Convert.ToInt32(ptfRectPoints[0].X), Convert.ToInt32(ptfRectPoints[0].Y)); //declare 4 points, integer type Point pt1 = new Point(Convert.ToInt32(ptfRectPoints[1].X), Convert.ToInt32(ptfRectPoints[1].Y)); Point pt2 = new Point(Convert.ToInt32(ptfRectPoints[2].X), Convert.ToInt32(ptfRectPoints[2].Y)); Point pt3 = new Point(Convert.ToInt32(ptfRectPoints[3].X), Convert.ToInt32(ptfRectPoints[3].Y)); CvInvoke.Line(imgOriginalScene, pt0, pt1, SCALAR_RED, 2); //draw 4 red lines CvInvoke.Line(imgOriginalScene, pt1, pt2, SCALAR_RED, 2); CvInvoke.Line(imgOriginalScene, pt2, pt3, SCALAR_RED, 2); CvInvoke.Line(imgOriginalScene, pt3, pt0, SCALAR_RED, 2); }
private void btnOpenFile_Click(object sender, EventArgs e) { Mat imgOriginalScene = new Mat(); //this is the original image scene bool blnImageOpenedSuccessfully = openImageWithErrorHandling(ref imgOriginalScene); //attempt to open image //if image was not opened successfully if ((!blnImageOpenedSuccessfully)) { ibOriginal.Image = null; //set the image box on the form to blank return; //and bail } lblChosenFile.Text = ofdOpenFile.FileName; //update label with file name CvInvoke.DestroyAllWindows(); //close any windows that are open from previous button press ibOriginal.Image = imgOriginalScene; //show original image on main form List <PossiblePlate> listOfPossiblePlates = detectPlates.detectPlatesInScene(imgOriginalScene); //detect plates listOfPossiblePlates = detectChars.detectCharsInPlates(listOfPossiblePlates); //detect chars in plates //check if list of plates is null or zero if ((listOfPossiblePlates == null)) { txtInfo.AppendText("\r\n" + "no license plates were detected" + "\r\n"); } else if ((listOfPossiblePlates.Count == 0)) { txtInfo.AppendText("\r\n" + "no license plates were detected" + "\r\n"); } else { //if we get in here list of possible plates has at leat one plate //sort the list of possible plates in DESCENDING order (most number of chars to least number of chars) listOfPossiblePlates.Sort((onePlate, otherPlate) => otherPlate.strChars.Length.CompareTo(onePlate.strChars.Length)); //suppose the plate with the most recognized chars PossiblePlate licPlate = listOfPossiblePlates[0]; //(the first plate in sorted by string length descending order) //is the actual plate CvInvoke.Imshow("final imgPlate", licPlate.imgPlate); //show the final color plate image CvInvoke.Imshow("final imgThresh", licPlate.imgThresh); //show the final thresh plate image //if no chars are present in the lic plate, if ((licPlate.strChars.Length == 0)) { txtInfo.AppendText("\r\n" + "no characters were detected" + licPlate.strChars + "\r\n"); //update info text box return; //and return } drawRedRectangleAroundPlate(imgOriginalScene, licPlate); //draw red rectangle around plate txtInfo.AppendText("\r\n" + "license plate read from image = " + licPlate.strChars + "\r\n"); //write license plate text to text box txtInfo.AppendText("\r\n" + "----------------------------------------" + "\r\n"); writeLicensePlateCharsOnImage(ref imgOriginalScene, licPlate); //write license plate text on the image ibOriginal.Image = imgOriginalScene; //update image on main form CvInvoke.Imwrite("imgOriginalScene.png", imgOriginalScene); //write image out to file } }
///'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' public List <PossiblePlate> detectPlatesInScene(Mat imgOriginalScene) { List <PossiblePlate> listOfPossiblePlates = new List <PossiblePlate>(); //this will be the return value Mat imgGrayscaleScene = new Mat(); Mat imgThreshScene = new Mat(); Mat imgContours = new Mat(imgOriginalScene.Size, DepthType.Cv8U, 3); Random random = new Random(); CvInvoke.DestroyAllWindows(); // show steps ''''''''''''''''''''''''''''''''' if ((frm.cbShowSteps.Checked == true)) { CvInvoke.Imshow("0", imgOriginalScene); } // show steps ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Preprocess.preprocess(imgOriginalScene, ref imgGrayscaleScene, ref imgThreshScene); //preprocess to get grayscale and threshold images // show steps ''''''''''''''''''''''''''''''''' if ((frm.cbShowSteps.Checked == true)) { CvInvoke.Imshow("1a", imgGrayscaleScene); CvInvoke.Imshow("1b", imgThreshScene); } // show steps ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //find all possible chars in the scene, //this function first finds all contours, then only includes contours that could be chars (without comparison to other chars yet) List <PossibleChar> listOfPossibleCharsInScene = findPossibleCharsInScene(imgThreshScene); // show steps ''''''''''''''''''''''''''''''''' if ((frm.cbShowSteps.Checked == true)) { frm.txtInfo.AppendText("step 2 - listOfPossibleCharsInScene.Count = " + listOfPossibleCharsInScene.Count.ToString() + "\r\n"); //131 with MCLRNF1 image VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); foreach (PossibleChar possibleChar in listOfPossibleCharsInScene) { contours.Push(possibleChar.contour); } CvInvoke.DrawContours(imgContours, contours, -1, SCALAR_WHITE); CvInvoke.Imshow("2b", imgContours); } // show steps ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //given a list of all possible chars, find groups of matching chars //in the next steps each group of matching chars will attempt to be recognized as a plate List <List <PossibleChar> > listOfListsOfMatchingCharsInScene = detectChars.findListOfListsOfMatchingChars(listOfPossibleCharsInScene); // show steps ''''''''''''''''''''''''''''''''' if ((frm.cbShowSteps.Checked == true)) { frm.txtInfo.AppendText("step 3 - listOfListsOfMatchingCharsInScene.Count = " + listOfListsOfMatchingCharsInScene.Count.ToString() + "\r\n"); //13 with MCLRNF1 image imgContours = new Mat(imgOriginalScene.Size, DepthType.Cv8U, 3); foreach (List <PossibleChar> listOfMatchingChars in listOfListsOfMatchingCharsInScene) { object intRandomBlue = random.Next(0, 256); object intRandomGreen = random.Next(0, 256); object intRandomRed = random.Next(0, 256); VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); foreach (PossibleChar matchingChar in listOfMatchingChars) { contours.Push(matchingChar.contour); } CvInvoke.DrawContours(imgContours, contours, -1, new MCvScalar(Convert.ToDouble(intRandomBlue), Convert.ToDouble(intRandomGreen), Convert.ToDouble(intRandomRed))); } CvInvoke.Imshow("3", imgContours); } // show steps ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //for each group of matching chars foreach (List <PossibleChar> listOfMatchingChars in listOfListsOfMatchingCharsInScene) { PossiblePlate possiblePlate = extractPlate(imgOriginalScene, listOfMatchingChars); //attempt to extract plate //if plate was found if (((possiblePlate.imgPlate != null))) { listOfPossiblePlates.Add(possiblePlate); //add to list of possible plates } } frm.txtInfo.AppendText("\r\n" + listOfPossiblePlates.Count.ToString() + " possible plates found" + "\r\n"); //update text box with # of plates found // show steps ''''''''''''''''''''''''''''''''' if ((frm.cbShowSteps.Checked == true)) { frm.txtInfo.AppendText("\r\n"); CvInvoke.Imshow("4a", imgContours); for (int i = 0; i <= listOfPossiblePlates.Count - 1; i++) { PointF[] ptfRectPoints = new PointF[5]; ptfRectPoints = listOfPossiblePlates[i].rrLocationOfPlateInScene.GetVertices(); Point pt0 = new Point(Convert.ToInt32(ptfRectPoints[0].X), Convert.ToInt32(ptfRectPoints[0].Y)); Point pt1 = new Point(Convert.ToInt32(ptfRectPoints[1].X), Convert.ToInt32(ptfRectPoints[1].Y)); Point pt2 = new Point(Convert.ToInt32(ptfRectPoints[2].X), Convert.ToInt32(ptfRectPoints[2].Y)); Point pt3 = new Point(Convert.ToInt32(ptfRectPoints[3].X), Convert.ToInt32(ptfRectPoints[3].Y)); CvInvoke.Line(imgContours, pt0, pt1, SCALAR_RED, 2); CvInvoke.Line(imgContours, pt1, pt2, SCALAR_RED, 2); CvInvoke.Line(imgContours, pt2, pt3, SCALAR_RED, 2); CvInvoke.Line(imgContours, pt3, pt0, SCALAR_RED, 2); CvInvoke.Imshow("4a", imgContours); frm.txtInfo.AppendText("possible plate " + i.ToString() + ", click on any image and press a key to continue . . ." + "\r\n"); CvInvoke.Imshow("4b", listOfPossiblePlates[i].imgPlate); CvInvoke.WaitKey(0); } frm.txtInfo.AppendText("\r\n" + "plate detection complete, click on any image and press a key to begin char recognition . . ." + "\r\n" + "\r\n"); CvInvoke.WaitKey(0); } // show steps ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' return(listOfPossiblePlates); }
private void RunLive(object sender, EventArgs e) { Mat imgOriginalScene = new Mat(); imgOriginalScene = capWebcam.QueryFrame(); if (imgOriginalScene == null) { MessageBox.Show("unable to read frame from webcam" + Environment.NewLine + Environment.NewLine + "exiting program"); Application.Idle -= RunLive; //Apli.Exit(0); return; } ibOriginal.Image = imgOriginalScene; //show original image on main form List <PossiblePlate> listOfPossiblePlates = detectPlates.detectPlatesInScene(imgOriginalScene); //detect plates listOfPossiblePlates = detectChars.detectCharsInPlates(listOfPossiblePlates); //detect chars in plates //check if list of plates is null or zero if ((listOfPossiblePlates == null)) { //txtInfo.AppendText("\r\n" + "no license plates were detected" + "\r\n"); } else if ((listOfPossiblePlates.Count == 0)) { // txtInfo.AppendText("\r\n" + "no license plates were detected" + "\r\n"); } else { //if we get in here list of possible plates has at leat one plate //sort the list of possible plates in DESCENDING order (most number of chars to least number of chars) listOfPossiblePlates.Sort((onePlate, otherPlate) => otherPlate.strChars.Length.CompareTo(onePlate.strChars.Length)); //suppose the plate with the most recognized chars PossiblePlate licPlate = listOfPossiblePlates[0]; //(the first plate in sorted by string length descending order) //is the actual plate //CvInvoke.Imshow("final imgPlate", licPlate.imgPlate); //show the final color plate image //CvInvoke.Imshow("final imgThresh", licPlate.imgThresh); //show the final thresh plate image //if no chars are present in the lic plate, if ((licPlate.strChars.Length == 0)) { // txtInfo.AppendText("\r\n" + "no characters were detected" + licPlate.strChars + "\r\n"); //update info text box return; //and return } drawRedRectangleAroundPlate(imgOriginalScene, licPlate); //draw red rectangle around plate txtInfo.AppendText("\r\n" + "license plate read from image = " + licPlate.strChars + "\r\n"); //write license plate text to text box txtInfo.AppendText("\r\n" + "----------------------------------------" + "\r\n"); writeLicensePlateCharsOnImage(ref imgOriginalScene, licPlate); //write license plate text on the image ibOriginal.Image = imgOriginalScene; //update image on main form foreach (var plates in MyPlates) { if (licPlate.strChars == plates) { if (PlatesRead.Exists(x => x.PlateNumber == licPlate.strChars && DateTime.Now - x.Time < new TimeSpan(0, 0, 45))) { PlatesRead.Add(new LicensePlates(licPlate.strChars, DateTime.Now)); } else if (PlatesRead.Count == 0) { PlatesRead.Add(new LicensePlates(licPlate.strChars, DateTime.Now)); } else if (DateTime.Now - PlatesRead.Last().Time > new TimeSpan(0, 1, 0)) { PlatesRead.Clear(); } //else if if (PlatesRead.Count >= 3) { SendCommandToESP8266(licPlate.strChars + "\n"); Application.Idle -= RunLive; } } } } }