Wrapped OpenALPR library in "alpr" namespace. Resolves issue #60.

This commit is contained in:
Matt Hill
2014-10-27 20:12:57 -04:00
parent 83ed86c6b4
commit 85f52a6b8c
79 changed files with 7234 additions and 6968 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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 */

View File

@@ -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;
}
}

View File

@@ -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 */

View File

@@ -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;
}
}

View File

@@ -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 */

View File

@@ -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;
}
}

View File

@@ -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 */