在我的车牌区域定位的方法的流程是:
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阈值二值化的效果图:
最后定位的车牌的区域:
该代码只能从图片中找出车牌位置并分割出来,还不可以识别车牌字符;还有该代码只能识别图片内容比较简单和清晰的图片,不能识别模糊、环境复杂的图片,还有车牌倾斜的也不行,只适合供初学者参考学习,遇到不明白的地方可以在评论中提出问题;同样也希望大神们指点一下,让我有所改进。