OpenCV3实现车牌识别(C++版)

这篇文章主要为大家详细介绍了OpenCV3实现车牌识别功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了OpenCV3实现车牌识别的具体代码,供大家参考,具体内容如下

车牌识别(基于OpenCV3.4.7+VS2017)

视频识别

蓝色车牌识别

视觉入坑的第一个Demo(注释很详细),因为本人之前拖延,一直没能写详细实现博客,先将代码贴出来供大家交流,个人认为精华部分在字符切割(直接用指针遍历像素加限制条件切割),车牌模板已上传,整个工程也已上传,后续完善各环节实现步骤详解。

头文件:Global.h

#ifdef GLOBAL

extern int flag_1;
extern bool flag;
extern bool specialFlag;
extern int captureRead
extern string carPlate;
extern char test[10];


extern struct stu1
{
    char number;
    Mat image;
    double matchDegree;
};
extern struct  stu
{
    Mat image;
    double matchDegree;
};

#endif

唯一的.cpp文件:PlateIdentify.cpp(说实话,这Demo挺 “C” 的)

#include <opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include"Global.h"
#include <windows.h>
#include <string>

using namespace std;
using namespace cv;

void fillHole(const Mat srcBw, Mat &dstBw);           //填补算法
Mat cutOne(Mat cutImage);         //边框切割算法
void CharCut(Mat srcImage);            //单个字符切割算法
Mat Location(Mat srcImage);            //图像识别算法
void SingleCharCut(Mat doubleImage, int k1, int k2);
void ShowChar();
void MatchProvince();
void MatchNumber();
void readProvince();
void readNumber();
void VideoShow(Mat videoImage);
void GetStringSize(HDC hDC, const char* str, int* w, int* h);
void putTextZH(Mat &dst, const char* str, Point org, Scalar color, int fontSize, const char* fn, bool italic, bool underline);


int flag_1;          //判断是否倾斜,需不需要二次定位车牌
bool flag;       //判断提取是否成功
bool specialFlag = false;    //针对嵌套车牌
int captureRead = 0;
int videoFlag = 0;
string carPlateProvince = " ";
string carPlate = " ";
char test[10];
vector<Mat>  singleChar;         //字符图片容器

int main(int argc, char *argv[])
{
    //计时开始
    double time0 = static_cast<double>(getTickCount());

    //视频操作
    VideoCapture capture("1.mp4");
    Mat srcImage;
    Mat theFirst;
    int singleCharLength;

    //读取字符图片
    readProvince();
    readNumber();

    while (1) {
        capture >> srcImage;
        try {

            if (!srcImage.data) { printf("视频识别结束    \n"); return 0; }

            if (srcImage.rows >= srcImage.cols)
            {
                resize(srcImage, srcImage, Size(640, 640 * srcImage.rows / srcImage.cols));
            }
            else
            {
                resize(srcImage, srcImage, Size(400 * srcImage.cols / srcImage.rows, 400));
            }

            //车牌定位

            theFirst = Location(srcImage);

            if (flag)
            {
                if (flag_1 == 1)                      //旋转后要再次定位去上下杂边
                {
                    theFirst = Location(theFirst);
                    flag_1 = 0;
                }
            }
            if (flag)
            {
                //车牌切割(切割上下边,去除干扰)
                theFirst = cutOne(theFirst);
                //单个字符切割
                CharCut(theFirst);
                singleCharLength = singleChar.size();
                printf("采取字符轮廓数                               %d\n", singleCharLength);
                ShowChar();
                if (singleCharLength >= 7)
                {

                    MatchProvince();
                    MatchNumber();
                }

                singleChar.clear();
            }

        }
        catch (Exception e) {

            cout << "Standard ecxeption : " << e.what() << " \n" << endl;

        }

        if (waitKey(30) >= 0)
            break;
        //延时30ms

    }
        //imwrite("match\\xxxxxx.bmp", singleChar[2]);


                    
    time0 = ((double)getTickCount() - time0) / getTickFrequency();
    cout << "运行时间" << time0 << "秒" << endl;


    waitKey(0);
}

void fillHole(const Mat srcBw, Mat &dstBw)
{
    Size imageSize = srcBw.size();
    Mat Temp = Mat::zeros(imageSize.height + 2, imageSize.width + 2, srcBw.type());//延展图像
    srcBw.copyTo(Temp(Range(1, imageSize.height + 1), Range(1, imageSize.width + 1)));

    cv::floodFill(Temp, Point(0, 0), Scalar(255));

    Mat cutImg;//裁剪延展的图像
    Temp(Range(1, imageSize.height + 1), Range(1, imageSize.width + 1)).copyTo(cutImg);

    dstBw = srcBw | (~cutImg);
}

Mat Location(Mat srcImage)
{
    //判断变量重赋值
    flag = false;

    //用于旋转车牌
    int    imageWidth, imageHeight;            //输入图像的长和宽
    imageWidth = srcImage.rows;                 //获取图片的宽
    imageHeight = srcImage.cols;                 //获取图像的长
    //!!!!!!!!!!!!!!!!!!!
    Mat blueROI = srcImage.clone();
    cvtColor(blueROI, blueROI, CV_BGR2HSV);
    //namedWindow("hsv图");
    //imshow("hsv图", blueROI);
    //中值滤波操作
    medianBlur(blueROI, blueROI, 3);
    //namedWindow("medianBlur图");
    //imshow("medianBlur图", blueROI);
    //将蓝色区域二值化
    inRange(blueROI, Scalar(100, 130, 50), Scalar(124, 255, 255), blueROI);
    //namedWindow("blue图");
    //imshow("blue图", blueROI);

    Mat element1 = getStructuringElement(MORPH_RECT, Size(2, 2));     //size()对速度有影响
    morphologyEx(blueROI, blueROI, MORPH_OPEN, element1);
    //namedWindow("0次K运算后图像");
    //imshow("0次K运算后图像", blueROI);

    Mat element0 = getStructuringElement(MORPH_ELLIPSE, Size(10, 10));     //size()对速度有影响
    morphologyEx(blueROI, blueROI, MORPH_CLOSE, element0);
    //namedWindow("0次闭运算后图像");
    //imshow("0次闭运算后图像", blueROI);
    vector<vector<Point>> contours;

    findContours(blueROI, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);


    int cnt = contours.size();
    cout << "number of contours   " << cnt << endl;  //打印轮廓个数
    if (cnt == 0)
    {
        if (!flag)        //在视频中显示
        {
            cout << "图中无车牌       " << endl;
            //namedWindow("提取车牌结果图");
            //imshow("提取车牌结果图", srcImage);    //显示最终结果图
            VideoShow(srcImage);
            return  srcImage;
        }
    }

    double area;
    double longside, temp, shortside, long2short;
    float  angle = 0;
    Rect rect;
    RotatedRect box;    //可旋转的矩形盒子
    Point2f vertex[4];        //四个顶点

    Mat image = srcImage.clone();        //为后来显示做准备
    Mat  rgbCutImg;                       //车牌裁剪图

    //box.points(vertex);            //获取矩形四个顶点坐标
    //length=arcLength(contour[i]);                        //获取轮廓周长
    //area=contourArea(contour[i]);                        //获取轮廓面积
    //angle=box.angle;           //得到车牌倾斜角度

    for (int i = 0; i < cnt; i++)
    {
        area = contourArea(contours[i]);              //获取轮廓面积
        if (area > 600 && area < 15000)     //矩形区域面积大小判断
        {
            rect = boundingRect(contours[i]);    //计算矩形边界
            box = minAreaRect(contours[i]);      //获取轮廓的矩形
            box.points(vertex);                  //获取矩形四个顶点坐标
            angle = box.angle;                   //得到车牌倾斜角度

            longside = sqrt(pow(vertex[1].x - vertex[0].x, 2) + pow(vertex[1].y - vertex[0].y, 2));
            shortside = sqrt(pow(vertex[2].x - vertex[1].x, 2) + pow(vertex[2].y - vertex[1].y, 2));
            if (shortside > longside)   //短轴大于长轴,交换数据
            {
                temp = longside;
                longside = shortside;
                shortside = temp;
                cout << "交换" << endl;
            }
            else
                angle += 90;
            long2short = longside / shortside;
            if (long2short > 1.5 && long2short < 4.5)
            {
                flag = true;
                for (int i = 0; i < 4; ++i)       //划线框出车牌区域
                    line(image, vertex[i], vertex[((i + 1) % 4) ? (i + 1) : 0], Scalar(0, 255, 0), 1, CV_AA);


                if (!flag_1)        //在视频中显示
                {
                    printf("提取成功\n");
                    /*namedWindow("提取车牌结果图");
                    imshow("提取车牌结果图", image);  */  //显示最终结果图
                    VideoShow(image);
                }

                rgbCutImg = srcImage(rect);
                //namedWindow("车牌图");
                //imshow("车牌图", rgbCutImg);//裁剪出车牌    
                break;              //退出循环,以免容器中变量变换
            }
        }
    }
    cout << "倾斜角度:" << angle << endl;
    if (flag  &&  fabs(angle) > 0.8)        //车牌过偏,转一下                偏移角度小时可不调用,后续找到合适范围再改进
    {
        flag_1 = 1;
        Mat RotractImg(imageWidth, imageHeight, CV_8UC1, Scalar(0, 0, 0));       //倾斜矫正图片
        Point2f center = box.center;           //获取车牌中心坐标
        Mat M2 = getRotationMatrix2D(center, angle, 1);       //计算旋转加缩放的变换矩阵 
        warpAffine(srcImage, RotractImg, M2, srcImage.size(), 1, 0, Scalar(0));       //进行倾斜矫正
        //namedWindow("倾斜矫正后图片",0);
        //imshow("倾斜矫正后图片", RotractImg);
        rgbCutImg = RotractImg(rect);      //截取车牌彩色照片
        //namedWindow("矫正后车牌照");
        //imshow("矫正后车牌照", rgbCutImg);
            /*cout << "矩形中心:" << box.center.x << "," << box.center.y << endl;*/
        return  rgbCutImg;
    }

    if (flag == false) {
        printf("提取失败\n");                      //后期加边缘检测法识别
        if (!flag_1)        //在视频中显示
        {
            /*namedWindow("提取车牌结果图");
            imshow("提取车牌结果图", image); */   //显示最终结果图
            VideoShow(image);
        }
    }
    return rgbCutImg;
}

Mat cutOne(Mat cutImage)
{
    //打印车牌长宽
    try {
        /*cout << "           cutImage.rows  :   " << cutImage.rows << endl;
        cout << "           cutImage.cols  :   " << cutImage.cols << endl;*/
        if(cutImage.rows >= cutImage.cols)
        resize(cutImage, cutImage, Size(320, 320 * cutImage.rows / cutImage.cols));
    }
    catch (Exception e)
    {
        resize(cutImage, cutImage, Size(320, 100));
    }
    /*namedWindow("Resize车牌图");
    imshow("Resize车牌图", cutImage);*/
    int height = cutImage.rows;
    cout << "\tHeight:" << height << "\tWidth:" << 320 << endl;
    if (height < 86)
    {
        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!处理新型嵌套车牌!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        printf("嵌套车牌\n");
        specialFlag = true;

    }

    Mat whiteROI = cutImage.clone();

    if (specialFlag)
    {
        cvtColor(whiteROI, whiteROI, CV_BGR2HSV);
        //将白色区域二值化
        //inRange(whiteROI, Scalar(0, 0, 0), Scalar(130, 50, 245), whiteROI);      //增大 S 即饱和度可以使hsv白色检测范围更大
        inRange(whiteROI, Scalar(0, 0, 0), Scalar(180, 100, 245), whiteROI);
        //namedWindow("specialFlagwhiteROI图");
        //imshow("specialFlagwhiteROI图", whiteROI);
    }
    else
    {
        GaussianBlur(whiteROI, whiteROI, Size(3, 3), 0, 0);
        /*namedWindow("GaussianBlur车牌图");
        imshow("GaussianBlur车牌图", whiteROI);*/

        cvtColor(whiteROI, whiteROI, CV_BGR2HSV);

        //medianBlur(whiteROI, whiteROI, 3);
        //namedWindow("Src_medianBlur图");
        //imshow("Src_medianBlur图", whiteROI);

        //将白色区域二值化
        //inRange(whiteROI, Scalar(0, 0, 10), Scalar(180, 30, 255), whiteROI);      //增大 S 即饱和度可以使hsv白色检测范围更大
        inRange(whiteROI, Scalar(0, 0, 10), Scalar(180, 120, 255), whiteROI);
        //namedWindow("whiteROI图");
        //imshow("whiteROI图", whiteROI);
    }


    /*
    Mat element0 = getStructuringElement(MORPH_ELLIPSE, Size(4, 4));     //size()对速度有影响
    morphologyEx(whiteROI, whiteROI, MORPH_OPEN, element0);
    namedWindow("OPEN图");
    imshow("OPEN图", whiteROI);
    */
    Mat dstImage = cutImage.clone();
    vector<vector<Point>> contours;
    findContours(whiteROI, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    drawContours(dstImage, contours, -1, Scalar(0, 0, 255), 1);
    //namedWindow("疑似字符轮廓识别图");
    //imshow("疑似字符轮廓识别图", dstImage);
    inRange(dstImage, Scalar(0, 0, 255), Scalar(0, 0, 255), dstImage);
    //namedWindow("字符大轮廓图");
    //imshow("字符大轮廓图", dstImage);
    /*fillHole(dstImage, dstImage);
    namedWindow("填补轮廓后图");
    imshow("填补轮廓后图", dstImage);*/

    int row1 = 2;
    int row2 = dstImage.rows;
    int rowMax = dstImage.rows - 1;            //开区间,防止越界
    int colMax = dstImage.cols - 1;            //开区间,防止越界
    int addFirst = 10;
    int addFirst0 = 0;
    int addFirst1 = 0;
    int addFirst2 = 0;
    //测中间像素
    //dstImage.at<uchar>(rowMax-1, colMax-1);
    //cout << "Width:" << j << endl;

    int addFirstTemp = addFirst;          //第一次用时已经改变数值,容易忽略!!!!!

    uchar* data;

    //裁剪上下边。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
    //上边
    for (int i = 2; i < rowMax / 3; i++, addFirst1 = 0)                           //   6     刚刚好
    {
        data = dstImage.ptr<uchar>(i);
        for (int j = 2; j < colMax; j++)
        {
            if (data[j] == 255)
            {
                addFirst1++;
            }
        }
        if (addFirst1 < addFirst)                       //筛选最小值所在行
        {
            row1 = i;
            addFirst = addFirst1 + 3;
            //cout << "行头" << row1 << endl;
            //flag_x = 1;
        }
    }
    //下边
    for (int i = rowMax - 2; i > rowMax - rowMax / 4; i--, addFirst2 = 0)                //   6     刚刚好
    {
        data = dstImage.ptr<uchar>(i);
        for (int j = 2; j < colMax; j++)
        {
            if (data[j] == 255)
            {
                addFirst2++;
            }
        }
        if (addFirst2 < addFirstTemp)                       //筛选最小值所在行
        {
            row2 = i;
            addFirstTemp = addFirst2 + 3;
            //cout << "行底" << row2 << endl;
            //flag_y = 1;
        }
    }


    int orow;
    orow = row2 - row1;
    Mat w_image;
    Mat  rgb_w_image;
    w_image = dstImage(Rect(0, row1, colMax, orow));
    rgb_w_image = cutImage(Rect(0, row1, colMax, orow));
    //namedWindow("裁剪上下图");
    //imshow("裁剪上下图", w_image);

    int rowMax_ALT = w_image.rows - 1;            //开区间,防止越界(注意,裁剪完上下后要重新写行和宽,因为行和宽已经改变)
    int colMax_ALT = w_image.cols - 1;            //开区间,防止越界(注意,裁剪完上下后要重新写行和宽,因为行和宽已经改变)
    int col_1 = 2;
    int col_2 = w_image.cols;
    int add = 2;
    int add1 = 0;
    int add2 = 0;

    int addTemp = add;        //第一次用时已经改变数值,容易忽略!!!!!

    //裁剪左右边。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
    //左边
    //for (int i = 0; i < colMax_ALT / 18; i++, add1 = 0)                           //       刚刚好
    //{
    //    for (int j = 2; j < rowMax_ALT; j++)
    //    {
    //        data = dstImage.ptr<uchar>(j);
    //        if (data[i] == 255)
    //        {
    //            add1++;
    //        }
    //    }
    //    if (add1 < add)                       //筛选最小值所在列
    //    {
    //        col_1 = i;
    //        add = add1 + 1;
    //    }
    /

本文标题为:OpenCV3实现车牌识别(C++版)

基础教程推荐