You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
EasyPR/src/core/chars_segment.cpp

386 lines
9.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "easypr/chars_segment.h"
#include "easypr/util.h"
/*! \namespace easypr
Namespace where all the C++ EasyPR functionality resides
*/
namespace easypr{
using namespace cv;
const float DEFAULT_BLUEPERCEMT = 0.3;
const float DEFAULT_WHITEPERCEMT = 0.1;
CCharsSegment::CCharsSegment()
{
//cout << "CCharsSegment" << endl;
m_LiuDingSize = DEFAULT_LIUDING_SIZE;
m_theMatWidth = DEFAULT_MAT_WIDTH;
//!车牌颜色判断参数
m_ColorThreshold = DEFAULT_COLORTHRESHOLD;
m_BluePercent = DEFAULT_BLUEPERCEMT;
m_WhitePercent = DEFAULT_WHITEPERCEMT;
m_debug = DEFAULT_DEBUG;
}
//! 字符尺寸验证
bool CCharsSegment::verifyCharSizes(Mat r){
//Char sizes 45x90
float aspect = 45.0f / 90.0f;
float charAspect = (float)r.cols / (float)r.rows;
float error = 0.7;
float minHeight = 10;
float maxHeight = 35;
//We have a different aspect ratio for number 1, and it can be ~0.2
float minAspect = 0.05;
float maxAspect = aspect + aspect*error;
//area of pixels
float area = countNonZero(r);
//bb area
float bbArea = r.cols*r.rows;
//% of pixel in area
float percPixels = area / bbArea;
if (percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect && r.rows >= minHeight && r.rows < maxHeight)
return true;
else
return false;
}
//! 字符预处理
Mat CCharsSegment::preprocessChar(Mat in){
//Remap image
int h = in.rows;
int w = in.cols;
int charSize = CHAR_SIZE; //统一每个字符的大小
Mat transformMat = Mat::eye(2, 3, CV_32F);
int m = max(w, h);
transformMat.at<float>(0, 2) = m / 2 - w / 2;
transformMat.at<float>(1, 2) = m / 2 - h / 2;
Mat warpImage(m, m, in.type());
warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0));
Mat out;
resize(warpImage, out, Size(charSize, charSize));
return out;
}
// implementation of otsu algorithm
// author: onezeros(@yahoo.cn)
// reference: Rafael C. Gonzalez. Digital Image Processing Using MATLAB
int staticIndex = 0;
int iTag = 0;
//! 字符分割与排序
int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec)
{
if (!input.data)
return -3;
int w = input.cols;
int h = input.rows;
Mat tmpMat = input(Rect(w*0.1,h*0.1,w*0.8,h*0.8));
//判断车牌颜色以此确认threshold方法
Color plateType = getPlateType(tmpMat, true);
Mat input_grey;
cvtColor(input, input_grey, CV_BGR2GRAY);
Mat img_threshold ;
if (BLUE == plateType)
{
img_threshold = input_grey.clone();
int w = input_grey.cols;
int h = input_grey.rows;
Mat tmp = input_grey(Rect(w*0.1,h*0.1,w*0.8,h*0.8));
int threadHoldV = ThresholdOtsu(tmp);
utils::imwrite("tmp/inputgray2.jpg",input_grey);
threshold(input_grey, img_threshold,threadHoldV, 255, CV_THRESH_BINARY);
//threshold(input_grey, img_threshold, 5, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
}
else if (YELLOW == plateType)
{
img_threshold = input_grey.clone();
int w = input_grey.cols;
int h = input_grey.rows;
Mat tmp = input_grey(Rect(w*0.1,h*0.1,w*0.8,h*0.8));
int threadHoldV = ThresholdOtsu(tmp);
utils::imwrite("tmp/inputgray2.jpg",input_grey);
threshold(input_grey, img_threshold,threadHoldV, 255, CV_THRESH_BINARY_INV);
//threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
}
else
threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
if (m_debug)
{
std::stringstream ss(std::stringstream::in | std::stringstream::out);
ss << "tmp/debug_char_threshold" <<iTag<< ".jpg";
utils::imwrite(ss.str(), img_threshold);
}
//去除车牌上方的柳钉以及下方的横线等干扰
if(!clearLiuDing(img_threshold))
{
return -3;
}
if (m_debug)
{
std::stringstream ss(std::stringstream::in | std::stringstream::out);
ss << "tmp/debug_char_clearLiuDing" <<iTag<< ".jpg";
utils::imwrite(ss.str(), img_threshold);
}
iTag++;
Mat img_contours;
img_threshold.copyTo(img_contours);
vector< vector< Point> > contours;
findContours(img_contours,
contours, // a vector of contours
CV_RETR_EXTERNAL, // retrieve the external contours
CV_CHAIN_APPROX_NONE); // all pixels of each contours
//Start to iterate to each contour founded
vector<vector<Point> >::iterator itc = contours.begin();
vector<Rect> vecRect;
//Remove patch that are no inside limits of aspect ratio and area.
//将不符合特定尺寸的图块排除出去
while (itc != contours.end())
{
Rect mr = boundingRect(Mat(*itc));
Mat auxRoi(img_threshold, mr);
if (verifyCharSizes(auxRoi))
vecRect.push_back(mr);
++itc;
}
if (vecRect.size() == 0)
return -3;
vector<Rect> sortedRect;
////对符合尺寸的图块按照从左到右进行排序
SortRect(vecRect, sortedRect);
/*vector<Rect> sortedRect(vecRect);
std::sort
(sortedRect.begin(), sortedRect.end(), [](const Rect &r1, const Rect &r2)
{
return r1.x < r2.x;
}
);*/
int specIndex = 0;
//获得指示城市的特定Rect,如苏A的"A"
specIndex = GetSpecificRect(sortedRect);
if (m_debug)
{
if (specIndex < sortedRect.size())
{
Mat specMat(img_threshold, sortedRect[specIndex]);
std::stringstream ss(std::stringstream::in | std::stringstream::out);
ss << "tmp/debug_specMat" << ".jpg";
utils::imwrite(ss.str(), specMat);
}
}
//根据特定Rect向左反推出中文字符
//这样做的主要原因是根据findContours方法很难捕捉到中文字符的准确Rect因此仅能
//退过特定算法来指定
Rect chineseRect;
if (specIndex < sortedRect.size())
chineseRect = GetChineseRect(sortedRect[specIndex]);
else
return -3;
if (m_debug)
{
Mat chineseMat(img_threshold, chineseRect);
std::stringstream ss(std::stringstream::in | std::stringstream::out);
ss << "tmp/debug_chineseMat" << ".jpg";
utils::imwrite(ss.str(), chineseMat);
}
//新建一个全新的排序Rect
//将中文字符Rect第一个加进来因为它肯定是最左边的
//其余的Rect只按照顺序去6个车牌只可能是7个字符这样可以避免阴影导致的“1”字符
vector<Rect> newSortedRect;
newSortedRect.push_back(chineseRect);
RebuildRect(sortedRect, newSortedRect, specIndex);
if (newSortedRect.size() == 0)
return -3;
for (int i = 0; i < newSortedRect.size(); i++)
{
Rect mr = newSortedRect[i];
Mat auxRoi(img_threshold, mr);
if (1)
{
auxRoi = preprocessChar(auxRoi);
if (m_debug)
{
std::stringstream ss(std::stringstream::in | std::stringstream::out);
ss << "tmp/debug_char_auxRoi_" << (i+staticIndex) << ".jpg";
utils::imwrite(ss.str(), auxRoi);
}
resultVec.push_back(auxRoi);
}
}
staticIndex+=newSortedRect.size();
return 0;
}
//! 将Rect按位置从左到右进行排序
int CCharsSegment::SortRect(const vector<Rect>& vecRect, vector<Rect>& out)
{
vector<int> orderIndex;
vector<int> xpositions;
for (int i = 0; i < vecRect.size(); i++)
{
orderIndex.push_back(i);
xpositions.push_back(vecRect[i].x);
}
float min = xpositions[0];
int minIdx = 0;
for (int i = 0; i< xpositions.size(); i++)
{
min = xpositions[i];
minIdx = i;
for (int j = i; j<xpositions.size(); j++)
{
if (xpositions[j]<min){
min = xpositions[j];
minIdx = j;
}
}
int aux_i = orderIndex[i];
int aux_min = orderIndex[minIdx];
orderIndex[i] = aux_min;
orderIndex[minIdx] = aux_i;
float aux_xi = xpositions[i];
float aux_xmin = xpositions[minIdx];
xpositions[i] = aux_xmin;
xpositions[minIdx] = aux_xi;
}
for (int i = 0; i<orderIndex.size(); i++)
{
out.push_back(vecRect[orderIndex[i]]);
}
return 0;
}
//! 根据特殊车牌来构造猜测中文字符的位置和大小
Rect CCharsSegment::GetChineseRect(const Rect rectSpe)
{
int height = rectSpe.height;
float newwidth = rectSpe.width * 1.15;
int x = rectSpe.x;
int y = rectSpe.y;
int newx = x - int(newwidth * 1.15);
newx = newx > 0 ? newx : 0;
Rect a(newx, y, int(newwidth), height);
return a;
}
//! 找出指示城市的字符的Rect例如苏A7003X就是"A"的位置
int CCharsSegment::GetSpecificRect(const vector<Rect>& vecRect)
{
vector<int> xpositions;
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < vecRect.size(); i++)
{
xpositions.push_back(vecRect[i].x);
if (vecRect[i].height > maxHeight)
{
maxHeight = vecRect[i].height;
}
if (vecRect[i].width > maxWidth)
{
maxWidth = vecRect[i].width;
}
}
int specIndex = 0;
for (int i = 0; i < vecRect.size(); i++)
{
Rect mr = vecRect[i];
int midx = mr.x + mr.width / 2;
//如果一个字符有一定的大小并且在整个车牌的1/7到2/7之间则是我们要找的特殊字符
//当前字符和下个字符的距离在一定的范围内
if ((mr.width > maxWidth * 0.8 || mr.height > maxHeight * 0.8) &&
(midx < int(m_theMatWidth / 7) * 2 && midx > int(m_theMatWidth / 7) * 1))
{
specIndex = i;
}
}
return specIndex;
}
//! 这个函数做两个事情
// 1.把特殊字符Rect左边的全部Rect去掉后面再重建中文字符的位置。
// 2.从特殊字符Rect开始依次选择6个Rect多余的舍去。
int CCharsSegment::RebuildRect(const vector<Rect>& vecRect, vector<Rect>& outRect, int specIndex)
{
//最大只能有7个Rect,减去中文的就只有6个Rect
//int count = 6;
//for (int i = 0; i < vecRect.size(); i++)
//{
// //将特殊字符左边的Rect去掉这个可能会去掉中文Rect不过没关系我们后面会重建。
// if (i < specIndex)
// continue;
// outRect.push_back(vecRect[i]);
// if (!--count)
// break;
//}
int count = 6;
for (size_t i = specIndex; i < vecRect.size() && count; ++i, --count) {
outRect.push_back(vecRect[i]);
}
return 0;
}
} /*! \namespace easypr*/