// Score a collection of lines as a possible license plate region. // If any segments are missing, extrapolate the missing pieces void PlateCorners::scoreHorizontals(int h1, int h2) { ScoreKeeper scoreKeeper; LineSegment top; LineSegment bottom; float charHeightToPlateHeightRatio = pipelineData->config->plateHeightMM / pipelineData->config->charHeightMM; float idealPixelHeight = tlc.charHeight * charHeightToPlateHeightRatio; float confidenceDiff = 0; float missingSegmentPenalty = 0; if (h1 == NO_LINE && h2 == NO_LINE) { // return; top = tlc.centerHorizontalLine.getParallelLine(idealPixelHeight / 2); bottom = tlc.centerHorizontalLine.getParallelLine(-1 * idealPixelHeight / 2 ); missingSegmentPenalty = 2; confidenceDiff += 2; } else if (h1 != NO_LINE && h2 != NO_LINE) { top = this->plateLines->horizontalLines[h1].line; bottom = this->plateLines->horizontalLines[h2].line; confidenceDiff += (1.0 - this->plateLines->horizontalLines[h1].confidence); confidenceDiff += (1.0 - this->plateLines->horizontalLines[h2].confidence); } else if (h1 == NO_LINE && h2 != NO_LINE) { bottom = this->plateLines->horizontalLines[h2].line; top = bottom.getParallelLine(idealPixelHeight); missingSegmentPenalty++; confidenceDiff += (1.0 - this->plateLines->horizontalLines[h2].confidence); } else if (h1 != NO_LINE && h2 == NO_LINE) { top = this->plateLines->horizontalLines[h1].line; bottom = top.getParallelLine(-1 * idealPixelHeight); missingSegmentPenalty++; confidenceDiff += (1.0 - this->plateLines->horizontalLines[h1].confidence); } scoreKeeper.setScore("SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL", missingSegmentPenalty, SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL); //scoreKeeper.setScore("SCORING_LINE_CONFIDENCE_WEIGHT", confidenceDiff, SCORING_LINE_CONFIDENCE_WEIGHT); // Make sure that the top and bottom lines are above and below // the text area if (tlc.isAboveText(top) < 1 || tlc.isAboveText(bottom) > -1) return; // We now have 4 possible lines. Let's put them to the test and score them... ////////////////////////////////////////////////////////////////////////// // SCORE the shape wrt character position and height relative to position ////////////////////////////////////////////////////////////////////////// Point topPoint = top.midpoint(); Point botPoint = bottom.closestPointOnSegmentTo(topPoint); float plateHeightPx = distanceBetweenPoints(topPoint, botPoint); // Get the height difference float heightRatio = tlc.charHeight / plateHeightPx; float idealHeightRatio = (pipelineData->config->charHeightMM / pipelineData->config->plateHeightMM); float heightRatioDiff = abs(heightRatio - idealHeightRatio); scoreKeeper.setScore("SCORING_PLATEHEIGHT_WEIGHT", heightRatioDiff, SCORING_PLATEHEIGHT_WEIGHT); ////////////////////////////////////////////////////////////////////////// // SCORE the middliness of the stuff. We want our top and bottom line to have the characters right towards the middle ////////////////////////////////////////////////////////////////////////// Point charAreaMidPoint = tlc.centerVerticalLine.midpoint(); Point topLineSpot = top.closestPointOnSegmentTo(charAreaMidPoint); Point botLineSpot = bottom.closestPointOnSegmentTo(charAreaMidPoint); float topDistanceFromMiddle = distanceBetweenPoints(topLineSpot, charAreaMidPoint); float bottomDistanceFromMiddle = distanceBetweenPoints(botLineSpot, charAreaMidPoint); float idealDistanceFromMiddle = idealPixelHeight / 2; float middleScore = abs(topDistanceFromMiddle - idealDistanceFromMiddle) / idealDistanceFromMiddle; middleScore += abs(bottomDistanceFromMiddle - idealDistanceFromMiddle) / idealDistanceFromMiddle; scoreKeeper.setScore("SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT", middleScore, SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT); ////////////////////////////////////////////////////////////// // SCORE: the shape for angles matching the character region ////////////////////////////////////////////////////////////// float charanglediff = abs(tlc.charAngle - top.angle) + abs(tlc.charAngle - bottom.angle); scoreKeeper.setScore("SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT", charanglediff, SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT); if (pipelineData->config->debugPlateCorners) { scoreKeeper.printDebugScores(); Mat debugImg(this->inputImage.size(), this->inputImage.type()); this->inputImage.copyTo(debugImg); cvtColor(debugImg, debugImg, CV_GRAY2BGR); line(debugImg, top.p1, top.p2, Scalar(0,0,255), 2); line(debugImg, bottom.p1, bottom.p2, Scalar(0,0,255), 2); //drawAndWait(&debugImg); } float score = scoreKeeper.getTotal(); if (score < this->bestHorizontalScore) { float scorecomponent; if (pipelineData->config->debugPlateCorners) { cout << "Horizontal breakdown Score:" << endl; scoreKeeper.printDebugScores(); } this->bestHorizontalScore = score; bestTop = LineSegment(top.p1.x, top.p1.y, top.p2.x, top.p2.y); bestBottom = LineSegment(bottom.p1.x, bottom.p1.y, bottom.p2.x, bottom.p2.y); } }
// Score a collection of lines as a possible license plate region. // If any segments are missing, extrapolate the missing pieces void PlateCorners::scoreHorizontals(int h1, int h2) { //if (this->debug) // cout << "PlateCorners::scorePlate" << endl; float score = 0; // Lower is better LineSegment top; LineSegment bottom; float charHeightToPlateHeightRatio = config->plateHeightMM / config->charHeightMM; float idealPixelHeight = this->charHeight * charHeightToPlateHeightRatio; if (h1 == NO_LINE && h2 == NO_LINE) { // return; Point centerLeft = charRegion->getCharBoxLeft().midpoint(); Point centerRight = charRegion->getCharBoxRight().midpoint(); LineSegment centerLine = LineSegment(centerLeft.x, centerLeft.y, centerRight.x, centerRight.y); top = centerLine.getParallelLine(idealPixelHeight / 2); bottom = centerLine.getParallelLine(-1 * idealPixelHeight / 2 ); score += SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL * 2; } else if (h1 != NO_LINE && h2 != NO_LINE) { top = this->plateLines->horizontalLines[h1]; bottom = this->plateLines->horizontalLines[h2]; } else if (h1 == NO_LINE && h2 != NO_LINE) { bottom = this->plateLines->horizontalLines[h2]; top = bottom.getParallelLine(idealPixelHeight); score += SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL; } else if (h1 != NO_LINE && h2 == NO_LINE) { top = this->plateLines->horizontalLines[h1]; bottom = top.getParallelLine(-1 * idealPixelHeight); score += SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL; } // Make sure this line is above our license plate letters if (top.isPointBelowLine(charRegion->getCharBoxTop().midpoint()) == false) return; // Make sure this line is below our license plate letters if (bottom.isPointBelowLine(charRegion->getCharBoxBottom().midpoint())) return; // We now have 4 possible lines. Let's put them to the test and score them... ///////////////////////////////////////////////////////////////////////// // Score "Boxiness" of the 4 lines. How close is it to a parallelogram? ///////////////////////////////////////////////////////////////////////// float horizontalAngleDiff = abs(top.angle - bottom.angle); score += (horizontalAngleDiff) * SCORING_BOXINESS_WEIGHT; // if (this->debug) // cout << "PlateCorners boxiness score: " << (horizontalAngleDiff + verticalAngleDiff) * SCORING_BOXINESS_WEIGHT << endl; ////////////////////////////////////////////////////////////////////////// // SCORE the shape wrt character position and height relative to position ////////////////////////////////////////////////////////////////////////// Point topPoint = top.midpoint(); Point botPoint = bottom.closestPointOnSegmentTo(topPoint); float plateHeightPx = distanceBetweenPoints(topPoint, botPoint); // Get the height difference float heightRatio = charHeight / plateHeightPx; float idealHeightRatio = (config->charHeightMM / config->plateHeightMM); //if (leftRatio < MIN_CHAR_HEIGHT_RATIO || leftRatio > MAX_CHAR_HEIGHT_RATIO || rightRatio < MIN_CHAR_HEIGHT_RATIO || rightRatio > MAX_CHAR_HEIGHT_RATIO) float heightRatioDiff = abs(heightRatio - idealHeightRatio); // Ideal ratio == ~.45 // Get the distance from the top and the distance from the bottom // Take the average distances from the corners of the character region to the top/bottom lines // float topDistance = distanceBetweenPoints(topMidLinePoint, charRegion->getCharBoxTop().midpoint()); // float bottomDistance = distanceBetweenPoints(bottomMidLinePoint, charRegion->getCharBoxBottom().midpoint()); // float idealTopDistance = charHeight * (TOP_WHITESPACE_HEIGHT_MM / CHARACTER_HEIGHT_MM); // float idealBottomDistance = charHeight * (BOTTOM_WHITESPACE_HEIGHT_MM / CHARACTER_HEIGHT_MM); // float distScore = abs(topDistance - idealTopDistance) + abs(bottomDistance - idealBottomDistance); score += heightRatioDiff * SCORING_PLATEHEIGHT_WEIGHT; ////////////////////////////////////////////////////////////////////////// // SCORE the middliness of the stuff. We want our top and bottom line to have the characters right towards the middle ////////////////////////////////////////////////////////////////////////// Point charAreaMidPoint = charRegion->getCharBoxLeft().midpoint(); Point topLineSpot = top.closestPointOnSegmentTo(charAreaMidPoint); Point botLineSpot = bottom.closestPointOnSegmentTo(charAreaMidPoint); float topDistanceFromMiddle = distanceBetweenPoints(topLineSpot, charAreaMidPoint); float bottomDistanceFromMiddle = distanceBetweenPoints(topLineSpot, charAreaMidPoint); float idealDistanceFromMiddle = idealPixelHeight / 2; float middleScore = abs(topDistanceFromMiddle - idealDistanceFromMiddle) + abs(bottomDistanceFromMiddle - idealDistanceFromMiddle); score += middleScore * SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT; // if (this->debug) // { // cout << "PlateCorners boxiness score: " << avgRatio * SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT << endl; // cout << "PlateCorners boxiness score: " << distScore * SCORING_PLATEHEIGHT_WEIGHT << endl; // } ////////////////////////////////////////////////////////////// // SCORE: the shape for angles matching the character region ////////////////////////////////////////////////////////////// float charanglediff = abs(charAngle - top.angle) + abs(charAngle - bottom.angle); score += charanglediff * SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT; // if (this->debug) // cout << "PlateCorners boxiness score: " << charanglediff * SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT << endl; if (score < this->bestHorizontalScore) { float scorecomponent; if (this->config->debugPlateCorners) { cout << "xx xx Score: charHeight " << this->charHeight << endl; cout << "xx xx Score: idealHeight " << idealPixelHeight << endl; cout << "xx xx Score: h1,h2= " << h1 << "," << h2 << endl; cout << "xx xx Score: Top= " << top.str() << endl; cout << "xx xx Score: Bottom= " << bottom.str() << endl; cout << "Horizontal breakdown Score:" << endl; cout << " -- Boxiness Score: " << horizontalAngleDiff << " -- Weight (" << SCORING_BOXINESS_WEIGHT << ")" << endl; scorecomponent = horizontalAngleDiff * SCORING_BOXINESS_WEIGHT; cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl; cout << " -- Height Ratio Diff Score: " << heightRatioDiff << " -- Weight (" << SCORING_PLATEHEIGHT_WEIGHT << ")" << endl; scorecomponent = heightRatioDiff * SCORING_PLATEHEIGHT_WEIGHT; cout << " -- -- " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl; cout << " -- Distance Score: " << middleScore << " -- Weight (" << SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT << ")" << endl; scorecomponent = middleScore * SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT; cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl; cout << " -- Char angle Score: " << charanglediff << " -- Weight (" << SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT << ")" << endl; scorecomponent = charanglediff * SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT; cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl; cout << " -- Score: " << score << endl; } this->bestHorizontalScore = score; bestTop = LineSegment(top.p1.x, top.p1.y, top.p2.x, top.p2.y); bestBottom = LineSegment(bottom.p1.x, bottom.p1.y, bottom.p2.x, bottom.p2.y); } }
void PlateCorners::scoreVerticals(int v1, int v2) { ScoreKeeper scoreKeeper; LineSegment left; LineSegment right; float charHeightToPlateWidthRatio = pipelineData->config->plateWidthMM / pipelineData->config->charHeightMM; float idealPixelWidth = tlc.charHeight * (charHeightToPlateWidthRatio * 1.03); // Add 3% so we don't clip any characters float confidenceDiff = 0; float missingSegmentPenalty = 0; if (v1 == NO_LINE && v2 == NO_LINE) { //return; left = tlc.centerVerticalLine.getParallelLine(-1 * idealPixelWidth / 2); right = tlc.centerVerticalLine.getParallelLine(idealPixelWidth / 2 ); missingSegmentPenalty = 2; confidenceDiff += 2; } else if (v1 != NO_LINE && v2 != NO_LINE) { left = this->plateLines->verticalLines[v1].line; right = this->plateLines->verticalLines[v2].line; confidenceDiff += (1.0 - this->plateLines->verticalLines[v1].confidence); confidenceDiff += (1.0 - this->plateLines->verticalLines[v2].confidence); } else if (v1 == NO_LINE && v2 != NO_LINE) { right = this->plateLines->verticalLines[v2].line; left = right.getParallelLine(idealPixelWidth); missingSegmentPenalty++; confidenceDiff += (1.0 - this->plateLines->verticalLines[v2].confidence); } else if (v1 != NO_LINE && v2 == NO_LINE) { left = this->plateLines->verticalLines[v1].line; right = left.getParallelLine(-1 * idealPixelWidth); missingSegmentPenalty++; confidenceDiff += (1.0 - this->plateLines->verticalLines[v1].confidence); } scoreKeeper.setScore("SCORING_LINE_CONFIDENCE_WEIGHT", confidenceDiff, SCORING_LINE_CONFIDENCE_WEIGHT); scoreKeeper.setScore("SCORING_MISSING_SEGMENT_PENALTY_VERTICAL", missingSegmentPenalty, SCORING_MISSING_SEGMENT_PENALTY_VERTICAL); // Make sure that the left and right lines are to the left and right of our text // area if (tlc.isLeftOfText(left) < 1 || tlc.isLeftOfText(right) > -1) return; ///////////////////////////////////////////////////////////////////////// // Score angle difference from detected character box ///////////////////////////////////////////////////////////////////////// float perpendicularCharAngle = tlc.charAngle - 90; float charanglediff = abs(perpendicularCharAngle - left.angle) + abs(perpendicularCharAngle - right.angle); scoreKeeper.setScore("SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT", charanglediff, SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT); ////////////////////////////////////////////////////////////////////////// // SCORE the shape wrt character position and height relative to position ////////////////////////////////////////////////////////////////////////// Point leftMidLinePoint = left.closestPointOnSegmentTo(tlc.centerVerticalLine.midpoint()); Point rightMidLinePoint = right.closestPointOnSegmentTo(tlc.centerVerticalLine.midpoint()); float plateDistance = abs(idealPixelWidth - distanceBetweenPoints(leftMidLinePoint, rightMidLinePoint)); // normalize for image width plateDistance = plateDistance / ((float)inputImage.cols); scoreKeeper.setScore("SCORING_DISTANCE_WEIGHT_VERTICAL", plateDistance, SCORING_DISTANCE_WEIGHT_VERTICAL); float score = scoreKeeper.getTotal(); if (score < this->bestVerticalScore) { if (pipelineData->config->debugPlateCorners) { cout << "Vertical breakdown Score:" << endl; scoreKeeper.printDebugScores(); } this->bestVerticalScore = score; bestLeft = LineSegment(left.p1.x, left.p1.y, left.p2.x, left.p2.y); bestRight = LineSegment(right.p1.x, right.p1.y, right.p2.x, right.p2.y); } }
void PlateCorners::scoreVerticals(int v1, int v2) { float score = 0; // Lower is better LineSegment left; LineSegment right; float charHeightToPlateWidthRatio = config->plateWidthMM / config->charHeightMM; float idealPixelWidth = this->charHeight * (charHeightToPlateWidthRatio * 1.03); // Add 3% so we don't clip any characters if (v1 == NO_LINE && v2 == NO_LINE) { //return; Point centerTop = charRegion->getCharBoxTop().midpoint(); Point centerBottom = charRegion->getCharBoxBottom().midpoint(); LineSegment centerLine = LineSegment(centerBottom.x, centerBottom.y, centerTop.x, centerTop.y); left = centerLine.getParallelLine(idealPixelWidth / 2); right = centerLine.getParallelLine(-1 * idealPixelWidth / 2 ); score += SCORING_MISSING_SEGMENT_PENALTY_VERTICAL * 2; } else if (v1 != NO_LINE && v2 != NO_LINE) { left = this->plateLines->verticalLines[v1]; right = this->plateLines->verticalLines[v2]; } else if (v1 == NO_LINE && v2 != NO_LINE) { right = this->plateLines->verticalLines[v2]; left = right.getParallelLine(idealPixelWidth); score += SCORING_MISSING_SEGMENT_PENALTY_VERTICAL; } else if (v1 != NO_LINE && v2 == NO_LINE) { left = this->plateLines->verticalLines[v1]; right = left.getParallelLine(-1 * idealPixelWidth); score += SCORING_MISSING_SEGMENT_PENALTY_VERTICAL; } // Make sure this line is to the left of our license plate letters if (left.isPointBelowLine(charRegion->getCharBoxLeft().midpoint()) == false) return; // Make sure this line is to the right of our license plate letters if (right.isPointBelowLine(charRegion->getCharBoxRight().midpoint())) return; ///////////////////////////////////////////////////////////////////////// // Score "Distance from the edge... ///////////////////////////////////////////////////////////////////////// float leftDistanceFromEdge = abs((float) (left.p1.x + left.p2.x) / 2); float rightDistanceFromEdge = abs(this->inputImage.cols - ((float) (right.p1.x + right.p2.x) / 2)); float distanceFromEdge = leftDistanceFromEdge + rightDistanceFromEdge; score += distanceFromEdge * SCORING_VERTICALDISTANCE_FROMEDGE_WEIGHT; ///////////////////////////////////////////////////////////////////////// // Score "Boxiness" of the 4 lines. How close is it to a parallelogram? ///////////////////////////////////////////////////////////////////////// float verticalAngleDiff = abs(left.angle - right.angle); score += (verticalAngleDiff) * SCORING_BOXINESS_WEIGHT; ////////////////////////////////////////////////////////////////////////// // SCORE the shape wrt character position and height relative to position ////////////////////////////////////////////////////////////////////////// Point leftMidLinePoint = left.closestPointOnSegmentTo(charRegion->getCharBoxLeft().midpoint()); Point rightMidLinePoint = right.closestPointOnSegmentTo(charRegion->getCharBoxRight().midpoint()); float plateDistance = abs(idealPixelWidth - distanceBetweenPoints(leftMidLinePoint, rightMidLinePoint)); score += plateDistance * SCORING_VERTICALDISTANCE_WEIGHT; if (score < this->bestVerticalScore) { float scorecomponent; if (this->config->debugPlateCorners) { cout << "xx xx Score: charHeight " << this->charHeight << endl; cout << "xx xx Score: idealwidth " << idealPixelWidth << endl; cout << "xx xx Score: v1,v2= " << v1 << "," << v2 << endl; cout << "xx xx Score: Left= " << left.str() << endl; cout << "xx xx Score: Right= " << right.str() << endl; cout << "Vertical breakdown Score:" << endl; cout << " -- Boxiness Score: " << verticalAngleDiff << " -- Weight (" << SCORING_BOXINESS_WEIGHT << ")" << endl; scorecomponent = verticalAngleDiff * SCORING_BOXINESS_WEIGHT; cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl; cout << " -- Distance From Edge Score: " << distanceFromEdge << " -- Weight (" << SCORING_VERTICALDISTANCE_FROMEDGE_WEIGHT << ")" << endl; scorecomponent = distanceFromEdge * SCORING_VERTICALDISTANCE_FROMEDGE_WEIGHT; cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl; cout << " -- Distance Score: " << plateDistance << " -- Weight (" << SCORING_VERTICALDISTANCE_WEIGHT << ")" << endl; scorecomponent = plateDistance * SCORING_VERTICALDISTANCE_WEIGHT; cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl; cout << " -- Score: " << score << endl; } this->bestVerticalScore = score; bestLeft = LineSegment(left.p1.x, left.p1.y, left.p2.x, left.p2.y); bestRight = LineSegment(right.p1.x, right.p1.y, right.p2.x, right.p2.y); } }