Opencv车牌识别之车牌区域定位(一)

在我的车牌区域定位的方法的流程是:

1.首先使用高斯滤波去掉一些干扰的元素

2.然后将彩色图转换成灰度图

3.然后利用Soble边缘提取的方法提取垂直方向的边缘

4.利用OTSU的二值化方法将步骤3中的图二值化

5.利用水平扫描与垂直扫描的方法定位出车牌的区域


下面详细讲解每一步的程序代码:

1.高斯滤波的详细讲解见:http://blog.csdn.net/linqianbi/article/details/78635941

//计算一维高斯的权值数组
double *getOneGuassionArray(int size, double sigma)
{
	double sum = 0.0;
	
	int kerR = size / 2;

	
	double *arr = new double[size];
	for (int i = 0; i < size; i++)
	{

		
		arr[i] = exp(-((i - kerR)*(i - kerR)) / (2 * sigma*sigma));
		sum += arr[i];//将所有的值进行相加

	}
	
	for (int i = 0; i < size; i++)
	{
		arr[i] /= sum;
		cout << arr[i] << endl;
	}
	return arr;
}




void MyGaussianBlur(Mat &srcImage, Mat &dst, int size)
{
	CV_Assert(srcImage.channels() || srcImage.channels() == 3); 
	int kerR = size / 2;
	dst = srcImage.clone();
	int channels = dst.channels();
	double* arr;
	arr = getOneGuassionArray(size, 1);

									  
	for (int i = kerR; i < dst.rows - kerR; i++)
	{
		for (int j = kerR; j < dst.cols - kerR; j++)
		{
			double GuassionSum[3] = { 0 };
			
			for (int k = -kerR; k <= kerR; k++)
			{

				if (channels == 1)
				{
					GuassionSum[0] += arr[kerR + k] * dst.at<uchar>(i, j + k);//行不变,列变换,先做水平方向的卷积
				}
				else if (channels == 3)//如果是三通道的情况
				{
					Vec3b bgr = dst.at<Vec3b>(i, j + k);
					auto a = arr[kerR + k];
					GuassionSum[0] += a*bgr[0];
					GuassionSum[1] += a*bgr[1];
					GuassionSum[2] += a*bgr[2];
				}
			}
			for (int k = 0; k < channels; k++)
			{
				if (GuassionSum[k] < 0)
					GuassionSum[k] = 0;
				else if (GuassionSum[k] > 255)
					GuassionSum[k] = 255;
			}
			if (channels == 1)
				dst.at<uchar>(i, j) = static_cast<uchar>(GuassionSum[0]);
			else if (channels == 3)
			{
				Vec3b bgr = { static_cast<uchar>(GuassionSum[0]), static_cast<uchar>(GuassionSum[1]), static_cast<uchar>(GuassionSum[2]) };
				dst.at<Vec3b>(i, j) = bgr;
			}

		}
	}

	//竖直方向
	for (int i = kerR; i < dst.rows - kerR; i++)
	{
		for (int j = kerR; j < dst.cols - kerR; j++)
		{
			double GuassionSum[3] = { 0 };
			//滑窗搜索完成高斯核平滑
			for (int k = -kerR; k <= kerR; k++)
			{

				if (channels == 1)//如果只是单通道
				{
					GuassionSum[0] += arr[kerR + k] * dst.at<uchar>(i + k, j);//行变,列不换,再做竖直方向的卷积
				}
				else if (channels == 3)//如果是三通道的情况
				{
					Vec3b bgr = dst.at<Vec3b>(i + k, j);
					auto a = arr[kerR + k];
					GuassionSum[0] += a*bgr[0];
					GuassionSum[1] += a*bgr[1];
					GuassionSum[2] += a*bgr[2];
				}
			}
			for (int k = 0; k < channels; k++)
			{
				if (GuassionSum[k] < 0)
					GuassionSum[k] = 0;
				else if (GuassionSum[k] > 255)
					GuassionSum[k] = 255;
			}
			if (channels == 1)
				dst.at<uchar>(i, j) = static_cast<uchar>(GuassionSum[0]);
			else if (channels == 3)
			{
				Vec3b bgr = { static_cast<uchar>(GuassionSum[0]), static_cast<uchar>(GuassionSum[1]), static_cast<uchar>(GuassionSum[2]) };
				dst.at<Vec3b>(i, j) = bgr;
			}

		}
	}
	delete[] arr;
}

2.将彩色图转换成灰度图

void ConvertRGB2GRAY(const Mat &image, Mat &imageGray)
{
	if (!image.data || image.channels() != 3)
	{
		return;
	}
	
	imageGray = Mat::zeros(image.size(), CV_8UC1);
	
	uchar *pointImage = image.data;
	uchar *pointImageGray = imageGray.data;
	
	size_t stepImage = image.step;
	size_t stepImageGray = imageGray.step;
	for (int i = 0; i < imageGray.rows; i++)
	{
		for (int j = 0; j < imageGray.cols; j++)
		{
			pointImageGray[i*stepImageGray + j] = (uchar)(0.114*pointImage[i*stepImage + 3 * j] + 0.587*pointImage[i*stepImage + 3 * j + 1] + 0.299*pointImage[i*stepImage + 3 * j + 2]);
		}
	}
}

3.然后利用Soble边缘提取的方法提取垂直方向的边缘

Soble的详细解释见:http://blog.csdn.net/linqianbi/article/details/78673903

//存储梯度膜长与梯度角
void SobelGradDirction(Mat &imageSource, Mat &imageSobelX, Mat &imageSobelY)
{
	
	imageSobelX = Mat::zeros(imageSource.size(), CV_32SC1);
	imageSobelY = Mat::zeros(imageSource.size(), CV_32SC1);
	//取出原图和X和Y梯度图的数组的首地址
	uchar *P = imageSource.data;
	uchar *PX = imageSobelX.data;
	uchar *PY = imageSobelY.data;

	
	int step = imageSource.step;
	int stepXY = imageSobelX.step;

	for (int i = 1; i < imageSource.rows - 1; ++i)
	{
		for (int j = 1; j < imageSource.cols - 1; ++j)
		{
			
			double gradY = P[(i + 1)*step + j - 1] + P[(i + 1)*step + j] * 2 + P[(i + 1)*step + j + 1] - P[(i - 1)*step + j - 1] - P[(i - 1)*step + j] * 2 - P[(i - 1)*step + j + 1];
			PY[i*stepXY + j*(stepXY / step)] = abs(gradY);

			double gradX = P[(i - 1)*step + j + 1] + P[i*step + j + 1] * 2 + P[(i + 1)*step + j + 1] - P[(i - 1)*step + j - 1] - P[i*step + j - 1] * 2 - P[(i + 1)*step + j - 1];
			PX[i*stepXY + j*(stepXY / step)] = abs(gradX);
			if (gradX == 0)
			{
				gradX = 0.00000000000000001;  //防止除数为0异常  
			}
			

		}
	}
	//将梯度数组转换成8位无符号整型
	convertScaleAbs(imageSobelX, imageSobelX);
	convertScaleAbs(imageSobelY, imageSobelY);
}

4.利用OTSU的二值化方法将步骤3中的图二值化

OTSU阈值二值化的详细解释见:http://blog.csdn.net/linqianbi/article/details/78592986

//OTSU算法函数实现
int OTSU(Mat &srcImage)
{
	int nRows = srcImage.rows;
	int nCols = srcImage.cols;

	int threshold = 0;
	double max = 0.0;
	double AvePix[256];
	int nSumPix[256];
	double nProDis[256];
	double nSumProDis[256];


						  
	for (int i = 0; i < 256; i++)
	{
		AvePix[i] = 0.0;
		nSumPix[i] = 0;
		nProDis[i] = 0.0;
		nSumProDis[i] = 0.0;
	}

	
	for (int i = 0; i < nRows; i++)
	{
		for (int j = 0; j < nCols; j++)
		{
			nSumPix[(int)srcImage.at<uchar>(i, j)]++;
		}
	}

	
	for (int i = 0; i < 256; i++)
	{
		nProDis[i] = (double)nSumPix[i] / (nRows*nCols);

	}

	
	AvePix[0] = 0;
	nSumProDis[0] = nProDis[0];

	
	for (int i = 1; i < 256; i++)
	{
		nSumProDis[i] = nSumProDis[i - 1] + nProDis[i];
		AvePix[i] = AvePix[i - 1] + i*nProDis[i];
	}

	double mean = AvePix[255];


	for (int k = 1; k < 256; k++)
	{
		double PA = nSumProDis[k];
		double PB = 1 - nSumProDis[k];
		double value = 0.0;
		if (fabs(PA) > 0.001 && fabs(PB) > 0.001)
		{
			double MA = AvePix[k];
			double MB = (mean - PA*MA) / PB;
			value = value = (double)(PA * PB * pow((MA - MB), 2));
																  //或者这样value = (double)(PA * PB * pow((MA-MB),2));//类间方差
																  //pow(PA,1)* pow((MA - mean),2) + pow(PB,1)* pow((MB - mean),2)
			if (value > max)
			{
				max = value;
				threshold = k;
			}
		}
	}
	return threshold;
}

5.利用水平扫描与垂直扫描的方法定位出车牌的区域

//查找车牌的上边线和下边线
void find_UpandDown_row(Mat_<uchar> dstimage, Mat src)
{
	int k = 0;
	
	for (int j = dstimage.rows/ 1.5; j < dstimage.rows -45; j++)
	{
		int count = 0;//记录每行白点的个数
		for (int i = 0; i < dstimage.cols - 1; i++)
		{
			if (dstimage.at<uchar>(j, i) != dstimage.at<uchar>(j, i + 1))
				count++;
			if (count > row_thresh)
			{
				row[k++] = j;
				break;
			}
		}
	}
	cout << "符合阈值的行数有:" << k + 1 << endl;
	/*从上边开始,三行连续时认为是起始行*/
	for (int i = 0; i < k - 2; i++)
	{
		if ((row[i] == row[i + 1] - 1) && (row[i] == row[i + 2] - 2))
		{
			rows_start = row[i];
			cout << "上划线所在的行数:" << rows_start << endl;
			break;
		}
	}
	//line(src, Point(0, rows_start), Point(dstimage.cols - 1, rows_start), Scalar(0, 0, 255));
	/*从下边开始,三行连续时认为是起始行*/
	for (int i = k - 1; i > 1; i--)
	{
		if ((row[i] == row[i - 1] + 1) && (row[i] == row[i - 2] + 2))
		{
			rows_end = row[i];
			cout << "下划线所在的行数:" << rows_end << endl;
			break;
		}
	}
	//line(src, Point(0, rows_end), Point(dstimage.cols - 1, rows_end), Scalar(0, 0, 255));
	imshow("原图", src);
}


//查找车牌的左边线和右边线
void find_LeftandRight_col(Mat_<uchar> dstimage, Mat src)
{
	int k = 0;//统计符合车牌信息的列数
	/*判断每行是否是含有车牌信息的列,通过查看白点像素的个数*/
	for (int j = dstimage.cols/3.2; j < dstimage.cols - dstimage.cols / 3.6; j++)
	{
		int count = 0;//记录每列白点的个数
		for (int i = rows_start; i < rows_end; i++)
		{
			if (dstimage.at<uchar>(i, j) != dstimage.at<uchar>(i + 1, j))
				count++;
			if (count > col_thresh)
			{
				col[k++] = j;
				break;
			}
		}
	}
	cout << "符合阈值的列数有:" << k + 1 << endl;
	/*从左边开始,三行连续时认为是起始行*/
	for (int i = 0; i < k - 2; i++)
	{
		if ((col[i] == col[i + 1] - 1) && (col[i] == col[i + 2] - 2))
		{
			cols_start = col[i];
			cout << "左划线所在的列数:" << cols_start << endl;
			break;
		}
	}
	//line(src, Point(cols_start, rows_start), Point(cols_start, rows_end), Scalar(0, 0, 255));
	/*从右边开始,三行连续时认为是起始行*/
	for (int i = k - 1; i > 1; i--)
	{
		if ((col[i] == col[i - 1] + 1) && (col[i] == col[i - 2] + 2))
		{
			cols_end = col[i];
			cout << "右划线所在的列数:" << cols_end << endl;
			break;
		}
	}
	//line(src, Point(cols_end, rows_start), Point(cols_end, rows_end), Scalar(0, 0, 255));
	imshow("原图", src);
}

//查找车牌区域
void find_ROI(Mat src)
{
	/*构建以(cols_start,rows_start)为左上角,长为cols_end - cols_start,宽为rows_end - rows_start的矩阵*/
	Rect rect = Rect(cols_start, rows_start, cols_end - cols_start, rows_end - rows_start);
	Mat ROI = src(rect);//建立车牌的图像
	imshow("car_plate", ROI);
}

最后放上完整的源代码仅供大家参考,还是有很多不足的地方,大家一起改正:

#include <iostream>
#include <vector>
#include <opencv2\core\core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
int cols_start = 0, cols_end = 0;//记录车牌开始、结束列
int rows_start = 0, rows_end = 0;//记录车牌开始、结束行
const int row_thresh = 28;//判断一行是不是车牌有效值的阈值
const int col_thresh = 2;//判断一列是不是车牌有效值的阈值
int row[200];//存放含有车牌有效信息的第j行,把所有有效行放在一个数组里,统一管理,便于判断
int col[30];//存放含有车牌有效信息的第j列

//计算一维高斯的权值数组
double *getOneGuassionArray(int size, double sigma);
void MyGaussianBlur(Mat &srcImage, Mat &dst, int size);
//******************灰度转换函数*************************  
//第一个参数image输入的彩色RGB图像的引用;  
//第二个参数imageGray是转换后输出的灰度图像的引用;  
//*******************************************************
void ConvertRGB2GRAY(const Mat &image, Mat &imageGray);

//******************Sobel卷积因子计算X、Y方向梯度和梯度方向角********************  
//第一个参数imageSourc原始灰度图像;  
//第二个参数imageSobelX是X方向梯度图像;  
//第三个参数imageSobelY是Y方向梯度图像;  
//第四个参数pointDrection是梯度方向角数组指针  
//*************************************************************  
void SobelGradDirction(Mat &imageSource, Mat &imageSobelX, Mat &imageSobelY);

//OTSU算法函数实现
int OTSU(Mat &srcImage);

void find_UpandDown_row(Mat_<uchar> dstimage, Mat src);//查找车牌的上边线和下边线
void find_LeftandRight_col(Mat_<uchar> dstimage, Mat src);//查找车牌的左边线和右边线
void find_ROI(Mat src);//查找车牌区域
int main()
{
	Mat srcImage = imread("1.jpg");
	if (!srcImage.data)
	{
		printf("could not load image...\n");
		return -1;
	}
	imshow("srcImage", srcImage);
	//高斯滤波
	Mat GuassionMat;
	MyGaussianBlur(srcImage, GuassionMat, 3);
	imshow("GuassionMat", GuassionMat);
	//转化为灰度图
	Mat srcGray;
	ConvertRGB2GRAY(GuassionMat, srcGray);
	imshow("srcGray", srcGray);
	//X方向的Soble边缘检测
	Mat imageSobelX, imageSobelY;
	SobelGradDirction(srcGray, imageSobelX, imageSobelY);
	imshow("imageSobelX", imageSobelX);
	//二值化
	//调用二值化函数得到最佳阈值
	int otsuThreshold = OTSU(imageSobelX);
	cout << otsuThreshold << endl;//输出最佳阈值

	Mat otsuResultImage = Mat::zeros(imageSobelX.rows, imageSobelX.cols, CV_8UC1);//创建一张一个通道的空的图像

	//利用得到的阈值进行二值操作
	for (int i = 0; i < imageSobelX.rows; i++)
	{
		for (int j = 0; j < imageSobelX.cols; j++)
		{
			if (imageSobelX.at<uchar>(i, j) > otsuThreshold)
			{
				otsuResultImage.at<uchar>(i, j) = 255;
			}
			else
			{
				otsuResultImage.at<uchar>(i, j) = 0;
			}
		}
	}
	imshow("otsuResultImage", otsuResultImage);
	find_UpandDown_row(otsuResultImage, srcImage);
	find_LeftandRight_col(otsuResultImage, srcImage);
	find_ROI(srcImage);
	waitKey(0);
	return 0;
}

double *getOneGuassionArray(int size, double sigma)
{
	double sum = 0.0;
	
	int kerR = size / 2;

	
	double *arr = new double[size];
	for (int i = 0; i < size; i++)
	{

		
		arr[i] = exp(-((i - kerR)*(i - kerR)) / (2 * sigma*sigma));
		sum += arr[i];

	}
		
	for (int i = 0; i < size; i++)
	{
		arr[i] /= sum;
		cout << arr[i] << endl;
	}
	return arr;
}




void MyGaussianBlur(Mat &srcImage, Mat &dst, int size)
{
	CV_Assert(srcImage.channels() || srcImage.channels() == 3); // 只处理单通道或者三通道图像
	int kerR = size / 2;
	dst = srcImage.clone();
	int channels = dst.channels();
	double* arr;
	arr = getOneGuassionArray(size, 1);//先求出高斯数组

									   //遍历图像 水平方向的卷积
	for (int i = kerR; i < dst.rows - kerR; i++)
	{
		for (int j = kerR; j < dst.cols - kerR; j++)
		{
			double GuassionSum[3] = { 0 };
			//滑窗搜索完成高斯核平滑
			for (int k = -kerR; k <= kerR; k++)
			{

				if (channels == 1)//如果只是单通道
				{
					GuassionSum[0] += arr[kerR + k] * dst.at<uchar>(i, j + k);//行不变,列变换,先做水平方向的卷积
				}
				else if (channels == 3)//如果是三通道的情况
				{
					Vec3b bgr = dst.at<Vec3b>(i, j + k);
					auto a = arr[kerR + k];
					GuassionSum[0] += a*bgr[0];
					GuassionSum[1] += a*bgr[1];
					GuassionSum[2] += a*bgr[2];
				}
			}
			for (int k = 0; k < channels; k++)
			{
				if (GuassionSum[k] < 0)
					GuassionSum[k] = 0;
				else if (GuassionSum[k] > 255)
					GuassionSum[k] = 255;
			}
			if (channels == 1)
				dst.at<uchar>(i, j) = static_cast<uchar>(GuassionSum[0]);
			else if (channels == 3)
			{
				Vec3b bgr = { static_cast<uchar>(GuassionSum[0]), static_cast<uchar>(GuassionSum[1]), static_cast<uchar>(GuassionSum[2]) };
				dst.at<Vec3b>(i, j) = bgr;
			}

		}
	}

	//竖直方向
	for (int i = kerR; i < dst.rows - kerR; i++)
	{
		for (int j = kerR; j < dst.cols - kerR; j++)
		{
			double GuassionSum[3] = { 0 };
			//滑窗搜索完成高斯核平滑
			for (int k = -kerR; k <= kerR; k++)
			{

				if (channels == 1)//如果只是单通道
				{
					GuassionSum[0] += arr[kerR + k] * dst.at<uchar>(i + k, j);//行变,列不换,再做竖直方向的卷积
				}
				else if (channels == 3)//如果是三通道的情况
				{
					Vec3b bgr = dst.at<Vec3b>(i + k, j);
					auto a = arr[kerR + k];
					GuassionSum[0] += a*bgr[0];
					GuassionSum[1] += a*bgr[1];
					GuassionSum[2] += a*bgr[2];
				}
			}
			for (int k = 0; k < channels; k++)
			{
				if (GuassionSum[k] < 0)
					GuassionSum[k] = 0;
				else if (GuassionSum[k] > 255)
					GuassionSum[k] = 255;
			}
			if (channels == 1)
				dst.at<uchar>(i, j) = static_cast<uchar>(GuassionSum[0]);
			else if (channels == 3)
			{
				Vec3b bgr = { static_cast<uchar>(GuassionSum[0]), static_cast<uchar>(GuassionSum[1]), static_cast<uchar>(GuassionSum[2]) };
				dst.at<Vec3b>(i, j) = bgr;
			}

		}
	}
	delete[] arr;
}

void ConvertRGB2GRAY(const Mat &image, Mat &imageGray)
{
	if (!image.data || image.channels() != 3)
	{
		return;
	}
	
	imageGray = Mat::zeros(image.size(), CV_8UC1);
	
	uchar *pointImage = image.data;
	uchar *pointImageGray = imageGray.data;
	
	size_t stepImage = image.step;
	size_t stepImageGray = imageGray.step;
	for (int i = 0; i < imageGray.rows; i++)
	{
		for (int j = 0; j < imageGray.cols; j++)
		{
			pointImageGray[i*stepImageGray + j] = (uchar)(0.114*pointImage[i*stepImage + 3 * j] + 0.587*pointImage[i*stepImage + 3 * j + 1] + 0.299*pointImage[i*stepImage + 3 * j + 2]);
		}
	}
}
//存储梯度膜长与梯度角
void SobelGradDirction(Mat &imageSource, Mat &imageSobelX, Mat &imageSobelY)
{
	
	imageSobelX = Mat::zeros(imageSource.size(), CV_32SC1);
	imageSobelY = Mat::zeros(imageSource.size(), CV_32SC1);
	
	uchar *P = imageSource.data;
	uchar *PX = imageSobelX.data;
	uchar *PY = imageSobelY.data;

	//取出每行所占据的字节数
	int step = imageSource.step;
	int stepXY = imageSobelX.step;

	for (int i = 1; i < imageSource.rows - 1; ++i)
	{
		for (int j = 1; j < imageSource.cols - 1; ++j)
		{
			//通过指针遍历图像上每一个像素   
			double gradY = P[(i + 1)*step + j - 1] + P[(i + 1)*step + j] * 2 + P[(i + 1)*step + j + 1] - P[(i - 1)*step + j - 1] - P[(i - 1)*step + j] * 2 - P[(i - 1)*step + j + 1];
			PY[i*stepXY + j*(stepXY / step)] = abs(gradY);

			double gradX = P[(i - 1)*step + j + 1] + P[i*step + j + 1] * 2 + P[(i + 1)*step + j + 1] - P[(i - 1)*step + j - 1] - P[i*step + j - 1] * 2 - P[(i + 1)*step + j - 1];
			PX[i*stepXY + j*(stepXY / step)] = abs(gradX);
			if (gradX == 0)
			{
				gradX = 0.00000000000000001;  //防止除数为0异常  
			}
			

		}
	}
	//将梯度数组转换成8位无符号整型
	convertScaleAbs(imageSobelX, imageSobelX);
	convertScaleAbs(imageSobelY, imageSobelY);
}

//OTSU算法函数实现
int OTSU(Mat &srcImage)
{
	int nRows = srcImage.rows;
	int nCols = srcImage.cols;

	int threshold = 0;
	double max = 0.0;
	double AvePix[256];
	int nSumPix[256];
	double nProDis[256];
	double nSumProDis[256];


						 
	for (int i = 0; i < 256; i++)
	{
		AvePix[i] = 0.0;
		nSumPix[i] = 0;
		nProDis[i] = 0.0;
		nSumProDis[i] = 0.0;
	}

	
	for (int i = 0; i < nRows; i++)
	{
		for (int j = 0; j < nCols; j++)
		{
			nSumPix[(int)srcImage.at<uchar>(i, j)]++;
		}
	}

	
	for (int i = 0; i < 256; i++)
	{
		nProDis[i] = (double)nSumPix[i] / (nRows*nCols);

	}

	
	AvePix[0] = 0;
	nSumProDis[0] = nProDis[0];

	
	for (int i = 1; i < 256; i++)
	{
		nSumProDis[i] = nSumProDis[i - 1] + nProDis[i];
		AvePix[i] = AvePix[i - 1] + i*nProDis[i];
	}

	double mean = AvePix[255];


	for (int k = 1; k < 256; k++)
	{
		double PA = nSumProDis[k];
		double PB = 1 - nSumProDis[k];
		double value = 0.0;
		if (fabs(PA) > 0.001 && fabs(PB) > 0.001)
		{
			double MA = AvePix[k];
			double MB = (mean - PA*MA) / PB;
			value = value = (double)(PA * PB * pow((MA - MB), 2)); 
																  //或者这样value = (double)(PA * PB * pow((MA-MB),2));//类间方差
																  //pow(PA,1)* pow((MA - mean),2) + pow(PB,1)* pow((MB - mean),2)
			if (value > max)
			{
				max = value;
				threshold = k;
			}
		}
	}
	return threshold;
}

//查找车牌的上边线和下边线
void find_UpandDown_row(Mat_<uchar> dstimage, Mat src)
{
	int k = 0;//统计符合车牌信息的行数
	/*判断每行是否是含有车牌信息的行,通过查看白点黑点交换的次数来决定的*/
	for (int j = dstimage.rows/ 1.5; j < dstimage.rows -45; j++)//一般车牌位于中下方,而且图像上方和下方环境复杂,所以不去检查
	{
		int count = 0;//记录每行白点的个数
		for (int i = 0; i < dstimage.cols - 1; i++)
		{
			if (dstimage.at<uchar>(j, i) != dstimage.at<uchar>(j, i + 1))//比较同一行相邻两个像素值
				count++;
			if (count > row_thresh)
			{
				row[k++] = j;
				break;
			}
		}
	}
	cout << "符合阈值的行数有:" << k + 1 << endl;
	/*从上边开始,三行连续时认为是起始行*/
	for (int i = 0; i < k - 2; i++)
	{
		if ((row[i] == row[i + 1] - 1) && (row[i] == row[i + 2] - 2))
		{
			rows_start = row[i];
			cout << "上划线所在的行数:" << rows_start << endl;
			break;
		}
	}
	//line(src, Point(0, rows_start), Point(dstimage.cols - 1, rows_start), Scalar(0, 0, 255));
	/*从下边开始,三行连续时认为是起始行*/
	for (int i = k - 1; i > 1; i--)
	{
		if ((row[i] == row[i - 1] + 1) && (row[i] == row[i - 2] + 2))
		{
			rows_end = row[i];
			cout << "下划线所在的行数:" << rows_end << endl;
			break;
		}
	}
	//line(src, Point(0, rows_end), Point(dstimage.cols - 1, rows_end), Scalar(0, 0, 255));
	imshow("原图", src);
}


//查找车牌的左边线和右边线
void find_LeftandRight_col(Mat_<uchar> dstimage, Mat src)
{
	int k = 0;//统计符合车牌信息的列数
	/*判断每行是否是含有车牌信息的列,通过查看白点像素的个数*/
	for (int j = dstimage.cols/3.2; j < dstimage.cols - dstimage.cols / 3.6; j++)
	{
		int count = 0;//记录每列白点的个数
		for (int i = rows_start; i < rows_end; i++)
		{
			if (dstimage.at<uchar>(i, j) != dstimage.at<uchar>(i + 1, j))
				count++;
			if (count > col_thresh)
			{
				col[k++] = j;
				break;
			}
		}
	}
	cout << "符合阈值的列数有:" << k + 1 << endl;
	/*从左边开始,三行连续时认为是起始行*/
	for (int i = 0; i < k - 2; i++)
	{
		if ((col[i] == col[i + 1] - 1) && (col[i] == col[i + 2] - 2))
		{
			cols_start = col[i];
			cout << "左划线所在的列数:" << cols_start << endl;
			break;
		}
	}
	//line(src, Point(cols_start, rows_start), Point(cols_start, rows_end), Scalar(0, 0, 255));
	/*从右边开始,三行连续时认为是起始行*/
	for (int i = k - 1; i > 1; i--)
	{
		if ((col[i] == col[i - 1] + 1) && (col[i] == col[i - 2] + 2))
		{
			cols_end = col[i];
			cout << "右划线所在的列数:" << cols_end << endl;
			break;
		}
	}
	//line(src, Point(cols_end, rows_start), Point(cols_end, rows_end), Scalar(0, 0, 255));
	imshow("原图", src);
}

//查找车牌区域
void find_ROI(Mat src)
{
	/*构建以(cols_start,rows_start)为左上角,长为cols_end - cols_start,宽为rows_end - rows_start的矩阵*/
	Rect rect = Rect(cols_start, rows_start, cols_end - cols_start, rows_end - rows_start);
	Mat ROI = src(rect);//建立车牌的图像
	imshow("car_plate", ROI);
}

原图:



高斯滤波效果图:



灰度图:



X方向的Soble边缘图:



OTSU阈值二值化的效果图:



最后定位的车牌的区域:


该代码只能从图片中找出车牌位置并分割出来,还不可以识别车牌字符;还有该代码只能识别图片内容比较简单和清晰的图片,不能识别模糊、环境复杂的图片,还有车牌倾斜的也不行,只适合供初学者参考学习,遇到不明白的地方可以在评论中提出问题;同样也希望大神们指点一下,让我有所改进。

猜你喜欢

转载自blog.csdn.net/linqianbi/article/details/79096630