OpenCV实现车牌定位(C++)

 更新时间:2020年11月27日 15:38:32   作者:Steven·简谈  
这篇文章主要为大家详细介绍了OpenCV实现车牌定位,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

最近开始接触 C++ 了,就拿一个 OpenCV 小项目来练练手。在车牌自动识别系统中,从汽车图像的获取到车牌字符处理是一个复杂的过程,本文就以一个简单的方法来处理车牌定位。

我国的汽车牌照一般由七个字符和一个点组成,车牌字符的高度和宽度是固定的,分别为90mm和45mm,七个字符之间的距离也是固定的12mm,点分割符的直径是10mm。

使用的图片是从百度上随便找的(侵删),展示一下原图和灰度图:

#include <iostream> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>

using namespace std;
using namespace cv;

int main() {
 // 读入原图
 Mat img = imread("license.jpg");
 Mat gray_img;
 // 生成灰度图像
 cvtColor(img, gray_img, CV_BGR2GRAY);
 // 在窗口中显示游戏原画
 imshow("原图", img);
 imshow("灰度图", gray_img);
 waitKey(0);
 return 0;
}

灰度图像的每一个像素都是由一个数字量化的,而彩色图像的每一个像素都是由三个数字组成的向量量化的,使用灰度图像会更方便后续的处理。

图像降噪

每一副图像都包含某种程度的噪声,在大多数情况下,需要平滑技术(也常称为滤波或者降噪技术)进行抑制或者去除,这些技术包括基于二维离散卷积的高斯平滑、均值平滑、基于统计学方法的中值平滑等。这里采用基于二维离散卷积的高斯平滑对灰度图像进行降噪处理,处理后的图像效果如下:

形态学处理

完成了高斯去噪以后,为了后面更加准确的提取车牌的轮廓,我们需要对图像进行形态学处理,在这里,我们对它进行开运算,处理后如下所示:

开运算呢就是先进行 erode 再进行 dilate 的过程就是开运算,它具有消除亮度较高的细小区域、在纤细点处分离物体,对于较大物体,可以在不明显改变其面积的情况下平滑其边界等作用。

erode 操作也就是腐蚀操作,类似于卷积,也是一种邻域运算,但计算的不是加权求和,而是对邻域中的像素点按灰度值进行排序,然后选择该组的最小值作为输出的灰度值。

dilate 操作就是膨胀操作,与腐蚀操作类似,膨胀是取每一个位置邻域内的最大值。既然是取邻域内的最大值,那么显然膨胀后的输出图像的总体亮度的平均值比起原图会有所上升,而图像中较亮物体的尺寸会变大;相反,较暗物体的尺寸会减小,甚至消失。

阈值分割

完成初步的形态学处理以后,我们需要对图像进行阈值分割,我们在这里采用了 Otsu 阈值处理,处理后的效果如下所示:

对图像进行数字处理时,我们需要把图像分成若干个特定的、具有独特性质的区域,每一个区域代表一个像素的集合,每一个集合又代表一个物体,而完成该过程的技术通常称为图像分割,它是从图像处理到图像分析的关键步骤。其实这个过程不难理解,就好比我们人类看景物一样,我们所看到的世界是由许许多多的物体组合而成的,就像教室是由人、桌子、书本、黑板等等组成。我们通过阈值处理,就是希望能够从背景中分离出我们的研究对象。

边缘检测

经过Otsu阈值分割以后,我们要对图像进行边缘检测,我们这里采用的是Canny边缘检测,处理后的结果如下:

接下来再进行一次闭运算和开运算,填充白色物体内细小黑色空洞的区域并平滑其边界,处理后的效果如下:

这个时候,车牌的轮廓已经初步被选出来了,只是还有一些白色块在干扰。

上述过程的代码:

// 得出轮廓
bool contour(Mat image, vector<vector<Point>> &contours, vector<Vec4i> &hierarchy) {
 Mat img_gau, img_open, img_seg, img_edge;
 // 高斯模糊
 GaussianBlur(image, img_gau, Size(7, 7), 0, 0);
 // 开运算
 Mat element = getStructuringElement(MORPH_RECT, Size(23, 23));
 morphologyEx(img_gau, img_open, MORPH_OPEN, element);
 addWeighted(img_gau, 1, img_open, -1, 0, img_open);
 // 阈值分割
 threshold(img_open, img_seg, 0, 255, THRESH_BINARY + THRESH_OTSU);
 // 边缘检测
 Canny(img_seg, img_edge, 200, 100);
 element = getStructuringElement(MORPH_RECT, Size(22, 22));
 morphologyEx(img_edge, img_edge, MORPH_CLOSE, element);
 morphologyEx(img_edge, img_edge, MORPH_OPEN, element);
 findContours(img_edge, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
 return true;
}

选取轮廓

现在我们已经有了轮廓,我们需要筛选出车牌所在的那个轮廓,由于车牌宽和高的比例是固定的,依据这个几何特征,我们进行筛选,效果如图:


代码如下:

// 车牌轮廓点
Point2f(*choose_contour(vector<vector<Point>> contours))[2] {
 int size = (int)contours.size();
 int i_init = 0;
 Point2f (*contours_result)[2] = new Point2f[size][2];
 for (int i = 0; i < size; i++){
 // 获取边框数据
 RotatedRect number_rect = minAreaRect(contours[i]);
 Point2f rect_point[4];
 number_rect.points(rect_point);
 float width = rect_point[0].x - rect_point[1].x;
 float height = rect_point[0].y - rect_point[3].y;
 // 用宽高比筛选
 if (width < height) {
 float temp = width;
 width= height;
 height = temp;
 }
 float ratio = width / height;
 if (2.5 < ratio && ratio < 5.5) {
 contours_result[i_init][0] = rect_point[0];
 contours_result[i_init][1] = rect_point[2];
 i_init++;
 }
 
 }
 return contours_result;
}

// 截取车牌区域
int license_gain(Point2f (*choose_license)[2], Mat img) {
 int size = (int)(_msize(choose_license) / sizeof(choose_license[0]));
 // 绘制方框
 for (int i = 0; i < size; i++) {
 if ((int)choose_license[i][0].x > 1 && (int)choose_license[i][0].y > 1) {
 int x = (int)choose_license[i][1].x;
 int y = (int)choose_license[i][1].y;
 int width = (int)(choose_license[i][0].x) - (int)(choose_license[i][1].x);
 int height = (int)(choose_license[i][0].y) - (int)(choose_license[i][1].y);
 Rect choose_rect(x, y, width, height);
 Mat number_img = img(choose_rect);
 rectangle(img, choose_license[i][0], choose_license[i][1], Scalar(0, 0, 255), 2, 1, 0);
 imshow("车牌单独显示" + to_string(i), number_img);
 }
 }
 imshow("绘制方框", img);
 return 0;
}

最后的 main 函数:

int main() {
 // 读入原图
 Mat img = imread("license.jpg");
 Mat gray_img;
 // 生成灰度图像
 cvtColor(img, gray_img, CV_BGR2GRAY);
 // 得出轮廓
 vector<vector<Point>> contours;
 vector<Vec4i> hierarchy;
 contour(gray_img, contours, hierarchy);
 // 截取车牌
 Point2f (*choose_license)[2] = choose_contour(contours);
 license_gain(choose_license, img);
 delete [] choose_license;
 waitKey(0);
 return 0;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 贪吃蛇C语言代码实现(难度可选)

    贪吃蛇C语言代码实现(难度可选)

    这篇文章主要为大家详细介绍了贪吃蛇C语言代码实现,游戏难度可供选择,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • C++实现LeetCode(150.计算逆波兰表达式)

    C++实现LeetCode(150.计算逆波兰表达式)

    这篇文章主要介绍了C++实现LeetCode(150.计算逆波兰表达式),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C++利用栈实现中缀表达式转后缀表达式

    C++利用栈实现中缀表达式转后缀表达式

    这篇文章主要为大家详细介绍了C++利用栈实现中缀表达式转后缀表达式,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • C中qsort快速排序使用实例

    C中qsort快速排序使用实例

    在学习C++ STL的sort函数,发现C中也存在一个qsort快速排序,要好好学习下C的库函数啊
    2014-01-01
  • Eclipse对printf()不能输出到控制台的快速解决方法

    Eclipse对printf()不能输出到控制台的快速解决方法

    Eclipse对printf()不能输出到控制台的快速解决方法。需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • C++模板实现顺序栈

    C++模板实现顺序栈

    这篇文章主要为大家详细介绍了C++模板实现顺序栈,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • C++中的extern声明变量详解

    C++中的extern声明变量详解

    这篇文章主要介绍了C++中的extern声明变量详解,本文讲解了声明和定义、extern声明全局变量、extern声明函数等内容,需要的朋友可以参考下
    2015-03-03
  • C语言基本排序算法之插入排序与直接选择排序实现方法

    C语言基本排序算法之插入排序与直接选择排序实现方法

    这篇文章主要介绍了C语言基本排序算法之插入排序与直接选择排序实现方法,结合具体实例形式分析了插入排序与直接选择排序的定义、使用方法及相关注意事项,需要的朋友可以参考下
    2017-09-09
  • C语言中改变目录的相关操作函数详解

    C语言中改变目录的相关操作函数详解

    这篇文章主要介绍了C语言中改变目录的相关操作函数详解,分别是fchdir()函数和rewinddir()函数的使用方法,需要的朋友可以参考下
    2015-09-09
  • C++中CSTRINGLIST用法详解

    C++中CSTRINGLIST用法详解

    这篇文章主要介绍了C++中CSTRINGLIST用法详解的相关资料,需要的朋友可以参考下
    2015-06-06

最新评论