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