02.OpenCV 车牌识别 sobel定位

opencv模块介绍

简介

​ OpenCV是一个基于BSD许可开源发行的跨平台计算机视觉库。拥有C++,Python和Java接口,并且支持Windows, Linux, Mac OS, iOS 和 Android系统。实现了图像处理和计算机视觉方面的很多通用算法。

模块 功能
Core 核心基础模块,定义了被所有其他模块和基本数据结构(包括重要的多维数组Mat)使用的基本函数、底层数据结构和算法函数
Imgproc 图像处理模块,包括:滤波、高斯模糊、形态学处理、几何变换、颜色空间转换及直方图计算等
Highgui 高层用户交互模块,包括:GUI、图像与视频I\O等
Video 视频分析,,运动分析及目标跟踪。
Calib3d 3D模块,包括:摄像机标定、立体匹配、3D重建等
Features2d 二维特征检测与描述模块,包括:图像特征检测、描述、匹配等
Objdetect 目标检测模块,如:人脸检测等
MI 机器学习模块,包括:支持向量机、神经网络等
Flann 最近邻开源库。包含一系列查找算法,自动选取最快算法的机制。
Imgcodecs 图像编解码模块,图像文件的读写操作
Photo 图像计算(处理)模块,图像修复及去噪。
Shape 形状匹配算法模块。描述形状、比较形状
Stitching 图像拼接
Superres 超分辨率模块
Videoio 视频读写模块,视频文件包括摄像头的输入。
Videostab 解决拍摄的视频稳定
Dnn 深度神经网络
contrib 可以引入额外模块

opencv手册百度网盘:

链接:https://pan.baidu.com/s/15w-bgIWOX8_M5CM2w3VAsQ
提取码:unim

整体流程:

在这里插入图片描述

1.高斯模糊

1、高斯模糊:http://www.ruanyifeng.com/blog/2012/11/gaussian_blur.html

目的:对图像去噪,为边缘检测算法做准备。

//1,高斯模糊
	Mat blur;
	//ksize: they both must be positive and odd
	//opencv 只接受奇数半径, 半径越大越模糊
	GaussianBlur(src, blur, Size(5, 5), 0);
	imshow("原图", src);
	//imshow("高斯模糊", blur);

2、灰度化

目的:为边缘检测算法准备灰度化环境。

//2,灰度化
	Mat gray;
	cvtColor(blur, gray, COLOR_BGR2GRAY);
	//imshow("灰度化", gray);

为什么先模糊再灰度化?
如果接收的彩色图片 比灰度化的效果要好。

3、sobel运算(得到图像的一阶水平方向导数)

https://docs.opencv.org/2.4/doc/tutorials/imgproc/imgtrans/sobel_derivatives/sobel_derivatives.html

目的:检测图像中的垂直边缘,便于区分车牌。(Sobel运算只能对灰度图像有效,因此进行sobel运算前必须进行前面的灰度化工作)

//3,Sobel运算
	Mat sobel_16;
	//输入图像是8位的, uint8
	//Sobel函数求导后,导数可能的值会大于255或小于0,
	Sobel(gray, sobel_16, CV_16S, 1, 0);
	//imshow("sobel_16", sobel_16);//无法显示
	//转回8位
	Mat sobel;
	convertScaleAbs(sobel_16, sobel);
	//imshow("sobel", sobel);
	

4、二值化:(非黑即白)

目的:对图像的每个像素做一个阈值处理。为后续的形态学操作准备。

(灰度图像中,每个像素值是0-255,表示灰暗程度。设定一个阈值t,小于t的设为0,否则设为1)

Mat shold;
	threshold(sobel, shold, 0, 255, THRESH_OTSU + THRESH_BINARY);

5、形态学操作(闭操作)

目的:将车牌字符连接成一个连通区域,便于取轮廓

形态学操作的对象是二值化图像,腐蚀,膨胀是许多形态学操作的基础。

Mat close;
	Mat element = getStructuringElement(MORPH_RECT, Size(17, 3));
	morphologyEx(shold, close, MORPH_CLOSE, element);
	//imshow("闭操作", close);

腐蚀:

原理:像素x至于模板的中心,根据模版的大小,遍历所有被模板覆盖的其他像素,修改像素x的值为所有像素中最小的值。(对于中心点像素x,模板范围内只要有黑色,就会变成黑色)
在这里插入图片描述在这里插入图片描述

膨胀:

原理:与腐蚀操作相反
在这里插入图片描述

开操作:

原理:先腐蚀,再膨胀

在这里插入图片描述

闭操作:

原理:先膨胀,再腐蚀

在这里插入图片描述

6、求轮廓

目的:将连通域的外围画出来,便于形成外接矩形

	//6,找轮廓
	vector<vector<Point>> contours;
	findContours(close, //输入图像
		contours, //输出轮廓
		RETR_EXTERNAL, //外轮廓
		CHAIN_APPROX_NONE	//轮廓上所有像素点
	);
	RotatedRect rotatedRect;
	vector<RotatedRect> vec_sobel_rects;

7、尺寸判断

目的:初步筛选排除不可能是车牌的矩形(中国车牌的一般大小是440mm*140mm,宽高比为3.14)

//7,遍历并判断矩形尺寸
	for each (vector<Point> points in contours)
	{
		//最小外接矩形
		rotatedRect = minAreaRect(points);//带角度的矩形
		//rectangle(src, rotatedRect.boundingRect(), Scalar(0, 0, 255));
		//尺寸校验
		if (verifySizes(rotatedRect)) {
			vec_sobel_rects.push_back(rotatedRect);
		}
	}
	for each (RotatedRect rect in vec_sobel_rects)
	{
		//rectangle(src, rect.boundingRect(), Scalar(0, 255, 0));
	}
	//imshow("找轮廓", src);

/**
* 尺寸校验(宽高比&面积)
*/
int PlateLocate::verifySizes(RotatedRect rotatedRect)
{
	//容错率
	float error = 0.75f;
	//理想宽高比
	float aspect = float(136) / float(36);
	//真实宽高比
	float realAspect = float(rotatedRect.size.width) / float(rotatedRect.size.height);
	if (realAspect < 1) realAspect = (float)rotatedRect.size.height / (float)rotatedRect.size.width;
	//真实面积
	float area = rotatedRect.size.height * rotatedRect.size.width;
	//最小 最大面积 不符合的丢弃
	//给个大概就行 随时调整
	//尽量给大一些没关系, 这还是初步筛选。
	int areaMin = 44 * aspect * 14;
	int areaMax = 440 * aspect * 140;

	//比例浮动 error认为也满足
	//最小宽高比
	float aspectMin = aspect - aspect * error;
	//最大宽高比
	float aspectMax = aspect + aspect * error;

	if ((area < areaMin || area > areaMax) || (realAspect < aspectMin || realAspect > aspectMax))
		return 0;
	return 1;
}

8、角度判断

目的:初步筛选排除不可能是车牌的矩形

9、旋转矩形

目的:将偏斜的车牌调整为水平,为后面的车牌判断与字符识别提高成功率

仿射变换

10、调整大小

目的:确保候选车牌导入机器学习模型之前尺寸一致

	// 矩形矫正(角度判断,旋转矩形,调整大小)
	tortuosity(src, vec_sobel_rects, dst_plates);
	for each (Mat m in dst_plates)
	{
		imshow("sobel定位候选车牌", m);
		waitKey();
	}
/**
* 矩形矫正
*/
void PlateLocate::tortuosity(Mat src, vector<RotatedRect>& rects, vector<Mat>& dst_plates)
{
	//循环要处理的矩形
	for (RotatedRect roi_rect : rects) {
		//矩形角度
		float roi_angle = roi_rect.angle;
		float r = (float)roi_rect.size.width / (float)roi_rect.size.height;
		if (r < 1) {
			roi_angle = 90 + roi_angle;
		}

		//矩形大小
		Size roi_rect_size = roi_rect.size;

		//让rect在一个安全的范围(不能超过src)
		Rect2f  safa_rect;
		safeRect(src, roi_rect, safa_rect);

		//候选车牌
		//抠图  这里不是产生一张新图片 而是在src身上定位到一个Mat 让我们处理
		//数据和src是同一份
		Mat src_rect = src(safa_rect);
		//真正的候选车牌
		Mat dst;
		//不需要旋转的 旋转角度小没必要旋转了
		if (roi_angle - 5 < 0 && roi_angle + 5 > 0) {
			dst = src_rect.clone();
		}
		else {
			//相对于roi的中心点 不减去左上角坐标是相对于整个图的
			//减去左上角则是相对于候选车牌的中心点 坐标
			Point2f roi_ref_center = roi_rect.center - safa_rect.tl();
			Mat rotated_mat;
			//矫正 rotated_mat: 矫正后的图片
			rotation(src_rect, rotated_mat, roi_rect_size, roi_ref_center, roi_angle);
			dst = rotated_mat;
		}

		//调整大小
		Mat plate_mat;
		//高+宽
		plate_mat.create(36, 136, CV_8UC3);
		resize(dst, plate_mat, plate_mat.size());

		dst_plates.push_back(plate_mat);
		dst.release();
	}
}
/**
* 转换安全矩形
*/
void PlateLocate::safeRect(Mat src, RotatedRect rect, Rect2f& safa_rect)
{
	//RotatedRect 没有坐标
	//转为正常的带坐标的边框
	Rect2f boudRect = rect.boundingRect2f();

	//左上角 x,y
	float tl_x = boudRect.x > 0 ? boudRect.x : 0;
	float tl_y = boudRect.y > 0 ? boudRect.y : 0;
	//这里是拿 坐标 x,y 从0开始的 所以-1
	//比如宽长度是10,x坐标最大是9, 所以src.clos-1 
	//右下角
	float br_x = boudRect.x + boudRect.width < src.cols
		? boudRect.x + boudRect.width - 1
		: src.cols - 1;

	float br_y = boudRect.y + boudRect.height < src.rows
		? boudRect.y + boudRect.height - 1
		: src.rows - 1;

	float  w = br_x - tl_x;
	float h = br_y - tl_y;
	if (w <= 0 || h <= 0) return;
	safa_rect = Rect2f(tl_x, tl_y, w, h);
}
/**
* 旋转
*/
void PlateLocate::rotation(Mat src, Mat& dst, Size rect_size, Point2f center, double angle)
{
	//获得旋转矩阵
	Mat rot_mat = getRotationMatrix2D(center, angle, 1);

	//运用仿射变换
	Mat mat_rotated;
	//矫正后 大小会不一样,但是对角线肯定能容纳
	int max = sqrt(pow(src.rows, 2) + pow(src.cols, 2));
	warpAffine(src, mat_rotated, rot_mat, Size(max, max),
		INTER_CUBIC);
	//imshow("旋转前", src);
	//imshow("旋转后", mat_rotated);
	//截取 尽量把车牌多余的区域截取掉
	getRectSubPix(mat_rotated, Size(rect_size.width, rect_size.height), center, dst);
	//imshow("截取后", dst);
	//waitKey();

	mat_rotated.release();
	rot_mat.release();
}

发布了93 篇原创文章 · 获赞 26 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/sxj159753/article/details/101791024