一、算法需求
基于基于纹理特征提取车辆照片中的车牌区域
![](https://img-blog.csdnimg.cn/108296f7066649ee8fc2d7308f7bdaf5.jpeg)
二、问题分析
在车辆照片中提取车牌区域,需要对图像进行系列变化,移除图像的非车牌区域,使车牌区域在图像中变得显著。目前分析发现,在车辆照片中,车牌区域的特点如下:
1、具有固定的颜色(一般车牌为蓝色、白色、黄色、和绿色)
2、具备特定的字符(车牌号包含汉字、字母、数字)
3、字符具备特定排列规则(车牌号都是水平排列的)
目前不考虑考虑颜色实现算法,对于字符而言其特征就是边缘特征丰富,对于水平排列而言其特征就是形态学算子可以较宽
三、核心思路
1、读取图像为灰度图
2、使用sobel算子提取梯度(sobel算子可以提取水平和垂直梯度)
3、利用横向特征使车牌更加显著
4、利用连通域特性优化车牌区域
四、具体实现
4.1 加载图像为灰度图
代码和执行效果如下所示
Mat grad_y, abs_grad_y, grad_x, abs_grad_x, close_mat, dil_mat, fs_img;
Mat srcImage;
srcImage = imread("C:\\Users\\aaa\\Pictures\\Car\\10.jpg");
resize(srcImage, srcImage, {
600,800 });
cvtColor(srcImage, srcImage, COLOR_BGR2GRAY);
imshow("01.灰度图", srcImage);
![](https://img-blog.csdnimg.cn/9662285b0f014bc3b55765af5dcc29a0.png)
4.2 利用垂直方向梯度特征
车牌字符的梯度特征较为明显和密集,故可使用sobel算子提取垂直方向梯度特征。对梯度特征进行二值化和闭运算后可以车牌区域在图像中更加显著。
Sobel(srcImage, grad_x, CV_16S, 1, 0, 3, 1, 0, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
imshow("02.Sobel 垂直梯度", abs_grad_x);
//图像全局二值化
Mat ezh_img;
threshold(abs_grad_x, ezh_img, 200, 255, THRESH_BINARY);
imshow("03.二值化", ezh_img);
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
morphologyEx(ezh_img, close_mat, MORPH_CLOSE, element);
imshow("04.闭运算 ", close_mat);
应图像过大,故只截取车牌周边区域
sobel提取出的车牌区域如下所示
![](https://img-blog.csdnimg.cn/baba3b3565ee494182b54c91731d29bc.png)
![](https://img-blog.csdnimg.cn/baba3b3565ee494182b54c91731d29bc.png)
![](https://img-blog.csdnimg.cn/b62fd920e61b49d9a24db49ff47e4b98.png)
4.3 利用横向特征
4.3.1 横向膨胀
使用宽远大于高的横向排列算子element1 对上述步骤的二值图进行腐蚀,可使车牌区域形成一个连通域,但由于车牌字符11之间空格区域太多,故对整图进行闭运算
//膨胀
cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(21, 5));
morphologyEx(close_mat, dil_mat, MORPH_DILATE, element1);
morphologyEx(dil_mat, dil_mat, MORPH_CLOSE,
getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7)));
imshow("05.横向膨胀+闭运算 ", dil_mat);
![](https://img-blog.csdnimg.cn/d5b14813ce694c7e94794a0494f03ddf.png)
4.3.2 横向开运算
进行横向膨胀后可以发现车牌由于其横向排列的特性,称为了最宽的连通域。故,对图像进行横向开运算,使横向宽度不够的连通域被移除。
Mat open_mat;
cv::Mat element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(71, 5));
morphologyEx(dil_mat, open_mat, MORPH_OPEN, element2);
imshow("06.横向开运算 ", open_mat);
![](https://img-blog.csdnimg.cn/a3c8f90f78064aec86d9d2d44b3e6e9f.png)
4.4 连通域优化
进行横向特征提取后可以发现,目前车牌区域已经是最大的连通域了,故设计函数提取最大的连通域
Mat max_area = findMaxArea2(open_mat);
imshow("07.最大连通域 ", max_area);
findMaxArea2函数的实现如下所示,利用findContours函数计算出所有的轮廓,然后遍历轮廓使用contourArea找出最大的连通域,最后使用drawContours函数绘制最大的连通域。
Mat findMaxArea2(Mat bin_mat) {
Mat borad = Mat::zeros(bin_mat.size(), CV_8UC1);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(bin_mat, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
int max_aero = 0;
int max_i = 0;
//遍历所有轮廓,获取最大连通域的面积和下标
for (int i = 0; i < contours.size(); i++)
{
int aero = contourArea(contours[i]);//计算轮廓面积
if (max_aero < aero) {
max_aero = aero;
max_i = i;
}
}
drawContours(borad, contours, max_i, Scalar(255, 255, 255), -1, 8, hierarchy);
cout << max_i << " aero:" << max_aero << endl;
return borad;
}
![](https://img-blog.csdnimg.cn/58f12925aeb24e6cbb8245f93ec24f6b.png)
4.5 车牌区域提取
上图提取的连通域无法完全覆盖到车牌区域,故对其进行膨胀,然后将膨胀结果与原图相乘得到只有车牌区域的图像(变量:only_car_num )。由于只有车牌区域的图像黑色背景过多,使用boundingRect裁剪出车牌区域。
代码和执行效果如下所示:
Mat max_area_pz;
cv::Mat element3 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(31, 31));
morphologyEx(max_area, max_area_pz, MORPH_DILATE, element3);
imshow("08.最大连通域膨胀 ", max_area_pz);
Mat max_area_pz_norm = max_area_pz / 255;
Mat only_car_num = srcImage.mul(max_area_pz_norm);
Rect bound=boundingRect(only_car_num);
Mat only_car_num2 = only_car_num(bound);
imshow("only_car_num2", only_car_num2); //轮廓
waitKey(0);
可见目前的车牌区域比4.4步骤中的车牌区域大一些,可覆盖到原图车牌上
![](https://img-blog.csdnimg.cn/e548b3386e894d77a0bd836e68cf7dd9.png)
最终提取的车牌区域图像如下所示
五、总结分析
5.1 算法适用性分析
上述算法在实现车牌检测中主要利用了两个特征:
1、车牌字符都是竖着------》对应关键点:使用sobel在x方式的算子提取特征(车辆的轮廓主要在水平方式上,在垂直方向上轮廓线较少),通过该操作后车牌区域纹理变得显著。
2、车牌字符都是横向排列-----》对于关键点:横向膨胀(使车牌区域连接到一起,构成全图中最大的连通域),通过该操作后将车牌区域特征定义为最大的连通域。
基于上述实现流程,发现上述算法在应用到一些在垂直方向上轮廓线较多的数据时可能存在问题,这主要是存在后部分车型的车头场景(车辆后面的车牌区域是没有太多的垂直纹理)较为复杂时,可能不行。因为车辆的发动起在前车位置,车牌旁边有散热的孔洞,部分车的散热孔是竖直排列的,所设计算法在应用到这类数据时可能会存在不足,具体如下图所示。在应用到以下数据的7和9时则存在误识别。
5.2 完整代码
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <assert.h>
#include <vector>
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
using namespace cv;
//找到二值图中最大的连通域,并返回
Mat findMaxArea2(Mat bin_mat) {
Mat borad = Mat::zeros(bin_mat.size(), CV_8UC1);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(bin_mat, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
int max_aero = 0;
int max_i = 0;
//遍历所有轮廓,获取最大连通域的面积和下标
for (int i = 0; i < contours.size(); i++)
{
int aero = contourArea(contours[i]);//计算轮廓面积
if (max_aero < aero) {
max_aero = aero;
max_i = i;
}
}
drawContours(borad, contours, max_i, Scalar(255, 255, 255), -1, 8, hierarchy);
cout << max_i << " aero:" << max_aero << endl;
return borad;
}
//使用垂直梯度特征求车牌区域
int main() {
Mat grad_y, abs_grad_y, grad_x, abs_grad_x, close_mat, dil_mat, fs_img;
Mat srcImage;
srcImage = imread("C:\\Users\\aaa\\Pictures\\Car\\10.jpg");
resize(srcImage, srcImage, {
600,800 });
cvtColor(srcImage, srcImage, COLOR_BGR2GRAY);
imshow("01.灰度图", srcImage);
Sobel(srcImage, grad_x, CV_16S, 1, 0, 3, 1, 0, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
imshow("02.Sobel 垂直梯度", abs_grad_x);
//图像全局二值化
Mat ezh_img;
threshold(abs_grad_x, ezh_img, 200, 255, THRESH_BINARY);
imshow("03.二值化", ezh_img);
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
morphologyEx(ezh_img, close_mat, MORPH_CLOSE, element);
imshow("04.闭运算 ", close_mat);
//膨胀
cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(21, 5));
morphologyEx(close_mat, dil_mat, MORPH_DILATE, element1);
morphologyEx(dil_mat, dil_mat, MORPH_CLOSE,
getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7)));
imshow("05.横向膨胀+闭运算 ", dil_mat);
Mat open_mat;
cv::Mat element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(71, 5));
morphologyEx(dil_mat, open_mat, MORPH_OPEN, element2);
imshow("06.横向开运算 ", open_mat);
Mat max_area = findMaxArea2(open_mat);
imshow("07.最大连通域 ", max_area);
Mat max_area_pz;
cv::Mat element3 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(31, 31));
morphologyEx(max_area, max_area_pz, MORPH_DILATE, element3);
imshow("08.最大连通域膨胀 ", max_area_pz);
Mat max_area_pz_norm = max_area_pz / 255;
Mat only_car_num = srcImage.mul(max_area_pz_norm);
Rect bound=boundingRect(only_car_num);
Mat only_car_num2 = only_car_num(bound);
imshow("only_car_num2", only_car_num2); //轮廓
waitKey(0);
return 0;
}