mirror of
https://github.com/kerberos-io/openalpr-base.git
synced 2025-10-06 07:37:00 +08:00
Wrapped OpenALPR library in "alpr" namespace. Resolves issue #60.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -29,46 +29,51 @@
|
||||
#include "platemask.h"
|
||||
#include "linefinder.h"
|
||||
|
||||
class CharacterAnalysis
|
||||
namespace alpr
|
||||
{
|
||||
|
||||
public:
|
||||
CharacterAnalysis(PipelineData* pipeline_data);
|
||||
virtual ~CharacterAnalysis();
|
||||
class CharacterAnalysis
|
||||
{
|
||||
|
||||
int confidence;
|
||||
public:
|
||||
CharacterAnalysis(PipelineData* pipeline_data);
|
||||
virtual ~CharacterAnalysis();
|
||||
|
||||
cv::Mat bestThreshold;
|
||||
|
||||
TextContours bestContours;
|
||||
int confidence;
|
||||
|
||||
cv::Mat bestThreshold;
|
||||
|
||||
TextContours bestContours;
|
||||
|
||||
|
||||
std::vector<TextContours> allTextContours;
|
||||
std::vector<TextContours> allTextContours;
|
||||
|
||||
void analyze();
|
||||
void analyze();
|
||||
|
||||
cv::Mat getCharacterMask();
|
||||
cv::Mat getCharacterMask();
|
||||
|
||||
private:
|
||||
PipelineData* pipeline_data;
|
||||
Config* config;
|
||||
private:
|
||||
PipelineData* pipeline_data;
|
||||
Config* config;
|
||||
|
||||
cv::Mat findOuterBoxMask( );
|
||||
cv::Mat findOuterBoxMask( );
|
||||
|
||||
bool isPlateInverted();
|
||||
void filter(cv::Mat img, TextContours& textContours);
|
||||
bool isPlateInverted();
|
||||
void filter(cv::Mat img, TextContours& textContours);
|
||||
|
||||
void filterByBoxSize(TextContours& textContours, int minHeightPx, int maxHeightPx);
|
||||
void filterByParentContour( TextContours& textContours );
|
||||
void filterContourHoles(TextContours& textContours);
|
||||
void filterByOuterMask(TextContours& textContours);
|
||||
void filterByBoxSize(TextContours& textContours, int minHeightPx, int maxHeightPx);
|
||||
void filterByParentContour( TextContours& textContours );
|
||||
void filterContourHoles(TextContours& textContours);
|
||||
void filterByOuterMask(TextContours& textContours);
|
||||
|
||||
std::vector<cv::Point> getCharArea(LineSegment topLine, LineSegment bottomLine);
|
||||
void filterBetweenLines(cv::Mat img, TextContours& textContours, std::vector<TextLine> textLines );
|
||||
std::vector<cv::Point> getCharArea(LineSegment topLine, LineSegment bottomLine);
|
||||
void filterBetweenLines(cv::Mat img, TextContours& textContours, std::vector<TextLine> textLines );
|
||||
|
||||
bool verifySize(cv::Mat r, float minHeightPx, float maxHeightPx);
|
||||
bool verifySize(cv::Mat r, float minHeightPx, float maxHeightPx);
|
||||
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OPENALPR_CHARACTERANALYSIS_H
|
||||
|
@@ -26,228 +26,233 @@
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
LineFinder::LineFinder(PipelineData* pipeline_data) {
|
||||
this->pipeline_data = pipeline_data;
|
||||
}
|
||||
|
||||
LineFinder::~LineFinder() {
|
||||
}
|
||||
|
||||
vector<vector<Point> > LineFinder::findLines(Mat image, const TextContours contours)
|
||||
namespace alpr
|
||||
{
|
||||
const float MIN_AREA_TO_IGNORE = 0.65;
|
||||
|
||||
vector<vector<Point> > linesFound;
|
||||
|
||||
cvtColor(image, image, CV_GRAY2BGR);
|
||||
|
||||
vector<CharPointInfo> charPoints;
|
||||
|
||||
for (uint i = 0; i < contours.contours.size(); i++)
|
||||
{
|
||||
if (contours.goodIndices[i] == false)
|
||||
continue;
|
||||
|
||||
charPoints.push_back( CharPointInfo(contours.contours[i], i) );
|
||||
|
||||
LineFinder::LineFinder(PipelineData* pipeline_data) {
|
||||
this->pipeline_data = pipeline_data;
|
||||
}
|
||||
|
||||
vector<Point> bestLine = getBestLine(contours, charPoints);
|
||||
|
||||
if (bestLine.size() > 0)
|
||||
linesFound.push_back(bestLine);
|
||||
|
||||
if (pipeline_data->isMultiline)
|
||||
|
||||
LineFinder::~LineFinder() {
|
||||
}
|
||||
|
||||
vector<vector<Point> > LineFinder::findLines(Mat image, const TextContours contours)
|
||||
{
|
||||
// we have a two-line plate. Find the next best line, removing the tops/bottoms from before.
|
||||
// Create a mask from the bestLine area, and remove all contours with tops that fall inside of it.
|
||||
|
||||
vector<CharPointInfo> remainingPoints;
|
||||
const float MIN_AREA_TO_IGNORE = 0.65;
|
||||
|
||||
vector<vector<Point> > linesFound;
|
||||
|
||||
cvtColor(image, image, CV_GRAY2BGR);
|
||||
|
||||
vector<CharPointInfo> charPoints;
|
||||
|
||||
for (uint i = 0; i < contours.contours.size(); i++)
|
||||
{
|
||||
if (contours.goodIndices[i] == false)
|
||||
continue;
|
||||
|
||||
charPoints.push_back( CharPointInfo(contours.contours[i], i) );
|
||||
}
|
||||
|
||||
vector<Point> bestLine = getBestLine(contours, charPoints);
|
||||
|
||||
if (bestLine.size() > 0)
|
||||
linesFound.push_back(bestLine);
|
||||
|
||||
if (pipeline_data->isMultiline)
|
||||
{
|
||||
// we have a two-line plate. Find the next best line, removing the tops/bottoms from before.
|
||||
// Create a mask from the bestLine area, and remove all contours with tops that fall inside of it.
|
||||
|
||||
vector<CharPointInfo> remainingPoints;
|
||||
for (uint i = 0; i < charPoints.size(); i++)
|
||||
{
|
||||
Mat mask = Mat::zeros(Size(contours.width, contours.height), CV_8U);
|
||||
fillConvexPoly(mask, bestLine.data(), bestLine.size(), Scalar(255,255,255));
|
||||
|
||||
float percentInside = getContourAreaPercentInsideMask(mask, contours.contours, contours.hierarchy, charPoints[i].contourIndex);
|
||||
|
||||
if (percentInside < MIN_AREA_TO_IGNORE)
|
||||
{
|
||||
remainingPoints.push_back(charPoints[i]);
|
||||
}
|
||||
}
|
||||
|
||||
vector<Point> nextBestLine = getBestLine(contours, remainingPoints);
|
||||
|
||||
if (nextBestLine.size() > 0)
|
||||
linesFound.push_back(nextBestLine);
|
||||
}
|
||||
|
||||
|
||||
return linesFound;
|
||||
}
|
||||
|
||||
|
||||
// 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> LineFinder::getBestLine(const TextContours contours, vector<CharPointInfo> charPoints)
|
||||
{
|
||||
vector<Point> bestStripe;
|
||||
|
||||
// Find the best fit line segment that is parallel with the most char segments
|
||||
if (charPoints.size() <= 1)
|
||||
{
|
||||
// Maybe do something about this later, for now let's just ignore
|
||||
return bestStripe;
|
||||
}
|
||||
|
||||
|
||||
vector<int> charheights;
|
||||
for (uint i = 0; i < charPoints.size(); i++)
|
||||
charheights.push_back(charPoints[i].boundingBox.height);
|
||||
float medianCharHeight = median(charheights.data(), charheights.size());
|
||||
|
||||
|
||||
|
||||
vector<LineSegment> topLines;
|
||||
vector<LineSegment> bottomLines;
|
||||
// Iterate through each possible char and find all possible lines for the top and bottom of each char segment
|
||||
for (uint i = 0; i < charPoints.size() - 1; i++)
|
||||
{
|
||||
Mat mask = Mat::zeros(Size(contours.width, contours.height), CV_8U);
|
||||
fillConvexPoly(mask, bestLine.data(), bestLine.size(), Scalar(255,255,255));
|
||||
|
||||
float percentInside = getContourAreaPercentInsideMask(mask, contours.contours, contours.hierarchy, charPoints[i].contourIndex);
|
||||
|
||||
if (percentInside < MIN_AREA_TO_IGNORE)
|
||||
for (uint k = i+1; k < charPoints.size(); k++)
|
||||
{
|
||||
remainingPoints.push_back(charPoints[i]);
|
||||
|
||||
int leftCPIndex, rightCPIndex;
|
||||
if (charPoints[i].top.x < charPoints[k].top.x)
|
||||
{
|
||||
leftCPIndex = i;
|
||||
rightCPIndex = k;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftCPIndex = k;
|
||||
rightCPIndex = i;
|
||||
}
|
||||
|
||||
|
||||
LineSegment top(charPoints[leftCPIndex].top, charPoints[rightCPIndex].top);
|
||||
LineSegment bottom(charPoints[leftCPIndex].bottom, charPoints[rightCPIndex].bottom);
|
||||
|
||||
|
||||
// Only allow lines that have a sane angle
|
||||
// if (abs(top.angle) <= pipeline_data->config->maxPlateAngleDegrees &&
|
||||
// abs(bottom.angle) <= pipeline_data->config->maxPlateAngleDegrees)
|
||||
// {
|
||||
// topLines.push_back(top);
|
||||
// bottomLines.push_back(bottom);
|
||||
// }
|
||||
|
||||
LineSegment parallelBot = top.getParallelLine(medianCharHeight * -1);
|
||||
LineSegment parallelTop = bottom.getParallelLine(medianCharHeight);
|
||||
|
||||
// Only allow lines that have a sane angle
|
||||
if (abs(top.angle) <= pipeline_data->config->maxPlateAngleDegrees &&
|
||||
abs(parallelBot.angle) <= pipeline_data->config->maxPlateAngleDegrees)
|
||||
{
|
||||
topLines.push_back(top);
|
||||
bottomLines.push_back(parallelBot);
|
||||
}
|
||||
|
||||
// Only allow lines that have a sane angle
|
||||
if (abs(parallelTop.angle) <= pipeline_data->config->maxPlateAngleDegrees &&
|
||||
abs(bottom.angle) <= pipeline_data->config->maxPlateAngleDegrees)
|
||||
{
|
||||
topLines.push_back(parallelTop);
|
||||
bottomLines.push_back(bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<Point> nextBestLine = getBestLine(contours, remainingPoints);
|
||||
|
||||
if (nextBestLine.size() > 0)
|
||||
linesFound.push_back(nextBestLine);
|
||||
}
|
||||
|
||||
|
||||
return linesFound;
|
||||
}
|
||||
|
||||
int bestScoreIndex = 0;
|
||||
int bestScore = -1;
|
||||
int bestScoreDistance = -1; // Line segment distance is used as a tie breaker
|
||||
|
||||
// Now, among all possible lines, find the one that is the best fit
|
||||
for (uint i = 0; i < topLines.size(); i++)
|
||||
{
|
||||
float SCORING_MIN_THRESHOLD = 0.97;
|
||||
float SCORING_MAX_THRESHOLD = 1.03;
|
||||
|
||||
int curScore = 0;
|
||||
for (uint charidx = 0; charidx < charPoints.size(); charidx++)
|
||||
{
|
||||
float topYPos = topLines[i].getPointAt(charPoints[charidx].top.x);
|
||||
float botYPos = bottomLines[i].getPointAt(charPoints[charidx].bottom.x);
|
||||
|
||||
float minTop = charPoints[charidx].top.y * SCORING_MIN_THRESHOLD;
|
||||
float maxTop = charPoints[charidx].top.y * SCORING_MAX_THRESHOLD;
|
||||
float minBot = (charPoints[charidx].bottom.y) * SCORING_MIN_THRESHOLD;
|
||||
float maxBot = (charPoints[charidx].bottom.y) * SCORING_MAX_THRESHOLD;
|
||||
if ( (topYPos >= minTop && topYPos <= maxTop) &&
|
||||
(botYPos >= minBot && botYPos <= maxBot))
|
||||
{
|
||||
curScore++;
|
||||
}
|
||||
|
||||
//cout << "Slope: " << topslope << " yPos: " << topYPos << endl;
|
||||
//drawAndWait(&tempImg);
|
||||
}
|
||||
|
||||
// Tie goes to the one with longer line segments
|
||||
if ((curScore > bestScore) ||
|
||||
(curScore == bestScore && topLines[i].length > bestScoreDistance))
|
||||
{
|
||||
bestScore = curScore;
|
||||
bestScoreIndex = i;
|
||||
// Just use x distance for now
|
||||
bestScoreDistance = topLines[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestScore < 0)
|
||||
return bestStripe;
|
||||
|
||||
if (pipeline_data->config->debugCharAnalysis)
|
||||
{
|
||||
cout << "The winning score is: " << bestScore << endl;
|
||||
// Draw the winning line segment
|
||||
|
||||
Mat tempImg = Mat::zeros(Size(contours.width, contours.height), CV_8U);
|
||||
cvtColor(tempImg, tempImg, CV_GRAY2BGR);
|
||||
|
||||
cv::line(tempImg, topLines[bestScoreIndex].p1, topLines[bestScoreIndex].p2, Scalar(0, 0, 255), 2);
|
||||
cv::line(tempImg, bottomLines[bestScoreIndex].p1, bottomLines[bestScoreIndex].p2, Scalar(0, 0, 255), 2);
|
||||
|
||||
displayImage(pipeline_data->config, "Winning lines", tempImg);
|
||||
}
|
||||
|
||||
Point topLeft = Point(0, topLines[bestScoreIndex].getPointAt(0) );
|
||||
Point topRight = Point(contours.width, topLines[bestScoreIndex].getPointAt(contours.width));
|
||||
Point bottomRight = Point(contours.width, bottomLines[bestScoreIndex].getPointAt(contours.width));
|
||||
Point bottomLeft = Point(0, bottomLines[bestScoreIndex].getPointAt(0));
|
||||
|
||||
bestStripe.push_back(topLeft);
|
||||
bestStripe.push_back(topRight);
|
||||
bestStripe.push_back(bottomRight);
|
||||
bestStripe.push_back(bottomLeft);
|
||||
|
||||
|
||||
// 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> LineFinder::getBestLine(const TextContours contours, vector<CharPointInfo> charPoints)
|
||||
{
|
||||
vector<Point> bestStripe;
|
||||
|
||||
// Find the best fit line segment that is parallel with the most char segments
|
||||
if (charPoints.size() <= 1)
|
||||
{
|
||||
// Maybe do something about this later, for now let's just ignore
|
||||
return bestStripe;
|
||||
}
|
||||
|
||||
|
||||
vector<int> charheights;
|
||||
for (uint i = 0; i < charPoints.size(); i++)
|
||||
charheights.push_back(charPoints[i].boundingBox.height);
|
||||
float medianCharHeight = median(charheights.data(), charheights.size());
|
||||
|
||||
CharPointInfo::CharPointInfo(vector<Point> contour, int index) {
|
||||
|
||||
|
||||
this->contourIndex = index;
|
||||
|
||||
vector<LineSegment> topLines;
|
||||
vector<LineSegment> bottomLines;
|
||||
// Iterate through each possible char and find all possible lines for the top and bottom of each char segment
|
||||
for (uint i = 0; i < charPoints.size() - 1; i++)
|
||||
{
|
||||
for (uint k = i+1; k < charPoints.size(); k++)
|
||||
{
|
||||
|
||||
int leftCPIndex, rightCPIndex;
|
||||
if (charPoints[i].top.x < charPoints[k].top.x)
|
||||
{
|
||||
leftCPIndex = i;
|
||||
rightCPIndex = k;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftCPIndex = k;
|
||||
rightCPIndex = i;
|
||||
}
|
||||
this->boundingBox = cv::boundingRect( Mat(contour) );
|
||||
|
||||
|
||||
LineSegment top(charPoints[leftCPIndex].top, charPoints[rightCPIndex].top);
|
||||
LineSegment bottom(charPoints[leftCPIndex].bottom, charPoints[rightCPIndex].bottom);
|
||||
int x = boundingBox.x + (boundingBox.width / 2);
|
||||
int y = boundingBox.y;
|
||||
|
||||
this->top = Point(x, y);
|
||||
|
||||
// Only allow lines that have a sane angle
|
||||
// if (abs(top.angle) <= pipeline_data->config->maxPlateAngleDegrees &&
|
||||
// abs(bottom.angle) <= pipeline_data->config->maxPlateAngleDegrees)
|
||||
// {
|
||||
// topLines.push_back(top);
|
||||
// bottomLines.push_back(bottom);
|
||||
// }
|
||||
x = boundingBox.x + (boundingBox.width / 2);
|
||||
y = boundingBox.y + boundingBox.height;
|
||||
|
||||
LineSegment parallelBot = top.getParallelLine(medianCharHeight * -1);
|
||||
LineSegment parallelTop = bottom.getParallelLine(medianCharHeight);
|
||||
this->bottom = Point(x,y);
|
||||
|
||||
// Only allow lines that have a sane angle
|
||||
if (abs(top.angle) <= pipeline_data->config->maxPlateAngleDegrees &&
|
||||
abs(parallelBot.angle) <= pipeline_data->config->maxPlateAngleDegrees)
|
||||
{
|
||||
topLines.push_back(top);
|
||||
bottomLines.push_back(parallelBot);
|
||||
}
|
||||
|
||||
// Only allow lines that have a sane angle
|
||||
if (abs(parallelTop.angle) <= pipeline_data->config->maxPlateAngleDegrees &&
|
||||
abs(bottom.angle) <= pipeline_data->config->maxPlateAngleDegrees)
|
||||
{
|
||||
topLines.push_back(parallelTop);
|
||||
bottomLines.push_back(bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int bestScoreIndex = 0;
|
||||
int bestScore = -1;
|
||||
int bestScoreDistance = -1; // Line segment distance is used as a tie breaker
|
||||
|
||||
// Now, among all possible lines, find the one that is the best fit
|
||||
for (uint i = 0; i < topLines.size(); i++)
|
||||
{
|
||||
float SCORING_MIN_THRESHOLD = 0.97;
|
||||
float SCORING_MAX_THRESHOLD = 1.03;
|
||||
|
||||
int curScore = 0;
|
||||
for (uint charidx = 0; charidx < charPoints.size(); charidx++)
|
||||
{
|
||||
float topYPos = topLines[i].getPointAt(charPoints[charidx].top.x);
|
||||
float botYPos = bottomLines[i].getPointAt(charPoints[charidx].bottom.x);
|
||||
|
||||
float minTop = charPoints[charidx].top.y * SCORING_MIN_THRESHOLD;
|
||||
float maxTop = charPoints[charidx].top.y * SCORING_MAX_THRESHOLD;
|
||||
float minBot = (charPoints[charidx].bottom.y) * SCORING_MIN_THRESHOLD;
|
||||
float maxBot = (charPoints[charidx].bottom.y) * SCORING_MAX_THRESHOLD;
|
||||
if ( (topYPos >= minTop && topYPos <= maxTop) &&
|
||||
(botYPos >= minBot && botYPos <= maxBot))
|
||||
{
|
||||
curScore++;
|
||||
}
|
||||
|
||||
//cout << "Slope: " << topslope << " yPos: " << topYPos << endl;
|
||||
//drawAndWait(&tempImg);
|
||||
}
|
||||
|
||||
// Tie goes to the one with longer line segments
|
||||
if ((curScore > bestScore) ||
|
||||
(curScore == bestScore && topLines[i].length > bestScoreDistance))
|
||||
{
|
||||
bestScore = curScore;
|
||||
bestScoreIndex = i;
|
||||
// Just use x distance for now
|
||||
bestScoreDistance = topLines[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestScore < 0)
|
||||
return bestStripe;
|
||||
|
||||
if (pipeline_data->config->debugCharAnalysis)
|
||||
{
|
||||
cout << "The winning score is: " << bestScore << endl;
|
||||
// Draw the winning line segment
|
||||
|
||||
Mat tempImg = Mat::zeros(Size(contours.width, contours.height), CV_8U);
|
||||
cvtColor(tempImg, tempImg, CV_GRAY2BGR);
|
||||
|
||||
cv::line(tempImg, topLines[bestScoreIndex].p1, topLines[bestScoreIndex].p2, Scalar(0, 0, 255), 2);
|
||||
cv::line(tempImg, bottomLines[bestScoreIndex].p1, bottomLines[bestScoreIndex].p2, Scalar(0, 0, 255), 2);
|
||||
|
||||
displayImage(pipeline_data->config, "Winning lines", tempImg);
|
||||
}
|
||||
|
||||
Point topLeft = Point(0, topLines[bestScoreIndex].getPointAt(0) );
|
||||
Point topRight = Point(contours.width, topLines[bestScoreIndex].getPointAt(contours.width));
|
||||
Point bottomRight = Point(contours.width, bottomLines[bestScoreIndex].getPointAt(contours.width));
|
||||
Point bottomLeft = Point(0, bottomLines[bestScoreIndex].getPointAt(0));
|
||||
|
||||
bestStripe.push_back(topLeft);
|
||||
bestStripe.push_back(topRight);
|
||||
bestStripe.push_back(bottomRight);
|
||||
bestStripe.push_back(bottomLeft);
|
||||
|
||||
|
||||
return bestStripe;
|
||||
}
|
||||
|
||||
CharPointInfo::CharPointInfo(vector<Point> contour, int index) {
|
||||
|
||||
|
||||
this->contourIndex = index;
|
||||
|
||||
this->boundingBox = cv::boundingRect( Mat(contour) );
|
||||
|
||||
|
||||
int x = boundingBox.x + (boundingBox.width / 2);
|
||||
int y = boundingBox.y;
|
||||
|
||||
this->top = Point(x, y);
|
||||
|
||||
x = boundingBox.x + (boundingBox.width / 2);
|
||||
y = boundingBox.y + boundingBox.height;
|
||||
|
||||
this->bottom = Point(x,y);
|
||||
|
||||
}
|
||||
}
|
@@ -27,29 +27,34 @@
|
||||
#include "textline.h"
|
||||
#include "pipeline_data.h"
|
||||
|
||||
class CharPointInfo
|
||||
namespace alpr
|
||||
{
|
||||
public:
|
||||
CharPointInfo(std::vector<cv::Point> contour, int index);
|
||||
|
||||
cv::Rect boundingBox;
|
||||
cv::Point top;
|
||||
cv::Point bottom;
|
||||
int contourIndex;
|
||||
|
||||
};
|
||||
|
||||
class LineFinder {
|
||||
public:
|
||||
LineFinder(PipelineData* pipeline_data);
|
||||
virtual ~LineFinder();
|
||||
|
||||
std::vector<std::vector<cv::Point> > findLines(cv::Mat image, const TextContours contours);
|
||||
private:
|
||||
PipelineData* pipeline_data;
|
||||
|
||||
std::vector<cv::Point> getBestLine(const TextContours contours, std::vector<CharPointInfo> charPoints);
|
||||
};
|
||||
class CharPointInfo
|
||||
{
|
||||
public:
|
||||
CharPointInfo(std::vector<cv::Point> contour, int index);
|
||||
|
||||
cv::Rect boundingBox;
|
||||
cv::Point top;
|
||||
cv::Point bottom;
|
||||
int contourIndex;
|
||||
|
||||
};
|
||||
|
||||
class LineFinder {
|
||||
public:
|
||||
LineFinder(PipelineData* pipeline_data);
|
||||
virtual ~LineFinder();
|
||||
|
||||
std::vector<std::vector<cv::Point> > findLines(cv::Mat image, const TextContours contours);
|
||||
private:
|
||||
PipelineData* pipeline_data;
|
||||
|
||||
std::vector<cv::Point> getBestLine(const TextContours contours, std::vector<CharPointInfo> charPoints);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* OPENALPR_LINEFINDER_H */
|
||||
|
||||
|
@@ -22,178 +22,183 @@
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
PlateMask::PlateMask(PipelineData* pipeline_data) {
|
||||
this->pipeline_data = pipeline_data;
|
||||
this->hasPlateMask = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
PlateMask::~PlateMask() {
|
||||
}
|
||||
|
||||
cv::Mat PlateMask::getMask() {
|
||||
return this->plateMask;
|
||||
}
|
||||
|
||||
void PlateMask::findOuterBoxMask( vector<TextContours > contours )
|
||||
namespace alpr
|
||||
{
|
||||
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.
|
||||
|
||||
int winningIndex = -1;
|
||||
int winningParentId = -1;
|
||||
int bestCharCount = 0;
|
||||
double lowestArea = 99999999999999;
|
||||
PlateMask::PlateMask(PipelineData* pipeline_data) {
|
||||
this->pipeline_data = pipeline_data;
|
||||
this->hasPlateMask = false;
|
||||
|
||||
if (pipeline_data->config->debugCharAnalysis)
|
||||
cout << "CharacterAnalysis::findOuterBoxMask" << endl;
|
||||
|
||||
for (uint imgIndex = 0; imgIndex < contours.size(); imgIndex++)
|
||||
{
|
||||
//vector<bool> charContours = filter(thresholds[imgIndex], allContours[imgIndex], allHierarchy[imgIndex]);
|
||||
|
||||
int charsRecognized = 0;
|
||||
int parentId = -1;
|
||||
bool hasParent = false;
|
||||
for (uint i = 0; i < contours[imgIndex].goodIndices.size(); i++)
|
||||
{
|
||||
if (contours[imgIndex].goodIndices[i]) charsRecognized++;
|
||||
if (contours[imgIndex].goodIndices[i] && contours[imgIndex].hierarchy[i][3] != -1)
|
||||
{
|
||||
parentId = contours[imgIndex].hierarchy[i][3];
|
||||
hasParent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (charsRecognized == 0)
|
||||
continue;
|
||||
|
||||
if (hasParent)
|
||||
{
|
||||
double boxArea = contourArea(contours[imgIndex].contours[parentId]);
|
||||
if (boxArea < min_parent_area)
|
||||
continue;
|
||||
|
||||
if ((charsRecognized > bestCharCount) ||
|
||||
(charsRecognized == bestCharCount && boxArea < lowestArea))
|
||||
//(boxArea < lowestArea)
|
||||
{
|
||||
bestCharCount = charsRecognized;
|
||||
winningIndex = imgIndex;
|
||||
winningParentId = parentId;
|
||||
lowestArea = boxArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pipeline_data->config->debugCharAnalysis)
|
||||
cout << "Winning image index (findOuterBoxMask) is: " << winningIndex << endl;
|
||||
|
||||
if (winningIndex != -1 && bestCharCount >= 3)
|
||||
PlateMask::~PlateMask() {
|
||||
}
|
||||
|
||||
cv::Mat PlateMask::getMask() {
|
||||
return this->plateMask;
|
||||
}
|
||||
|
||||
void PlateMask::findOuterBoxMask( vector<TextContours > contours )
|
||||
{
|
||||
int longestChildIndex = -1;
|
||||
double longestChildLength = 0;
|
||||
// Find the child with the longest permiter/arc length ( just for kicks)
|
||||
for (uint i = 0; i < contours[winningIndex].size(); i++)
|
||||
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.
|
||||
|
||||
int winningIndex = -1;
|
||||
int winningParentId = -1;
|
||||
int bestCharCount = 0;
|
||||
double lowestArea = 99999999999999;
|
||||
|
||||
if (pipeline_data->config->debugCharAnalysis)
|
||||
cout << "CharacterAnalysis::findOuterBoxMask" << endl;
|
||||
|
||||
for (uint imgIndex = 0; imgIndex < contours.size(); imgIndex++)
|
||||
{
|
||||
for (uint j = 0; j < contours[winningIndex].size(); j++)
|
||||
//vector<bool> charContours = filter(thresholds[imgIndex], allContours[imgIndex], allHierarchy[imgIndex]);
|
||||
|
||||
int charsRecognized = 0;
|
||||
int parentId = -1;
|
||||
bool hasParent = false;
|
||||
for (uint i = 0; i < contours[imgIndex].goodIndices.size(); i++)
|
||||
{
|
||||
if (contours[winningIndex].hierarchy[j][3] == winningParentId)
|
||||
if (contours[imgIndex].goodIndices[i]) charsRecognized++;
|
||||
if (contours[imgIndex].goodIndices[i] && contours[imgIndex].hierarchy[i][3] != -1)
|
||||
{
|
||||
double arclength = arcLength(contours[winningIndex].contours[j], false);
|
||||
if (arclength > longestChildLength)
|
||||
{
|
||||
longestChildIndex = j;
|
||||
longestChildLength = arclength;
|
||||
}
|
||||
parentId = contours[imgIndex].hierarchy[i][3];
|
||||
hasParent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (charsRecognized == 0)
|
||||
continue;
|
||||
|
||||
if (hasParent)
|
||||
{
|
||||
double boxArea = contourArea(contours[imgIndex].contours[parentId]);
|
||||
if (boxArea < min_parent_area)
|
||||
continue;
|
||||
|
||||
if ((charsRecognized > bestCharCount) ||
|
||||
(charsRecognized == bestCharCount && boxArea < lowestArea))
|
||||
//(boxArea < lowestArea)
|
||||
{
|
||||
bestCharCount = charsRecognized;
|
||||
winningIndex = imgIndex;
|
||||
winningParentId = parentId;
|
||||
lowestArea = boxArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mat mask = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U);
|
||||
if (pipeline_data->config->debugCharAnalysis)
|
||||
cout << "Winning image index (findOuterBoxMask) is: " << winningIndex << endl;
|
||||
|
||||
// get rid of the outline by drawing a 1 pixel width black line
|
||||
drawContours(mask, contours[winningIndex].contours,
|
||||
winningParentId, // draw this contour
|
||||
cv::Scalar(255,255,255), // in
|
||||
CV_FILLED,
|
||||
8,
|
||||
contours[winningIndex].hierarchy,
|
||||
0
|
||||
);
|
||||
|
||||
// Morph Open the mask to get rid of any little connectors to non-plate portions
|
||||
int morph_elem = 2;
|
||||
int morph_size = 3;
|
||||
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||
|
||||
//morphologyEx( mask, mask, MORPH_CLOSE, element );
|
||||
morphologyEx( mask, mask, MORPH_OPEN, element );
|
||||
|
||||
//morph_size = 1;
|
||||
//element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||
//dilate(mask, mask, element);
|
||||
|
||||
// Drawing the edge black effectively erodes the image. This may clip off some extra junk from the edges.
|
||||
// We'll want to do the contour again and find the larges one so that we remove the clipped portion.
|
||||
|
||||
vector<vector<Point> > contoursSecondRound;
|
||||
|
||||
findContours(mask, contoursSecondRound, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
|
||||
int biggestContourIndex = -1;
|
||||
double largestArea = 0;
|
||||
for (uint c = 0; c < contoursSecondRound.size(); c++)
|
||||
if (winningIndex != -1 && bestCharCount >= 3)
|
||||
{
|
||||
double area = contourArea(contoursSecondRound[c]);
|
||||
if (area > largestArea)
|
||||
int longestChildIndex = -1;
|
||||
double longestChildLength = 0;
|
||||
// Find the child with the longest permiter/arc length ( just for kicks)
|
||||
for (uint i = 0; i < contours[winningIndex].size(); i++)
|
||||
{
|
||||
biggestContourIndex = c;
|
||||
largestArea = area;
|
||||
for (uint j = 0; j < contours[winningIndex].size(); j++)
|
||||
{
|
||||
if (contours[winningIndex].hierarchy[j][3] == winningParentId)
|
||||
{
|
||||
double arclength = arcLength(contours[winningIndex].contours[j], false);
|
||||
if (arclength > longestChildLength)
|
||||
{
|
||||
longestChildIndex = j;
|
||||
longestChildLength = arclength;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (biggestContourIndex != -1)
|
||||
{
|
||||
mask = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U);
|
||||
Mat mask = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U);
|
||||
|
||||
vector<Point> smoothedMaskPoints;
|
||||
approxPolyDP(contoursSecondRound[biggestContourIndex], smoothedMaskPoints, 2, true);
|
||||
|
||||
vector<vector<Point> > tempvec;
|
||||
tempvec.push_back(smoothedMaskPoints);
|
||||
//fillPoly(mask, smoothedMaskPoints.data(), smoothedMaskPoints, Scalar(255,255,255));
|
||||
drawContours(mask, tempvec,
|
||||
0, // draw this contour
|
||||
// get rid of the outline by drawing a 1 pixel width black line
|
||||
drawContours(mask, contours[winningIndex].contours,
|
||||
winningParentId, // draw this contour
|
||||
cv::Scalar(255,255,255), // in
|
||||
CV_FILLED,
|
||||
8,
|
||||
contours[winningIndex].hierarchy,
|
||||
0
|
||||
);
|
||||
|
||||
// Morph Open the mask to get rid of any little connectors to non-plate portions
|
||||
int morph_elem = 2;
|
||||
int morph_size = 3;
|
||||
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||
|
||||
//morphologyEx( mask, mask, MORPH_CLOSE, element );
|
||||
morphologyEx( mask, mask, MORPH_OPEN, element );
|
||||
|
||||
//morph_size = 1;
|
||||
//element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||
//dilate(mask, mask, element);
|
||||
|
||||
// Drawing the edge black effectively erodes the image. This may clip off some extra junk from the edges.
|
||||
// We'll want to do the contour again and find the larges one so that we remove the clipped portion.
|
||||
|
||||
vector<vector<Point> > contoursSecondRound;
|
||||
|
||||
findContours(mask, contoursSecondRound, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
|
||||
int biggestContourIndex = -1;
|
||||
double largestArea = 0;
|
||||
for (uint c = 0; c < contoursSecondRound.size(); c++)
|
||||
{
|
||||
double area = contourArea(contoursSecondRound[c]);
|
||||
if (area > largestArea)
|
||||
{
|
||||
biggestContourIndex = c;
|
||||
largestArea = area;
|
||||
}
|
||||
}
|
||||
|
||||
if (biggestContourIndex != -1)
|
||||
{
|
||||
mask = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U);
|
||||
|
||||
vector<Point> smoothedMaskPoints;
|
||||
approxPolyDP(contoursSecondRound[biggestContourIndex], smoothedMaskPoints, 2, true);
|
||||
|
||||
vector<vector<Point> > tempvec;
|
||||
tempvec.push_back(smoothedMaskPoints);
|
||||
//fillPoly(mask, smoothedMaskPoints.data(), smoothedMaskPoints, Scalar(255,255,255));
|
||||
drawContours(mask, tempvec,
|
||||
0, // draw this contour
|
||||
cv::Scalar(255,255,255), // in
|
||||
CV_FILLED,
|
||||
8,
|
||||
contours[winningIndex].hierarchy,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
if (pipeline_data->config->debugCharAnalysis)
|
||||
{
|
||||
vector<Mat> debugImgs;
|
||||
Mat debugImgMasked = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U);
|
||||
|
||||
pipeline_data->thresholds[winningIndex].copyTo(debugImgMasked, mask);
|
||||
|
||||
debugImgs.push_back(mask);
|
||||
debugImgs.push_back(pipeline_data->thresholds[winningIndex]);
|
||||
debugImgs.push_back(debugImgMasked);
|
||||
|
||||
Mat dashboard = drawImageDashboard(debugImgs, CV_8U, 1);
|
||||
displayImage(pipeline_data->config, "Winning outer box", dashboard);
|
||||
}
|
||||
|
||||
hasPlateMask = true;
|
||||
this->plateMask = mask;
|
||||
}
|
||||
|
||||
if (pipeline_data->config->debugCharAnalysis)
|
||||
{
|
||||
vector<Mat> debugImgs;
|
||||
Mat debugImgMasked = Mat::zeros(pipeline_data->thresholds[winningIndex].size(), CV_8U);
|
||||
hasPlateMask = false;
|
||||
Mat fullMask = Mat::zeros(pipeline_data->thresholds[0].size(), CV_8U);
|
||||
bitwise_not(fullMask, fullMask);
|
||||
|
||||
pipeline_data->thresholds[winningIndex].copyTo(debugImgMasked, mask);
|
||||
|
||||
debugImgs.push_back(mask);
|
||||
debugImgs.push_back(pipeline_data->thresholds[winningIndex]);
|
||||
debugImgs.push_back(debugImgMasked);
|
||||
|
||||
Mat dashboard = drawImageDashboard(debugImgs, CV_8U, 1);
|
||||
displayImage(pipeline_data->config, "Winning outer box", dashboard);
|
||||
}
|
||||
|
||||
hasPlateMask = true;
|
||||
this->plateMask = mask;
|
||||
this->plateMask = fullMask;
|
||||
}
|
||||
|
||||
hasPlateMask = false;
|
||||
Mat fullMask = Mat::zeros(pipeline_data->thresholds[0].size(), CV_8U);
|
||||
bitwise_not(fullMask, fullMask);
|
||||
|
||||
this->plateMask = fullMask;
|
||||
}
|
||||
}
|
@@ -24,24 +24,28 @@
|
||||
#include "pipeline_data.h"
|
||||
#include "textcontours.h"
|
||||
|
||||
class PlateMask {
|
||||
public:
|
||||
PlateMask(PipelineData* pipeline_data);
|
||||
virtual ~PlateMask();
|
||||
|
||||
bool hasPlateMask;
|
||||
|
||||
cv::Mat getMask();
|
||||
|
||||
void findOuterBoxMask(std::vector<TextContours > contours);
|
||||
|
||||
private:
|
||||
|
||||
PipelineData* pipeline_data;
|
||||
cv::Mat plateMask;
|
||||
namespace alpr
|
||||
{
|
||||
|
||||
|
||||
};
|
||||
class PlateMask {
|
||||
public:
|
||||
PlateMask(PipelineData* pipeline_data);
|
||||
virtual ~PlateMask();
|
||||
|
||||
bool hasPlateMask;
|
||||
|
||||
cv::Mat getMask();
|
||||
|
||||
void findOuterBoxMask(std::vector<TextContours > contours);
|
||||
|
||||
private:
|
||||
|
||||
PipelineData* pipeline_data;
|
||||
cv::Mat plateMask;
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#endif /* OPENALPR_PLATEMASK_H */
|
||||
|
||||
|
@@ -22,117 +22,119 @@
|
||||
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);
|
||||
|
||||
this->width = threshold.cols;
|
||||
this->height = threshold.rows;
|
||||
}
|
||||
|
||||
|
||||
uint TextContours::size() {
|
||||
return contours.size();
|
||||
}
|
||||
|
||||
|
||||
|
||||
int TextContours::getGoodIndicesCount()
|
||||
namespace alpr
|
||||
{
|
||||
int count = 0;
|
||||
for (uint i = 0; i < goodIndices.size(); i++)
|
||||
{
|
||||
if (goodIndices[i])
|
||||
count++;
|
||||
|
||||
TextContours::TextContours() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
TextContours::TextContours(cv::Mat threshold) {
|
||||
|
||||
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]);
|
||||
load(threshold);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
TextContours::~TextContours() {
|
||||
}
|
||||
else
|
||||
{
|
||||
assert("Invalid set operation on indices");
|
||||
|
||||
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);
|
||||
|
||||
this->width = threshold.cols;
|
||||
this->height = threshold.rows;
|
||||
}
|
||||
}
|
||||
|
||||
Mat TextContours::drawDebugImage() {
|
||||
|
||||
Mat img_contours = Mat::zeros(Size(width, height), CV_8U);
|
||||
|
||||
return drawDebugImage(img_contours);
|
||||
}
|
||||
uint TextContours::size() {
|
||||
return contours.size();
|
||||
}
|
||||
|
||||
Mat TextContours::drawDebugImage(Mat baseImage) {
|
||||
Mat img_contours(baseImage.size(), CV_8U);
|
||||
baseImage.copyTo(img_contours);
|
||||
|
||||
cvtColor(img_contours, img_contours, CV_GRAY2RGB);
|
||||
|
||||
vector<vector<Point> > allowedContours;
|
||||
for (uint i = 0; i < this->contours.size(); i++)
|
||||
|
||||
int TextContours::getGoodIndicesCount()
|
||||
{
|
||||
int count = 0;
|
||||
for (uint i = 0; i < goodIndices.size(); i++)
|
||||
{
|
||||
if (this->goodIndices[i])
|
||||
allowedContours.push_back(this->contours[i]);
|
||||
if (goodIndices[i])
|
||||
count++;
|
||||
}
|
||||
|
||||
drawContours(img_contours, this->contours,
|
||||
-1, // draw all contours
|
||||
cv::Scalar(255,0,0), // in blue
|
||||
1); // with a thickness of 1
|
||||
return count;
|
||||
}
|
||||
|
||||
drawContours(img_contours, allowedContours,
|
||||
-1, // draw all contours
|
||||
cv::Scalar(0,255,0), // in green
|
||||
1); // with a thickness of 1
|
||||
|
||||
|
||||
return img_contours;
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
Mat TextContours::drawDebugImage() {
|
||||
|
||||
Mat img_contours = Mat::zeros(Size(width, height), CV_8U);
|
||||
|
||||
return drawDebugImage(img_contours);
|
||||
}
|
||||
|
||||
Mat TextContours::drawDebugImage(Mat baseImage) {
|
||||
Mat img_contours(baseImage.size(), CV_8U);
|
||||
baseImage.copyTo(img_contours);
|
||||
|
||||
cvtColor(img_contours, img_contours, CV_GRAY2RGB);
|
||||
|
||||
vector<vector<Point> > allowedContours;
|
||||
for (uint i = 0; i < this->contours.size(); i++)
|
||||
{
|
||||
if (this->goodIndices[i])
|
||||
allowedContours.push_back(this->contours[i]);
|
||||
}
|
||||
|
||||
drawContours(img_contours, this->contours,
|
||||
-1, // draw all contours
|
||||
cv::Scalar(255,0,0), // in blue
|
||||
1); // with a thickness of 1
|
||||
|
||||
drawContours(img_contours, allowedContours,
|
||||
-1, // draw all contours
|
||||
cv::Scalar(0,255,0), // in green
|
||||
1); // with a thickness of 1
|
||||
|
||||
|
||||
return img_contours;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -23,34 +23,39 @@
|
||||
#include <vector>
|
||||
#include "opencv2/imgproc/imgproc.hpp"
|
||||
|
||||
class TextContours {
|
||||
public:
|
||||
TextContours();
|
||||
TextContours(cv::Mat threshold);
|
||||
virtual ~TextContours();
|
||||
|
||||
void load(cv::Mat threshold);
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
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);
|
||||
|
||||
cv::Mat drawDebugImage();
|
||||
cv::Mat drawDebugImage(cv::Mat baseImage);
|
||||
|
||||
private:
|
||||
|
||||
namespace alpr
|
||||
{
|
||||
|
||||
};
|
||||
class TextContours {
|
||||
public:
|
||||
TextContours();
|
||||
TextContours(cv::Mat threshold);
|
||||
virtual ~TextContours();
|
||||
|
||||
void load(cv::Mat threshold);
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
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);
|
||||
|
||||
cv::Mat drawDebugImage();
|
||||
cv::Mat drawDebugImage(cv::Mat baseImage);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TEXTCONTOURS_H */
|
||||
|
||||
|
@@ -24,84 +24,89 @@
|
||||
|
||||
using namespace cv;
|
||||
|
||||
TextLine::TextLine(std::vector<cv::Point2f> textArea, std::vector<cv::Point2f> linePolygon) {
|
||||
std::vector<Point> textAreaInts, linePolygonInts;
|
||||
|
||||
for (uint i = 0; i < textArea.size(); i++)
|
||||
textAreaInts.push_back(Point(round(textArea[i].x), round(textArea[i].y)));
|
||||
for (uint i = 0; i < linePolygon.size(); i++)
|
||||
linePolygonInts.push_back(Point(round(linePolygon[i].x), round(linePolygon[i].y)));
|
||||
|
||||
initialize(textAreaInts, linePolygonInts);
|
||||
}
|
||||
namespace alpr
|
||||
{
|
||||
|
||||
TextLine::TextLine(std::vector<cv::Point> textArea, std::vector<cv::Point> linePolygon) {
|
||||
initialize(textArea, linePolygon);
|
||||
}
|
||||
TextLine::TextLine(std::vector<cv::Point2f> textArea, std::vector<cv::Point2f> linePolygon) {
|
||||
std::vector<Point> textAreaInts, linePolygonInts;
|
||||
|
||||
|
||||
TextLine::~TextLine() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TextLine::initialize(std::vector<cv::Point> textArea, std::vector<cv::Point> linePolygon) {
|
||||
if (textArea.size() > 0)
|
||||
{
|
||||
if (this->textArea.size() > 0)
|
||||
this->textArea.clear();
|
||||
if (this->linePolygon.size() > 0)
|
||||
this->linePolygon.clear();
|
||||
|
||||
for (uint i = 0; i < textArea.size(); i++)
|
||||
this->textArea.push_back(textArea[i]);
|
||||
|
||||
textAreaInts.push_back(Point(round(textArea[i].x), round(textArea[i].y)));
|
||||
for (uint i = 0; i < linePolygon.size(); i++)
|
||||
this->linePolygon.push_back(linePolygon[i]);
|
||||
|
||||
this->topLine = LineSegment(linePolygon[0].x, linePolygon[0].y, linePolygon[1].x, linePolygon[1].y);
|
||||
this->bottomLine = LineSegment(linePolygon[3].x, linePolygon[3].y, linePolygon[2].x, linePolygon[2].y);
|
||||
linePolygonInts.push_back(Point(round(linePolygon[i].x), round(linePolygon[i].y)));
|
||||
|
||||
this->charBoxTop = LineSegment(textArea[0].x, textArea[0].y, textArea[1].x, textArea[1].y);
|
||||
this->charBoxBottom = LineSegment(textArea[3].x, textArea[3].y, textArea[2].x, textArea[2].y);
|
||||
this->charBoxLeft = LineSegment(textArea[3].x, textArea[3].y, textArea[0].x, textArea[0].y);
|
||||
this->charBoxRight = LineSegment(textArea[2].x, textArea[2].y, textArea[1].x, textArea[1].y);
|
||||
|
||||
// Calculate line height
|
||||
float x = ((float) linePolygon[1].x) / 2;
|
||||
Point midpoint = Point(x, bottomLine.getPointAt(x));
|
||||
Point acrossFromMidpoint = topLine.closestPointOnSegmentTo(midpoint);
|
||||
this->lineHeight = distanceBetweenPoints(midpoint, acrossFromMidpoint);
|
||||
|
||||
// Subtract a pixel since the height is a little overestimated by the bounding box
|
||||
this->lineHeight = this->lineHeight - 1;
|
||||
|
||||
this->angle = (topLine.angle + bottomLine.angle) / 2;
|
||||
|
||||
initialize(textAreaInts, linePolygonInts);
|
||||
}
|
||||
|
||||
TextLine::TextLine(std::vector<cv::Point> textArea, std::vector<cv::Point> linePolygon) {
|
||||
initialize(textArea, linePolygon);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cv::Mat TextLine::drawDebugImage(cv::Mat baseImage) {
|
||||
cv::Mat debugImage(baseImage.size(), baseImage.type());
|
||||
|
||||
baseImage.copyTo(debugImage);
|
||||
|
||||
cv::cvtColor(debugImage, debugImage, CV_GRAY2BGR);
|
||||
|
||||
|
||||
fillConvexPoly(debugImage, linePolygon.data(), linePolygon.size(), Scalar(0,0,165));
|
||||
TextLine::~TextLine() {
|
||||
}
|
||||
|
||||
fillConvexPoly(debugImage, textArea.data(), textArea.size(), Scalar(125,255,0));
|
||||
|
||||
line(debugImage, topLine.p1, topLine.p2, Scalar(255,0,0), 1);
|
||||
line(debugImage, bottomLine.p1, bottomLine.p2, Scalar(255,0,0), 1);
|
||||
|
||||
line(debugImage, charBoxTop.p1, charBoxTop.p2, Scalar(0,125,125), 1);
|
||||
line(debugImage, charBoxLeft.p1, charBoxLeft.p2, Scalar(0,125,125), 1);
|
||||
line(debugImage, charBoxRight.p1, charBoxRight.p2, Scalar(0,125,125), 1);
|
||||
line(debugImage, charBoxBottom.p1, charBoxBottom.p2, Scalar(0,125,125), 1);
|
||||
|
||||
|
||||
return debugImage;
|
||||
}
|
||||
|
||||
|
||||
void TextLine::initialize(std::vector<cv::Point> textArea, std::vector<cv::Point> linePolygon) {
|
||||
if (textArea.size() > 0)
|
||||
{
|
||||
if (this->textArea.size() > 0)
|
||||
this->textArea.clear();
|
||||
if (this->linePolygon.size() > 0)
|
||||
this->linePolygon.clear();
|
||||
|
||||
for (uint i = 0; i < textArea.size(); i++)
|
||||
this->textArea.push_back(textArea[i]);
|
||||
|
||||
for (uint i = 0; i < linePolygon.size(); i++)
|
||||
this->linePolygon.push_back(linePolygon[i]);
|
||||
|
||||
this->topLine = LineSegment(linePolygon[0].x, linePolygon[0].y, linePolygon[1].x, linePolygon[1].y);
|
||||
this->bottomLine = LineSegment(linePolygon[3].x, linePolygon[3].y, linePolygon[2].x, linePolygon[2].y);
|
||||
|
||||
this->charBoxTop = LineSegment(textArea[0].x, textArea[0].y, textArea[1].x, textArea[1].y);
|
||||
this->charBoxBottom = LineSegment(textArea[3].x, textArea[3].y, textArea[2].x, textArea[2].y);
|
||||
this->charBoxLeft = LineSegment(textArea[3].x, textArea[3].y, textArea[0].x, textArea[0].y);
|
||||
this->charBoxRight = LineSegment(textArea[2].x, textArea[2].y, textArea[1].x, textArea[1].y);
|
||||
|
||||
// Calculate line height
|
||||
float x = ((float) linePolygon[1].x) / 2;
|
||||
Point midpoint = Point(x, bottomLine.getPointAt(x));
|
||||
Point acrossFromMidpoint = topLine.closestPointOnSegmentTo(midpoint);
|
||||
this->lineHeight = distanceBetweenPoints(midpoint, acrossFromMidpoint);
|
||||
|
||||
// Subtract a pixel since the height is a little overestimated by the bounding box
|
||||
this->lineHeight = this->lineHeight - 1;
|
||||
|
||||
this->angle = (topLine.angle + bottomLine.angle) / 2;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cv::Mat TextLine::drawDebugImage(cv::Mat baseImage) {
|
||||
cv::Mat debugImage(baseImage.size(), baseImage.type());
|
||||
|
||||
baseImage.copyTo(debugImage);
|
||||
|
||||
cv::cvtColor(debugImage, debugImage, CV_GRAY2BGR);
|
||||
|
||||
|
||||
fillConvexPoly(debugImage, linePolygon.data(), linePolygon.size(), Scalar(0,0,165));
|
||||
|
||||
fillConvexPoly(debugImage, textArea.data(), textArea.size(), Scalar(125,255,0));
|
||||
|
||||
line(debugImage, topLine.p1, topLine.p2, Scalar(255,0,0), 1);
|
||||
line(debugImage, bottomLine.p1, bottomLine.p2, Scalar(255,0,0), 1);
|
||||
|
||||
line(debugImage, charBoxTop.p1, charBoxTop.p2, Scalar(0,125,125), 1);
|
||||
line(debugImage, charBoxLeft.p1, charBoxLeft.p2, Scalar(0,125,125), 1);
|
||||
line(debugImage, charBoxRight.p1, charBoxRight.p2, Scalar(0,125,125), 1);
|
||||
line(debugImage, charBoxBottom.p1, charBoxBottom.p2, Scalar(0,125,125), 1);
|
||||
|
||||
|
||||
return debugImage;
|
||||
}
|
||||
|
||||
}
|
@@ -24,31 +24,36 @@
|
||||
#include "utility.h"
|
||||
#include "opencv2/imgproc/imgproc.hpp"
|
||||
|
||||
class TextLine {
|
||||
public:
|
||||
TextLine(std::vector<cv::Point> textArea, std::vector<cv::Point> linePolygon);
|
||||
TextLine(std::vector<cv::Point2f> textArea, std::vector<cv::Point2f> linePolygon);
|
||||
virtual ~TextLine();
|
||||
|
||||
|
||||
std::vector<cv::Point> linePolygon;
|
||||
std::vector<cv::Point> textArea;
|
||||
LineSegment topLine;
|
||||
LineSegment bottomLine;
|
||||
namespace alpr
|
||||
{
|
||||
|
||||
LineSegment charBoxTop;
|
||||
LineSegment charBoxBottom;
|
||||
LineSegment charBoxLeft;
|
||||
LineSegment charBoxRight;
|
||||
|
||||
float lineHeight;
|
||||
float angle;
|
||||
class TextLine {
|
||||
public:
|
||||
TextLine(std::vector<cv::Point> textArea, std::vector<cv::Point> linePolygon);
|
||||
TextLine(std::vector<cv::Point2f> textArea, std::vector<cv::Point2f> linePolygon);
|
||||
virtual ~TextLine();
|
||||
|
||||
cv::Mat drawDebugImage(cv::Mat baseImage);
|
||||
private:
|
||||
|
||||
void initialize(std::vector<cv::Point> textArea, std::vector<cv::Point> linePolygon);
|
||||
};
|
||||
std::vector<cv::Point> linePolygon;
|
||||
std::vector<cv::Point> textArea;
|
||||
LineSegment topLine;
|
||||
LineSegment bottomLine;
|
||||
|
||||
LineSegment charBoxTop;
|
||||
LineSegment charBoxBottom;
|
||||
LineSegment charBoxLeft;
|
||||
LineSegment charBoxRight;
|
||||
|
||||
float lineHeight;
|
||||
float angle;
|
||||
|
||||
cv::Mat drawDebugImage(cv::Mat baseImage);
|
||||
private:
|
||||
|
||||
void initialize(std::vector<cv::Point> textArea, std::vector<cv::Point> linePolygon);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* OPENALPR_TEXTLINE_H */
|
||||
|
||||
|
Reference in New Issue
Block a user