【深度学习模型】智云视图中文车牌识别源码解析(一)

【深度学习模型】智云视图中文车牌识别源码解析

HyperLPR是 智云视图(http://www.zeusee.com)开源的一个使用深度学习针对对中文车牌识别的实现,HyperLPR可以识别多种中文车牌包括白牌,新能源车牌,使馆车牌,教练车牌,武警车牌等。
Github地址:https://github.com/zeusees/HyperLPR

代码结构

  • CNNRecognizer.cpp:加载模型预测标签;
  • FastDeskew.cpp:快速旋转歪斜的车牌;
  • FineMapping.cpp:绘制车牌检测框;
  • Pipeline.cpp:这是串联程序,串联起了plateDetection、fineMapping、plateSegmentation、generalRecognizer、segmentationFreeRecognizer;
  • PlateDetection.cpp:探测车牌在图中何处位置;
  • PlateSegmentation.cpp:将每一个字符从车牌图像中分离;
  • Recognizer.cpp:挨个识别字符并返回识别结果;
  • SegmentationFreeRecognizer.cpp:检测单个图像并把结果保存在mapping_table;

代码量还是不小,所以打算两周写完8篇解析,说实话代码质量… …但是功能还是实现了的吧,如果看到后面代码质量太无语,就停更(话说在github上人气还蛮高);

可识别和待支持的车牌的类型

  • [x] 单行蓝牌
  • [x] 单行黄牌
  • [x] 新能源车牌
  • [x] 白色警用车牌
  • [x] 使馆/港澳车牌
  • [x] 教练车牌
  • [x] 武警车牌
  • [ ] 民航车牌
  • [ ] 双层黄牌
  • [ ] 双层武警
  • [ ] 双层军牌
  • [ ] 双层农用车牌
  • [ ] 双层个性化车牌

特性

  • 速度快 720p ,单核 Intel 2.2G CPU (macbook Pro 2015)平均识别时间低于100ms
  • 基于端到端的车牌识别无需进行字符分割
  • 识别率高,仅仅针对车牌ROI在EasyPR数据集上,0-error达到 95.2%, 1-error识别率达到 97.4% (指在定位成功后的车牌识别率)
  • 轻量 总代码量不超1k行

解析

今天先解析FastDeskew.cpp:

/*----------------------------------------------*/
/*--------------用于快速旋转图像----------------*/
/*----------------------------------------------*/

#include <../include/FastDeskew.h>


namespace pr{
    //定义最小旋转角度;
    const int ANGLE_MIN = 30 ;
    //定义最大旋转角度;
    const int ANGLE_MAX = 150 ;
    //定义车牌高;
    const int PLATE_H = 36;
    //定义车牌宽;
    const int PLATE_W = 136;

    /*
    -----------计算角度------------

    @x : 边长;
    @y : 边长;
    */
    int angle(float x,float y)
    {
        //计算角度;
        return atan2(x,y)*180/3.1415;
    }

    /*
    -----------计算一组角度的平均角度(类似平滑卷积)------------

    @angle_list  : 一组角度值;
    @windowsSize : 平滑计算时窗口大小;
    */
    std::vector<float> avgfilter(std::vector<float> angle_list,int windowsSize) {
        //定义vector angle_list_filtered;
        std::vector<float> angle_list_filtered(angle_list.size() - windowsSize + 1);
        //循环计算每个
        for (int i = 0; i < angle_list.size() - windowsSize + 1; i++) {
            //清空当前angle的均值;
            float avg = 0.00f;
            //累计计算总的angle;
            for (int j = 0; j < windowsSize; j++) {
                avg += angle_list[i + j];
            }
            //求平均;
            avg = avg / windowsSize;
            //对应平均值写入angle_list_filtered;
            angle_list_filtered[i] = avg;
        }
        //返回结果;
        return angle_list_filtered;
    }


    /*
    ------------绘制直方图---------------

    @seq 一组数据,需要画到直方图上;
    */
    void drawHist(std::vector<float> seq){
        //直方图声明;
        cv::Mat image(300,seq.size(),CV_8U);
        //直方图清零;
        image.setTo(0);

        //循环,将seq中每个数值画上去;
        for(int i = 0;i<seq.size();i++)
        {
            //先画seq中最大的值;
            float l = *std::max_element(seq.begin(),seq.end());
            //高度挤压到在0~300内;
            int p = int(float(seq[i])/l*300);
            //画上去;
            cv::line(image,cv::Point(i,300),cv::Point(i,300-p),cv::Scalar(255,255,255));
        }
        //展示这个结果;
        cv::imshow("vis",image);
    }

    /*
    ------------校正车牌图像---------------

    @skewPlate : 需要校正的图像;
    @angle     : 需要旋转的角度;
    @maxAngle  : 最大旋转的角度;
    */
    cv::Mat  correctPlateImage(cv::Mat skewPlate,float angle,float maxAngle)
    {
        //声明旋转后的图像;
        cv::Mat dst;
        //获取待处理图像的尺寸size_o;
        cv::Size size_o(skewPlate.cols,skewPlate.rows);
        //延长填充变量声明;
        int extend_padding = 0;
        //计算延长填充大小;
        extend_padding = static_cast<int>(skewPlate.rows*tan(cv::abs(angle)/180* 3.14) );
        //计算延展后的图像尺寸size;
        cv::Size size(skewPlate.cols + extend_padding ,skewPlate.rows);
        //计算旋转后的宽;
        float interval = abs(sin((angle /180) * 3.14)* skewPlate.rows);
        //原图像构成的矩形的四个顶点坐标;
        cv::Point2f pts1[4] = {cv::Point2f(0,0),cv::Point2f(0,size_o.height),cv::Point2f(size_o.width,0),cv::Point2f(size_o.width,size_o.height)};
        //逆时针旋转;
        if(angle>0) {
            //新图像构成的矩形的四个顶点坐标;
            cv::Point2f pts2[4] = {cv::Point2f(interval, 0), cv::Point2f(0, size_o.height),
                                   cv::Point2f(size_o.width, 0), cv::Point2f(size_o.width - interval, size_o.height)};
            //计算变换矩阵;
            cv::Mat M  = cv::getPerspectiveTransform(pts1,pts2);
            //将skewPlate经M变换为大小为size的图像dst;
            cv::warpPerspective(skewPlate,dst,M,size);
        }
        //顺时针旋转;
        else {
            //新图像构成的矩形的四个顶点坐标;
            cv::Point2f pts2[4] = {cv::Point2f(0, 0), cv::Point2f(interval, size_o.height), cv::Point2f(size_o.width-interval, 0),
                                   cv::Point2f(size_o.width, size_o.height)};
            //计算变换矩阵;
            cv::Mat M  = cv::getPerspectiveTransform(pts1,pts2);
            //将skewPlate经M变换为大小为size的图像dst;
            cv::warpPerspective(skewPlate,dst,M,size,cv::INTER_CUBIC);
        }
        //返回结果;
        return  dst;
    }


    /*
    ------------快速旋转车牌图像---------------

    @skewPlate : 需要校正的图像;
    @blockSize : 角点检测步长;
    */
    cv::Mat fastdeskew(cv::Mat skewImage,int blockSize){
        //过滤的窗口大小;
        const int FILTER_WINDOWS_SIZE = 5;
        //声明一个angle_list存储角度;
        std::vector<float> angle_list(180);
        //为其分配内存;
        memset(angle_list.data(),0,angle_list.size()*sizeof(int));
        //用于备份原图像的图像bak;
        cv::Mat bak;
        //将原图赋值给bak;
        skewImage.copyTo(bak);
        //如果是rgb图像;
        if(skewImage.channels() == 3)
            //先把它转化为黑白图;
            cv::cvtColor(skewImage,skewImage,cv::COLOR_RGB2GRAY);
        //如果是黑白图;
        if(skewImage.channels() == 1)
        {
            //声明特征矩阵eigen;
            cv::Mat eigen;
            //计算图像块的特征值和特征向量,用于角点检测,结果保存在eigen;
            cv::cornerEigenValsAndVecs(skewImage,eigen,blockSize,5);
            //遍历skewImage的每个像素;
            for( int j = 0; j < skewImage.rows; j+=blockSize )
            { for( int i = 0; i < skewImage.cols; i+=blockSize )
                {
                    //(x2,y2)存储的是skewImage存在eigen的角点信息;
                    float x2 = eigen.at<cv::Vec6f>(j, i)[4];
                    float y2 = eigen.at<cv::Vec6f>(j, i)[5];
                    //计算角度;
                    int angle_cell = angle(x2,y2);
                    //在对应角度上作累计计数;
                    angle_list[(angle_cell + 180)%180]+=1.0;
                }
            }
        }
        //计算平滑窗口大小为FILTER_WINDOWS_SIZE的平均角度过滤;
        std::vector<float> filtered = avgfilter(angle_list,FILTER_WINDOWS_SIZE);
        //计算均角过滤的最大位置;
        int maxPos = std::max_element(filtered.begin(),filtered.end()) - filtered.begin() + FILTER_WINDOWS_SIZE/2;
        //超过了ANGLE_MAX;
        if(maxPos>ANGLE_MAX)
            //作角度变换;
            maxPos = (-maxPos+90+180)%180;
        //未超过ANGLE_MAX;
        if(maxPos<ANGLE_MIN)
            //控制在90内;
            maxPos-=90;
        //再变换;
        maxPos=90-maxPos;
        //按maxPos校正图像;
        cv::Mat deskewed = correctPlateImage(bak, static_cast<float>(maxPos),60.0f);
        //返回结果;
        return deskewed;
    }



}//namespace pr

猜你喜欢

转载自blog.csdn.net/hanss2/article/details/81031497