码迷,mamicode.com
首页 > 其他好文 > 详细

利用颜色和形态学两种方法进行车牌区域提取的OpenCV代码

时间:2016-07-18 20:21:19      阅读:1463      评论:0      收藏:0      [点我收藏+]

标签:

要想提取车牌号,首先你要定位车牌区域嘛,本文分别两种方法用,即颜色和形态学的方法,对车牌区域进行判定。说得是两种方法,其实两种方法并无多大的区别,只是有一步的判断标准不一样而已,你看了下面整理出的的思路就知道两者的区别真的很小了。

方法一:利用颜色提取车牌区域的思路

①求得原图像的sobel边缘sobelMat

②在HSV空间内利用车牌颜色阈值对图像进行二值化处理,得到图像bw_blue→

③由下面的判别标准得到图像bw_blue_edge

	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;
		}
	}

④对bw_blue_edge进行形态学闭操作,将那些小洞,小孔之类的连接起来,进而使图形中矩形更像矩形

⑤对连通区域的轮廓进行检测,如果有车牌,车牌肯定占一个轮廓撒

⑥对每一个轮廓求其外接矩形,并进行检测,从而把最像车牌的区域检测出来,按下面的标准进行检测:

	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);
		}
	}

从上面的过程来看,具体的应用中,如下语句中涉汲到的参数是需要调整的,如果设置不当,很有可能会提取出错误的车牌区域或是提取不出车牌区域。很明显的一个例子,离车牌远近不同拍摄出来的照片车牌区域的大小肯定不一样,那么rect.height > 12 && rect.width > 60之类的语句肯定要随时要调调才行,所以,如果解决不好这类的问题,这个程序是不具备通用性的,这也是这个程序最大的缺陷之一。

cv::countNonZero(bw_blue(rct)) >= 1//‘1’是需要根据据体的应用场景调整的
ratio > 0.5 && wh_ratio > 2 && wh_ratio < 5 &&ect.height > 12 && rect.width > 60)//这里面的参数也需要根据据体的应用场景调整的

方法二:利用形态学原理进行车牌提取的方法

①利用形态学梯度的方法进行图像的边缘检测

②根据不同的图形大小选取不同的形态学闭操作窗口进行形态学水平方向和垂直方向的闭操作

③对连通区域的轮廓进行检测,如果有车牌,车牌肯定占一个轮廓撒

④对每一个轮廓求其外接矩形,并进行检测,从而把最像车牌的区域检测出来,按下面的标准进行检测:

	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提取边缘,方法二采用形态学梯度的方法提取边缘;②方法一在提取边缘后还利用车牌的颜色对图像作了阈值限制,方法二则没有。③方法一在作形态学闭操作时,没有根据图形的尺寸选取不同的窗,而方法二则做了这样的处理。

下面附两种方法的源码即运行结果

方法一的源码

//OpenCV版本3.0.0 
//交流QQ2487872782 


#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;
}

方法一的运行结果

技术分享

方法二的源码

//OpenCV版本3.0.0 
//交流QQ2487872782 

#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;
}

方法二的运行结果

技术分享
-------------------------------------------
欢迎大家加入图像识别技术交流群:271891601,另外,特别欢迎成都从事图像识别工作的朋友交流,我的QQ号2487872782


利用颜色和形态学两种方法进行车牌区域提取的OpenCV代码

标签:

原文地址:http://blog.csdn.net/wenhao_ir/article/details/51923264

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!