上一章节完成了车牌图片的切割,这一章节进入到ANN识别过程:
同样地流程:先去燥,灰度,二值化,得到以下图片,发现有两个螺丝孔。
//imshow("车牌", dst);
//识别 ...... ann :神经网络
Mat plate_gray;
cvtColor(dst, plate_gray,COLOR_BGR2GRAY);
//二值化
Mat plate_shold;
threshold(plate_gray, plate_shold,0,255, THRESH_OTSU + THRESH_BINARY);
去掉 车牌在车子上的固定点
//因为固定点所在的行 它的黑到白、白到黑的改变次数是最小的
clearFixPoint(plate_shold);
imshow("去干扰", plate_shold);
//去螺丝钉的挂牌,根据每一行的跳变 小于10的都描黑。
void CarPlateRecgnize::clearFixPoint(Mat &src) {
//最大改变次数
int maxChange = 10;
//一个集合统计每一行的跳变次数
vector<int> c;
for (size_t i = 0; i < src.rows; i++)
{
//记录这一行的改变次数
int change = 0;
for (size_t j = 0; j < src.cols - 1; j++)
{
//获得像素值
char p = src.at<char>(i, j);
//当前的像素点与下一个像素点值是否相同
if (p != src.at<char>(i, j + 1)) {
change++;
}
}
c.push_back(change);
}
for (size_t i = 0; i < c.size(); i++)
{
//取出每一行的改变次数
int change = c[i];
//如果小与max ,则可能就是干扰点所在的行
if (change <= maxChange) {
//把这一行都抹黑
for (size_t j = 0; j < src.cols; j++)
{
src.at<char>(i, j) = 0;
}
}
}
}
去掉干扰点后的图片,发现上面被切掉了一些。
字符分割
每一个都是一个轮廓,调用轮廓查找:
vector< vector<Point>> contours;
findContours(plate_shold, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
for (vector<Point> point:contours) {
RotatedRect rotatedRect= minAreaRect(point);
rectangle(dst, rotatedRect.boundingRect(), Scalar(255, 0, 255));
}
imshow("查找轮廓", dst);
矩形框太多,一些无用的框,需要用之前判断宽高比去掉明显不合规矩的。
bool CarPlateRecgnize::verityCharSize(Mat src) {
//最理想情况 车牌字符的标准宽高比
float aspect = 45.0f/90;
//当前的真是的宽高比
float realAspect = src.cols / src.rows;
//最小的字符高
float minHeight = 10.0f;
//最大的字符高
float maxHeight = 35.0f;
//1. 判断高符合范围 2.宽、高比符合范围
float error = 0.7f;
float maxAspect = aspect + aspect*error;
float minAspect = aspect - aspect*error;
//这里只判断高,因为字符 1 宽度太窄了。
if (realAspect >= minAspect && realAspect <= maxAspect && src.rows >= minHeight && src.rows <= maxHeight){
return true;
}
return false;
}
汉字的字符不好取,需要通过城市的字符来定位,所以先拿城市的字符:
//通过拿到城市的轮廓取到 省份汉字的轮廓
int CarPlateRecgnize::getCityIndex(vector<Rect> src) {
int cityIndex = 0;
for (int i = 0; i < src.size(); ++i) {
Rect rect = src[i];
//获得矩形
//把车牌区域划分为7个字符
//如果当前获得的矩形 它的中心点 比 1/7 大,比2/7小,那么就是城市的轮廓
int midX = rect.x + rect.width / 2;
if (midX < 136 / 7 * 2 && midX > 136 / 7) {
cityIndex = i;
break;
}
}
return cityIndex;
}
通过城市的轮廓 判断定位 汉字:拿到汉字。
//获取省份汉字框
void CarPlateRecgnize::getChineseRect(Rect city, Rect &chineseRect) {
//把宽度稍微扩大一点
float width = city.width * 1.15f;
//城市轮廓的x坐标
int x = city.x;
int newX = x - width;
chineseRect.x = newX >= 0 ? newX : 0;
chineseRect.y = city.y;
chineseRect.width = width;
chineseRect.height = city.height;
}
//通过城市的下标 判断获取汉字轮廓
Rect chineseRect;
getChineseRect(charVec[cityIndex], chineseRect);
rectangle(dst, chineseRect, Scalar(255, 0, 255));
imshow("省份汉字", dst);
把这些截取的矩形框放入集合中:测试显示。
//集合里面首先加入汉字
vector<Mat> plateChar;
plateChar.push_back(plate_shold(chineseRect));
//从城市开始加入字符
int count = 0;
for (size_t j = cityIndex; j < charVec.size(), count<6; count++, ++j) {
plateChar.push_back(plate_shold(charVec[j]));
}
for(Mat s: plateChar){
imshow("", s);
waitKey();
}
人工神经网络
把截取的字符集合送给ANN然后
//通过ANN进行识别
void CarPlateRecgnize::predict(vector<Mat> vector, String &result) {
for (size_t i = 0; i < vector.size(); ++i) {
Mat src = vector[i];
Mat features;
//
getHogFeatures(annHog, src, features);
Mat response;
Point maxLoc;
Point minLoc;
Mat samples = features.reshape(1, 1);
if (i){
//识别字母和数字
ann->predict(samples,response);
//获取最大可信度 匹配度最高的属于31种中的哪一个。
minMaxLoc(response, 0, 0, &minLoc, &maxLoc);
//跟你训练时候有关
int index = maxLoc.x;
result += CHARS[index];
}else{
//识别汉字
annCh->predict(samples, response);
minMaxLoc(response, 0, 0, 0, &maxLoc);
//跟你训练的有关
int index = maxLoc.x;
//识别出来的汉字,拼到string当中去
result += ZHCHARS[index];
}
cout << "匹配度:" << minLoc.x << endl;
}
}
这里需要用到训练的两个模型:
#define ANN_ZH_XML "/Users/xiuchengyin/Documents/Tina-NDK/CarAnnTrain/CarAnnTrain/resource/HOG_ANN_ZH_DATA2.xml"
#define ANN_XML "/Users/xiuchengyin/Documents/Tina-NDK/CarAnnTrain/CarAnnTrain/resource/HOG_ANN_DATA2.xml"
因为样本模型不是很全,中文字识别率比较低,以下是识别出来的车牌号。
源码可参考:车牌定位