void CharacterAnalysis::filterByOuterMask(TextContours& textContours) { float MINIMUM_PERCENT_LEFT_AFTER_MASK = 0.1; float MINIMUM_PERCENT_OF_CHARS_INSIDE_PLATE_MASK = 0.6; if (this->pipeline_data->hasPlateBorder == false) return; cv::Mat plateMask = pipeline_data->plateBorderMask; Mat tempMaskedContour = Mat::zeros(plateMask.size(), CV_8U); Mat tempFullContour = Mat::zeros(plateMask.size(), CV_8U); int charsInsideMask = 0; int totalChars = 0; vector<bool> originalindices; for (unsigned int i = 0; i < textContours.size(); i++) originalindices.push_back(textContours.goodIndices[i]); for (unsigned int i=0; i < textContours.size(); i++) { if (textContours.goodIndices[i] == false) continue; totalChars++; tempFullContour = Mat::zeros(plateMask.size(), CV_8U); drawContours(tempFullContour, textContours.contours, i, Scalar(255,255,255), CV_FILLED, 8, textContours.hierarchy); bitwise_and(tempFullContour, plateMask, tempMaskedContour); float beforeMaskWhiteness = mean(tempFullContour)[0]; float afterMaskWhiteness = mean(tempMaskedContour)[0]; if (afterMaskWhiteness / beforeMaskWhiteness > MINIMUM_PERCENT_LEFT_AFTER_MASK) { charsInsideMask++; textContours.goodIndices[i] = true; } } if (totalChars == 0) { textContours.goodIndices = originalindices; return; } // Check to make sure that this is a valid box. If the box is too small (e.g., 1 char is inside, and 3 are outside) // then don't use this to filter. float percentCharsInsideMask = ((float) charsInsideMask) / ((float) totalChars); if (percentCharsInsideMask < MINIMUM_PERCENT_OF_CHARS_INSIDE_PLATE_MASK) { textContours.goodIndices = originalindices; return; } }
void CharacterAnalysis::filterContourHoles(TextContours& textContours) { for (uint i = 0; i < textContours.size(); i++) { if (textContours.goodIndices[i] == false) continue; textContours.goodIndices[i] = false; // Set it to not included unless it proves valid int parentIndex = textContours.hierarchy[i][3]; if (parentIndex >= 0 && textContours.goodIndices[parentIndex]) { // this contour is a child of an already identified contour. REMOVE it if (this->config->debugCharAnalysis) { cout << "filterContourHoles: contour index: " << i << endl; } } else { textContours.goodIndices[i] = true; } } }
// Goes through the contours for the plate and picks out possible char segments based on min/max height void CharacterAnalysis::filterByBoxSize(TextContours& textContours, int minHeightPx, int maxHeightPx) { float idealAspect=config->charWidthMM / config->charHeightMM; float aspecttolerance=0.25; for (uint i = 0; i < textContours.size(); i++) { if (textContours.goodIndices[i] == false) continue; textContours.goodIndices[i] = false; // Set it to not included unless it proves valid //Create bounding rect of object Rect mr= boundingRect(textContours.contours[i]); float minWidth = mr.height * 0.2; //Crop image //cout << "Height: " << minHeightPx << " - " << mr.height << " - " << maxHeightPx << " ////// Width: " << mr.width << " - " << minWidth << endl; if(mr.height >= minHeightPx && mr.height <= maxHeightPx && mr.width > minWidth) { float charAspect= (float)mr.width/(float)mr.height; //cout << " -- stage 2 aspect: " << abs(charAspect) << " - " << aspecttolerance << endl; if (abs(charAspect - idealAspect) < aspecttolerance) textContours.goodIndices[i] = true; } } }
void CharacterAnalysis::filter(Mat img, TextContours& textContours) { static int STARTING_MIN_HEIGHT = round (((float) img.rows) * config->charAnalysisMinPercent); static int STARTING_MAX_HEIGHT = round (((float) img.rows) * (config->charAnalysisMinPercent + config->charAnalysisHeightRange)); static int HEIGHT_STEP = round (((float) img.rows) * config->charAnalysisHeightStepSize); static int NUM_STEPS = config->charAnalysisNumSteps; int bestFitScore = -1; vector<bool> bestIndices; for (int i = 0; i < NUM_STEPS; i++) { //vector<bool> goodIndices(contours.size()); for (uint z = 0; z < textContours.size(); z++) textContours.goodIndices[z] = true; this->filterByBoxSize(textContours, STARTING_MIN_HEIGHT + (i * HEIGHT_STEP), STARTING_MAX_HEIGHT + (i * HEIGHT_STEP)); int goodIndices = textContours.getGoodIndicesCount(); if ( goodIndices == 0 || goodIndices <= bestFitScore) // Don't bother doing more filtering if we already lost... continue; this->filterContourHoles(textContours); goodIndices = textContours.getGoodIndicesCount(); if ( goodIndices == 0 || goodIndices <= bestFitScore) // Don't bother doing more filtering if we already lost... continue; int segmentCount = textContours.getGoodIndicesCount(); if (segmentCount > bestFitScore) { bestFitScore = segmentCount; bestIndices = textContours.getIndicesCopy(); } } textContours.setIndices(bestIndices); }
void CharacterAnalysis::filterBetweenLines(Mat img, TextContours& textContours, vector<TextLine> textLines ) { static float MIN_AREA_PERCENT_WITHIN_LINES = 0.88; static float MAX_DISTANCE_PERCENT_FROM_LINES = 0.15; if (textLines.size() == 0) return; vector<Point> validPoints; // Create a white mask for the area inside the polygon Mat outerMask = Mat::zeros(img.size(), CV_8U); for (uint i = 0; i < textLines.size(); i++) fillConvexPoly(outerMask, textLines[i].linePolygon.data(), textLines[i].linePolygon.size(), Scalar(255,255,255)); // For each contour, determine if enough of it is between the lines to qualify for (uint i = 0; i < textContours.size(); i++) { if (textContours.goodIndices[i] == false) continue; float percentInsideMask = getContourAreaPercentInsideMask(outerMask, textContours.contours, textContours.hierarchy, (int) i); if (percentInsideMask < MIN_AREA_PERCENT_WITHIN_LINES) { // Not enough area is inside the lines. if (config->debugCharAnalysis) cout << "Rejecting due to insufficient area" << endl; textContours.goodIndices[i] = false; continue; } // now check to make sure that the top and bottom of the contour are near enough to the lines // First get the high and low point for the contour // Remember that origin is top-left, so the top Y values are actually closer to 0. Rect brect = boundingRect(textContours.contours[i]); int xmiddle = brect.x + (brect.width / 2); Point topMiddle = Point(xmiddle, brect.y); Point botMiddle = Point(xmiddle, brect.y+brect.height); // Get the absolute distance from the top and bottom lines for (uint i = 0; i < textLines.size(); i++) { Point closestTopPoint = textLines[i].topLine.closestPointOnSegmentTo(topMiddle); Point closestBottomPoint = textLines[i].bottomLine.closestPointOnSegmentTo(botMiddle); float absTopDistance = distanceBetweenPoints(closestTopPoint, topMiddle); float absBottomDistance = distanceBetweenPoints(closestBottomPoint, botMiddle); float maxDistance = textLines[i].lineHeight * MAX_DISTANCE_PERCENT_FROM_LINES; if (absTopDistance < maxDistance && absBottomDistance < maxDistance) { // It's ok, leave it as-is. } else { textContours.goodIndices[i] = false; if (config->debugCharAnalysis) cout << "Rejecting due to top/bottom points that are out of range" << endl; } } } }
// Goes through the contours for the plate and picks out possible char segments based on min/max height // returns a vector of indices corresponding to valid contours void CharacterAnalysis::filterByParentContour( TextContours& textContours) { vector<int> parentIDs; vector<int> votes; for (uint i = 0; i < textContours.size(); i++) { if (textContours.goodIndices[i] == false) continue; textContours.goodIndices[i] = false; // Set it to not included unless it proves int voteIndex = -1; int parentID = textContours.hierarchy[i][3]; // check if parentID is already in the lsit for (uint j = 0; j < parentIDs.size(); j++) { if (parentIDs[j] == parentID) { voteIndex = j; break; } } if (voteIndex == -1) { parentIDs.push_back(parentID); votes.push_back(1); } else { votes[voteIndex] = votes[voteIndex] + 1; } } // Tally up the votes, pick the winner int totalVotes = 0; int winningParentId = 0; int highestVotes = 0; for (uint i = 0; i < parentIDs.size(); i++) { if (votes[i] > highestVotes) { winningParentId = parentIDs[i]; highestVotes = votes[i]; } totalVotes += votes[i]; } // Now filter out all the contours with a different parent ID (assuming the totalVotes > 2) for (uint i = 0; i < textContours.size(); i++) { if (textContours.goodIndices[i] == false) continue; if (totalVotes <= 2) { textContours.goodIndices[i] = true; } else if (textContours.hierarchy[i][3] == winningParentId) { textContours.goodIndices[i] = true; } } }