利用HSV颜色空间和形态学两种思路进行车牌区域的提取
车牌号的提取首先需要定位车牌区域。
本文用HSV颜色空间和形态学两种思路实现对车牌区域的定位。
一、利用HSV颜色空间提取车牌区域
利用HSV颜色空间提取车牌区域的思路如下:
①求原图像的sobel边缘。
②因为普通民用小型车都是蓝底的车牌,所以我们可以在HSV颜色空间通过H通道的值去筛选出图像中的蓝色点,进而定位出我们想的车牌区域。
③对上一步得到的图像进行形态学闭操作,将那些小洞,小孔之类的连接起来,进而使图形中矩形更像矩形。
④对连通区域进行轮廓检测,如果有车牌,提取出车牌的轮廓。
⑤对每一个轮廓求其外接矩形,并根据车牌大小判断出属于车牌的外接矩形。不同的场景下判定的具体条件不同,所以判断的参数需要根据实际情况去调整。很典型的,当摄像头距离车牌不同距离时,车牌区域在图像中的大小也是不一样的。
这种思路的代码如下:
博主2022-06-09 22:23:10注:未完待续...
方法二:利用形态学原理进行车牌提取的方法:
①利用形态学梯度的方法进行图像的边缘检测
②根据不同的图形大小选取不同的形态学闭操作窗口进行形态学水平方向和垂直方向的闭操作
③对连通区域的轮廓进行检测,如果有车牌,车牌肯定占一个轮廓撒
④对每一个轮廓求其外接矩形,并进行检测,从而把最像车牌的区域检测出来,按下面的标准进行检测:
for (size_t i = 0; i != blue_contours.size(); ++i)
{
cv::Rect rect = cv::boundingRect(blue_contours[i]);
double wh_ratio = double(rect.width) / rect.height;
int sub = cv::countNonZero(result(rect));
double ratio = double(sub) / rect.area();
if (wh_ratio > 2 && wh_ratio < 8 && rect.height >
12 &&rect.width > 60 && ratio > 0.4)
{
//blue_rect.push_back(rect);
cv::imshow("rect", srcGray(rect));
cv::waitKey(0);
}
}
同方法一一样,方法二下面的这句语句中的参数也需要根据实际的应用进行设置,如果设置不当,很有可能会提取出错误的车牌区域或是提取不出车牌区域。很明显的一个例子,离车牌远近不同拍摄出来的照片车牌区域的大小肯定不一样,那么rect.height > 12 &&rect.width > 60之类的语句肯定要随时要调调才行,所以,如果解决不好这类的问题,这个程序是不具备通用性的,这也是这个程序最大的缺陷之一。
wh_ratio > 2 && wh_ratio < 8 && rect.height > 12 &&rect.width > 60 && ratio > 0.4
对比一下两种方法:
两种方法都要先对图像作预处理,以去除多余信息,去除多余信息后,进行形态学闭操作,以凸显出目标轮廓,求得轮廓后,再求轮廓的外接矩形,利用车牌的矩形特征去检测这些外接矩形,符合车牌特征的即是车牌区域。两种方法都不能自适应因拍摄距离远近不同导致的车牌在图形中的大小不同。
两种方法不同的部分是:①方法一采用sobel提取边缘,方法二采用形态学梯度的方法提取边缘;②方法一在提取边缘后还利用车牌的颜色对图像作了阈值限制,方法二则没有。③方法一在作形态学闭操作时,没有根据图形的尺寸选取不同的窗,而方法二则做了这样的处理。
下面附两种方法的源码即运行结果:
方法一的源码:(代码中用到的图像下载链接为 http://pan.baidu.com/s/1c2cBnFE)
//OpenCV版本3.0.0
//图像处理开发需求、图像处理接私活挣零花钱,请加微信/QQ 2487872782
//图像处理开发资料、图像处理技术交流请加QQ群,群号 271891601
#include <iostream>
#include <vector>
#include <stdint.h>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/features2d/features2d.hpp"
// 提取竖直的sobel边缘
bool SobelVerEdge(cv::Mat srcImage, cv::Mat& resultImage)
{
CV_Assert(srcImage.channels() == 1);
srcImage.convertTo(srcImage, CV_32FC1);
// 水平方向的 Sobel 算子
cv::Mat sobelx = (cv::Mat_<float>(3, 3) << -0.125, 0, 0.125,
-0.25, 0, 0.25,
-0.125, 0, 0.125);
cv::Mat ConResMat;
// 卷积运算
cv::filter2D(srcImage, ConResMat, srcImage.type(), sobelx);
// 计算梯度的幅度
cv::Mat graMagMat;
cv::multiply(ConResMat, ConResMat, graMagMat);
// 根据梯度幅度及参数设置阈值
int scaleVal = 4;
double thresh = scaleVal * cv::mean(graMagMat).val[0];
cv::Mat resultTempMat = cv::Mat::zeros(
graMagMat.size(), graMagMat.type());
float* pDataMag = (float*)graMagMat.data;
float* pDataRes = (float*)resultTempMat.data;
const int nRows = ConResMat.rows;
const int nCols = ConResMat.cols;
for (int i = 1; i != nRows - 1; ++i) {
for (int j = 1; j != nCols - 1; ++j) {
// 计算该点梯度与水平或垂直梯度值大小比较结果
bool b1 = (pDataMag[i * nCols + j] > pDataMag[i *
nCols + j - 1]);
bool b2 = (pDataMag[i * nCols + j] > pDataMag[i *
nCols + j + 1]);
bool b3 = (pDataMag[i * nCols + j] > pDataMag[(i - 1)
* nCols + j]);
bool b4 = (pDataMag[i * nCols + j] > pDataMag[(i + 1)
* nCols + j]);
// 判断邻域梯度是否满足大于水平或垂直梯度
// 并根据自适应阈值参数进行二值化
pDataRes[i * nCols + j] = 255 * ((pDataMag[i *
nCols + j] > thresh) &&
((b1 && b2) || (b3 && b4)));
}
}
resultTempMat.convertTo(resultTempMat, CV_8UC1);
resultImage = resultTempMat.clone();
return true;
}
// 疑似区域提取
cv::Mat getPlateArea(cv::Mat srcImage, cv::Mat sobelMat)
{
// 转换成hsv
cv::Mat img_h, img_s, img_v, imghsv;
std::vector<cv::Mat> hsv_vec;
cv::cvtColor(srcImage, imghsv, CV_BGR2HSV);
cv::imshow("hsv", imghsv);
cv::waitKey(0);
// hsv 限定范围元素提取
cv::Mat bw_blue;
cv::inRange(imghsv,cv::Scalar(81,38,63),cv::Scalar(135,255,255),bw_blue);
int height = bw_blue.rows;
int width = bw_blue.cols;
cv::Mat bw_blue_edge = cv::Mat::zeros(bw_blue.size(), bw_blue.type());
cv::imshow("bw_blue", bw_blue);
cv::waitKey(0);
// 车牌疑似区域提取
for (int k = 1; k != height - 2; ++k)
{
for (int l = 1; l != width - 2; ++l)
{
cv::Rect rct;
rct.x = l - 1;
rct.y = k - 1;
rct.height = 3;
rct.width = 3;
if ((sobelMat.at<uchar>(k, l) == 255) && (cv::countNonZero(bw_blue(rct)) >= 1))
bw_blue_edge.at<uchar>(k, l) = 255;
}
}
// 形态学闭操作
cv::Mat morph;
cv::morphologyEx(bw_blue_edge, morph, cv::MORPH_CLOSE,
cv::Mat::ones(2, 25, CV_8UC1));
cv::imshow("bw_blue_edge", bw_blue_edge);
cv::waitKey(0);
// 连通区域轮廓检测
cv::imshow("morph", morph);
std::vector<std::vector<cv::Point> > region_contours;
cv::findContours(morph.clone(), region_contours,
CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
cv::Mat result;
for (size_t n = 0; n != region_contours.size(); ++n)
{
// 去除高度宽度不符合条件区域
cv::Rect rect = cv::boundingRect(region_contours[n]);
int sub = cv::countNonZero(morph(rect));
double ratio = double(sub) / rect.area();
double wh_ratio = double(rect.width) / rect.height;
if (ratio > 0.5 && wh_ratio > 2 && wh_ratio < 5 &&
rect.height > 12 && rect.width > 60)
{
cv::Mat small = bw_blue_edge(rect);
result = srcImage(rect);
cv::imshow("rect", srcImage(rect));
cv::waitKey(0);
}
}
return result;
}
int main()
{
cv::Mat srcImage = cv::imread("car.jpg");
if (!srcImage.data)
return 1;
cv::Mat srcGray;
cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
cv::imshow("srcImage", srcImage);
// sobel 提取边缘
cv::Mat sobelMat;
//Sobel(srcGray, sobelMat, CV_16S, 1, 0, 3, 1, 0, 4);
//convertScaleAbs(sobelMat, sobelMat);
SobelVerEdge(srcGray, sobelMat);
cv::imshow("Sobel", sobelMat);
//cv::waitKey(0);
// 疑似区域提取
cv::Mat result = getPlateArea(srcImage, sobelMat);
return 0;
}
方法一的运行结果:
方法二的源码:(代码中用到的图像下载链接为 car.jpg_免费高速下载|百度网盘-分享无限制)
//OpenCV版本3.0.0
//图像处理开发需求、图像处理接私活挣零花钱,请加微信/QQ 2487872782
//图像处理开发资料、图像处理技术交流请加QQ群,群号 271891601
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace cv;
cv::Mat getPlate(int width, int height, cv::Mat srcGray)
{
cv::Mat result;
// 形态学梯度检测边缘
morphologyEx(srcGray, result, MORPH_GRADIENT,
Mat(1, 2, CV_8U, Scalar(1)));
cv::imshow("1result", result);
// 阈值化
threshold(result, result, 255 * (0.1), 255,
THRESH_BINARY);
cv::imshow("2result", result);
// 水平方向闭运算
if (width >= 400 && width < 600)
morphologyEx(result, result, MORPH_CLOSE,
Mat(1, 25, CV_8U, Scalar(1)));
else if (width >= 200 && width < 300)
morphologyEx(result, result, MORPH_CLOSE,
Mat(1, 20, CV_8U, Scalar(1)));
else if (width >= 600)
morphologyEx(result, result, MORPH_CLOSE,
Mat(1, 28, CV_8U, Scalar(1)));
else
morphologyEx(result, result, MORPH_CLOSE,
Mat(1, 15, CV_8U, Scalar(1)));
// 垂直方向闭运算
if (height >= 400 && height < 600)
morphologyEx(result, result, MORPH_CLOSE,
Mat(8, 1, CV_8U, Scalar(1)));
else if (height >= 200 && height < 300)
morphologyEx(result, result, MORPH_CLOSE,
Mat(6, 1, CV_8U, Scalar(1)));
else if (height >= 600)
morphologyEx(result, result, MORPH_CLOSE,
Mat(10, 1, CV_8U, Scalar(1)));
else
morphologyEx(result, result, MORPH_CLOSE,
Mat(4, 1, CV_8U, Scalar(1)));
return result;
}
int main()
{
cv::Mat srcImage = cv::imread("car.jpg");
if (!srcImage.data)
return 1;
cv::Mat srcGray;
cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
cv::imshow("srcGray", srcGray);
cv::Mat result = getPlate(400, 300, srcGray);
// 连通区域轮廓检测
std::vector<std::vector<cv::Point> > blue_contours;
std::vector<cv::Rect> blue_rect;
cv::findContours(result.clone(),
blue_contours, CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
// 连通域遍历 车牌目标提取
for (size_t i = 0; i != blue_contours.size(); ++i)
{
cv::Rect rect = cv::boundingRect(blue_contours[i]);
double wh_ratio = double(rect.width) / rect.height;
int sub = cv::countNonZero(result(rect));
double ratio = double(sub) / rect.area();
if (wh_ratio > 2 && wh_ratio < 8 && rect.height >
12 &&rect.width > 60 && ratio > 0.4)
{
//blue_rect.push_back(rect);
cv::imshow("rect", srcGray(rect));
cv::waitKey(0);
}
}
cv::imshow("result", result);
cv::waitKey(0);
return 0;
}
方法二的运行结果:
延伸阅读:
相关文章
- 【技术思路】极客时间-左耳听风-程序员攻略开篇
- java实现遍历树形菜单方法——设计思路【含源代码】
- Atitit 常见项目角色与职责 目录 1.1. 常见项目角色与职责1 1.2. 解决问题思路:一般百度,问同事,问上一级1 1.3. 解决问题时限:与跳过法1 1.4. 解决方法,一般实
- DL之Encoder-Decoder:Encoder-Decoder结构的相关论文、设计思路、关键步骤等配图集合之详细攻略
- 2022“华为杯”(E、F题)思路分析、代码......
- 2023美赛ABCDEF思路汇总
- 前后端分离中的权限管理思路
- 互联网大厂有哪些分库分表的思路和技巧?
- 二进制编译安装/usr/bin/ld: cannot find -latomic报错排查思路
- APP UI自动化测试思路总结 | 干货
- python 利用quick sort思路实现median函数
- 数据位宽转换(任意整数倍)设计——思路和源码
- “秒杀系统优化思路” 记录
- Redis主从复制集群及数据异常丢失恢复思路(七)
- [HCTF 2018]WarmUp1解题思路