基于OpenCV的车牌识别与分割

编程入门 行业动态 更新时间:2024-10-16 02:29:41

基于OpenCV的<a href=https://www.elefans.com/category/jswz/34/1761824.html style=车牌识别与分割"/>

基于OpenCV的车牌识别与分割

基于OpenCV的车牌识别与分割

车牌识别的整个流程分为车牌位置查找, 车牌分割, 字符分割三部分, 车牌位置查找主要基于色彩空间查找的方法, 车牌分割主要基于位置查找之后的车牌二值图的行列加和统计.

车牌位置查找

以目前最常见的蓝色车牌为例, 车牌查找过程首先要进行一次基于色彩的特殊灰度化, 主要原理是将原图进行rgb通道分离, 然后进行通道相减提取蓝色区域, 并与普通的灰度图进行一次加权平均, 得到最终结果, 代码如下:

// 针对蓝色区域的特殊灰度化
//input是输入的原图
//output是输出的灰度图
//rate是基于色彩分割所占比重
void GrayscaleSegmentation(const Mat& input, Mat& output, float rate)
{Mat result;if(input.channels() == 1){result = input;output = result;}Mat bgr_channel[3];split(input, bgr_channel);Mat b_r = bgr_channel[0] - bgr_channel[2];Mat b_g = bgr_channel[0] - bgr_channel[1];Mat gray;cvtColor(input, gray, COLOR_BGR2GRAY);result = (b_r / 2 + b_g / 2) * rate + gray * (1 - rate);output = result;
}

效果如下图:

完成灰度化之后在进行二值化, 两次膨胀一次腐蚀, 如下图所示:

之后再查找图中轮廓, 计算轮廓的最小外接旋转矩形, 找出面积最大的一个便是车牌.

上述过程代码如下:

RotatedRect FindLicense(const Mat& input)	//input是输入的原图
{Mat img = input.clone();GrayscaleSegmentation(img, img, 0.8);threshold(img, img, 70, 255, THRESH_BINARY);dilate(img, img, getStructuringElement(MORPH_RECT, Size(3, 3)), Point(-1, -1), 2);erode(img, img, getStructuringElement(MORPH_RECT, Size(3, 3)), Point(-1, -1), 1);vector<vector<cv::Point>> vpp;findContours(img, vpp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);RotatedRect max_r_rect;for(auto& i :vpp){RotatedRect r_rect = minAreaRect(i);if(r_rect.size.area() > max_r_rect.size.area()){max_r_rect = r_rect;}}return max_r_rect;
}

至此, 车牌查找完成.

车牌分割

对得到的旋转矩形提取感兴趣区域, 然后对区域进行放射变换, 得到进一步的车牌分割图, 如下图所示:

代码实现如下:

Mat lic_r;
Rect lic_rect = r_rect.boundingRect();	//r_rect是车牌的旋转矩形
warpAffine(src(lic_rect),lic_r,getRotationMatrix2D(lic_rect.tl()/2,r_rect.angle-90,1),lic_rect.size());

字符分割

字符分割首先对得到的车牌图进行灰度化, 然后使用自适应二值化算法进行二值化, 其代码实现如下:

void AdaptiveThreshold(const Mat& input, Mat& output, double rate)
{Mat src = input.clone();int height = (int)sqrt(double(src.rows * (src.cols + src.rows)) / double(src.cols));int width = src.cols * height / src.rows;for(int i = 0; i < src.rows; i++)for (int j = 0; j < src.cols; j++){int h1 = max(1, i - height / 2);int h2 = max(1, i + height / 2);int w1 = min(j - width / 2, src.cols);int w2 = min(j + width / 2, src.cols);double avg = 0;for (int x = h1; x < h2; x++)for (int y = w1; y < w2; y++)avg += (double) src.at<uint8_t>(x, y);src.at<uint8_t>(i, j) = uint8_t(avg / ((w2 - w1) * (h2 - h1)));}for(int i = 0; i< src.rows; i++)for(int j = 0; j < src.cols; j++)src.at<uint8_t>(i, j) = input.at<uint8_t>(i, j) < (src.at<uint8_t>(i, j) * rate) ? 0 : 255;output = src;
}

之后对二值化后的车牌进行水平方向灰度值统计, 找出其中垂直方向宽度最大的连续行组, 截取之作为进一步分割出的车牌, 如下图:

代码实现如下:

    // 横向投票, 得到列向量, 取最宽Mat v_vector(Size(1, 35), CV_32F, Scalar(0));reduce(src, v_vector, 1, REDUCE_SUM, CV_MAKE_TYPE(CV_32F, 32));v_vector.convertTo(v_vector, CV_8UC1, 0.01);threshold(v_vector, v_vector, 50, 255, THRESH_BINARY);vector<vector<Point>> v_vvp;findContours(v_vector, v_vvp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);Rect max_rect;for(auto& i : v_vvp){Rect rect = boundingRect(i);if(rect.area() > max_rect.area()){max_rect = rect;}}src = src(Rect(max_rect.tl(), Point(110, max_rect.br().y)));resize(src, src, Size(110, 35));

然后对车牌进行垂直方向投票, 找到其中较宽的部分列组, 分割为每一位字符, 如下图:

代码实现如下:

    // 纵向投票, 取出每一个字符Mat h_vector(Size(110, 1), CV_32F, Scalar(0));reduce(src, h_vector, 0, REDUCE_SUM, CV_MAKE_TYPE(CV_32F, 32));h_vector.convertTo(h_vector, CV_8UC1, 0.03);threshold(h_vector, h_vector, 20, 255, THRESH_BINARY);vector<vector<Point>> h_vvp;findContours(h_vector, h_vvp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);vector<Mat> result(7);int index = 7;for(auto& i : h_vvp){Rect rect = boundingRect(i);if(rect.area() < 5){continue;}Mat character = src(Rect(rect.tl(), Point(rect.br().x, 35)));resize(character, character, Size(20, 20));index--;if(index < 0){break;}result[index] = character;}

总结

至此, 可以完成车牌识别并分割, 更换不同的输入测试识别的稳定性, 结果如下:

缺点与不足

仅仅依赖色彩特征查找, 易受图中相似颜色干扰, 并且对于倾斜度较大的图片识别效果不佳, 有待加入基于边缘检测的部分组成混合车牌查找与评估.

参考资料

图像的自适应二值化()

利用Hough变换和先验知识的车牌字符分割算法(=637456931763232421&DBCODE=CJFD&TABLEName=CJFD2004&FileName=JSJX200401016&RESULT=1&SIGN=c4LdsOAPtniwR9kXPsNeSqq0KHA%3d)

附录(完整代码实现)

#include <iostream>
#include <opencv2/opencv.hpp>using namespace std;
using namespace cv;RotatedRect FindLicense(const Mat& input);
void SplitCharacters(const Mat& input, vector<Mat>& output);void GrayscaleSegmentation(const Mat& input, Mat& output, float rate);
void AdaptiveThreshold(const Mat& input, Mat& output, double rate);int main(int argc, char** argv)
{Mat src = imread("img/3.png");RotatedRect r_rect = FindLicense(src);Mat lic_r;Rect lic_rect = r_rect.boundingRect();warpAffine(src(lic_rect), lic_r, getRotationMatrix2D(lic_rect.tl()/2, r_rect.angle - 90, 1), lic_rect.size());vector<Mat> characters;SplitCharacters(lic_r, characters);imshow("src", src);imshow("lic", lic_r);if(characters.size() == 7){imshow("0", characters[0]);imshow("1", characters[1]);imshow("2", characters[2]);imshow("3", characters[3]);imshow("4", characters[4]);imshow("5", characters[5]);imshow("6", characters[6]);}waitKey();return 0;
}RotatedRect FindLicense(const Mat& input)
{Mat img = input.clone();GrayscaleSegmentation(img, img, 0.8);threshold(img, img, 70, 255, THRESH_BINARY);dilate(img, img, getStructuringElement(MORPH_RECT, Size(3, 3)), Point(-1, -1), 2);erode(img, img, getStructuringElement(MORPH_RECT, Size(3, 3)), Point(-1, -1), 1);vector<vector<cv::Point>> vpp;findContours(img, vpp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);// TODO: 添加边缘检测与色彩检测共同评估, 或需要将色彩检测与边缘检测分离//    Mat o1, o2;
//    Mat kernel = (Mat_<float>(2, 2) << 1, 0, 0, -1);
//    filter2D(output, o1, -1, kernel);
//    kernel = (Mat_<float>(2, 2) << 0, 1, -1, 0);
//    filter2D(output, o2, -1, kernel);
//    output = o1+o2;RotatedRect max_r_rect;for(auto& i :vpp){RotatedRect r_rect = minAreaRect(i);if(r_rect.size.area() > max_r_rect.size.area()){max_r_rect = r_rect;}}return max_r_rect;
}void SplitCharacters(const Mat& input, vector<Mat>& output)
{// 归一化Mat src = input.clone();resize(src, src, Size(110, 35));cvtColor(src, src, COLOR_BGR2GRAY);AdaptiveThreshold(src, src, 1.2);// 横向投票, 得到列向量, 取最宽Mat v_vector(Size(1, 35), CV_32F, Scalar(0));reduce(src, v_vector, 1, REDUCE_SUM, CV_MAKE_TYPE(CV_32F, 32)); // NOLINT(hicpp-signed-bitwise)v_vector.convertTo(v_vector, CV_8UC1, 0.01); // NOLINT(hicpp-signed-bitwise)threshold(v_vector, v_vector, 50, 255, THRESH_BINARY);vector<vector<Point>> v_vvp;findContours(v_vector, v_vvp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);Rect max_rect;for(auto& i : v_vvp){Rect rect = boundingRect(i);if(rect.area() > max_rect.area()){max_rect = rect;}}src = src(Rect(max_rect.tl(), Point(110, max_rect.br().y)));resize(src, src, Size(110, 35));// 纵向投票, 取出每一个字符Mat h_vector(Size(110, 1), CV_32F, Scalar(0));reduce(src, h_vector, 0, REDUCE_SUM, CV_MAKE_TYPE(CV_32F, 32)); // NOLINT(hicpp-signed-bitwise)h_vector.convertTo(h_vector, CV_8UC1, 0.03); // NOLINT(hicpp-signed-bitwise)threshold(h_vector, h_vector, 20, 255, THRESH_BINARY);vector<vector<Point>> h_vvp;findContours(h_vector, h_vvp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);vector<Mat> result(7);int index = 7;for(auto& i : h_vvp){Rect rect = boundingRect(i);if(rect.area() < 5){continue;}Mat character = src(Rect(rect.tl(), Point(rect.br().x, 35)));resize(character, character, Size(20, 20));index--;if(index < 0){break;}result[index] = character;}// 输出result.swap(output);
}// 针对蓝色区域的特殊灰度化
void GrayscaleSegmentation(const Mat& input, Mat& output, float rate)
{Mat result;if(input.channels() == 1){result = input;output = result;}Mat bgr_channel[3];split(input, bgr_channel);Mat b_r = bgr_channel[0] - bgr_channel[2];Mat b_g = bgr_channel[0] - bgr_channel[1];Mat gray;cvtColor(input, gray, COLOR_BGR2GRAY);result = (b_r / 2 + b_g / 2) * rate + gray * (1 - rate);output = result;
}void AdaptiveThreshold(const Mat& input, Mat& output, double rate)
{Mat src = input.clone();int height = (int)sqrt(double(src.rows * (src.cols + src.rows)) / double(src.cols));int width = src.cols * height / src.rows;for(int i = 0; i < src.rows; i++)for (int j = 0; j < src.cols; j++){int h1 = max(1, i - height / 2);int h2 = max(1, i + height / 2);int w1 = min(j - width / 2, src.cols);int w2 = min(j + width / 2, src.cols);double avg = 0;for (int x = h1; x < h2; x++)for (int y = w1; y < w2; y++)avg += (double) src.at<uint8_t>(x, y);src.at<uint8_t>(i, j) = uint8_t(avg / ((w2 - w1) * (h2 - h1)));}for(int i = 0; i< src.rows; i++)for(int j = 0; j < src.cols; j++)src.at<uint8_t>(i, j) = input.at<uint8_t>(i, j) < (src.at<uint8_t>(i, j) * rate) ? 0 : 255;output = src;
}

更多推荐

基于OpenCV的车牌识别与分割

本文发布于:2024-03-05 01:06:58,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1710906.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:车牌   OpenCV

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!