OpenCV C++案例实战十《车牌号识别》

OpenCV C++案例实战十《车牌号识别》

前言

本文将使用OpenCV C++ 进行车牌号识别。

一、车牌检测

OpenCV C++案例实战十《车牌号识别》
原图如图所示。本案例的需求是进行车牌号码识别。所以,首先我们得定位车牌所在的位置,然后将车牌切割出来。接下来我们就来看看是如何实现。

; 1.1.图像预处理

首先经过一些常规的图像预处理,我们可以提取出图像的大致轮廓。然后根据轮廓的特征进一步确定我们所需要查找的轮廓。在这里,不同的图像需要根据本身图像特征设定预处理算法。所以,本案例的一个缺点就是不具有鲁棒性,只针对特定需求。

    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    Mat thresh;
    threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat open;
    morphologyEx(thresh, open, MORPH_OPEN, kernel);

OpenCV C++案例实战十《车牌号识别》
如图为经过二值化后的图像,接下来我们就可以使用findContours寻找我们需要的轮廓。根据图像的轮廓特征就可以定位到车牌所在位置,然后将其从原图中切割出来,以便后续的识别工作。在这里,我定义了一个License结构体,用于存储ROI图像,以及其相对于原图所在位置。这样在后续的绘制工作中,我们就可以定位到ROI所在位置。

1.2.轮廓提取


struct License
{
    Mat mat;
    Rect rect;
};

    vector<vector<Point>>contours;
    findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    vector<vector<Point>>conPoly(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {
        double area = contourArea(contours[i]);
        double peri = arcLength(contours[i], true);

        if (area > 1000)
        {

            approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);

            if (conPoly[i].size() == 4)
            {

                Rect box = boundingRect(contours[i]);
                double ratio = double(box.width) / double(box.height);
                if (ratio > 2 && ratio < 4)
                {

                    Rect rect = boundingRect(contours[i]);
                    License_ROI = { src(rect),rect };
                }
            }
        }
    }

1.3.功能效果

OpenCV C++案例实战十《车牌号识别》
如图为从汽车上定位到的车牌,并将其切割出来以便下面的识别工作。

; 1.4.功能源码


bool Get_License_ROI(Mat src, License &License_ROI)
{
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    Mat thresh;
    threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat open;
    morphologyEx(thresh, open, MORPH_OPEN, kernel);

    vector<vector<Point>>contours;
    findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    vector<vector<Point>>conPoly(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {
        double area = contourArea(contours[i]);
        double peri = arcLength(contours[i], true);

        if (area > 1000)
        {

            approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);

            if (conPoly[i].size() == 4)
            {

                Rect box = boundingRect(contours[i]);
                double ratio = double(box.width) / double(box.height);
                if (ratio > 2 && ratio < 4)
                {

                    Rect rect = boundingRect(contours[i]);
                    License_ROI = { src(rect),rect };
                }
            }
        }
    }

    if (License_ROI.mat.empty())
    {
        return false;
    }
    return true;
}

二、字符切割

2.1.图像预处理

通过刚才的车牌定位,我们已经将车牌从原图中切割出来了。接下来,我们还需要将车牌上的字符一一切割出来,以便进行后续的识别工作。同理,我们也需要对车牌做同样的预处理操作。

    Mat gray;
    cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);

    Mat thresh;
    threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat close;
    morphologyEx(thresh, close, MORPH_CLOSE, kernel);

经过灰度、阈值、形态学操作后的图像如下图所示。

OpenCV C++案例实战十《车牌号识别》

2.2.轮廓提取

接下来我们进行轮廓提取就可以提取出车牌上的每一个字符了。

    vector<vector<Point>>contours;
    findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    for (int i = 0; i < contours.size(); i++)
    {
        double area = contourArea(contours[i]);

        if (area > 200)
        {
            Rect rect = boundingRect(contours[i]);

            double ratio = double(rect.height) / double(rect.width);
            if (ratio > 1)
            {
                Mat roi = License_ROI.mat(rect);
                resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
                Character_ROI.push_back({ roi ,rect });
            }
        }
    }

OpenCV C++案例实战十《车牌号识别》

    for (int i = 0; i < Character_ROI.size()-1; i++)
    {
        for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
        {
            if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
            {
                License temp = Character_ROI[j];
                Character_ROI[j] = Character_ROI[j + 1];
                Character_ROI[j + 1] = temp;
            }
        }
    }

2.3.功能效果

OpenCV C++案例实战十《车牌号识别》

; 2.4.功能源码


bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)
{
    Mat gray;
    cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);

    Mat thresh;
    threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat close;
    morphologyEx(thresh, close, MORPH_CLOSE, kernel);

    vector<vector<Point>>contours;
    findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    for (int i = 0; i < contours.size(); i++)
    {
        double area = contourArea(contours[i]);

        if (area > 200)
        {
            Rect rect = boundingRect(contours[i]);

            double ratio = double(rect.height) / double(rect.width);
            if (ratio > 1)
            {
                Mat roi = License_ROI.mat(rect);
                resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
                Character_ROI.push_back({ roi ,rect });
            }
        }
    }

    for (int i = 0; i < Character_ROI.size()-1; i++)
    {
        for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
        {
            if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
            {
                License temp = Character_ROI[j];
                Character_ROI[j] = Character_ROI[j + 1];
                Character_ROI[j + 1] = temp;
            }
        }
    }

    if (Character_ROI.size() != 7)
    {
        return false;
    }
    return true;
}

三、字符识别

3.1.读取文件

OpenCV C++案例实战十《车牌号识别》
如图所示,为模板图像以及对应的label。我们需要读取文件,进行匹配。在这里我使用UTF8ToGB函数实现读取txt文件,目的是为了在控制台显示中文时,不会出现乱码情况。

bool Read_Data(string filename,vector<Mat>&dataset)
{
    vector<String>imagePathList;
    glob(filename, imagePathList);
    if (imagePathList.empty())return false;

    for (int i = 0; i < imagePathList.size(); i++)
    {
        Mat image = imread(imagePathList[i]);
        resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);
        dataset.push_back(image);
    }

    return true;
}

bool Read_Data(string filename, vector<string>&data_name)
{
    fstream fin;
    fin.open(filename, ios::in);
    if (!fin.is_open())
    {
        cout << "can not open the file!" << endl;
        return false;
    }

    string s;
    while (std::getline(fin, s))
    {
        string str = UTF8ToGB(s.c_str()).c_str();
        data_name.push_back(str);
    }
    fin.close();

    return true;
}

3.2.字符匹配

在这里,我的思路是:使用一个for循环,将我们切割出来的字符与现有的模板进行匹配。而这个匹配算法是求两张图像的像素差,以此来判断图像的相似程度。具体是使用OpenCV absdiff函数计算两张图像的像素差.。

OpenCV C++案例实战十《车牌号识别》
OpenCV C++案例实战十《车牌号识别》
如图为使用absdiff得到的效果图。接下来,我们只需要计算图像中灰度值为0的像素点个数就可以了。像素点个数最少的那个label即为我们的匹配结果。当然,此方法肯定是会存在误识别的情况的。进行字符匹配的方法还有:模板匹配,基于Hu矩轮廓匹配。大家可以试试。

; 3.3.功能源码


bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)
{
    string filename = "data/";
    vector<Mat>dataset;
    if (!Read_Data(filename, dataset)) return false;

    for (int i = 0; i < Character_ROI.size(); i++)
    {
        Mat roi_gray;
        cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);

        Mat roi_thresh;
        threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

        int minCount = 1000000;
        int index = 0;
        for (int j = 0; j < dataset.size(); j++)
        {
            Mat temp_gray;
            cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);

            Mat temp_thresh;
            threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

            Mat dst;
            absdiff(roi_thresh, temp_thresh, dst);

            int count = pixCount(dst);
            if (count < minCount)
            {
                minCount = count;
                index = j;
            }
        }

        result_index.push_back(index);
    }

    return true;
}

四、效果显示


bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)
{
    rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);

    vector<string>data_name;
    if (!Read_Data("data_name.txt", data_name))return false;

    for (int i = 0; i < Character_ROI.size(); i++)
    {
        cout << data_name[result_index[i]] << " ";

        CvxText text("C://Windows/Fonts/方正粗黑宋简体.ttf");
        string str = data_name[result_index[i]];
        const char*msg = str.data();
        IplImage *temp;
        temp = &IplImage(src);
        text.putText(temp, msg, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y),Scalar(0,0,255));
    }
    return true;
}

在这里,为了使用putText显示中文,我这里加了一些额外的代码。如果需要使用putText显示中文效果的朋友可以自行百度一下如何配置环境。
最终效果如图所示:

OpenCV C++案例实战十《车牌号识别》
OpenCV C++案例实战十《车牌号识别》
OpenCV C++案例实战十《车牌号识别》

五、源码—版本一

版本一 :putText能够显示中文,需要配置freetype库。目前我使用的环境是:win10、vs2017、opencv4.1。

#include
#include
#include
#include
#include"CvxText.h"

using namespace std;
using namespace cv;

struct License
{
    Mat mat;
    Rect rect;
};

bool Get_License_ROI(Mat src, License &License_ROI)
{
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    Mat thresh;
    threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat open;
    morphologyEx(thresh, open, MORPH_OPEN, kernel);

    vector<vector<Point>>contours;
    findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    vector<vector<Point>>conPoly(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {
        double area = contourArea(contours[i]);
        double peri = arcLength(contours[i], true);

        if (area > 1000)
        {

            approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);

            if (conPoly[i].size() == 4)
            {

                Rect box = boundingRect(contours[i]);
                double ratio = double(box.width) / double(box.height);
                if (ratio > 2 && ratio < 4)
                {

                    Rect rect = boundingRect(contours[i]);
                    License_ROI = { src(rect),rect };
                }
            }
        }
    }

    if (License_ROI.mat.empty())
    {
        return false;
    }
    return true;
}

bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)
{
    Mat gray;
    cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);

    Mat thresh;
    threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat close;
    morphologyEx(thresh, close, MORPH_CLOSE, kernel);

    vector<vector<Point>>contours;
    findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    for (int i = 0; i < contours.size(); i++)
    {
        double area = contourArea(contours[i]);

        if (area > 200)
        {
            Rect rect = boundingRect(contours[i]);

            double ratio = double(rect.height) / double(rect.width);
            if (ratio > 1)
            {
                Mat roi = License_ROI.mat(rect);
                resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
                Character_ROI.push_back({ roi ,rect });
            }
        }
    }

    for (int i = 0; i < Character_ROI.size()-1; i++)
    {
        for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
        {
            if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
            {
                License temp = Character_ROI[j];
                Character_ROI[j] = Character_ROI[j + 1];
                Character_ROI[j + 1] = temp;
            }
        }
    }

    if (Character_ROI.size() != 7)
    {
        return false;
    }
    return true;
}

string UTF8ToGB(const char* str)
{
    string result;
    WCHAR *strSrc;
    LPSTR szRes;

    int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
    strSrc = new WCHAR[i + 1];
    MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);

    i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
    szRes = new CHAR[i + 1];
    WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);

    result = szRes;
    delete[]strSrc;
    delete[]szRes;

    return result;
}

bool Read_Data(string filename,vector<Mat>&dataset)
{
    vector<String>imagePathList;
    glob(filename, imagePathList);
    if (imagePathList.empty())return false;

    for (int i = 0; i < imagePathList.size(); i++)
    {
        Mat image = imread(imagePathList[i]);
        resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);
        dataset.push_back(image);
    }

    return true;
}

bool Read_Data(string filename, vector<string>&data_name)
{
    fstream fin;
    fin.open(filename, ios::in);
    if (!fin.is_open())
    {
        cout << "can not open the file!" << endl;
        return false;
    }

    string s;
    while (std::getline(fin, s))
    {
        string str = UTF8ToGB(s.c_str()).c_str();
        data_name.push_back(str);
    }
    fin.close();

    return true;
}

int pixCount(Mat image)
{
    int count = 0;
    if (image.channels() == 1)
    {
        for (int i = 0; i < image.rows; i++)
        {
            for (int j = 0; j < image.cols; j++)
            {
                if (image.at<uchar>(i, j) == 0)
                {
                    count++;
                }
            }
        }

        return count;
    }
    else
    {
        return -1;
    }
}

bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)
{
    string filename = "data/";
    vector<Mat>dataset;
    if (!Read_Data(filename, dataset)) return false;

    for (int i = 0; i < Character_ROI.size(); i++)
    {
        Mat roi_gray;
        cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);

        Mat roi_thresh;
        threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

        int minCount = 1000000;
        int index = 0;
        for (int j = 0; j < dataset.size(); j++)
        {
            Mat temp_gray;
            cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);

            Mat temp_thresh;
            threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

            Mat dst;
            absdiff(roi_thresh, temp_thresh, dst);

            int count = pixCount(dst);
            if (count < minCount)
            {
                minCount = count;
                index = j;
            }
        }

        result_index.push_back(index);
    }

    return true;
}

bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)
{
    rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);

    vector<string>data_name;
    if (!Read_Data("data_name.txt", data_name))return false;

    for (int i = 0; i < Character_ROI.size(); i++)
    {
        cout << data_name[result_index[i]] << " ";

        CvxText text("C://Windows/Fonts/方正粗黑宋简体.ttf");
        string str = data_name[result_index[i]];
        const char*msg = str.data();
        IplImage *temp;
        temp = &IplImage(src);
        text.putText(temp, msg, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y),Scalar(0,0,255));
    }
    return true;
}

int main()
{

    Mat src = imread("car.jpg");
    if (src.empty())
    {
        cout << "No image!" << endl;
        system("pause");
        return -1;
    }

    License License_ROI;
    if (Get_License_ROI(src, License_ROI))
    {
        vector<License>Character_ROI;
        if (Get_Character_ROI(License_ROI, Character_ROI))
        {
            vector<int>result_index;
            if (License_Recognition(Character_ROI, result_index))
            {
                Draw_Result(src, License_ROI, Character_ROI,result_index);
            }
            else
            {
                cout << "未能识别字符!" << endl;
                system("pause");
                return -1;
            }
        }
        else
        {
            cout << "未能切割出字符!" << endl;
            system("pause");
            return -1;
        }
    }
    else
    {
        cout << "未定位到车牌位置!" << endl;
        system("pause");
        return -1;
    }

    imshow("src", src);
    waitKey(0);
    system("pause");
    return 0;
}

六、源码—版本二

版本二:很多小伙伴向我反馈由于vs、opencv版本问题,利用putText显示中文会出现各种各样的错误。故在这里提供一个putText不显示中文的版本,所以freetype库也不用配置了,直接就可以运行了。

#include
#include
#include
#include

using namespace std;
using namespace cv;

struct License
{
    Mat mat;
    Rect rect;
};

bool Get_License_ROI(Mat src, License &License_ROI)
{
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    Mat thresh;
    threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat open;
    morphologyEx(thresh, open, MORPH_OPEN, kernel);

    vector<vector<Point>>contours;
    findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    vector<vector<Point>>conPoly(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {
        double area = contourArea(contours[i]);
        double peri = arcLength(contours[i], true);

        if (area > 1000)
        {

            approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);

            if (conPoly[i].size() == 4)
            {

                Rect box = boundingRect(contours[i]);
                double ratio = double(box.width) / double(box.height);
                if (ratio > 2 && ratio < 4)
                {

                    Rect rect = boundingRect(contours[i]);
                    License_ROI = { src(rect),rect };
                }
            }
        }
    }

    if (License_ROI.mat.empty())
    {
        return false;
    }
    return true;
}

bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)
{
    Mat gray;
    cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);

    Mat thresh;
    threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat close;
    morphologyEx(thresh, close, MORPH_CLOSE, kernel);

    vector<vector<Point>>contours;
    findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    for (int i = 0; i < contours.size(); i++)
    {
        double area = contourArea(contours[i]);

        if (area > 200)
        {
            Rect rect = boundingRect(contours[i]);

            double ratio = double(rect.height) / double(rect.width);
            if (ratio > 1)
            {
                Mat roi = License_ROI.mat(rect);
                resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
                Character_ROI.push_back({ roi ,rect });
            }
        }
    }

    for (int i = 0; i < Character_ROI.size()-1; i++)
    {
        for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
        {
            if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
            {
                License temp = Character_ROI[j];
                Character_ROI[j] = Character_ROI[j + 1];
                Character_ROI[j + 1] = temp;
            }
        }
    }

    if (Character_ROI.size() != 7)
    {
        return false;
    }
    return true;
}

string UTF8ToGB(const char* str)
{
    string result;
    WCHAR *strSrc;
    LPSTR szRes;

    int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
    strSrc = new WCHAR[i + 1];
    MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);

    i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
    szRes = new CHAR[i + 1];
    WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);

    result = szRes;
    delete[]strSrc;
    delete[]szRes;

    return result;
}

bool Read_Data(string filename,vector<Mat>&dataset)
{
    vector<String>imagePathList;
    glob(filename, imagePathList);
    if (imagePathList.empty())return false;

    for (int i = 0; i < imagePathList.size(); i++)
    {
        Mat image = imread(imagePathList[i]);
        resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);
        dataset.push_back(image);
    }

    return true;
}

bool Read_Data(string filename, vector<string>&data_name)
{
    fstream fin;
    fin.open(filename, ios::in);
    if (!fin.is_open())
    {
        cout << "can not open the file!" << endl;
        return false;
    }

    string s;
    while (std::getline(fin, s))
    {
        string str = UTF8ToGB(s.c_str()).c_str();
        data_name.push_back(str);
    }
    fin.close();

    return true;
}

int pixCount(Mat image)
{
    int count = 0;
    if (image.channels() == 1)
    {
        for (int i = 0; i < image.rows; i++)
        {
            for (int j = 0; j < image.cols; j++)
            {
                if (image.at<uchar>(i, j) == 0)
                {
                    count++;
                }
            }
        }

        return count;
    }
    else
    {
        return -1;
    }
}

bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)
{
    string filename = "data/";
    vector<Mat>dataset;
    if (!Read_Data(filename, dataset)) return false;

    for (int i = 0; i < Character_ROI.size(); i++)
    {
        Mat roi_gray;
        cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);

        Mat roi_thresh;
        threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

        int minCount = 1000000;
        int index = 0;
        for (int j = 0; j < dataset.size(); j++)
        {
            Mat temp_gray;
            cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);

            Mat temp_thresh;
            threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

            Mat dst;
            absdiff(roi_thresh, temp_thresh, dst);

            int count = pixCount(dst);
            if (count < minCount)
            {
                minCount = count;
                index = j;
            }
        }

        result_index.push_back(index);
    }

    return true;
}

bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)
{
    rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);

    vector<string>data_name;
    if (!Read_Data("data_name.txt", data_name))return false;

    for (int i = 0; i < Character_ROI.size(); i++)
    {

        string str = data_name[result_index[i]];
        cout << str << " ";
        putText(src, str, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y), 3, FONT_HERSHEY_PLAIN, Scalar(0, 0, 255), 2);
    }

    return true;
}

int main()
{

    Mat src = imread("car.jpg");
    if (src.empty())
    {
        cout << "No image!" << endl;
        system("pause");
        return -1;
    }

    License License_ROI;
    if (Get_License_ROI(src, License_ROI))
    {
        vector<License>Character_ROI;
        if (Get_Character_ROI(License_ROI, Character_ROI))
        {
            vector<int>result_index;
            if (License_Recognition(Character_ROI, result_index))
            {
                Draw_Result(src, License_ROI, Character_ROI,result_index);
            }
            else
            {
                cout << "未能识别字符!" << endl;
                system("pause");
                return -1;
            }
        }
        else
        {
            cout << "未能切割出字符!" << endl;
            system("pause");
            return -1;
        }
    }
    else
    {
        cout << "未定位到车牌位置!" << endl;
        system("pause");
        return -1;
    }

    imshow("src", src);
    waitKey(0);
    system("pause");
    return 0;
}

1、效果显示

OpenCV C++案例实战十《车牌号识别》

; 总结

本文使用OpenCV C++进行车牌号识别,关键步骤有以下几点。
1、车牌定位。案例需求是进行车牌识别。那么我们就得知道车牌在什么位置。将车牌找到之后,需要将车牌切割出来,作为一个整体进行下面工作。
2、字符分割。我们得到了车牌,需要将车牌上的字符一一分割出来才能进行下面的识别工作。有个小细节就是需要将字符重新排序。
3、字符识别。我们将得到的字符与我们准备好的模板一一进行匹配。匹配算法有很多,大家可以自行尝试。我这里使用的是基于两幅图像的像素差进行图像比对。

需要说明的是:本案例是根据特定图像、特定需求设定的算法。并不具有鲁棒性。所有在图像预处理阶段很重要。我们需要提取出我们需要的图像特征,这样才能够进行后续的工作。所以本案例也只是使用传统的图像处理手段实现车牌识别功能。将大致流程作了一个说明,这里只提供一个参考作用!!!

注:关于有很多小伙伴提出的问题” “ft2build.h”: No such file or directory”。这是因为由于OpenCV putText 不支持显示中文,在本案例中,我为了显示中文,故编译了freetype库。如果大家觉得有需要的话,可以自行编译配置环境。如果觉得麻烦的话,将源码中的中文显示函数注释掉也是可以直接运行的。

freetype库配置

freetype库下载地址:http://download.savannah.gnu.org/releases/freetype/

下载解压后,选择合适vs版本进行编译就可以啦!!!

OpenCV C++案例实战十《车牌号识别》
OpenCV C++案例实战十《车牌号识别》
OpenCV C++案例实战十《车牌号识别》
OpenCV C++案例实战十《车牌号识别》
编译好之后,像配置OpenCV环境一样,将include、lib文件配置在vs环境中就可以了

欢迎大家点赞、关注,可私信找我领取完整源码、模板图像以及测试图像!!!

欢迎大家交流学习!!!

Original: https://blog.csdn.net/ZeroChen/article/details/122020643
Author: Zero
Chen
Title: OpenCV C++案例实战十《车牌号识别》

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/705133/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球