基于模板匹配的车牌识别系统(UI界面+模板匹配C++实现)

有问题欢迎联系作者WX Qwe1398276934

摘要: 车牌识别系统用于识别车牌,从图像、视频、摄像设备等图像中分析,对小区门口的车辆进行自动化识别检测。在介绍算法原理的同时,给出C++的模板匹配实现代码,QT的UI界面以及模板图像。车牌常用于小区门口、停车场等车流量较为密集的场所,利用传统图像模板匹配算法,自动对图片进行识别、分析。博文提供了完整的C++代码和使用教程,适合入门的朋友参考,完整的代码资源文件请转到文末的下载链接。

visusal studio完整工程项目下载,评论区留言或私信作者

前言

车牌识别技术是一项基于计算机视觉和图像处理的先进技术,它的出现改变了交通管理和安防监控的方式。随着城市交通的快速增长和车辆数量的不断增加,传统手动检查车牌的方法已经无法满足日益增长的需求。而车牌识别技术的出现,为自动化、高效的车辆识别提供了可行的解决方案。通过使用高分辨率摄像头和复杂的图像处理算法,车牌识别技术能够准确地捕捉车辆车牌的信息,并将其转化为数字或文字形式,实现自动化的车辆识别和数据管理。这项技术不仅提高了交通管理和安防监控的效率,还为城市交通的智能化发展奠定了坚实的基础。

这里给出博主设计的软件界面,一贯的简单作风哈哈,功能也可以满足图片初始界面如下图:

选择图片之后,灰度图效果如下图所示

选择图片之后,边缘检测结果图片如下图所示 

获取车牌结果如下图所示: 

识别图片结果,下图所示,这里没有显示中文和特殊符号 

模板匹配的原理基于假设:车牌在图像中的出现形式与模板图像非常相似。通过计算模板与图像区域的相似度,可以找到最佳匹配区域,从而定位到车牌的位置。

需要注意的是,模板匹配方法对光照、尺度变化和视角变化等因素较为敏感。在实际应用中,可能需要使用其他预处理步骤、特征提取算法和机器学习方法来提高车牌识别系统的准确性和鲁棒性。模板匹配通常作为车牌识别系统中的一部分,并结合其他技术共同完成车牌字符的分割和识别任务。

  1. 车牌识别的模板匹配是一种基于相似性度量的图像匹配方法。原理基于以下步骤:

  2. 载入车牌图像和模板图像:首先,需要准备一张包含车牌的图像作为输入图像,以及一个包含车牌字符样式的模板图像。

  3. 转换为灰度图像:为了简化处理过程,将输入图像和模板图像转换为灰度图像。这可以通过使用灰度转换函数(如OpenCV中的cv2.cvtColor()函数)来实现。

  4. 获取模板图像的尺寸:在模板匹配过程中,需要知道模板图像的宽度和高度。这将用于定位匹配区域。

  5. 应用模板匹配算法:使用模板匹配算法(如OpenCV中的cv2.matchTemplate()函数),在灰度图像中搜索与模板最相似的区域。该算法通过在输入图像中滑动模板图像,计算模板与图像区域的相似度。常用的匹配方法包括平方差匹配、相关系数匹配和归一化相关系数匹配等。

  6. 获取最佳匹配结果:根据模板匹配算法的结果,可以通过函数(如OpenCV中的cv2.minMaxLoc()函数)找到匹配结果中的最大值和对应的位置。最大值表示最相似的匹配程度,位置表示匹配区域的左上角坐标。

  7. 绘制匹配结果:使用左上角坐标和模板图像的尺寸,可以在原始图像中绘制车牌区域的矩形框。这样可以直观地显示出车牌的位置。

进行图像预处理操作

#include"source.h"

using namespace std;
Mat Image_Preprocessing(Mat temp)//图像预处理
{
    Mat kernel = getStructuringElement(MORPH_RECT, Size(25, 25), Point(-1, -1));
    Mat open_gray_blur_Image;
    morphologyEx(temp, open_gray_blur_Image, MORPH_OPEN, kernel);
    Mat rst;
    subtract(temp, open_gray_blur_Image, rst, Mat());
    //imshow("rst", rst);
    Mat Canny_Image;
    Canny(rst, Canny_Image, 400, 200, 3);
    return Canny_Image;
}


进行图像形态学处理
 

Mat Morphological_Processing(Mat temp)//形态学处理
{
    //图片膨胀处理
    Mat dilate_image, erode_image;
    //自定义核:进行 x 方向的膨胀腐蚀
    Mat elementX = getStructuringElement(MORPH_RECT, Size(25, 1));
    Mat elementY = getStructuringElement(MORPH_RECT, Size(1, 19));
    Point point(-1, -1);
    dilate(temp, dilate_image, elementX, point, 2);
    erode(dilate_image, erode_image, elementX, point, 4);
    dilate(erode_image, dilate_image, elementX, point, 2);
    //自定义核:进行 Y 方向的膨胀腐蚀
    erode(dilate_image, erode_image, elementY, point, 1);
    dilate(erode_image, dilate_image, elementY, point, 2);
    //平滑处理 中值滤波
    Mat median_Image;
    medianBlur(dilate_image, median_Image, 15);
    medianBlur(median_Image, median_Image, 15);
    //imshow("中值滤波", median_Image);
    return median_Image;
}

对车牌进行定位

Mat Locate_License_Plate(Mat temp, Mat src, Mat gray_src)//车牌定位
{
    vector<vector<Point>> contours;
    findContours(temp, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    //画出轮廓
    drawContours(temp, contours, -1, Scalar(255), 1);
    //轮廓表示为一个矩形
    Mat Roi;
    for (int i = 0; i < contours.size(); i++)
    {
        RotatedRect rect = minAreaRect(Mat(contours[i]));
        Point2f p[4];
        rect.points(p);
        double axisLongTemp = 0.0, axisShortTemp = 0.0;//矩形的长边和短边
        axisLongTemp = sqrt(pow(p[1].x - p[0].x, 2) + pow(p[1].y - p[0].y, 2));  //计算长轴(勾股定理)
        axisShortTemp = sqrt(pow(p[2].x - p[1].x, 2) + pow(p[2].y - p[1].y, 2)); //计算短轴(勾股定理)
        double LengthTemp;     //中间变量
        if (axisShortTemp > axisLongTemp)   //短轴大于长轴,交换数据
        {
            LengthTemp = axisLongTemp;
            axisLongTemp = axisShortTemp;
            axisShortTemp = LengthTemp;
        }
        double rectArea = axisLongTemp * axisShortTemp;//计算矩形面积
        double Area = contourArea(Mat(contours[i]));//轮廓面积
        double rectDegree = Area / rectArea;//计算矩形度
        //这部分的条件判断可视实际情况做调整
        if (axisLongTemp / axisShortTemp >= 2.2 && axisLongTemp / axisShortTemp <= 5.1 && rectDegree > 0.63 && rectDegree < 1.37 && rectArea>2000 && rectArea < 50000)
        {
            for (int i = 0; i < 4; i++)       //划线框出车牌区域
                line(src, p[i], p[((i + 1) % 4) ? (i + 1) : 0], Scalar(0, 0, 255), 2, 8, 0);

            float width_height = (float)rect.size.width / (float)rect.size.height;
            float angle = rect.angle;
            if (width_height < 1)//处理图像中旋转角度大于90度的车牌
                angle = angle + 270;
            Mat rotMat = getRotationMatrix2D(rect.center, angle, 1);//获得矩形的旋转矩阵
            Mat warpImg;
            warpAffine(gray_src, warpImg, rotMat, src.size(), INTER_CUBIC);
            //imshow("仿射变换", warpImg);
            //图像切割
            Size minRectSize = rect.size;
            if (width_height < 1)
                swap(minRectSize.width, minRectSize.height);
            getRectSubPix(warpImg, minRectSize, rect.center, Roi);
        }
    }
    //imshow("test", src);
    //imshow("车牌提取结果", Roi);
    return Roi;
}


进行仿射变换,校正图片

Mat Affine_Transform(Mat temp)
{
    Mat warp_dstImage = Mat::zeros(100, 500, temp.type());
    Point2f srcTri[3];
    Point2f dstTri[3];
    //设置三个点来计算仿射变换
    srcTri[0] = Point2f(0, 0);
    srcTri[1] = Point2f(temp.cols, 0);
    srcTri[2] = Point2f(0, temp.rows);

    dstTri[0] = Point2f(0, 0);
    dstTri[1] = Point2f(500, 0);
    dstTri[2] = Point2f(0, 100);
    //计算仿射变换矩阵
    Mat warp_mat(2, 3, CV_32FC1);
    warp_mat = getAffineTransform(srcTri, dstTri);
    //对加载图形进行仿射变换操作
    warpAffine(temp, warp_dstImage, warp_mat, warp_dstImage.size());
    return warp_dstImage;
}

切割垂直边界

Mat Remove_Vertial_Border(Mat temp)
{
    Mat vline = getStructuringElement(MORPH_RECT, Size(1, temp.rows), Point(-1, -1));
    Mat dst1, temp1;
    erode(temp, temp1, vline);
    dilate(temp1, dst1, vline);
    //namedWindow("提取垂直线", WINDOW_AUTOSIZE);
    //imshow("提取垂直线", dst1);
    subtract(temp, dst1, temp, Mat());
    //imshow("切割车牌垂直边框结果", temp);
    return temp;
}

切割水平边界

Mat Remove_Horizon_Border(Mat temp)
{
    Mat hline = getStructuringElement(MORPH_RECT, Size(60, 1), Point(-1, -1));//矩形形状为:1*src.cols
    Mat dst1, temp1;
    erode(temp, temp1, hline);
    dilate(temp1, dst1, hline);
    //namedWindow("提取水平线", WINDOW_AUTOSIZE);
    //imshow("提取水平线", dst1);
    subtract(temp, dst1, temp, Mat());
    //imshow("切割车牌水平边框结果", temp);
    return temp;
}
Mat Horizon_Cut(Mat temp)
{
    int* counter_y = new int[temp.rows];
    for (int i = 0; i < temp.rows; i++)
        counter_y[i] = 0;
    for (int row = 0; row < temp.rows; row++)
    {
        int count = 0;
        for (int col = 0; col < temp.cols; col++)
        {
            if (temp.at<uchar>(row, col) == 255)
            {
                count++;
            }
        }
        if (count > 50)
        {
            counter_y[row] = 1;
        }
    }
    int count_temp = 0;
    int* record = new int[temp.rows];
    for (int i = 0; i < temp.rows; i++)
        record[i] = 0;
    for (int i = 0; i < temp.rows; i++)
    {
        if (counter_y[i] == 1)
        {
            count_temp++;
            record[i] = count_temp;
        }
        else
            count_temp = 0;
    }
    int max = record[0];
    int index = 0;
    for (int i = 1; i < temp.rows; i++)
    {
        if (max < record[i])
        {
            max = record[i];
            index = i;
        }
    }
    int index_row_begin = index - max + 1;
    int index_row_end = index;
    int height = index_row_end - index_row_begin;
    Mat image_preprocess = Mat::zeros(height, temp.cols, CV_8UC1);
    for (int row = 0; row < image_preprocess.rows; row++)
    {
        for (int col = 0; col < image_preprocess.cols; col++)
        {
            image_preprocess.at<uchar>(row, col) = temp.at<uchar>(row + index_row_begin, col);
        }
    }
    //imshow("image_preprocess", image_preprocess);
    return image_preprocess;
}

定位字符

void Locate_String(int* x_begin, int* x_end, Mat temp)
{
    int* counter_x = new int[temp.cols];//记录每一列的白像素个数
    for (int i = 0; i < temp.cols; i++)
        counter_x[i] = 0;
    for (int col = 0; col < temp.cols; col++)
    {
        int count = 0;
        for (int row = 0; row < temp.rows; row++)
        {
            if (temp.at<uchar>(row, col) == 255)
            {
                count++;
            }
        }
        counter_x[col] = count;
    }
    int index_col = 0;
    int number_width = 0;//记录字符宽度
    for (int i = 0; i < temp.cols - 1; i++)
    {
        if (counter_x[i] >= 4)//当每一列的白像素个数大于某一阈值,字符宽度开始累加
        {
            number_width++;
            if (number_width > 8)//10 当白像素个数大于某一阈值的连续列的个数大于10,开始更新字符位置信息
            {
                x_end[index_col] = i;
                x_begin[index_col] = i - number_width + 1;
                if (counter_x[i + 1] < 4)
                {
                    number_width = 0;
                    index_col++;
                }
            }
        }
        else
        {
            number_width = 0;
        }
        if (index_col >= 8)
            break;
    }
}

绘制结果

void Draw_Result(int* x_begin, int* x_end, Mat temp)
{
    int x, y;
    int width;
    int length;
    Mat Result = temp.clone();
    for (int i = 0; i < 8; i++)
    {
        x = x_begin[i];
        y = 0;
        width = x_end[i] - x_begin[i];
        length = temp.cols;
        Rect rect(x, y, width, length);
        Scalar color(255, 255, 255);
        rectangle(Result, rect, color, 2, LINE_AA);
    }
    //imshow("车牌号码分割结果", Result);
}

识别车牌

string Recognize_Lisence(int* x_begin, int* x_end, Mat temp)
{
    string res = "";
    int cycle_index = 0;
    for (int i = 0; i < 8; i++)
    {
        if (x_end[i] > 0)
            cycle_index++;
    }
    for (int i = 0; i < cycle_index; i++)
    {
        float error[28] = { 0 };
        //    //picture1是二值图像
        Mat picture1 = Mat::zeros(temp.rows, x_end[i] - x_begin[i], temp.type());
        for (int row = 0; row < picture1.rows; row++)
        {
            for (int col = 0; col < picture1.cols; col++)
            {
                picture1.at<uchar>(row, col) = temp.at<uchar>(row, col + x_begin[i]);
            }
        }
        Mat NUM[28];//字符匹配模板
        for (int i = 0; i < 28; i++)
        {
            stringstream stream;
            stream << "pictures/num_";
            stream << i;
            stream << ".bmp";
            String name = stream.str();
            NUM[i] = imread(name);
            if (NUM[i].empty())
            {
                cout << "未能读取" << name << endl;
            }
            cvtColor(NUM[i], NUM[i], COLOR_BGR2GRAY);
            threshold(NUM[i], NUM[i], 0, 255, THRESH_BINARY);

            Point2f srcTri[3];
            Point2f dstTri[3];
            Mat warp_mat(2, 3, CV_32FC1);
            //创建仿射变换目标图像与原图像尺寸类型相同
            Mat result = Mat::zeros(picture1.rows, picture1.cols, picture1.type());
            //设置三个点来计算仿射变换
            srcTri[0] = Point2f(0, 0);
            srcTri[1] = Point2f(NUM[i].cols, 0);
            srcTri[2] = Point2f(0, NUM[i].rows);
            dstTri[0] = Point2f(0, 0);
            dstTri[1] = Point2f(picture1.cols, 0);
            dstTri[2] = Point2f(0, picture1.rows);
            //计算仿射变换矩阵
            warp_mat = getAffineTransform(srcTri, dstTri);
            //对加载图形进行仿射变换操作
            warpAffine(NUM[i], result, warp_mat, picture1.size());
            threshold(result, result, 0, 255, THRESH_BINARY_INV);
            float error_sum = 0;
            float error_temp = 0;
            for (int row = 0; row < result.rows; row++)
            {
                for (int col = 0; col < result.cols; col++)
                {
                    error_temp = picture1.at<uchar>(row, col) - result.at<uchar>(row, col);
                    error_sum = error_sum + pow(error_temp, 2);
                }
            }
            error[i] = error_sum / (picture1.rows * picture1.cols * 255);
        }
        float min_error = error[0];
        int Index = 0;
        for (int i = 1; i < 28; i++)
        {
            if (min_error > error[i])
            {
                min_error = error[i];
                Index = i;
            }
        }
        if (Index == 10)
        {
            res += "E";
            cout << "E" << '\t';
        }
        else if (Index == 11) {
            res += "V";
            cout << "V" << '\t';
        }
        else if (Index == 12) {
            res += "苏";
            cout << "苏" << '\t';
        }
        else if (Index == 13)
        {
            res += "沪";
            cout << "沪" << '\t';
        }
        else if (Index == 14)
        {
            res += "B";
            cout << "B" << '\t';
        }
        else if (Index == 15)
        {
            res += "S";
            cout << "S" << '\t';
        }
        else if (Index == 16)
        {
            res += "京";
            cout << "京" << '\t';
        }
        else if (Index == 17)
        {
            res += "N";
            cout << "N" << '\t';
        }
        else if (Index == 18)
        {
            res += "J";
            cout << "J" << '\t';
        }
        else if (Index == 19)
        {
            res += "P";
            cout << "P" << '\t';
        }
        else if (Index == 20)
        {    
            res += "A";
            cout << "A" << '\t';
        }
        else if (Index == 21)
        {
            res += "浙";
            cout << "浙" << '\t';
        }
        else if (Index == 22)
        {
            res += "G";
            cout << "G" << '\t';
        }
        else if (Index == 23)
        {
            res += "U";
            cout << "U" << '\t';
        }
        else if (Index == 24)

        {
            res += "豫";
            cout << "豫" << '\t';
        }
        else if (Index == 25)
        {
            res += "K";
            cout << "K" << '\t';
        }
        else if (Index == 26)
        {
            res += "陕";
            cout << "陕" << '\t';
        }
        else if (Index == 27)
        {
            res += "·";
            cout << "·" << '\t';
        }
        else if (Index >= 0 && Index <= 9)
        {
            res += to_string(Index);
            cout << Index << '\t';
        }
    }
    cout << endl;
    return res;
}

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TechMasterPlus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值