在opencv的初等应用上,对运动物体的识别主要有帧差或背景差两种方式。
帧差法主要的原理是当前帧与前一帧作差取绝对值;
背景差主要的原理是当前帧与背景帧作差取绝对值;
在识别运动车辆上主要需要以下9个步骤:
(1)读取帧(VideoCapture,Mat)
(2)ROI选定(Rect)
(3)平滑处理(GaussianBlur)
(4)灰度处理(cvtColor,CV_RGB2GRAY)
(5)帧差或背景差(absdiff)
(6)二值化(threshold)
(7)膨胀(dilate)
(8)腐蚀(erode)
(9)绘制运动车辆(findContours,rectangle)
(1)读取帧(外部视频读取:Videocapture;帧读取:Mat)
这个属于比较基础的内容这里不做解释,直接附上源码:
1 /****************************************************** 2 函数名称: MyClass 3 函数功能: 初始化 4 传入参数: 5 返 回 值: 6 建立时间: 2018-05-17 7 修改时间: 8 建 立 人: 9 修 改 人: 10 其它说明: 11 ******************************************************/ 12 MyClass::MyClass() 13 { 14 //capture.open("F:/code/vsc++/Car_Find/Car_Find/交大七公里交通监控.avi"); 15 capture.open("交大七公里交通监控.avi"); 16 //capture = 0; 17 if (!capture.isOpened())//判断是否打开视频文件 18 { 19 exit(1); 20 } 21 FPS = capture.get(CV_CAP_PROP_FPS); 22 } 23 /****************************************************** 24 函数名称: ~MyClass 25 函数功能: 释放空间 26 传入参数: 27 返 回 值: 28 建立时间: 2018-05-17 29 修改时间: 30 建 立 人: 31 修 改 人: 32 其它说明: 33 ******************************************************/ 34 MyClass::~MyClass() 35 { 36 capture.release(); 37 } 38 /****************************************************** 39 函数名称: play 40 函数功能: 播放法 41 传入参数: 42 返 回 值: 43 建立时间: 2018-05-17 44 修改时间: 45 建 立 人: 46 修 改 人: 47 其它说明: 48 ******************************************************/ 49 void MyClass::play(){ 50 Mat frame; 51 namedWindow("播放界面按Esc退出", 0); 52 cvResizeWindow("播放界面按Esc退出", 600, 500); 53 while (true) 54 { 55 capture >> frame; 56 if (frame.empty())break; 57 imshow("播放界面按Esc退出", frame); 58 if (waitKey(1000.0 / FPS) == 27)//按原FPS显示 59 { 60 cout << "ESC退出!" << endl; 61 break; 62 } 63 } 64 }
处理结果:
(2)ROI选定(Rect)
这里使用Rect进行划ROI(感兴趣区域),画出一个矩阵的ROI。在这里最好的ROI是梯形(减少除了道路外的不必要的干扰)
这里提下做法:做一个灰度的mask(遮罩层),然后调整mask的形状大小。ps:感兴趣的可以做下。
(3)平滑处理(处理方法:GaussianBlur)
这里采用高斯平滑处理,在拍摄视频的时候会受到电流的干扰,但这个干扰时均匀存在的,所以采用高斯平滑处理可以去除电流的干扰。
方法很简单,源码如下:
1 /****************************************************** 2 函数名称: getSmooth 3 函数功能: 平滑处理 4 传入参数: 5 返 回 值: 6 建立时间: 2018-05-17 7 修改时间: 8 建 立 人: 9 修 改 人: 10 其它说明: 11 ******************************************************/ 12 Mat MyClass::getSmooth(Mat frame) 13 { 14 Mat img; 15 GaussianBlur(frame, img, Size(13, 13),2,2); 16 return img; 17 }
处理结果:
(4)灰度处理(处理方法:cvtColor;核:CV_RGB2GRAY)
RGB的图对我们的识别会造成一定的干扰或者说增加处理的难度,这里将原帧转换为灰度图像;
在平滑处理 后,直接调用opencv的cvtColor方法:
1 /****************************************************** 2 函数名称: getGray 3 函数功能: 灰度处理 4 传入参数: 5 返 回 值: 6 建立时间: 2018-05-17 7 修改时间: 8 建 立 人: 9 修 改 人: 10 其它说明: 11 ******************************************************/ 12 Mat MyClass::getGray(Mat frame) 13 { 14 Mat img; 15 cvtColor(frame, img, CV_RGB2GRAY); 16 return img; 17 }
处理结果:
(5)帧差或背景差(处理方法:absdiff)
不管是帧差和背景差都是需要获取当前帧跟对比帧,其中对比帧的获取和帧的作差是处理的关键。
在获取到处理好的灰度图在进行帧差处理。
以下为对比帧的获取 的源码:
1 /****************************************************** 2 函数名称: play 3 函数功能: 播放帧差法 4 传入参数: 5 返 回 值: 6 建立时间: 2018-05-17 7 修改时间: 8 建 立 人: 9 修 改 人: 10 其它说明: 11 ******************************************************/ 12 void MyClass::play(){ 13 Mat frame,preframe,curframe,result; 14 namedWindow("播放界面按Esc退出", 0); 15 //cvResizeWindow("播放界面按Esc退出", 600, 500); 16 while (true) 17 { 18 capture >> frame; 19 if (frame.empty())break; 20 if (preframe.empty())preframe = frame.clone();//首帧处理 21 curframe = frame.clone(); 22 imshow("播放界面按Esc退出", frame); 23 if (waitKey(1000.0 / FPS) == 27)//按原FPS显示 24 { 25 cout << "ESC退出!" << endl; 26 break; 27 } 28 preframe = frame.clone();//记录当前帧为下一帧的前帧 29 } 30 }
以下为帧差处理的源码:
/****************************************************** 函数名称: getDiff 函数功能: 帧差化处理 传入参数: 返 回 值: 建立时间: 2018-05-17 修改时间: 建 立 人: 修 改 人: 其它说明: ******************************************************/ Mat MyClass::getDiff(Mat preframe,Mat frame) { Mat img; absdiff(preframe, frame, img); return img; }
处理结果:
这里一个车的灰度轮廓已经可以识别出来了,但是我们的目的是将车辆的轮廓识别处理,为了更加精准,去除不必要的干扰,需要下面的处理。
(6)二值化(处理方法:threshold,阈值类型:CV_THRESH_BINARY)
Threshold函数详解,其中CV_THRESH_BINARY:当前点值大于阈值时,取Maxval,也就是第四个参数,下面再不说明,否则设置为0
由于这些车辆原画偏暗,这里设定阈值为30,处理当前点值大于阈值时,取白色255。
源码如下:
1 /****************************************************** 2 函数名称: getEz 3 函数功能: 二值化处理 4 传入参数: 5 返 回 值: 6 建立时间: 2018-05-17 7 修改时间: 8 建 立 人: 9 修 改 人: 10 其它说明: 11 ******************************************************/ 12 Mat MyClass::getEz(Mat frame) 13 { 14 Mat img; 15 threshold(frame, img,30, 255, CV_THRESH_BINARY); 16 return img; 17 }
处理结果:
车的黑白轮廓已经显示出来了,不过这里还不是一块整体,所以需要进行膨胀处理。
ps:有些博文的处理方式是先腐蚀处理后膨胀处理,这个在一定程度上会消除干扰点。但是这里考虑到车辆中有摩托车,如果先腐蚀处理的话会将摩托车给消除掉,造成识别精度不高。
(7)膨胀(处理方法:dilate)
膨胀的目的在于将一辆车拼合成一块完整的个体,以达到标识的目的
opencv提供了dilate的方法进行处理,源码如下:
1 /****************************************************** 2 函数名称: getPz 3 函数功能: 膨胀处理 4 传入参数: 5 返 回 值: 6 建立时间: 2018-05-17 7 修改时间: 8 建 立 人: 9 修 改 人: 10 其它说明: 11 ******************************************************/ 12 Mat MyClass::getPz(Mat frame) 13 { 14 Mat img; 15 Mat element=getStructuringElement(MORPH_RECT, Size(11, 30)); 16 dilate(frame, img, element); 17 return img; 18 }
处理结果:
这时候已经合成一块了,但是如果旁边的车靠的太近的话,会导致多辆车黏合成一块,所以下面做腐蚀处理。
(8)腐蚀(处理方法:erode)
腐蚀的目的在于将因为膨胀而导致的黏合,还有非关键点和区域的清除,以达到区分标识的目的。
opencv提供了erode的方法进行处理,源码如下:
1 /****************************************************** 2 函数名称: getFs 3 函数功能: 腐蚀处理 4 传入参数: 5 返 回 值: 6 建立时间: 2018-05-17 7 修改时间: 8 建 立 人: 9 修 改 人: 10 其它说明: 11 ******************************************************/ 12 Mat MyClass::getFs(Mat frame) 13 { 14 Mat img; 15 Mat element = getStructuringElement(MORPH_RECT, Size(10, 16)); 16 erode(frame, img, element); 17 return img; 18 }
处理结果:
ps:这里截的图不是很好,看不出效果分离的效果。感兴趣的可以下载下源码去调试。
(9)绘制运动车辆(处理方法:findContours,rectangle)
这里需要将车辆的外围轮廓描绘出来即可,所以findContours采用
mode取值“CV_RETR_EXTERNAL”,method取值“CV_CHAIN_APPROX_NONE”,即只检测最外层轮廓,并且保存轮廓上所有点;
rectangle的使用并不难,但是这里要注意的是:
a.处理的图像是原图像的ROI;
b.findContours输出的是处理过图像的坐标位置
所以在使用rectangle的时候,画区域的时候要加上原来ROI的起始位置的坐标,改成原来图像的坐标位置。
源码如下:
1 /****************************************************** 2 函数名称: Deal 3 函数功能: ROI区域处理 4 传入参数: 5 返 回 值: 6 建立时间: 2018-05-17 7 修改时间: 8 建 立 人: 9 修 改 人: 10 其它说明: 11 ******************************************************/ 12 Mat MyClass::Deal(Mat preframe, Mat frame) 13 { 14 Mat result = frame.clone(); 15 Mat curimageROI = preframe(Rect(preframe.cols / 5, preframe.rows / 5, preframe.cols / 1.5, preframe.rows / 1.5)); 16 Mat preimageROI = frame(Rect(frame.cols / 5, frame.rows / 5, frame.cols / 1.5, frame.rows / 1.5)); 17 18 Mat sm_pre, sm_cur; 19 sm_pre = getSmooth(preimageROI); 20 sm_cur = getSmooth(curimageROI); 21 imshow("平滑处理", sm_cur); 22 23 Mat gray_pre, gray_cur; 24 gray_pre = getGray(sm_pre); 25 gray_cur = getGray(sm_cur); 26 imshow("灰度处理",gray_cur); 27 28 Mat diff; 29 diff = getDiff(gray_pre, gray_cur); 30 imshow("帧差处理",diff); 31 32 Mat ez; 33 ez = getEz(diff); 34 imshow("二值化处理", ez); 35 36 Mat pz; 37 pz = getPz(ez); 38 imshow("膨胀处理", pz); 39 40 Mat fs; 41 fs = getFs(pz); 42 imshow("腐蚀处理", fs); 43 44 vector<vector<Point> > contours; 45 findContours(fs, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); 46 vector<Rect> boundRect(contours.size()); 47 for (int i = 0; i < contours.size(); i++) 48 { 49 boundRect[i] = boundingRect(contours[i]); 50 rectangle(result, Rect(boundRect[i].x + frame.cols/5, boundRect[i].y + frame.rows/5, boundRect[i].width, boundRect[i].height), Scalar(0, 255, 0), 1);//在result上绘制正外接矩形 51 } 52 return result; 53 }
处理结果:
这里效果不是非常好可以勉强识别出车辆轮廓。但作为初级应用是足够的。
如需要源码请转移至码云:https://gitee.com/cjqbaba/MediaTest/tree/Car_Find进行源码克隆下载
如有问题请留言评论。转载请注明出处,谢谢。