diff --git a/src/openalpr/CMakeLists.txt b/src/openalpr/CMakeLists.txt index fc5fe8b..659916c 100644 --- a/src/openalpr/CMakeLists.txt +++ b/src/openalpr/CMakeLists.txt @@ -25,6 +25,7 @@ set(lpr_source_files textdetection/characteranalysis.cpp textdetection/characteranalysis2l.cpp textdetection/platemask.cpp + textdetection/textcontours.cpp pipeline_data.cpp trex.c cjson.c diff --git a/src/openalpr/characterregion.cpp b/src/openalpr/characterregion.cpp index 6321ffb..ce47747 100644 --- a/src/openalpr/characterregion.cpp +++ b/src/openalpr/characterregion.cpp @@ -58,9 +58,9 @@ CharacterRegion::CharacterRegion(PipelineData* pipeline_data) for (uint z = 0; z < charAnalysis->bestContours.size(); z++) { Scalar dcolor(255,0,0); - if (charAnalysis->bestCharSegments[z]) + if (charAnalysis->bestContours.goodIndices[z]) dcolor = Scalar(0,255,0); - drawContours(bestVal, charAnalysis->bestContours, z, dcolor, 1); + drawContours(bestVal, charAnalysis->bestContours.contours, z, dcolor, 1); } tempDash.push_back(bestVal); displayImage(config, "Character Region Step 1 Thresholds", drawImageDashboard(tempDash, bestVal.type(), 3)); @@ -70,7 +70,7 @@ CharacterRegion::CharacterRegion(PipelineData* pipeline_data) if (charAnalysis->linePolygon.size() > 0) { int confidenceDrainers = 0; - int charSegmentCount = charAnalysis->bestCharSegmentsCount; + int charSegmentCount = charAnalysis->bestContours.getGoodIndicesCount(); if (charSegmentCount == 1) confidenceDrainers += 91; else if (charSegmentCount < 5) diff --git a/src/openalpr/segmentation/charactersegmenter.cpp b/src/openalpr/segmentation/charactersegmenter.cpp index 76b482a..1d1aefc 100644 --- a/src/openalpr/segmentation/charactersegmenter.cpp +++ b/src/openalpr/segmentation/charactersegmenter.cpp @@ -60,11 +60,11 @@ CharacterSegmenter::CharacterSegmenter(PipelineData* pipeline_data) vector > allowedContours; for (uint i = 0; i < charAnalysis->bestContours.size(); i++) { - if (charAnalysis->bestCharSegments[i]) - allowedContours.push_back(charAnalysis->bestContours[i]); + if (charAnalysis->bestContours.goodIndices[i]) + allowedContours.push_back(charAnalysis->bestContours.contours[i]); } - drawContours(img_contours, charAnalysis->bestContours, + drawContours(img_contours, charAnalysis->bestContours.contours, -1, // draw all contours cv::Scalar(255,0,0), // in blue 1); // with a thickness of 1 @@ -94,10 +94,10 @@ CharacterSegmenter::CharacterSegmenter(PipelineData* pipeline_data) for (uint i = 0; i < charAnalysis->bestContours.size(); i++) { - if (charAnalysis->bestCharSegments[i] == false) + if (charAnalysis->bestContours.goodIndices[i] == false) continue; - Rect mr = boundingRect(charAnalysis->bestContours[i]); + Rect mr = boundingRect(charAnalysis->bestContours.contours[i]); charWidths.push_back(mr.width); charHeights.push_back(mr.height); @@ -106,7 +106,7 @@ CharacterSegmenter::CharacterSegmenter(PipelineData* pipeline_data) float avgCharWidth = median(charWidths.data(), charWidths.size()); float avgCharHeight = median(charHeights.data(), charHeights.size()); - removeSmallContours(pipeline_data->thresholds, charAnalysis->allContours, avgCharWidth, avgCharHeight); + removeSmallContours(pipeline_data->thresholds, charAnalysis->allTextContours, avgCharWidth, avgCharHeight); // Do the histogram analysis to figure out char regions @@ -116,7 +116,7 @@ CharacterSegmenter::CharacterSegmenter(PipelineData* pipeline_data) vector allHistograms; vector allBoxes; - for (uint i = 0; i < charAnalysis->allContours.size(); i++) + for (uint i = 0; i < charAnalysis->allTextContours.size(); i++) { Mat histogramMask = Mat::zeros(pipeline_data->thresholds[i].size(), CV_8U); @@ -443,23 +443,23 @@ vector CharacterSegmenter::get1DHits(Mat img, int yOffset) return hits; } -void CharacterSegmenter::removeSmallContours(vector thresholds, vector > > allContours, float avgCharWidth, float avgCharHeight) +void CharacterSegmenter::removeSmallContours(vector thresholds, vector contours, float avgCharWidth, float avgCharHeight) { //const float MIN_CHAR_AREA = 0.02 * avgCharWidth * avgCharHeight; // To clear out the tiny specks const float MIN_CONTOUR_HEIGHT = 0.3 * avgCharHeight; for (uint i = 0; i < thresholds.size(); i++) { - for (uint c = 0; c < allContours[i].size(); c++) + for (uint c = 0; c < contours[i].contours.size(); c++) { - if (allContours[i][c].size() == 0) + if (contours[i].contours[c].size() == 0) continue; - Rect mr = boundingRect(allContours[i][c]); + Rect mr = boundingRect(contours[i].contours[c]); if (mr.height < MIN_CONTOUR_HEIGHT) { // Erase it - drawContours(thresholds[i], allContours[i], c, Scalar(0, 0, 0), -1); + drawContours(thresholds[i], contours[i].contours, c, Scalar(0, 0, 0), -1); continue; } } diff --git a/src/openalpr/segmentation/charactersegmenter.h b/src/openalpr/segmentation/charactersegmenter.h index 54cb4fc..31dc53b 100644 --- a/src/openalpr/segmentation/charactersegmenter.h +++ b/src/openalpr/segmentation/charactersegmenter.h @@ -28,6 +28,7 @@ #include "colorfilter.h" #include "verticalhistogram.h" #include "config.h" +#include "textdetection/textcontours.h" //const float MIN_BOX_WIDTH_PX = 4; // 4 pixels @@ -73,7 +74,7 @@ class CharacterSegmenter cv::Mat getCharacterMask(cv::Mat img_threshold, std::vector > contours, std::vector hierarchy, std::vector goodIndices); cv::Mat getCharBoxMask(cv::Mat img_threshold, std::vector charBoxes); - void removeSmallContours(std::vector thresholds, std::vector > > allContours, float avgCharWidth, float avgCharHeight); + void removeSmallContours(std::vector thresholds, std::vector contours, float avgCharWidth, float avgCharHeight); cv::Mat getVerticalHistogram(cv::Mat img, cv::Mat mask); std::vector getHistogramBoxes(VerticalHistogram histogram, float avgCharWidth, float avgCharHeight, float* score); diff --git a/src/openalpr/textdetection/characteranalysis.cpp b/src/openalpr/textdetection/characteranalysis.cpp index 9999ff5..426e477 100644 --- a/src/openalpr/textdetection/characteranalysis.cpp +++ b/src/openalpr/textdetection/characteranalysis.cpp @@ -18,7 +18,6 @@ */ #include "characteranalysis.h" -#include "platemask.h" using namespace cv; using namespace std; @@ -52,19 +51,9 @@ void CharacterAnalysis::analyze() for (uint i = 0; i < pipeline_data->thresholds.size(); i++) { - vector > contours; - vector hierarchy; + TextContours tc(pipeline_data->thresholds[i]); - Mat tempThreshold(pipeline_data->thresholds[i].size(), CV_8U); - pipeline_data->thresholds[i].copyTo(tempThreshold); - findContours(tempThreshold, - contours, // a vector of contours - hierarchy, - CV_RETR_TREE, // retrieve all contours - CV_CHAIN_APPROX_SIMPLE ); // all pixels of each contours - - allContours.push_back(contours); - allHierarchy.push_back(hierarchy); + allTextContours.push_back(tc); } if (config->debugTiming) @@ -79,11 +68,10 @@ void CharacterAnalysis::analyze() for (uint i = 0; i < pipeline_data->thresholds.size(); i++) { - vector goodIndices = this->filter(pipeline_data->thresholds[i], allContours[i], allHierarchy[i]); - charSegments.push_back(goodIndices); + this->filter(pipeline_data->thresholds[i], allTextContours[i]); if (config->debugCharAnalysis) - cout << "Threshold " << i << " had " << getGoodIndicesCount(goodIndices) << " good indices." << endl; + cout << "Threshold " << i << " had " << allTextContours[i].getGoodIndicesCount() << " good indices." << endl; } if (config->debugTiming) @@ -94,7 +82,7 @@ void CharacterAnalysis::analyze() } PlateMask plateMask(pipeline_data); - plateMask.findOuterBoxMask(charSegments, allContours, allHierarchy); + plateMask.findOuterBoxMask(allTextContours); pipeline_data->hasPlateBorder = plateMask.hasPlateMask; pipeline_data->plateBorderMask = plateMask.getMask(); @@ -104,7 +92,7 @@ void CharacterAnalysis::analyze() // Filter out bad contours now that we have an outer box mask... for (uint i = 0; i < pipeline_data->thresholds.size(); i++) { - charSegments[i] = filterByOuterMask(allContours[i], allHierarchy[i], charSegments[i]); + filterByOuterMask(allTextContours[i]); } } @@ -115,17 +103,14 @@ void CharacterAnalysis::analyze() //vector goodIndices = this->filter(thresholds[i], allContours[i], allHierarchy[i]); //charSegments.push_back(goodIndices); - int segmentCount = getGoodIndicesCount(charSegments[i]); + int segmentCount = allTextContours[i].getGoodIndicesCount(); if (segmentCount > bestFitScore) { bestFitScore = segmentCount; bestFitIndex = i; - bestCharSegments = charSegments[i]; bestThreshold = pipeline_data->thresholds[i]; - bestContours = allContours[i]; - bestHierarchy = allHierarchy[i]; - bestCharSegmentsCount = segmentCount; + bestContours = allTextContours[i]; } } @@ -146,11 +131,11 @@ void CharacterAnalysis::analyze() vector > allowedContours; for (uint i = 0; i < bestContours.size(); i++) { - if (bestCharSegments[i]) - allowedContours.push_back(bestContours[i]); + if (bestContours.goodIndices[i]) + allowedContours.push_back(bestContours.contours[i]); } - drawContours(img_contours, bestContours, + drawContours(img_contours, bestContours.contours, -1, // draw all contours cv::Scalar(255,0,0), // in blue 1); // with a thickness of 1 @@ -165,14 +150,14 @@ void CharacterAnalysis::analyze() //charsegments = this->getPossibleCharRegions(img_threshold, allContours, allHierarchy, STARTING_MIN_HEIGHT + (bestFitIndex * HEIGHT_STEP), STARTING_MAX_HEIGHT + (bestFitIndex * HEIGHT_STEP)); - this->linePolygon = getBestVotedLines(pipeline_data->crop_gray, bestContours, bestCharSegments); + this->linePolygon = getBestVotedLines(pipeline_data->crop_gray, bestContours); if (this->linePolygon.size() > 0) { this->topLine = LineSegment(this->linePolygon[0].x, this->linePolygon[0].y, this->linePolygon[1].x, this->linePolygon[1].y); this->bottomLine = LineSegment(this->linePolygon[3].x, this->linePolygon[3].y, this->linePolygon[2].x, this->linePolygon[2].y); //this->charArea = getCharSegmentsBetweenLines(bestThreshold, bestContours, this->linePolygon); - filterBetweenLines(bestThreshold, bestContours, bestHierarchy, linePolygon, bestCharSegments); + filterBetweenLines(bestThreshold, bestContours, linePolygon); this->charArea = getCharArea(); @@ -188,17 +173,6 @@ void CharacterAnalysis::analyze() this->thresholdsInverted = isPlateInverted(); } -int CharacterAnalysis::getGoodIndicesCount(vector goodIndices) -{ - int count = 0; - for (uint i = 0; i < goodIndices.size(); i++) - { - if (goodIndices[i]) - count++; - } - - return count; -} @@ -208,15 +182,15 @@ Mat CharacterAnalysis::getCharacterMask() for (uint i = 0; i < bestContours.size(); i++) { - if (bestCharSegments[i] == false) + if (bestContours.goodIndices[i] == false) continue; - drawContours(charMask, bestContours, + drawContours(charMask, bestContours.contours, i, // draw this contour cv::Scalar(255,255,255), // in CV_FILLED, 8, - bestHierarchy, + bestContours.hierarchy, 1 ); @@ -226,7 +200,7 @@ Mat CharacterAnalysis::getCharacterMask() } // Returns a polygon "stripe" across the width of the character region. The lines are voted and the polygon starts at 0 and extends to image width -vector CharacterAnalysis::getBestVotedLines(Mat img, vector > contours, vector goodIndices) +vector CharacterAnalysis::getBestVotedLines(Mat img, TextContours textContours) { //if (this->debug) // cout << "CharacterAnalysis::getBestVotedLines" << endl; @@ -235,10 +209,10 @@ vector CharacterAnalysis::getBestVotedLines(Mat img, vector vector charRegions; - for (uint i = 0; i < contours.size(); i++) + for (uint i = 0; i < textContours.size(); i++) { - if (goodIndices[i]) - charRegions.push_back(boundingRect(contours[i])); + if (textContours.goodIndices[i]) + charRegions.push_back(boundingRect(textContours.contours[i])); } // Find the best fit line segment that is parallel with the most char segments @@ -308,7 +282,6 @@ vector CharacterAnalysis::getBestVotedLines(Mat img, vector //cv::line(tempImg, Point(x1, y1), Point(x2, y2), Scalar(0, 0, 255)); bottomLines.push_back(LineSegment(x1, y1, x2, y2)); - //drawAndWait(&tempImg); } } @@ -382,65 +355,66 @@ vector CharacterAnalysis::getBestVotedLines(Mat img, vector return bestStripe; } -vector CharacterAnalysis::filter(Mat img, vector > contours, vector hierarchy) +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; - vector charSegments; int bestFitScore = -1; + + vector bestIndices; + for (int i = 0; i < NUM_STEPS; i++) { - int goodIndicesCount; + + //vector goodIndices(contours.size()); + for (uint z = 0; z < textContours.size(); z++) textContours.goodIndices[z] = true; - vector goodIndices(contours.size()); - for (uint z = 0; z < goodIndices.size(); z++) goodIndices[z] = true; + this->filterByBoxSize(textContours, STARTING_MIN_HEIGHT + (i * HEIGHT_STEP), STARTING_MAX_HEIGHT + (i * HEIGHT_STEP)); - goodIndices = this->filterByBoxSize(contours, goodIndices, STARTING_MIN_HEIGHT + (i * HEIGHT_STEP), STARTING_MAX_HEIGHT + (i * HEIGHT_STEP)); - - goodIndicesCount = getGoodIndicesCount(goodIndices); - if ( goodIndicesCount == 0 || goodIndicesCount <= bestFitScore) // Don't bother doing more filtering if we already lost... + int goodIndices = textContours.getGoodIndicesCount(); + if ( goodIndices == 0 || goodIndices <= bestFitScore) // Don't bother doing more filtering if we already lost... continue; - goodIndices = this->filterContourHoles(contours, hierarchy, goodIndices); + + this->filterContourHoles(textContours); - goodIndicesCount = getGoodIndicesCount(goodIndices); - if ( goodIndicesCount == 0 || goodIndicesCount <= bestFitScore) // Don't bother doing more filtering if we already lost... + goodIndices = textContours.getGoodIndicesCount(); + if ( goodIndices == 0 || goodIndices <= bestFitScore) // Don't bother doing more filtering if we already lost... continue; - //goodIndices = this->filterByParentContour( contours, hierarchy, goodIndices); - vector lines = getBestVotedLines(img, contours, goodIndices); - goodIndices = this->filterBetweenLines(img, contours, hierarchy, lines, goodIndices); + + vector lines = getBestVotedLines(img, textContours); + this->filterBetweenLines(img, textContours, lines); - int segmentCount = getGoodIndicesCount(goodIndices); + int segmentCount = textContours.getGoodIndicesCount(); if (segmentCount > bestFitScore) { bestFitScore = segmentCount; - charSegments = goodIndices; + bestIndices = textContours.getIndicesCopy(); } } - return charSegments; + textContours.setIndices(bestIndices); } // Goes through the contours for the plate and picks out possible char segments based on min/max height -vector CharacterAnalysis::filterByBoxSize(vector< vector< Point> > contours, vector goodIndices, int minHeightPx, int maxHeightPx) +void CharacterAnalysis::filterByBoxSize(TextContours& textContours, int minHeightPx, int maxHeightPx) { float idealAspect=config->charWidthMM / config->charHeightMM; float aspecttolerance=0.25; - vector includedIndices(contours.size()); - for (uint j = 0; j < contours.size(); j++) - includedIndices.push_back(false); - for (uint i = 0; i < contours.size(); i++) + for (uint i = 0; i < textContours.size(); i++) { - if (goodIndices[i] == false) + 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(contours[i]); + Rect mr= boundingRect(textContours.contours[i]); float minWidth = mr.height * 0.2; //Crop image @@ -450,27 +424,25 @@ vector CharacterAnalysis::filterByBoxSize(vector< vector< Point> > contour float charAspect= (float)mr.width/(float)mr.height; if (abs(charAspect - idealAspect) < aspecttolerance) - includedIndices[i] = true; + textContours.goodIndices[i] = true; } } - return includedIndices; } -vector< bool > CharacterAnalysis::filterContourHoles(vector< vector< Point > > contours, vector< Vec4i > hierarchy, vector< bool > goodIndices) +void CharacterAnalysis::filterContourHoles(TextContours& textContours) { - vector includedIndices(contours.size()); - for (uint j = 0; j < contours.size(); j++) - includedIndices.push_back(false); - for (uint i = 0; i < contours.size(); i++) + for (uint i = 0; i < textContours.size(); i++) { - if (goodIndices[i] == false) + if (textContours.goodIndices[i] == false) continue; - int parentIndex = hierarchy[i][3]; + textContours.goodIndices[i] = false; // Set it to not included unless it proves valid + + int parentIndex = textContours.hierarchy[i][3]; - if (parentIndex >= 0 && goodIndices[parentIndex]) + if (parentIndex >= 0 && textContours.goodIndices[parentIndex]) { // this contour is a child of an already identified contour. REMOVE it if (this->config->debugCharAnalysis) @@ -480,31 +452,29 @@ vector< bool > CharacterAnalysis::filterContourHoles(vector< vector< Point > > c } else { - includedIndices[i] = true; + textContours.goodIndices[i] = true; } } - return includedIndices; } // 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 -vector CharacterAnalysis::filterByParentContour( vector< vector< Point> > contours, vector hierarchy, vector goodIndices) +void CharacterAnalysis::filterByParentContour( TextContours& textContours) { - vector includedIndices(contours.size()); - for (uint j = 0; j < contours.size(); j++) - includedIndices[j] = false; vector parentIDs; vector votes; - for (uint i = 0; i < contours.size(); i++) + for (uint i = 0; i < textContours.size(); i++) { - if (goodIndices[i] == false) + if (textContours.goodIndices[i] == false) continue; + textContours.goodIndices[i] = false; // Set it to not included unless it proves + int voteIndex = -1; - int parentID = hierarchy[i][3]; + int parentID = textContours.hierarchy[i][3]; // check if parentID is already in the lsit for (uint j = 0; j < parentIDs.size(); j++) { @@ -540,35 +510,30 @@ vector CharacterAnalysis::filterByParentContour( vector< vector< Point> > } // Now filter out all the contours with a different parent ID (assuming the totalVotes > 2) - for (uint i = 0; i < contours.size(); i++) + for (uint i = 0; i < textContours.size(); i++) { - if (goodIndices[i] == false) + if (textContours.goodIndices[i] == false) continue; if (totalVotes <= 2) { - includedIndices[i] = true; + textContours.goodIndices[i] = true; } - else if (hierarchy[i][3] == winningParentId) + else if (textContours.hierarchy[i][3] == winningParentId) { - includedIndices[i] = true; + textContours.goodIndices[i] = true; } } - return includedIndices; } -vector CharacterAnalysis::filterBetweenLines(Mat img, vector > contours, vector hierarchy, vector outerPolygon, vector goodIndices) +void CharacterAnalysis::filterBetweenLines(Mat img, TextContours& textContours, vector outerPolygon ) { static float MIN_AREA_PERCENT_WITHIN_LINES = 0.88; static float MAX_DISTANCE_PERCENT_FROM_LINES = 0.15; - vector includedIndices(contours.size()); - for (uint j = 0; j < contours.size(); j++) - includedIndices[j] = false; - if (outerPolygon.size() == 0) - return includedIndices; + return; vector validPoints; @@ -587,19 +552,21 @@ vector CharacterAnalysis::filterBetweenLines(Mat img, vector fillConvexPoly(outerMask, outerPolygon.data(), outerPolygon.size(), Scalar(255,255,255)); // For each contour, determine if enough of it is between the lines to qualify - for (uint i = 0; i < contours.size(); i++) + for (uint i = 0; i < textContours.size(); i++) { - if (goodIndices[i] == false) + if (textContours.goodIndices[i] == false) continue; + + textContours.goodIndices[i] = false; // Set it to not included unless it proves innerArea.setTo(Scalar(0,0,0)); - drawContours(innerArea, contours, + drawContours(innerArea, textContours.contours, i, // draw this contour cv::Scalar(255,255,255), // in CV_FILLED, 8, - hierarchy, + textContours.hierarchy, 0 ); @@ -610,7 +577,7 @@ vector CharacterAnalysis::filterBetweenLines(Mat img, vector CV_RETR_EXTERNAL, // retrieve the external contours CV_CHAIN_APPROX_SIMPLE ); // all pixels of each contours ); - double totalArea = contourArea(contours[i]); + double totalArea = contourArea(textContours.contours[i]); double areaBetweenLines = 0; for (uint tempContourIdx = 0; tempContourIdx < tempContours.size(); tempContourIdx++) @@ -635,50 +602,46 @@ vector CharacterAnalysis::filterBetweenLines(Mat img, vector int highPointValue = 999999999; int lowPointIndex = 0; int lowPointValue = 0; - for (uint cidx = 0; cidx < contours[i].size(); cidx++) + for (uint cidx = 0; cidx < textContours.contours[i].size(); cidx++) { - if (contours[i][cidx].y < highPointValue) + if (textContours.contours[i][cidx].y < highPointValue) { highPointIndex = cidx; - highPointValue = contours[i][cidx].y; + highPointValue = textContours.contours[i][cidx].y; } - if (contours[i][cidx].y > lowPointValue) + if (textContours.contours[i][cidx].y > lowPointValue) { lowPointIndex = cidx; - lowPointValue = contours[i][cidx].y; + lowPointValue = textContours.contours[i][cidx].y; } } // Get the absolute distance from the top and bottom lines - Point closestTopPoint = topLine.closestPointOnSegmentTo(contours[i][highPointIndex]); - Point closestBottomPoint = bottomLine.closestPointOnSegmentTo(contours[i][lowPointIndex]); + Point closestTopPoint = topLine.closestPointOnSegmentTo(textContours.contours[i][highPointIndex]); + Point closestBottomPoint = bottomLine.closestPointOnSegmentTo(textContours.contours[i][lowPointIndex]); - float absTopDistance = distanceBetweenPoints(closestTopPoint, contours[i][highPointIndex]); - float absBottomDistance = distanceBetweenPoints(closestBottomPoint, contours[i][lowPointIndex]); + float absTopDistance = distanceBetweenPoints(closestTopPoint, textContours.contours[i][highPointIndex]); + float absBottomDistance = distanceBetweenPoints(closestBottomPoint, textContours.contours[i][lowPointIndex]); float maxDistance = lineHeight * MAX_DISTANCE_PERCENT_FROM_LINES; if (absTopDistance < maxDistance && absBottomDistance < maxDistance) { - includedIndices[i] = true; + textContours.goodIndices[i] = true; } } - return includedIndices; } -std::vector< bool > CharacterAnalysis::filterByOuterMask(vector< vector< Point > > contours, vector< Vec4i > hierarchy, std::vector< bool > goodIndices) +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 goodIndices; + return; - vector passingIndices; - for (uint i = 0; i < goodIndices.size(); i++) - passingIndices.push_back(false); cv::Mat plateMask = pipeline_data->plateBorderMask; @@ -688,14 +651,18 @@ std::vector< bool > CharacterAnalysis::filterByOuterMask(vector< vector< Point > int charsInsideMask = 0; int totalChars = 0; - for (uint i=0; i < goodIndices.size(); i++) + vector originalindices; + for (uint i = 0; i < textContours.size(); i++) + originalindices.push_back(textContours.goodIndices[i]); + + for (uint i=0; i < textContours.size(); i++) { - if (goodIndices[i] == false) + if (textContours.goodIndices[i] == false) continue; totalChars++; - drawContours(tempFullContour, contours, i, Scalar(255,255,255), CV_FILLED, 8, hierarchy); + drawContours(tempFullContour, textContours.contours, i, Scalar(255,255,255), CV_FILLED, 8, textContours.hierarchy); bitwise_and(tempFullContour, plateMask, tempMaskedContour); float beforeMaskWhiteness = mean(tempFullContour)[0]; @@ -704,20 +671,25 @@ std::vector< bool > CharacterAnalysis::filterByOuterMask(vector< vector< Point > if (afterMaskWhiteness / beforeMaskWhiteness > MINIMUM_PERCENT_LEFT_AFTER_MASK) { charsInsideMask++; - passingIndices[i] = true; + textContours.goodIndices[i] = true; } } if (totalChars == 0) - return goodIndices; + { + 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) - return goodIndices; + { + textContours.goodIndices = originalindices; + return; + } - return passingIndices; } bool CharacterAnalysis::isPlateInverted() @@ -771,15 +743,15 @@ vector CharacterAnalysis::getCharArea() for (uint i = 0; i < bestContours.size(); i++) { - if (bestCharSegments[i] == false) + if (bestContours.goodIndices[i] == false) continue; - for (uint z = 0; z < bestContours[i].size(); z++) + for (uint z = 0; z < bestContours.contours[i].size(); z++) { - if (bestContours[i][z].x < leftX) - leftX = bestContours[i][z].x; - if (bestContours[i][z].x > rightX) - rightX = bestContours[i][z].x; + if (bestContours.contours[i][z].x < leftX) + leftX = bestContours.contours[i][z].x; + if (bestContours.contours[i][z].x > rightX) + rightX = bestContours.contours[i][z].x; } } diff --git a/src/openalpr/textdetection/characteranalysis.h b/src/openalpr/textdetection/characteranalysis.h index e539720..94c0516 100644 --- a/src/openalpr/textdetection/characteranalysis.h +++ b/src/openalpr/textdetection/characteranalysis.h @@ -24,6 +24,8 @@ #include "utility.h" #include "config.h" #include "pipeline_data.h" +#include "textcontours.h" +#include "platemask.h" class CharacterAnalysis { @@ -34,10 +36,12 @@ class CharacterAnalysis cv::Mat bestThreshold; - std::vector > bestContours; - std::vector bestHierarchy; - std::vector bestCharSegments; - int bestCharSegmentsCount; + + TextContours bestContours; + //std::vector > bestContours; + //std::vector bestHierarchy; + //std::vector bestCharSegments; + //int bestCharSegmentsCount; LineSegment topLine; LineSegment bottomLine; @@ -52,9 +56,10 @@ class CharacterAnalysis bool thresholdsInverted; bool isTwoLine; - std::vector > > allContours; - std::vector > allHierarchy; - std::vector > charSegments; + std::vector allTextContours; + //std::vector > > allContours; + //std::vector > allHierarchy; + //std::vector > charSegments; void analyze(); @@ -67,21 +72,19 @@ class CharacterAnalysis cv::Mat findOuterBoxMask( ); bool isPlateInverted(); - std::vector filter(cv::Mat img, std::vector > contours, std::vector hierarchy); + void filter(cv::Mat img, TextContours& textContours); - std::vector filterByBoxSize(std::vector > contours, std::vector goodIndices, int minHeightPx, int maxHeightPx); - std::vector filterByParentContour( std::vector< std::vector > contours, std::vector hierarchy, std::vector goodIndices); - std::vector filterContourHoles(std::vector > contours, std::vector hierarchy, std::vector goodIndices); - std::vector filterByOuterMask(std::vector > contours, std::vector hierarchy, std::vector goodIndices); + void filterByBoxSize(TextContours& textContours, int minHeightPx, int maxHeightPx); + void filterByParentContour( TextContours& textContours ); + void filterContourHoles(TextContours& textContours); + void filterByOuterMask(TextContours& textContours); std::vector getCharArea(); - std::vector getBestVotedLines(cv::Mat img, std::vector > contours, std::vector goodIndices); - //vector getCharSegmentsBetweenLines(Mat img, vector > contours, vector outerPolygon); - std::vector filterBetweenLines(cv::Mat img, std::vector > contours, std::vector hierarchy, std::vector outerPolygon, std::vector goodIndices); + std::vector getBestVotedLines(cv::Mat img, TextContours textContours); + void filterBetweenLines(cv::Mat img, TextContours& textContours, std::vector outerPolygon ); bool verifySize(cv::Mat r, float minHeightPx, float maxHeightPx); - int getGoodIndicesCount(std::vector goodIndices); }; diff --git a/src/openalpr/textdetection/platemask.cpp b/src/openalpr/textdetection/platemask.cpp index 7828e64..04dabbd 100644 --- a/src/openalpr/textdetection/platemask.cpp +++ b/src/openalpr/textdetection/platemask.cpp @@ -36,9 +36,7 @@ cv::Mat PlateMask::getMask() { return this->plateMask; } -void PlateMask::findOuterBoxMask(vector > charSegments, - vector > > allContours, - vector > allHierarchy) +void PlateMask::findOuterBoxMask( vector contours ) { double min_parent_area = pipeline_data->config->templateHeightPx * pipeline_data->config->templateWidthPx * 0.10; // Needs to be at least 10% of the plate area to be considered. @@ -50,19 +48,19 @@ void PlateMask::findOuterBoxMask(vector > charSegments, if (pipeline_data->config->debugCharAnalysis) cout << "CharacterAnalysis::findOuterBoxMask" << endl; - for (uint imgIndex = 0; imgIndex < allContours.size(); imgIndex++) + for (uint imgIndex = 0; imgIndex < contours.size(); imgIndex++) { //vector charContours = filter(thresholds[imgIndex], allContours[imgIndex], allHierarchy[imgIndex]); int charsRecognized = 0; int parentId = -1; bool hasParent = false; - for (uint i = 0; i < charSegments[imgIndex].size(); i++) + for (uint i = 0; i < contours[imgIndex].goodIndices.size(); i++) { - if (charSegments[imgIndex][i]) charsRecognized++; - if (charSegments[imgIndex][i] && allHierarchy[imgIndex][i][3] != -1) + if (contours[imgIndex].goodIndices[i]) charsRecognized++; + if (contours[imgIndex].goodIndices[i] && contours[imgIndex].hierarchy[i][3] != -1) { - parentId = allHierarchy[imgIndex][i][3]; + parentId = contours[imgIndex].hierarchy[i][3]; hasParent = true; } } @@ -72,7 +70,7 @@ void PlateMask::findOuterBoxMask(vector > charSegments, if (hasParent) { - double boxArea = contourArea(allContours[imgIndex][parentId]); + double boxArea = contourArea(contours[imgIndex].contours[parentId]); if (boxArea < min_parent_area) continue; @@ -96,13 +94,13 @@ void PlateMask::findOuterBoxMask(vector > charSegments, int longestChildIndex = -1; double longestChildLength = 0; // Find the child with the longest permiter/arc length ( just for kicks) - for (uint i = 0; i < allContours[winningIndex].size(); i++) + for (uint i = 0; i < contours[winningIndex].size(); i++) { - for (uint j = 0; j < allContours[winningIndex].size(); j++) + for (uint j = 0; j < contours[winningIndex].size(); j++) { - if (allHierarchy[winningIndex][j][3] == winningParentId) + if (contours[winningIndex].hierarchy[j][3] == winningParentId) { - double arclength = arcLength(allContours[winningIndex][j], false); + double arclength = arcLength(contours[winningIndex].contours[j], false); if (arclength > longestChildLength) { longestChildIndex = j; @@ -115,12 +113,12 @@ void PlateMask::findOuterBoxMask(vector > charSegments, Mat mask = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U); // get rid of the outline by drawing a 1 pixel width black line - drawContours(mask, allContours[winningIndex], + drawContours(mask, contours[winningIndex].contours, winningParentId, // draw this contour cv::Scalar(255,255,255), // in CV_FILLED, 8, - allHierarchy[winningIndex], + contours[winningIndex].hierarchy, 0 ); @@ -169,7 +167,7 @@ void PlateMask::findOuterBoxMask(vector > charSegments, cv::Scalar(255,255,255), // in CV_FILLED, 8, - allHierarchy[winningIndex], + contours[winningIndex].hierarchy, 0 ); } diff --git a/src/openalpr/textdetection/platemask.h b/src/openalpr/textdetection/platemask.h index ef57fc3..e31405a 100644 --- a/src/openalpr/textdetection/platemask.h +++ b/src/openalpr/textdetection/platemask.h @@ -22,6 +22,7 @@ #include "opencv2/imgproc/imgproc.hpp" #include "pipeline_data.h" +#include "textcontours.h" class PlateMask { public: @@ -32,7 +33,7 @@ public: cv::Mat getMask(); - void findOuterBoxMask(std::vector > charSegments, std::vector > > allContours, std::vector > allHierarchy); + void findOuterBoxMask(std::vector contours); private: diff --git a/src/openalpr/textdetection/textcontours.cpp b/src/openalpr/textdetection/textcontours.cpp new file mode 100644 index 0000000..ed53fbd --- /dev/null +++ b/src/openalpr/textdetection/textcontours.cpp @@ -0,0 +1,86 @@ +/* + * File: textcontours.cpp + * Author: mhill + * + * Created on October 9, 2014, 7:40 PM + */ + +#include "textcontours.h" + +using namespace std; +using namespace cv; + + +TextContours::TextContours() { + + +} + + +TextContours::TextContours(cv::Mat threshold) { + + load(threshold); +} + + +TextContours::~TextContours() { +} + +void TextContours::load(cv::Mat threshold) { + + Mat tempThreshold(threshold.size(), CV_8U); + threshold.copyTo(tempThreshold); + findContours(tempThreshold, + contours, // a vector of contours + hierarchy, + CV_RETR_TREE, // retrieve all contours + CV_CHAIN_APPROX_SIMPLE ); // all pixels of each contours + + for (uint i = 0; i < contours.size(); i++) + goodIndices.push_back(true); +} + + +uint TextContours::size() { + return contours.size(); +} + + + +int TextContours::getGoodIndicesCount() +{ + int count = 0; + for (uint i = 0; i < goodIndices.size(); i++) + { + if (goodIndices[i]) + count++; + } + + return count; +} + + +std::vector TextContours::getIndicesCopy() +{ + vector copyArray; + for (uint i = 0; i < goodIndices.size(); i++) + { + bool val = goodIndices[i]; + copyArray.push_back(goodIndices[i]); + } + + return copyArray; +} + +void TextContours::setIndices(std::vector newIndices) +{ + if (newIndices.size() == goodIndices.size()) + { + for (uint i = 0; i < newIndices.size(); i++) + goodIndices[i] = newIndices[i]; + } + else + { + assert("Invalid set operation on indices"); + } +} \ No newline at end of file diff --git a/src/openalpr/textdetection/textcontours.h b/src/openalpr/textdetection/textcontours.h new file mode 100644 index 0000000..bc38067 --- /dev/null +++ b/src/openalpr/textdetection/textcontours.h @@ -0,0 +1,38 @@ +/* + * File: textcontours.h + * Author: mhill + * + * Created on October 9, 2014, 7:40 PM + */ + +#ifndef TEXTCONTOURS_H +#define TEXTCONTOURS_H + +#include +#include "opencv2/imgproc/imgproc.hpp" + +class TextContours { +public: + TextContours(); + TextContours(cv::Mat threshold); + virtual ~TextContours(); + + void load(cv::Mat threshold); + + std::vector goodIndices; + std::vector > contours; + std::vector hierarchy; + + uint size(); + int getGoodIndicesCount(); + + std::vector getIndicesCopy(); + void setIndices(std::vector newIndices); + +private: + + +}; + +#endif /* TEXTCONTOURS_H */ +