车牌识别理论不做概述,网上比比皆是,请自行搜索哦。一个纯干货的公众号,断断续续写了一周,贴出代码,供交流学习。
完整测试视频 http://mpvideo.qpic.cn/0b78omaakaaa4iab6q2a7jpfa46davzqabia.f10002.mp4?dis_k=9d499190dcbab2995e57bf15fbec140a&dis_t=1655361080&vid=wxv_1202375059562840064&format_id=10002&support_redirect=0&mmversion=false
图像处理部分
读入图像
代码语言:javascript复制void img_read(Mat& img)
{
cvtColor(img,gray_img,CV_BGR2GRAY);
imshow("gray",gray_img);
waitKey(0);
}
图像缩放
代码语言:javascript复制//对图像进行缩放
void img_resize(Mat& gray)
{
double w=gray.rows, h=gray.cols;
if (h>MAX_WIDTH)
{
double change_rate=float(MAX_WIDTH/h);
cout<<change_rate<<endl;
resize(gray,resize_img,cv::Size(MAX_WIDTH, w*change_rate),
cv::INTER_LINEAR);
}
cout<<h<<w;
imshow("resize_img",resize_img);
waitKey(0);
}
可能车牌
代码语言:javascript复制/返回所有可能的矩形点集
vector<vector<Point>> img_contour(Mat& img)
{
Mat img_,copy_img,open_img,gauss_img,thresh_img,edge_img,
edge_img1,edge_img2;
/*
IplImage* img_copy = cvCreateImage(cv::Size(img.cols, img.rows),
img.step, img.channels());
Mat img_copy_=cvarrToMat(img_copy);
cvCopy(img,img_copy);*/
img.copyTo(copy_img);
GaussianBlur(img,gauss_img, cv::Size(3,3), 0, 0, cv::BORDER_DEFAULT);
imshow("GaussianBlur",gauss_img);
waitKey(0);
Mat element = getStructuringElement(MORPH_RECT, Size(20,20));
morphologyEx(gauss_img, img_, CV_MOP_OPEN, element);
imshow("morphologyEx",img_);
waitKey(0);
//图像叠加
addWeighted(gauss_img,1,img_,-1,0,open_img);
imshow("addWeighted",open_img);
waitKey(0);
threshold(open_img, thresh_img, 0, 255, THRESH_BINARY && CV_THRESH_OTSU);
Canny(thresh_img,edge_img,100,200);
imshow("edge_img",edge_img);
waitKey(0);
Mat kernel = getStructuringElement(MORPH_RECT, Size(10,10));
morphologyEx(edge_img, edge_img1, CV_MOP_CLOSE, kernel);
morphologyEx(edge_img1, edge_img2, CV_MOP_OPEN, kernel);
imshow("edge_img2",edge_img2);
waitKey(0);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours( edge_img2, contours, hierarchy, CV_RETR_TREE,
CHAIN_APPROX_NONE, Point(0, 0));
//轮廓个数
cout<<contours.size()<<endl;
return contours;
}
准确车牌
代码语言:javascript复制//返回车牌拟合矩形点集
vector<vector<Point>> find_car_plate(vector<vector<Point>>& contours)
{
vector<vector<Point>> tmp_contours(contours.size());
int n=0;
for(unsigned int i=0;i<contours.size();i )
{
//计算面积
if(contourArea(contours[i]) > MIN_Area)
{
for(unsigned int j=0;j<contours[i].size();j )
tmp_contours[n].push_back(contours[i][j]);
n ;
//cout<<contourArea(tmp_contours[i])<<endl;
}
}
vector<vector<Point>> car_plate(1);//返回的车牌轮廓
vector<RotatedRect> Rect_plate(n);
Point2f rect_points[4];
for( int i=0;i<n;i )
{
cout<<contourArea(tmp_contours[i])<<endl;
//得到包裹的最小外切矩形,依次返回矩形 中心坐标,长和宽,倾斜角度
Rect_plate[i]=minAreaRect(Mat(tmp_contours[i]));
Rect_plate[i].points(rect_points);
//外接矩形的四个顶点坐标
// for(int j=0;j<n;j )
// cout<<rect_points[j]<<endl;
// 数据类型 Point2f center; Size2f size; float angle;//
// cout<< Rect_plate[i].center<<endl;//中心坐标
// cout<< Rect_plate[i].size<<endl;//长和宽
// cout<< Rect_plate[i].center.x<<endl;//中心坐标
//cout<< Rect_plate[i].size.width<<endl;//长和宽
// cout<< Rect_plate[i].angle<<endl;//倾斜角度
int leng=Rect_plate[i].size.height;
int width=Rect_plate[i].size.width;
if(leng < width)
{
int tmp=leng;
leng=width;
width=tmp;
}
cout << leng <<" "<<width<<endl;
double ratio=double(leng)/double(width);
cout<<"ratio "<<ratio<<endl;
if( (ratio > 3) && (ratio < 5.5))
{
for(unsigned int j=0;j<tmp_contours[i].size();j )
car_plate[0].push_back(tmp_contours[i][j]);
}
}
cout<<contourArea(car_plate[0])<<endl;
return car_plate;
}
保存车牌
代码语言:javascript复制//画出车牌矩形框,并输出车牌矩形
Mat draw_car_plate(vector<vector<Point>>& car_plate)
{
//车牌四个顶点,找到对角顶点就好
Point2f rect[4];
RotatedRect rect_plate;
rect_plate=minAreaRect(Mat(car_plate[0]));
rect_plate.points(rect);
cout<<"draw car plate"<<endl;
for(int i=0;i<4;i )
cout<<rect[i]<<endl;
//找对角顶点
int row_min=min(min(rect[0].x,rect[1].x),min(rect[1].x,rect[2].x));
int col_min=min(min(rect[0].y,rect[1].y),min(rect[1].y,rect[2].y));
int row_max=max(max(rect[0].x,rect[1].x),max(rect[1].x,rect[2].x));
int col_max=max(max(rect[0].y,rect[1].y),max(rect[1].y,rect[2].y));
cvtColor(resize_img,resize_img,CV_GRAY2BGR);
rectangle(resize_img,Point(row_min,col_min), Point(row_max, col_max), Scalar(0,255,0),3);
imshow("draw_img",resize_img);
waitKey(0);
int l=row_max-row_min;
int w=col_max-col_min;
Mat car_plate_img=resize_img(Rect(row_min,col_min,l,w));
imshow("car_plate_img",car_plate_img);
waitKey(0);
imwrite("car_plate_img.jpg",car_plate_img);
cout<<row_min<<" "<<col_min<<" "<<row_max<<" "<<col_max<<endl;
return car_plate_img;
}
图像分割部分
分割车牌前奏
代码语言:javascript复制//根据设定的阈值和图片直方图,找出波峰,用于分隔字符
vector<double> find_waves(double threshold, vector<double> sum_each_row)
{
double up_point=-1;//上升点
bool is_peak=false;
if (sum_each_row[0] > threshold)
{
up_point=0;
is_peak=true;
}
vector<double> wave_peaks(sum_each_row.size());
unsigned int i;
for( i=0;i<sum_each_row.size();i )
{
if(is_peak && sum_each_row[i] < threshold)
{
if (i-up_point >2)
{
is_peak=false;
wave_peaks.push_back(up_point);//??????????
}
}
else if(!is_peak && sum_each_row[i] >= threshold)
{
is_peak = true;
up_point = i;
}
}
if( is_peak && up_point !=-1 && (i-up_point > 4))
{
wave_peaks.push_back(up_point);
}
return wave_peaks;
}
分割字符二值
代码语言:javascript复制
//返回车牌的 二值 图像 用于分割字符
Mat car_plate_binary_img(Mat& car_plate_img)
{
Mat gray_car_plate,binary_car_plate;
cvtColor(car_plate_img,gray_car_plate,CV_BGR2GRAY);
threshold(car_plate_img,binary_car_plate, 0, 255, THRESH_BINARY && CV_THRESH_OTSU);
imshow("binary_car_plate",binary_car_plate);
waitKey(0);
//存储每一行对应列的像素值
vector<vector<double>> row_hists(binary_car_plate.rows);//必须设置大小,不然出错
//cout<<binary_car_plate.rows<<" "<<binary_car_plate.cols<<endl;
for(int i=0;i<binary_car_plate.rows;i )
{
for(int j=0;j<binary_car_plate.cols;j )
{
//cout<<binary_car_plate.at<double>(i,j)<<endl;
row_hists[i].push_back(binary_car_plate.at<double>(i,j));
cout<<row_hists[i][j]<<" "<<
binary_car_plate.at<double>(i,j)<<endl;
}
}
vector<double> sum_each_row(binary_car_plate.cols);
double sum=0;
unsigned int n=sum_each_row.size();
for(unsigned int j=0;j<n;j )
{
for(unsigned int i=0;i<row_hists.size();i )
{
sum =row_hists[i][0];
}
sum_each_row.push_back(sum);
sum=0;
}
cout<<"***************"<<endl;
// std::vector<double>::iterator biggest
// = std::max_element(std::begin(v), std::end(v));
//找到vector中的最小值
auto row_min = std::min_element(std::begin(sum_each_row), std::end(sum_each_row));
cout<<"row_min "<<*row_min<<endl;
//对vector中元素求和
double sum_sum_each_row=accumulate(sum_each_row.begin(),sum_each_row.end(),0);
cout<<"sum_sum_each_row "<<sum_sum_each_row<<endl;
//row的平均值
double row_average=sum_sum_each_row/double(binary_car_plate.cols);
double row_threshold=double(double(*row_min) row_average)/2.0;
cout<<"row_threshold "<<row_threshold<<endl;
vector<double> wave_peaks=find_waves(row_threshold,sum_each_row);
// for(auto iter=wave_peaks.cbegin();iter!=wave_peaks.cend();iter )
// {
// cout<<*iter<<endl;
// }
//挑选跨度最大的波峰
double wave_span=0.0;
vector<double> select_span;
for(unsigned int i=0;i<wave_peaks.size();i )
{
double span=wave_peaks[1]-wave_peaks[0];
if( span >= wave_span)
{
wave_span=span;
select_span.push_back(wave_peaks[i]);
}
}
Mat plate_binary_img= binary_car_plate(Rect(select_span[0],select_span[1],
binary_car_plate.cols, binary_car_plate.rows));
imshow("plate_binary_img",plate_binary_img);
waitKey(0);
return plate_binary_img;
}
图像聚类部分
计算街区距离
代码语言:javascript复制//进行字符分割
//计算曼哈顿距离,即街区距离
int dist_Vec( vector<double>& A,vector<double>& B )
{
int dist=0;
for(unsigned i=0, n=A.size();i<n;i )
{
dist =abs(A[i]-B[i]);
}
return dist;
}
可能聚类中心
代码语言:javascript复制//返回中心矩阵
//功能:随机选取k个质心
//输出:centroids #返回一个m*n的质心矩阵
Mat cent_coord(Mat data, double k)
{
int n=data.cols;//宽,shape[1]
Mat __data=cv::Mat::zeros(Size(k,n),CV_8UC1);
//double row_min=1000;
//double row_max=0;
for(int j=0;j<n;j )
{
/*
for( int i=0;i<data.rows;i )
{
if(row_min < data.at<double>(i,j))
{
row_min=data.at<double>(i,j);
}
}
double row_min_J=row_min;
for( int i=0;i<data.rows;i )
{
if(row_max > data.at<double>(i,j))
{
row_max=data.at<double>(i,j);
}
}
double range_J=row_max-row_min_J;
for(int i=0;i< k;i )
{
__data.at<float>(i,j)=row_min_J range_J*(i 1)/k;
}*/
//尝试以下代替上面注释的
//取出第j列
Mat data_row;
for(int i=0;i<data.rows;i )
{
data_row=data.colRange(i,j);
}
//取出mat 中最大值和最小值
double minVal=0,maxVal=0;
Point minPt,maxPt;
minMaxLoc(data_row,&minVal,&maxVal,&minPt,&maxPt);
double row_min_J=minVal;
double range_J=maxVal-row_min_J;
for(int i=0;i< k;i )
{
__data.at<float>(i,j)=row_min_J range_J*(i 1)/k;
}
}
return __data;
}
K-均值聚类
代码语言:javascript复制//c 返回多个函数值
//设定一个结构体就好
struct Mul_Mat
{
Mat center;
Mat cluster;
};
Mul_Mat K_means;
//K均值聚类算法分割字符
Mul_Mat kMeans(Mat data, double k)
{
int m=data.rows;//高 ,shape[0]
K_means.cluster=cv::Mat::zeros(Size(m,2),CV_8UC1);
K_means.center= cent_coord( data, k);
bool clusterChanged=true;
vector<double> center,dataset;
while(clusterChanged)
{
clusterChanged = false;
for(int i=0;i<m;i )
{
double min_dist=DBL_MAX;
double min_index=-1;
for(int j=0;j<k;j )
{
center.push_back(K_means.center.at<double>(i,j));
dataset.push_back(K_means.center.at<double>(j,j));
}
for(int j=0;j<k;j )
{
double dist_means=dist_Vec(center,dataset);
if(dist_means < min_dist)
{
min_dist=dist_means;
min_index=j;
}
}
if(K_means.cluster.at<double>(i,0) != min_index)
clusterChanged = true;
for(int j=0;j<k;j )
K_means.cluster.at<double>(i,j)=min_dist*min_dist;
}
vector<double> pts_cluster;
for(int i=0;i<k;i )
{
for(int i=0;i<m;i )
{
if(K_means.cluster.at<double>(i,0)!=0)
pts_cluster.push_back(K_means.cluster.at<double>(i,0));
}
//vector 求均值
double sum = std::accumulate(std::begin( pts_cluster), std::end( pts_cluster), 0.0);
double mean = sum / pts_cluster.size(); //均值
for(int j=0;j<m;j )
K_means.center.at<double>(i,j)=mean;
}
}
return K_means;
}
改进的K聚类
代码语言:javascript复制//bik 均值,降低SSE, 二分K均值算法
//返回一个包含质心的列表 和 数组
pair<vector<pair<double,double>>,Mat> bk_means(Mat& data,double& k)
{
int m=data.rows;
Mat cluster_=cv::Mat::zeros(Size(m,2),CV_8UC1);
vector<pair<double,double>> centList(m);
vector<double> centroid;
double sum=0.0;
//对所有列取均值
for(int i=0;i<data.cols;i )
{
for(int j=0;j<m;j )
{
sum =data.at<double>(i,j);
}
double mean_row=sum/double(m);
centroid.push_back(mean_row);
sum=0;
}
vector<double> row_data;
//计算每个点到质心的距离差
for(int i=0;i<m;i )
{
for( int j=0;j<data.cols;j )
{
row_data.push_back(data.at<double>(i,j));
}
cout<<" "<<dist_Vec(centroid,row_data)<<endl;
// cluster_.at<double>(i,1)=dist_Vec(centroid,row_data);
cluster_.at<double>(i,1)=255;
}
vector<double> row_cluster_;
cout<<"cluster.row "<<cluster_.rows<<"cluster "<<cluster_.cols<<endl;
for(int j=0;j<cluster_.cols;j )
row_cluster_.push_back(cluster_.at<double>(j,0));
vector<double> is_i;
cout<<"centList.size() "<<centList.size()<<endl;
while(k)
{
double lowest_SSE=DBL_MAX;
for(unsigned int i=0;i<centList.size();i )
{
if(cluster_.at<double>(i,0)==i)
is_i.push_back(1);
else
is_i.push_back(0);
}
Mat is_no0_cluster_;
for(unsigned int i=0;i<is_i.size();i )
{
if(is_i[i]!=0)
is_no0_cluster_.at<double>(0,i)=i;
is_no0_cluster_.at<double>(1,i)=0;
}
Mat _data;
for(unsigned int i=0;i<is_i.size();i )
{
int n=is_no0_cluster_.at<double>(0,i);
for(int j=0;j<data.rows;j )
_data.at<double>(i,j)=data.at<double>(n,j);
}
Mul_Mat bik=kMeans( _data, 2.0);
double sum_cluster_row1=0;
cout<<bik.cluster.rows<<" bik.cluster.rows"<<endl;
// for(int i=0;i<bik.cluster.rows;i )
// sum_cluster_row1 =bik.cluster.at<double>(i,1);
//
vector<double> is_no_i;
for(unsigned int i=0;i<centList.size();i )
{
if(cluster_.at<double>(i,0)!=i)
is_no_i.push_back(1);
else
is_no_i.push_back(0);
}
double sum_no_splite=0;
for(unsigned int i=0;i<is_no_i.size();i )
sum_no_splite =cluster_.at<double>(is_no_i[i],1);
//
unsigned int best_cent_splite=0;
unsigned int v;
Mat best_cents;
Mat best_cluster;
for(v=0;v<centList.size();v )
{
if(sum_cluster_row1 sum_no_splite < lowest_SSE )
{
best_cent_splite=v;
best_cents=bik.center;
best_cluster=bik.cluster;
lowest_SSE=sum_cluster_row1 sum_no_splite;
}
}
//
vector<double> is_1,is_0;
for(unsigned int i=0;i<centList.size();i )
{
if(cluster_.at<double>(i,0)==1)
{
is_1.push_back(1);
is_0.push_back(0);
}
else
{
is_1.push_back(0);
is_0.push_back(1);
}
}
Mat is_1_, is_0_;
for(unsigned int i=0;i<is_1.size();i )
{
if(is_1[i]!=0)
is_1_.at<double>(0,i)=i;
is_1_.at<double>(1,i)=0;
if(is_0[i] !=0)
is_0_.at<double>(0,i)=i;
is_0_.at<double>(1,i)=0;
}
for(unsigned int i=0;i<is_i.size();i )
{
int n=is_1_.at<double>(0,i);
int m=best_cluster.at<double>(i,0);
int k=is_0_.at<double>(0,i);
for(int j=0;j<best_cluster.cols;j )
{
best_cluster.at<double>(n,m)=centList.size();
best_cluster.at<double>(k,m)=best_cent_splite;
}
}
//返回值
cluster_= best_cluster;
for(unsigned int i=0;i<centList.size();i )
{
// centList.push_back(make_pair(best_cents.at<double>(0,i),
// best_cents.at<double>(1,i)));
centList.push_back(make_pair(255,
255));
}
k--;
}
pair<vector<pair<double,double>>, Mat> __data=std::make_pair(centList, cluster_);
return __data;
}
分割字符
代码语言:javascript复制//此函数用来对车牌的二值图进行水平方向的切分,将字符分割出来
//输入:车牌的二值图,rows * cols的数组形式
//输出: 由分割后的车牌单个字符图像二值图矩阵组成的列表
//自定义pair排序
/*
bool cmp(pair<double,double> a, pair<double,double> b)
{
if (a.first < b.first)
return true;
else
return false;
}*/
//对vector 进行排序,并返回索引值
struct node
{
pair<double,double> value;
int index;
};
bool cmp(struct node a, struct node b)
{
if (a.value.first< b.value.first)
{
return true;
}
return false;
}
void sort_indexes(vector<int> &idx, vector<pair<double,double>> &v)
{
node* a = new node[v.size()];
for (unsigned int i = 0; i < v.size(); i )
{
a[i].value = v[i];
a[i].index = i;
}
std::sort(a, a v.size(), cmp);
for (unsigned int i = 0; i < v.size(); i )
{
idx.push_back(a[i].index);
}
delete[] a;
}
vector<double> row_list,col_list;
vector<Mat> split_carPlate_col(Mat& img)
{
for(int i=0;i<img.cols;i )
{
for(int j=0;j<img.rows;j )
{
// cout<<img.at<double>(i,j)<<endl;
if(img.at<double>(i,j)==0)//????? 255
{
row_list.push_back(i);
col_list.push_back(j);
}
}
}
cout<<row_list.size()<<" "<<col_list.size()<<endl;
Mat dataArr(row_list.size(),col_list.size(), CV_8UC3);
for(unsigned int i=0;i<col_list.size();i )
{
dataArr.at<double>(col_list[i],row_list[i])=255;
}
vector<pair<double,double>> centroids;
Mat clusterAssment;
double k=7.0;
std::tie(centroids,clusterAssment)=bk_means(dataArr,k);
//sort(centroids.begin(),centroids.end(),cmp);
vector<int> index;
sort_indexes(index,centroids);
vector<double> split_list;
for(unsigned int i=0;i<centroids.size();i )
{
int j=index[i];
//nozero
vector<int> is_index;
for(unsigned int i=0;i<centroids.size();i )
{
if(clusterAssment.at<double>(i,0)==j)
is_index.push_back(1);
else
is_index.push_back(0);
}
Mat is_index_cluster_;
for(unsigned int i=0;i<is_index.size();i )
{
if(is_index[i]!=0)
is_index_cluster_.at<double>(0,i)=i;
is_index_cluster_.at<double>(1,i)=0;
}
Mat _data;
for(unsigned int i=0;i<is_index.size();i )
{
int n=is_index_cluster_.at<double>(0,i);
for(int j=0;j<dataArr.rows;j )
_data.at<double>(i,j)=dataArr.at<double>(n,j);
}
//取出mat 中最大值和最小值
double minVal=0,maxVal=0;
Point minPt,maxPt;
minMaxLoc(_data,&minVal,&maxVal,&minPt,&maxPt);
split_list.push_back(minPt.y);
split_list.push_back(maxPt.y);
split_list.push_back(minPt.x);
split_list.push_back(maxPt.x);
}
vector<Mat> character_list;
for(unsigned int i=0;split_list.size();i )
{
//????????????
Mat single_character_Arr=img(Rect(split_list[0],split_list[1],split_list[2],split_list[3]));
character_list.push_back(single_character_Arr);
imshow("single_character_Arr",single_character_Arr);
waitKey(0);
}
return character_list;
}