车牌识别从0到1之C++实现

2022-06-16 14:32:01 浏览数 (1)

车牌识别理论不做概述,网上比比皆是,请自行搜索哦。一个纯干货的公众号,断断续续写了一周,贴出代码,供交流学习

完整测试视频 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;

}

0 人点赞