mirror of
https://github.com/kerberos-io/openalpr-base.git
synced 2025-10-06 04:26:52 +08:00
Refactored CharacterAnalysis code to use a class that wraps the raw Contour data
This commit is contained in:
@@ -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
|
||||||
|
@@ -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)
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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:
|
||||||
|
|
||||||
|
86
src/openalpr/textdetection/textcontours.cpp
Normal file
86
src/openalpr/textdetection/textcontours.cpp
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
38
src/openalpr/textdetection/textcontours.h
Normal file
38
src/openalpr/textdetection/textcontours.h
Normal 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 */
|
||||||
|
|
Reference in New Issue
Block a user