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();
}