终于答辩结束了,这个项目忘的也差不多了,今天正巧没事准备整理一下,本人菜鸡一枚,本科毕业设计题目是《基于OpenCV的车牌识别系统》,这个项目差不多研究了半个月才看懂,当时我给导师看这个项目的时候,导师直接说这些东西都是别人的,你什么都不做,这工作量有点少,怕我答辩时候过不了,之后就是让我加功能,(你这个可以检测绿牌吗?还有你这个识别算法,用的是什么,能不能用其他的,建议加个绿牌,并且做个其他识别算法和你这个对比一下,工作量看起来多一些),还是太懒了我只加了绿牌,之后又被导师数落了一顿。
这个项目写的听清楚的,还有开发者文档参考,作者也把各个版本的配置方式贴出来了。我自己只是用MFC画了一个界面,里面就是调作者写好的函数,我自己加了绿牌,修改了部分代码。
在枚举颜色里面添加GREEN。在config.h文件中:
enum Color { BLUE, YELLOW, WHITE, GREEN,UNKNOWN };
这里我设置的是35-80,在core_func.cpp文件中colorMatch函数中添加。修改后的代码如下:
Mat colorMatch(const Mat &src, Mat &match, const Color r,
const bool adaptive_minsv) {
// if use adaptive_minsv
// min value of s and v is adaptive to h
const float max_sv = 255;
const float minref_sv = 64;
const float minabs_sv = 95; //95;
// H range of blue
const int min_blue = 100; // 100
const int max_blue = 140; // 140
// H range of yellow
const int min_yellow = 15; // 15
const int max_yellow = 40; // 40
// H range of white
const int min_white = 0; // 15
const int max_white = 30; // 40
//Louis add,green
// H range of green
const int min_green = 35; // 35
const int max_green = 80; // 80
Mat src_hsv;
// convert to HSV space
cvtColor(src, src_hsv, CV_BGR2HSV);
std::vector<cv::Mat> hsvSplit;
split(src_hsv, hsvSplit);
equalizeHist(hsvSplit[2], hsvSplit[2]);
merge(hsvSplit, src_hsv);
// match to find the color
int min_h = 0;
int max_h = 0;
switch (r) {
case BLUE:
min_h = min_blue;
max_h = max_blue;
break;
case YELLOW:
min_h = min_yellow;
max_h = max_yellow;
break;
case WHITE:
min_h = min_white;
max_h = max_white;
break;
//Louis add,green
case GREEN:
min_h = min_green;
max_h = max_green;
break;
default:
// Color::UNKNOWN
break;
}
float diff_h = float((max_h - min_h) / 2);
float avg_h = min_h + diff_h;
int channels = src_hsv.channels();
int nRows = src_hsv.rows;
// consider multi channel image
int nCols = src_hsv.cols * channels;
if (src_hsv.isContinuous()) {
nCols *= nRows;
nRows = 1;
}
int i, j;
uchar* p;
float s_all = 0;
float v_all = 0;
float count = 0;
for (i = 0; i < nRows; ++i) {
p = src_hsv.ptr<uchar>(i);
for (j = 0; j < nCols; j += 3) {
int H = int(p[j]); // 0-180
int S = int(p[j + 1]); // 0-255
int V = int(p[j + 2]); // 0-255
s_all += S;
v_all += V;
count++;
bool colorMatched = false;
if (H > min_h && H < max_h) {
float Hdiff = 0;
if (H > avg_h)
Hdiff = H - avg_h;
else
Hdiff = avg_h - H;
float Hdiff_p = float(Hdiff) / diff_h;
float min_sv = 0;
if (true == adaptive_minsv)
min_sv =
minref_sv -
minref_sv / 2 *
(1
- Hdiff_p); // inref_sv - minref_sv / 2 * (1 - Hdiff_p)
else
min_sv = minabs_sv; // add
if ((S > min_sv && S < max_sv) && (V > min_sv && V < max_sv))
colorMatched = true;
}
if (colorMatched == true) {
p[j] = 0;
p[j + 1] = 0;
p[j + 2] = 255;
}
else {
p[j] = 0;
p[j + 1] = 0;
p[j + 2] = 0;
}
}
}
// cout << "avg_s:" << s_all / count << endl;
// cout << "avg_v:" << v_all / count << endl;
// get the final binary
Mat src_grey;
std::vector<cv::Mat> hsvSplit_done;
split(src_hsv, hsvSplit_done);
src_grey = hsvSplit_done[2];
match = src_grey;
return src_grey;
}
这部分也在core_func.cpp文件中。
Color getPlateType(const Mat &src, const bool adaptive_minsv) {
float max_percent = 0;
Color max_color = UNKNOWN;
float blue_percent = 0;
float yellow_percent = 0;
float white_percent = 0;
float green_percent = 0;
if (plateColorJudge(src, BLUE, adaptive_minsv, blue_percent) == true) {
// cout << "BLUE" << endl;
return BLUE;
} else if (plateColorJudge(src, YELLOW, adaptive_minsv, yellow_percent) ==
true) {
// cout << "YELLOW" << endl;
return YELLOW;
} else if (plateColorJudge(src, WHITE, adaptive_minsv, white_percent) ==
true) {
// cout << "WHITE" << endl;
return WHITE;
}
else if (plateColorJudge(src, GREEN, adaptive_minsv, green_percent) ==
true) {
// cout << "GREEN" << endl;
return GREEN;
}
else {
//std::cout << "OTHER" << std::endl;
/*max_percent = blue_percent > yellow_percent ? blue_percent : yellow_percent;
max_color = blue_percent > yellow_percent ? BLUE : YELLOW;
max_color = max_percent > white_percent ? max_color : WHITE;*/
// always return green
return GREEN;
}
}
plate_locate.cpp中plateColorLocate函数,修改后如下:
int CPlateLocate::plateColorLocate(Mat src, vector<CPlate> &candPlates,
int index) {
vector<RotatedRect> rects_color_blue;
rects_color_blue.reserve(64);
vector<RotatedRect> rects_color_yellow;
rects_color_yellow.reserve(64);
vector<RotatedRect> rects_color_green;
rects_color_green.reserve(64);
vector<CPlate> plates_blue;
plates_blue.reserve(64);
vector<CPlate> plates_yellow;
plates_yellow.reserve(64);
vector<CPlate> plates_green;
plates_green.reserve(64);
Mat src_clone = src.clone();
Mat src_b_blue;
Mat src_b_yellow;
Mat src_b_green;
#pragma omp parallel sections
{
#pragma omp section
{
colorSearch(src, BLUE, src_b_blue, rects_color_blue);
deskew(src, src_b_blue, rects_color_blue, plates_blue, true, BLUE);
//imshow("blue", src_b_blue);
}
#pragma omp section
{
colorSearch(src_clone, YELLOW, src_b_yellow, rects_color_yellow);
deskew(src_clone, src_b_yellow, rects_color_yellow, plates_yellow, true, YELLOW);
}
#pragma omp section
{
colorSearch(src_clone, GREEN, src_b_green, rects_color_green);
deskew(src_clone, src_b_green, rects_color_green, plates_green, true, GREEN);
//imshow("green", src_b_green);
}
}
candPlates.insert(candPlates.end(), plates_blue.begin(), plates_blue.end());
candPlates.insert(candPlates.end(), plates_yellow.begin(), plates_yellow.end());
candPlates.insert(candPlates.end(), plates_green.begin(), plates_green.end());
return 0;
}
以上处理完基本可以实现对绿牌的定位,如果需要更高的准确率,可以自己重新训练一下,加上绿牌,因为绿牌的大小,颜色特征等都和蓝牌差距太大。
这部分主要更改个分割字符数量和二值化参数,因为绿牌是8个字符,蓝牌7个字符,并且绿牌是背景颜色浅,车牌号颜色深,蓝牌是背景颜色深,车牌号颜色浅,所以蓝牌进行反二值化,绿牌则是正二值化。如下是core.func.cpp文件中。
void spatial_ostu(InputArray _src, int grid_x, int grid_y, Color type) {
Mat src = _src.getMat();
int width = src.cols / grid_x;
int height = src.rows / grid_y;
// iterate through grid
for (int i = 0; i < grid_y; i++) {
for (int j = 0; j < grid_x; j++) {
Mat src_cell = Mat(src, Range(i * height, (i + 1) * height), Range(j * width, (j + 1) * width));
if (type == BLUE) {
cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
} else if (type == YELLOW) {
cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
} else if (type == WHITE) {
cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
}
else if (type == GREEN) {
cv::threshold(src_cell, src_cell, 0, 255, THRESH_OTSU + CV_THRESH_BINARY_INV);
}
else {
cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
}
}
}
}
如下是字符分割函数修改后,在chars_segment.cpp中:
int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color) {
if (!input.data) return 0x01;
Color plateType = color;
Mat input_grey;
cvtColor(input, input_grey, CV_BGR2GRAY);
//imshow("cvt", input_grey);
if (0) {
imshow("plate", input_grey);
waitKey(0);
destroyWindow("plate");
}
Mat img_threshold;
img_threshold = input_grey.clone();
spatial_ostu(img_threshold, 8, 2, plateType);
//imshow("ostu", img_threshold);
if (0) {
imshow("plate", img_threshold);
waitKey(0);
destroyWindow("plate");
}
// remove liuding and hor lines
// also judge weather is plate use jump count
if (!clearLiuDing(img_threshold)) return 0x02;
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
// Mat dst = input;
//for (vector point : contours) {
// RotatedRect rotatedRect = minAreaRect(point);
// rectangle(dst, rotatedRect.boundingRect(), Scalar(255,0,255));
//}
//imshow("dst", dst);
vector<vector<Point> >::iterator itc = contours.begin();
vector<Rect> vecRect;
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 0x03;
vector<Rect> sortedRect(vecRect);
std::sort(sortedRect.begin(), sortedRect.end(),
[](const Rect& r1, const Rect& r2) { return r1.x < r2.x; });
size_t specIndex = 0;
if(color == GREEN)
specIndex = GetSpecificRectNew(sortedRect);//new car
else
specIndex = GetSpecificRect(sortedRect);//old car
Rect chineseRect;
if (specIndex < sortedRect.size())
chineseRect = GetChineseRect(sortedRect[specIndex]);
else
return 0x04;
if (0) {
rectangle(img_threshold, chineseRect, Scalar(255));
imshow("plate", img_threshold);
waitKey(0);
destroyWindow("plate");
}
vector<Rect> newSortedRect;
newSortedRect.push_back(chineseRect);
if (color == GREEN)
RebuildRectNew(sortedRect, newSortedRect, specIndex);
else
RebuildRect(sortedRect, newSortedRect, specIndex);
if (newSortedRect.size() == 0) return 0x05;
Mat dst = input;
bool useSlideWindow = true;
bool useAdapThreshold = true;
//bool useAdapThreshold = CParams::instance()->getParam1b();
for (size_t i = 0; i < newSortedRect.size(); i++) {
Rect mr = newSortedRect[i];
//rectangle(dst, mr, Scalar(255, 0, 255));
// Mat auxRoi(img_threshold, mr);
Mat auxRoi(input_grey, mr);
Mat newRoi;
if (i == 0) {
if (useSlideWindow) {
float slideLengthRatio = 0.1f;
//float slideLengthRatio = CParams::instance()->getParam1f();
if (!slideChineseWindow(input_grey, mr, newRoi, plateType, slideLengthRatio, useAdapThreshold))
judgeChinese(auxRoi, newRoi, plateType);
}
else
judgeChinese(auxRoi, newRoi, plateType);
}
else {
if (BLUE == plateType) {
threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU);
}
else if (YELLOW == plateType) {
threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU);
}
else if (WHITE == plateType) {
threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
}
else if (GREEN == plateType) {
threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
}
else {
threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
}
newRoi = preprocessChar(newRoi);
}
if (0) {
if (i == 0) {
imshow("input_grey", input_grey);
waitKey(0);
destroyWindow("input_grey");
}
if (i == 0) {
imshow("newRoi", newRoi);
waitKey(0);
destroyWindow("newRoi");
}
}
resultVec.push_back(newRoi);
}
//imshow("dst", dst);
return 0;
}
添加如下两个函数,因为字符数不同,切割函数也不相同。
int CCharsSegment::GetSpecificRectNew(const vector<Rect>& vecRect) {
vector<int> xpositions;
int maxHeight = 0;
int maxWidth = 0;
for (size_t 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 (size_t i = 0; i < vecRect.size(); i++) {
Rect mr = vecRect[i];
int midx = mr.x + mr.width / 2;
// use prior knowledage to find the specific character
// position in 1/8 and 2/8
if ((mr.width > maxWidth * 0.6 || mr.height > maxHeight * 0.6) &&
(midx < int(m_theMatWidth / 8.15f) * kSymbolIndex &&
midx > int(m_theMatWidth / 8.15f) * (kSymbolIndex - 1))) {
specIndex = i;
}
}
return specIndex;
}
int CCharsSegment::RebuildRectNew(const vector<Rect>& vecRect,
vector<Rect>& outRect, int specIndex) {
int count = 7;//Louis changed 6->7, for green plate car
for (size_t i = specIndex; i < vecRect.size() && count; ++i, --count) {
outRect.push_back(vecRect[i]);
}
return 0;
}
}
以上改完基本可以实现对绿牌的识别,我只改了我用到的部分函数,其他未修改的同理。好久没看这个项目了,应该改的就这么多了,还有就是我训练的时候只用了10几张绿牌,关键是没数据集。。。