Refactored CharacterAnalysis code to use a class that wraps the raw Contour data

This commit is contained in:
Matt Hill
2014-10-09 23:23:22 -04:00
parent add77f6b0c
commit d55e66d665
10 changed files with 286 additions and 186 deletions

View File

@@ -25,6 +25,7 @@ set(lpr_source_files
textdetection/characteranalysis.cpp textdetection/characteranalysis.cpp
textdetection/characteranalysis2l.cpp textdetection/characteranalysis2l.cpp
textdetection/platemask.cpp textdetection/platemask.cpp
textdetection/textcontours.cpp
pipeline_data.cpp pipeline_data.cpp
trex.c trex.c
cjson.c cjson.c

View File

@@ -58,9 +58,9 @@ CharacterRegion::CharacterRegion(PipelineData* pipeline_data)
for (uint z = 0; z < charAnalysis->bestContours.size(); z++) for (uint z = 0; z < charAnalysis->bestContours.size(); z++)
{ {
Scalar dcolor(255,0,0); Scalar dcolor(255,0,0);
if (charAnalysis->bestCharSegments[z]) if (charAnalysis->bestContours.goodIndices[z])
dcolor = Scalar(0,255,0); dcolor = Scalar(0,255,0);
drawContours(bestVal, charAnalysis->bestContours, z, dcolor, 1); drawContours(bestVal, charAnalysis->bestContours.contours, z, dcolor, 1);
} }
tempDash.push_back(bestVal); tempDash.push_back(bestVal);
displayImage(config, "Character Region Step 1 Thresholds", drawImageDashboard(tempDash, bestVal.type(), 3)); 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) if (charAnalysis->linePolygon.size() > 0)
{ {
int confidenceDrainers = 0; int confidenceDrainers = 0;
int charSegmentCount = charAnalysis->bestCharSegmentsCount; int charSegmentCount = charAnalysis->bestContours.getGoodIndicesCount();
if (charSegmentCount == 1) if (charSegmentCount == 1)
confidenceDrainers += 91; confidenceDrainers += 91;
else if (charSegmentCount < 5) else if (charSegmentCount < 5)

View File

@@ -60,11 +60,11 @@ CharacterSegmenter::CharacterSegmenter(PipelineData* pipeline_data)
vector<vector<Point> > allowedContours; vector<vector<Point> > allowedContours;
for (uint i = 0; i < charAnalysis->bestContours.size(); i++) for (uint i = 0; i < charAnalysis->bestContours.size(); i++)
{ {
if (charAnalysis->bestCharSegments[i]) if (charAnalysis->bestContours.goodIndices[i])
allowedContours.push_back(charAnalysis->bestContours[i]); allowedContours.push_back(charAnalysis->bestContours.contours[i]);
} }
drawContours(img_contours, charAnalysis->bestContours, drawContours(img_contours, charAnalysis->bestContours.contours,
-1, // draw all contours -1, // draw all contours
cv::Scalar(255,0,0), // in blue cv::Scalar(255,0,0), // in blue
1); // with a thickness of 1 1); // with a thickness of 1
@@ -94,10 +94,10 @@ CharacterSegmenter::CharacterSegmenter(PipelineData* pipeline_data)
for (uint i = 0; i < charAnalysis->bestContours.size(); i++) for (uint i = 0; i < charAnalysis->bestContours.size(); i++)
{ {
if (charAnalysis->bestCharSegments[i] == false) if (charAnalysis->bestContours.goodIndices[i] == false)
continue; continue;
Rect mr = boundingRect(charAnalysis->bestContours[i]); Rect mr = boundingRect(charAnalysis->bestContours.contours[i]);
charWidths.push_back(mr.width); charWidths.push_back(mr.width);
charHeights.push_back(mr.height); charHeights.push_back(mr.height);
@@ -106,7 +106,7 @@ CharacterSegmenter::CharacterSegmenter(PipelineData* pipeline_data)
float avgCharWidth = median(charWidths.data(), charWidths.size()); float avgCharWidth = median(charWidths.data(), charWidths.size());
float avgCharHeight = median(charHeights.data(), charHeights.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 // Do the histogram analysis to figure out char regions
@@ -116,7 +116,7 @@ CharacterSegmenter::CharacterSegmenter(PipelineData* pipeline_data)
vector<Mat> allHistograms; vector<Mat> allHistograms;
vector<Rect> allBoxes; vector<Rect> 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); Mat histogramMask = Mat::zeros(pipeline_data->thresholds[i].size(), CV_8U);
@@ -443,23 +443,23 @@ vector<Rect> CharacterSegmenter::get1DHits(Mat img, int yOffset)
return hits; return hits;
} }
void CharacterSegmenter::removeSmallContours(vector<Mat> thresholds, vector<vector<vector<Point > > > allContours, float avgCharWidth, float avgCharHeight) void CharacterSegmenter::removeSmallContours(vector<Mat> thresholds, vector<TextContours> contours, float avgCharWidth, float avgCharHeight)
{ {
//const float MIN_CHAR_AREA = 0.02 * avgCharWidth * avgCharHeight; // To clear out the tiny specks //const float MIN_CHAR_AREA = 0.02 * avgCharWidth * avgCharHeight; // To clear out the tiny specks
const float MIN_CONTOUR_HEIGHT = 0.3 * avgCharHeight; const float MIN_CONTOUR_HEIGHT = 0.3 * avgCharHeight;
for (uint i = 0; i < thresholds.size(); i++) 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; continue;
Rect mr = boundingRect(allContours[i][c]); Rect mr = boundingRect(contours[i].contours[c]);
if (mr.height < MIN_CONTOUR_HEIGHT) if (mr.height < MIN_CONTOUR_HEIGHT)
{ {
// Erase it // 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; continue;
} }
} }

View File

@@ -28,6 +28,7 @@
#include "colorfilter.h" #include "colorfilter.h"
#include "verticalhistogram.h" #include "verticalhistogram.h"
#include "config.h" #include "config.h"
#include "textdetection/textcontours.h"
//const float MIN_BOX_WIDTH_PX = 4; // 4 pixels //const float MIN_BOX_WIDTH_PX = 4; // 4 pixels
@@ -73,7 +74,7 @@ class CharacterSegmenter
cv::Mat getCharacterMask(cv::Mat img_threshold, std::vector<std::vector<cv::Point> > contours, std::vector<cv::Vec4i> hierarchy, std::vector<bool> goodIndices); cv::Mat getCharacterMask(cv::Mat img_threshold, std::vector<std::vector<cv::Point> > contours, std::vector<cv::Vec4i> hierarchy, std::vector<bool> goodIndices);
cv::Mat getCharBoxMask(cv::Mat img_threshold, std::vector<cv::Rect> charBoxes); cv::Mat getCharBoxMask(cv::Mat img_threshold, std::vector<cv::Rect> charBoxes);
void removeSmallContours(std::vector<cv::Mat> thresholds, std::vector<std::vector<std::vector<cv::Point > > > allContours, float avgCharWidth, float avgCharHeight); void removeSmallContours(std::vector<cv::Mat> thresholds, std::vector<TextContours> contours, float avgCharWidth, float avgCharHeight);
cv::Mat getVerticalHistogram(cv::Mat img, cv::Mat mask); cv::Mat getVerticalHistogram(cv::Mat img, cv::Mat mask);
std::vector<cv::Rect> getHistogramBoxes(VerticalHistogram histogram, float avgCharWidth, float avgCharHeight, float* score); std::vector<cv::Rect> getHistogramBoxes(VerticalHistogram histogram, float avgCharWidth, float avgCharHeight, float* score);

View File

@@ -18,7 +18,6 @@
*/ */
#include "characteranalysis.h" #include "characteranalysis.h"
#include "platemask.h"
using namespace cv; using namespace cv;
using namespace std; using namespace std;
@@ -52,19 +51,9 @@ void CharacterAnalysis::analyze()
for (uint i = 0; i < pipeline_data->thresholds.size(); i++) for (uint i = 0; i < pipeline_data->thresholds.size(); i++)
{ {
vector<vector<Point> > contours; TextContours tc(pipeline_data->thresholds[i]);
vector<Vec4i> hierarchy;
Mat tempThreshold(pipeline_data->thresholds[i].size(), CV_8U); allTextContours.push_back(tc);
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);
} }
if (config->debugTiming) if (config->debugTiming)
@@ -79,11 +68,10 @@ void CharacterAnalysis::analyze()
for (uint i = 0; i < pipeline_data->thresholds.size(); i++) for (uint i = 0; i < pipeline_data->thresholds.size(); i++)
{ {
vector<bool> goodIndices = this->filter(pipeline_data->thresholds[i], allContours[i], allHierarchy[i]); this->filter(pipeline_data->thresholds[i], allTextContours[i]);
charSegments.push_back(goodIndices);
if (config->debugCharAnalysis) 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) if (config->debugTiming)
@@ -94,7 +82,7 @@ void CharacterAnalysis::analyze()
} }
PlateMask plateMask(pipeline_data); PlateMask plateMask(pipeline_data);
plateMask.findOuterBoxMask(charSegments, allContours, allHierarchy); plateMask.findOuterBoxMask(allTextContours);
pipeline_data->hasPlateBorder = plateMask.hasPlateMask; pipeline_data->hasPlateBorder = plateMask.hasPlateMask;
pipeline_data->plateBorderMask = plateMask.getMask(); pipeline_data->plateBorderMask = plateMask.getMask();
@@ -104,7 +92,7 @@ void CharacterAnalysis::analyze()
// Filter out bad contours now that we have an outer box mask... // Filter out bad contours now that we have an outer box mask...
for (uint i = 0; i < pipeline_data->thresholds.size(); i++) 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<bool> goodIndices = this->filter(thresholds[i], allContours[i], allHierarchy[i]); //vector<bool> goodIndices = this->filter(thresholds[i], allContours[i], allHierarchy[i]);
//charSegments.push_back(goodIndices); //charSegments.push_back(goodIndices);
int segmentCount = getGoodIndicesCount(charSegments[i]); int segmentCount = allTextContours[i].getGoodIndicesCount();
if (segmentCount > bestFitScore) if (segmentCount > bestFitScore)
{ {
bestFitScore = segmentCount; bestFitScore = segmentCount;
bestFitIndex = i; bestFitIndex = i;
bestCharSegments = charSegments[i];
bestThreshold = pipeline_data->thresholds[i]; bestThreshold = pipeline_data->thresholds[i];
bestContours = allContours[i]; bestContours = allTextContours[i];
bestHierarchy = allHierarchy[i];
bestCharSegmentsCount = segmentCount;
} }
} }
@@ -146,11 +131,11 @@ void CharacterAnalysis::analyze()
vector<vector<Point> > allowedContours; vector<vector<Point> > allowedContours;
for (uint i = 0; i < bestContours.size(); i++) for (uint i = 0; i < bestContours.size(); i++)
{ {
if (bestCharSegments[i]) if (bestContours.goodIndices[i])
allowedContours.push_back(bestContours[i]); allowedContours.push_back(bestContours.contours[i]);
} }
drawContours(img_contours, bestContours, drawContours(img_contours, bestContours.contours,
-1, // draw all contours -1, // draw all contours
cv::Scalar(255,0,0), // in blue cv::Scalar(255,0,0), // in blue
1); // with a thickness of 1 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)); //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) 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->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->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); //this->charArea = getCharSegmentsBetweenLines(bestThreshold, bestContours, this->linePolygon);
filterBetweenLines(bestThreshold, bestContours, bestHierarchy, linePolygon, bestCharSegments); filterBetweenLines(bestThreshold, bestContours, linePolygon);
this->charArea = getCharArea(); this->charArea = getCharArea();
@@ -188,17 +173,6 @@ void CharacterAnalysis::analyze()
this->thresholdsInverted = isPlateInverted(); this->thresholdsInverted = isPlateInverted();
} }
int CharacterAnalysis::getGoodIndicesCount(vector<bool> 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++) for (uint i = 0; i < bestContours.size(); i++)
{ {
if (bestCharSegments[i] == false) if (bestContours.goodIndices[i] == false)
continue; continue;
drawContours(charMask, bestContours, drawContours(charMask, bestContours.contours,
i, // draw this contour i, // draw this contour
cv::Scalar(255,255,255), // in cv::Scalar(255,255,255), // in
CV_FILLED, CV_FILLED,
8, 8,
bestHierarchy, bestContours.hierarchy,
1 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 // 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<Point> CharacterAnalysis::getBestVotedLines(Mat img, vector<vector<Point> > contours, vector<bool> goodIndices) vector<Point> CharacterAnalysis::getBestVotedLines(Mat img, TextContours textContours)
{ {
//if (this->debug) //if (this->debug)
// cout << "CharacterAnalysis::getBestVotedLines" << endl; // cout << "CharacterAnalysis::getBestVotedLines" << endl;
@@ -235,10 +209,10 @@ vector<Point> CharacterAnalysis::getBestVotedLines(Mat img, vector<vector<Point>
vector<Rect> charRegions; vector<Rect> charRegions;
for (uint i = 0; i < contours.size(); i++) for (uint i = 0; i < textContours.size(); i++)
{ {
if (goodIndices[i]) if (textContours.goodIndices[i])
charRegions.push_back(boundingRect(contours[i])); charRegions.push_back(boundingRect(textContours.contours[i]));
} }
// Find the best fit line segment that is parallel with the most char segments // Find the best fit line segment that is parallel with the most char segments
@@ -308,7 +282,6 @@ vector<Point> CharacterAnalysis::getBestVotedLines(Mat img, vector<vector<Point>
//cv::line(tempImg, Point(x1, y1), Point(x2, y2), Scalar(0, 0, 255)); //cv::line(tempImg, Point(x1, y1), Point(x2, y2), Scalar(0, 0, 255));
bottomLines.push_back(LineSegment(x1, y1, x2, y2)); bottomLines.push_back(LineSegment(x1, y1, x2, y2));
//drawAndWait(&tempImg);
} }
} }
@@ -382,65 +355,66 @@ vector<Point> CharacterAnalysis::getBestVotedLines(Mat img, vector<vector<Point>
return bestStripe; return bestStripe;
} }
vector<bool> CharacterAnalysis::filter(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy) void CharacterAnalysis::filter(Mat img, TextContours& textContours)
{ {
static int STARTING_MIN_HEIGHT = round (((float) img.rows) * config->charAnalysisMinPercent); 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 STARTING_MAX_HEIGHT = round (((float) img.rows) * (config->charAnalysisMinPercent + config->charAnalysisHeightRange));
static int HEIGHT_STEP = round (((float) img.rows) * config->charAnalysisHeightStepSize); static int HEIGHT_STEP = round (((float) img.rows) * config->charAnalysisHeightStepSize);
static int NUM_STEPS = config->charAnalysisNumSteps; static int NUM_STEPS = config->charAnalysisNumSteps;
vector<bool> charSegments;
int bestFitScore = -1; int bestFitScore = -1;
vector<bool> bestIndices;
for (int i = 0; i < NUM_STEPS; i++) for (int i = 0; i < NUM_STEPS; i++)
{ {
int goodIndicesCount;
//vector<bool> goodIndices(contours.size());
for (uint z = 0; z < textContours.size(); z++) textContours.goodIndices[z] = true;
vector<bool> goodIndices(contours.size()); this->filterByBoxSize(textContours, STARTING_MIN_HEIGHT + (i * HEIGHT_STEP), STARTING_MAX_HEIGHT + (i * HEIGHT_STEP));
for (uint z = 0; z < goodIndices.size(); z++) goodIndices[z] = true;
goodIndices = this->filterByBoxSize(contours, goodIndices, 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...
goodIndicesCount = getGoodIndicesCount(goodIndices);
if ( goodIndicesCount == 0 || goodIndicesCount <= bestFitScore) // Don't bother doing more filtering if we already lost...
continue; continue;
goodIndices = this->filterContourHoles(contours, hierarchy, goodIndices);
this->filterContourHoles(textContours);
goodIndicesCount = getGoodIndicesCount(goodIndices); goodIndices = textContours.getGoodIndicesCount();
if ( goodIndicesCount == 0 || goodIndicesCount <= bestFitScore) // Don't bother doing more filtering if we already lost... if ( goodIndices == 0 || goodIndices <= bestFitScore) // Don't bother doing more filtering if we already lost...
continue; continue;
//goodIndices = this->filterByParentContour( contours, hierarchy, goodIndices);
vector<Point> lines = getBestVotedLines(img, contours, goodIndices); vector<Point> lines = getBestVotedLines(img, textContours);
goodIndices = this->filterBetweenLines(img, contours, hierarchy, lines, goodIndices); this->filterBetweenLines(img, textContours, lines);
int segmentCount = getGoodIndicesCount(goodIndices); int segmentCount = textContours.getGoodIndicesCount();
if (segmentCount > bestFitScore) if (segmentCount > bestFitScore)
{ {
bestFitScore = segmentCount; 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 // Goes through the contours for the plate and picks out possible char segments based on min/max height
vector<bool> CharacterAnalysis::filterByBoxSize(vector< vector< Point> > contours, vector<bool> goodIndices, int minHeightPx, int maxHeightPx) void CharacterAnalysis::filterByBoxSize(TextContours& textContours, int minHeightPx, int maxHeightPx)
{ {
float idealAspect=config->charWidthMM / config->charHeightMM; float idealAspect=config->charWidthMM / config->charHeightMM;
float aspecttolerance=0.25; float aspecttolerance=0.25;
vector<bool> 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; continue;
textContours.goodIndices[i] = false; // Set it to not included unless it proves valid
//Create bounding rect of object //Create bounding rect of object
Rect mr= boundingRect(contours[i]); Rect mr= boundingRect(textContours.contours[i]);
float minWidth = mr.height * 0.2; float minWidth = mr.height * 0.2;
//Crop image //Crop image
@@ -450,27 +424,25 @@ vector<bool> CharacterAnalysis::filterByBoxSize(vector< vector< Point> > contour
float charAspect= (float)mr.width/(float)mr.height; float charAspect= (float)mr.width/(float)mr.height;
if (abs(charAspect - idealAspect) < aspecttolerance) 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<bool> 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; 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 // this contour is a child of an already identified contour. REMOVE it
if (this->config->debugCharAnalysis) if (this->config->debugCharAnalysis)
@@ -480,31 +452,29 @@ vector< bool > CharacterAnalysis::filterContourHoles(vector< vector< Point > > c
} }
else 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 // 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 // returns a vector of indices corresponding to valid contours
vector<bool> CharacterAnalysis::filterByParentContour( vector< vector< Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices) void CharacterAnalysis::filterByParentContour( TextContours& textContours)
{ {
vector<bool> includedIndices(contours.size());
for (uint j = 0; j < contours.size(); j++)
includedIndices[j] = false;
vector<int> parentIDs; vector<int> parentIDs;
vector<int> votes; vector<int> 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; continue;
textContours.goodIndices[i] = false; // Set it to not included unless it proves
int voteIndex = -1; int voteIndex = -1;
int parentID = hierarchy[i][3]; int parentID = textContours.hierarchy[i][3];
// check if parentID is already in the lsit // check if parentID is already in the lsit
for (uint j = 0; j < parentIDs.size(); j++) for (uint j = 0; j < parentIDs.size(); j++)
{ {
@@ -540,35 +510,30 @@ vector<bool> CharacterAnalysis::filterByParentContour( vector< vector< Point> >
} }
// Now filter out all the contours with a different parent ID (assuming the totalVotes > 2) // 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; continue;
if (totalVotes <= 2) 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<bool> CharacterAnalysis::filterBetweenLines(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<Point> outerPolygon, vector<bool> goodIndices) void CharacterAnalysis::filterBetweenLines(Mat img, TextContours& textContours, vector<Point> outerPolygon )
{ {
static float MIN_AREA_PERCENT_WITHIN_LINES = 0.88; static float MIN_AREA_PERCENT_WITHIN_LINES = 0.88;
static float MAX_DISTANCE_PERCENT_FROM_LINES = 0.15; static float MAX_DISTANCE_PERCENT_FROM_LINES = 0.15;
vector<bool> includedIndices(contours.size());
for (uint j = 0; j < contours.size(); j++)
includedIndices[j] = false;
if (outerPolygon.size() == 0) if (outerPolygon.size() == 0)
return includedIndices; return;
vector<Point> validPoints; vector<Point> validPoints;
@@ -587,19 +552,21 @@ vector<bool> CharacterAnalysis::filterBetweenLines(Mat img, vector<vector<Point>
fillConvexPoly(outerMask, outerPolygon.data(), outerPolygon.size(), Scalar(255,255,255)); 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 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; continue;
textContours.goodIndices[i] = false; // Set it to not included unless it proves
innerArea.setTo(Scalar(0,0,0)); innerArea.setTo(Scalar(0,0,0));
drawContours(innerArea, contours, drawContours(innerArea, textContours.contours,
i, // draw this contour i, // draw this contour
cv::Scalar(255,255,255), // in cv::Scalar(255,255,255), // in
CV_FILLED, CV_FILLED,
8, 8,
hierarchy, textContours.hierarchy,
0 0
); );
@@ -610,7 +577,7 @@ vector<bool> CharacterAnalysis::filterBetweenLines(Mat img, vector<vector<Point>
CV_RETR_EXTERNAL, // retrieve the external contours CV_RETR_EXTERNAL, // retrieve the external contours
CV_CHAIN_APPROX_SIMPLE ); // all pixels of each contours ); CV_CHAIN_APPROX_SIMPLE ); // all pixels of each contours );
double totalArea = contourArea(contours[i]); double totalArea = contourArea(textContours.contours[i]);
double areaBetweenLines = 0; double areaBetweenLines = 0;
for (uint tempContourIdx = 0; tempContourIdx < tempContours.size(); tempContourIdx++) for (uint tempContourIdx = 0; tempContourIdx < tempContours.size(); tempContourIdx++)
@@ -635,50 +602,46 @@ vector<bool> CharacterAnalysis::filterBetweenLines(Mat img, vector<vector<Point>
int highPointValue = 999999999; int highPointValue = 999999999;
int lowPointIndex = 0; int lowPointIndex = 0;
int lowPointValue = 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; 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; lowPointIndex = cidx;
lowPointValue = contours[i][cidx].y; lowPointValue = textContours.contours[i][cidx].y;
} }
} }
// Get the absolute distance from the top and bottom lines // Get the absolute distance from the top and bottom lines
Point closestTopPoint = topLine.closestPointOnSegmentTo(contours[i][highPointIndex]); Point closestTopPoint = topLine.closestPointOnSegmentTo(textContours.contours[i][highPointIndex]);
Point closestBottomPoint = bottomLine.closestPointOnSegmentTo(contours[i][lowPointIndex]); Point closestBottomPoint = bottomLine.closestPointOnSegmentTo(textContours.contours[i][lowPointIndex]);
float absTopDistance = distanceBetweenPoints(closestTopPoint, contours[i][highPointIndex]); float absTopDistance = distanceBetweenPoints(closestTopPoint, textContours.contours[i][highPointIndex]);
float absBottomDistance = distanceBetweenPoints(closestBottomPoint, contours[i][lowPointIndex]); float absBottomDistance = distanceBetweenPoints(closestBottomPoint, textContours.contours[i][lowPointIndex]);
float maxDistance = lineHeight * MAX_DISTANCE_PERCENT_FROM_LINES; float maxDistance = lineHeight * MAX_DISTANCE_PERCENT_FROM_LINES;
if (absTopDistance < maxDistance && absBottomDistance < maxDistance) 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_LEFT_AFTER_MASK = 0.1;
float MINIMUM_PERCENT_OF_CHARS_INSIDE_PLATE_MASK = 0.6; float MINIMUM_PERCENT_OF_CHARS_INSIDE_PLATE_MASK = 0.6;
if (this->pipeline_data->hasPlateBorder == false) if (this->pipeline_data->hasPlateBorder == false)
return goodIndices; return;
vector<bool> passingIndices;
for (uint i = 0; i < goodIndices.size(); i++)
passingIndices.push_back(false);
cv::Mat plateMask = pipeline_data->plateBorderMask; cv::Mat plateMask = pipeline_data->plateBorderMask;
@@ -688,14 +651,18 @@ std::vector< bool > CharacterAnalysis::filterByOuterMask(vector< vector< Point >
int charsInsideMask = 0; int charsInsideMask = 0;
int totalChars = 0; int totalChars = 0;
for (uint i=0; i < goodIndices.size(); i++) vector<bool> 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; continue;
totalChars++; 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); bitwise_and(tempFullContour, plateMask, tempMaskedContour);
float beforeMaskWhiteness = mean(tempFullContour)[0]; 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) if (afterMaskWhiteness / beforeMaskWhiteness > MINIMUM_PERCENT_LEFT_AFTER_MASK)
{ {
charsInsideMask++; charsInsideMask++;
passingIndices[i] = true; textContours.goodIndices[i] = true;
} }
} }
if (totalChars == 0) 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) // 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. // then don't use this to filter.
float percentCharsInsideMask = ((float) charsInsideMask) / ((float) totalChars); float percentCharsInsideMask = ((float) charsInsideMask) / ((float) totalChars);
if (percentCharsInsideMask < MINIMUM_PERCENT_OF_CHARS_INSIDE_PLATE_MASK) if (percentCharsInsideMask < MINIMUM_PERCENT_OF_CHARS_INSIDE_PLATE_MASK)
return goodIndices; {
textContours.goodIndices = originalindices;
return;
}
return passingIndices;
} }
bool CharacterAnalysis::isPlateInverted() bool CharacterAnalysis::isPlateInverted()
@@ -771,15 +743,15 @@ vector<Point> CharacterAnalysis::getCharArea()
for (uint i = 0; i < bestContours.size(); i++) for (uint i = 0; i < bestContours.size(); i++)
{ {
if (bestCharSegments[i] == false) if (bestContours.goodIndices[i] == false)
continue; 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) if (bestContours.contours[i][z].x < leftX)
leftX = bestContours[i][z].x; leftX = bestContours.contours[i][z].x;
if (bestContours[i][z].x > rightX) if (bestContours.contours[i][z].x > rightX)
rightX = bestContours[i][z].x; rightX = bestContours.contours[i][z].x;
} }
} }

View File

@@ -24,6 +24,8 @@
#include "utility.h" #include "utility.h"
#include "config.h" #include "config.h"
#include "pipeline_data.h" #include "pipeline_data.h"
#include "textcontours.h"
#include "platemask.h"
class CharacterAnalysis class CharacterAnalysis
{ {
@@ -34,10 +36,12 @@ class CharacterAnalysis
cv::Mat bestThreshold; cv::Mat bestThreshold;
std::vector<std::vector<cv::Point> > bestContours;
std::vector<cv::Vec4i> bestHierarchy; TextContours bestContours;
std::vector<bool> bestCharSegments; //std::vector<std::vector<cv::Point> > bestContours;
int bestCharSegmentsCount; //std::vector<cv::Vec4i> bestHierarchy;
//std::vector<bool> bestCharSegments;
//int bestCharSegmentsCount;
LineSegment topLine; LineSegment topLine;
LineSegment bottomLine; LineSegment bottomLine;
@@ -52,9 +56,10 @@ class CharacterAnalysis
bool thresholdsInverted; bool thresholdsInverted;
bool isTwoLine; bool isTwoLine;
std::vector<std::vector<std::vector<cv::Point> > > allContours; std::vector<TextContours> allTextContours;
std::vector<std::vector<cv::Vec4i> > allHierarchy; //std::vector<std::vector<std::vector<cv::Point> > > allContours;
std::vector<std::vector<bool> > charSegments; //std::vector<std::vector<cv::Vec4i> > allHierarchy;
//std::vector<std::vector<bool> > charSegments;
void analyze(); void analyze();
@@ -67,21 +72,19 @@ class CharacterAnalysis
cv::Mat findOuterBoxMask( ); cv::Mat findOuterBoxMask( );
bool isPlateInverted(); bool isPlateInverted();
std::vector<bool> filter(cv::Mat img, std::vector<std::vector<cv::Point> > contours, std::vector<cv::Vec4i> hierarchy); void filter(cv::Mat img, TextContours& textContours);
std::vector<bool> filterByBoxSize(std::vector<std::vector<cv::Point> > contours, std::vector<bool> goodIndices, int minHeightPx, int maxHeightPx); void filterByBoxSize(TextContours& textContours, int minHeightPx, int maxHeightPx);
std::vector<bool> filterByParentContour( std::vector< std::vector<cv::Point> > contours, std::vector<cv::Vec4i> hierarchy, std::vector<bool> goodIndices); void filterByParentContour( TextContours& textContours );
std::vector<bool> filterContourHoles(std::vector<std::vector<cv::Point> > contours, std::vector<cv::Vec4i> hierarchy, std::vector<bool> goodIndices); void filterContourHoles(TextContours& textContours);
std::vector<bool> filterByOuterMask(std::vector<std::vector<cv::Point> > contours, std::vector<cv::Vec4i> hierarchy, std::vector<bool> goodIndices); void filterByOuterMask(TextContours& textContours);
std::vector<cv::Point> getCharArea(); std::vector<cv::Point> getCharArea();
std::vector<cv::Point> getBestVotedLines(cv::Mat img, std::vector<std::vector<cv::Point> > contours, std::vector<bool> goodIndices); std::vector<cv::Point> getBestVotedLines(cv::Mat img, TextContours textContours);
//vector<Point> getCharSegmentsBetweenLines(Mat img, vector<vector<Point> > contours, vector<Point> outerPolygon); void filterBetweenLines(cv::Mat img, TextContours& textContours, std::vector<cv::Point> outerPolygon );
std::vector<bool> filterBetweenLines(cv::Mat img, std::vector<std::vector<cv::Point> > contours, std::vector<cv::Vec4i> hierarchy, std::vector<cv::Point> outerPolygon, std::vector<bool> goodIndices);
bool verifySize(cv::Mat r, float minHeightPx, float maxHeightPx); bool verifySize(cv::Mat r, float minHeightPx, float maxHeightPx);
int getGoodIndicesCount(std::vector<bool> goodIndices);
}; };

View File

@@ -36,9 +36,7 @@ cv::Mat PlateMask::getMask() {
return this->plateMask; return this->plateMask;
} }
void PlateMask::findOuterBoxMask(vector<vector<bool> > charSegments, void PlateMask::findOuterBoxMask( vector<TextContours > contours )
vector<vector<vector<Point> > > allContours,
vector<vector<Vec4i> > allHierarchy)
{ {
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. 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<vector<bool> > charSegments,
if (pipeline_data->config->debugCharAnalysis) if (pipeline_data->config->debugCharAnalysis)
cout << "CharacterAnalysis::findOuterBoxMask" << endl; cout << "CharacterAnalysis::findOuterBoxMask" << endl;
for (uint imgIndex = 0; imgIndex < allContours.size(); imgIndex++) for (uint imgIndex = 0; imgIndex < contours.size(); imgIndex++)
{ {
//vector<bool> charContours = filter(thresholds[imgIndex], allContours[imgIndex], allHierarchy[imgIndex]); //vector<bool> charContours = filter(thresholds[imgIndex], allContours[imgIndex], allHierarchy[imgIndex]);
int charsRecognized = 0; int charsRecognized = 0;
int parentId = -1; int parentId = -1;
bool hasParent = false; 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 (contours[imgIndex].goodIndices[i]) charsRecognized++;
if (charSegments[imgIndex][i] && allHierarchy[imgIndex][i][3] != -1) if (contours[imgIndex].goodIndices[i] && contours[imgIndex].hierarchy[i][3] != -1)
{ {
parentId = allHierarchy[imgIndex][i][3]; parentId = contours[imgIndex].hierarchy[i][3];
hasParent = true; hasParent = true;
} }
} }
@@ -72,7 +70,7 @@ void PlateMask::findOuterBoxMask(vector<vector<bool> > charSegments,
if (hasParent) if (hasParent)
{ {
double boxArea = contourArea(allContours[imgIndex][parentId]); double boxArea = contourArea(contours[imgIndex].contours[parentId]);
if (boxArea < min_parent_area) if (boxArea < min_parent_area)
continue; continue;
@@ -96,13 +94,13 @@ void PlateMask::findOuterBoxMask(vector<vector<bool> > charSegments,
int longestChildIndex = -1; int longestChildIndex = -1;
double longestChildLength = 0; double longestChildLength = 0;
// Find the child with the longest permiter/arc length ( just for kicks) // 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) if (arclength > longestChildLength)
{ {
longestChildIndex = j; longestChildIndex = j;
@@ -115,12 +113,12 @@ void PlateMask::findOuterBoxMask(vector<vector<bool> > charSegments,
Mat mask = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U); Mat mask = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U);
// get rid of the outline by drawing a 1 pixel width black line // 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 winningParentId, // draw this contour
cv::Scalar(255,255,255), // in cv::Scalar(255,255,255), // in
CV_FILLED, CV_FILLED,
8, 8,
allHierarchy[winningIndex], contours[winningIndex].hierarchy,
0 0
); );
@@ -169,7 +167,7 @@ void PlateMask::findOuterBoxMask(vector<vector<bool> > charSegments,
cv::Scalar(255,255,255), // in cv::Scalar(255,255,255), // in
CV_FILLED, CV_FILLED,
8, 8,
allHierarchy[winningIndex], contours[winningIndex].hierarchy,
0 0
); );
} }

View File

@@ -22,6 +22,7 @@
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/imgproc/imgproc.hpp"
#include "pipeline_data.h" #include "pipeline_data.h"
#include "textcontours.h"
class PlateMask { class PlateMask {
public: public:
@@ -32,7 +33,7 @@ public:
cv::Mat getMask(); cv::Mat getMask();
void findOuterBoxMask(std::vector<std::vector<bool> > charSegments, std::vector<std::vector<std::vector<cv::Point> > > allContours, std::vector<std::vector<cv::Vec4i> > allHierarchy); void findOuterBoxMask(std::vector<TextContours > contours);
private: private:

View File

@@ -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<bool> TextContours::getIndicesCopy()
{
vector<bool> 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<bool> 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");
}
}

View File

@@ -0,0 +1,38 @@
/*
* File: textcontours.h
* Author: mhill
*
* Created on October 9, 2014, 7:40 PM
*/
#ifndef TEXTCONTOURS_H
#define TEXTCONTOURS_H
#include <vector>
#include "opencv2/imgproc/imgproc.hpp"
class TextContours {
public:
TextContours();
TextContours(cv::Mat threshold);
virtual ~TextContours();
void load(cv::Mat threshold);
std::vector<bool> goodIndices;
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
uint size();
int getGoodIndicesCount();
std::vector<bool> getIndicesCopy();
void setIndices(std::vector<bool> newIndices);
private:
};
#endif /* TEXTCONTOURS_H */