Opencv二帧差法检测运动目标与提取轮廓

时间:2022-06-20 06:41:35

Opencv学习之二帧差法运动目标检测与轮廓提取 ,供大家参考,具体内容如下

代码是从网上摘抄学习的,加了好多注释,感觉就像边看书边做笔记一样,给人以满足的享受。Let's do this!

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include "highgui.h"
#include "cv.h"
#include "stdio.h"
#include <time.h>
#include <math.h>
#include <string.h>
 
const double MHI_DURATION=0.1;//运动跟踪的最大持续时间0.1s
const double MAX_TIME_DELTA=0.5//最大时间增量0.5s
const double MIN_TIME_DELTA=0.05;//最小时间增量0.05s
const int N=3;
const int CONTOUR_MAX_AERA=16;
 
/*做帧差时要用到的图像缓冲*/
IplImage **buf=0;
int last=0;
/*临时图像*/
IplImage* mhi=0;//运动历史图像mhi
 
CvConnectedComp* cur_comp,mincomp;
/*typedef struct CvConnectedComp
 {
 double area; //区域的面积
 CvScalar value; //区域颜色的平均值
 CvRect rect; //是一个区域的外接矩形
 CvSeq * contour; //指向另一个序列的指针
 };*/
/*定义一个内存存储器*/
CvMemStorage* storage;
/*二维坐标系下的点,类型为整型,通常以0点为原点,有x、y坐标*/
CvPoint pt[4];
 
/*当前画面索引*/
int nCurFrameIndex=0;
 
/*定义用来更新运动历史图像的函数*/
/*img-输入视频帧;dst-检测结果*/
void update(IplImage *img,IplImage *dst,int diff_threshold)
{
 /*获得当前时间,单位是秒*/
 double timestamp=clock()/100;
 /*获得输入视频帧的尺寸,用存到size中*/
 CvSize size=cvSize(img->width,img->height);
 /*做帧差要用到的中间变量*/
 int i,idx1,idx2;
 /*当前帧与上一帧做帧差之后,得到的图像数据存储在nimg中*/
 IplImage* nimg;
 /*这步暂时没看懂- -!*/
 IplImage* pyr=cvCreateImage(cvSize((size.width&-2)/2,(size.height&-2)/2),8,1);
 /*定义一个内存存储器*/
 CvMemStorage* stor;
 /*创建一个可增长的序列seq*/
 CvSeq* seq;
 
 /*先进行数据的初始化*/
 /*如果历史图像为空,或者历史图像尺寸与输入的当前帧尺寸不吻合(这意味着打开了新的视频?)*/
 if(!mhi||mhi->width!=size.width||mhi->height!=size.height)
 {
 /*如果buf还未初始化,则为buf分配内存*/
 if(buf==0)
 {
  /*N=3*/
  buf=(IplImage**)malloc(N*sizeof(buf[0]));
  /*将指针s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值,块的大小由第三个参数指定:memset(void *s,char ch,unsigned n)。此处作用相当于将buf内的元素全部置零*/
  memset(buf,0,N*sizeof(buf[0]));
 }
 /*若buf已经初始化了,也将buf置零*/
 for(i=0;i<N;i++)
 {
  cvReleaseImage(&buf[i]);
  buf[i]=cvCreateImage(size,IPL_DEPTH_8U,1);
  cvZero(buf[i]);
 }
 /*重新初始化运动历史图像mhi*/
 cvReleaseImage(&mhi);
 mhi=cvCreateImage(size,IPL_DEPTH_32F,1);
 cvZero(mhi);
 }
 
 /*将当前要处理的帧转化为灰度图,放到buf的最后一帧*/
 cvCvtColor(img,buf[last],CV_BGR2GRAY);
 /*这三部是为了做帧差,让buf[idx1]永远保存的是上一帧,buf[idx2]保存当前帧*/
 idx1=last;
 idx2=(last+1)%N;
 last=idx2;
 /*做帧差,函数 cvAbsDiff 计算两个数组差的绝对值*/
 nimg=buf[idx2];
 cvAbsDiff(buf[idx1],buf[idx2],nimg);
 /*帧差之后,将得到的图像二值化*/
 cvThreshold(nimg,nimg,50,255,CV_THRESH_BINARY);
 /*去掉超时的影像以更新运动历史图像*/
 cvUpdateMotionHistory(nimg,mhi,timestamp,MHI_DURATION);
 cvConvert(mhi,dst);
 /*中值滤波,消除小的噪声
 函数cvPyrDown使用Gaussian金字塔分解对输入图像向下采样,去除噪声,图像是原图像的四分之一
 函数cvDialate做膨胀操作,去除目标的不连续空洞
 函数cvPyrUp使用Gaussian金字塔分解对输入图像向上采样,恢复图像,图象是原图像的四倍*/
 cvSmooth(dst,dst,CV_MEDIAN,3,0,0,0);
 cvPyrDown(dst,pyr,CV_GAUSSIAN_5x5);
 cvDilate(pyr,pyr,0,1);
 cvPyrUp(pyr,dst,CV_GAUSSIAN_5x5);
 
 /*创建轮廓*/
 stor=cvCreateMemStorage(0);
 seq=cvCreateSeq(CV_SEQ_ELTYPE_POINT,//从预定义的序列类型中选择一合适的类型
 sizeof(CvSeq),//此参数表示序列头部的大小;必须大于或等于sizeof(CvSeq)
 /*第三个参数是元素的大小,以字节计。这个大小必须与序列类型(由seq_flags指定)相一致,例如,对于一个点的序列,元素类型 CV_SEQ_ELTYPE_POINT应当被指定,参数elem_size必须等同于sizeof(CvPoint)。
*/
 sizeof(CvPoint),
 stor);//指向前面定义的内存存储器的指针
 
 /*找到所有轮廓*/
 cvFindContours(dst,//源二值图像
 stor,//返回轮廓的容器
 &seq,//输出参数,第一个外接轮廓的地址。
 sizeof(CvContour),
 CV_RETR_EXTERNAL,//mode:EXTERNAL——只查找最外的轮廓
 CV_CHAIN_APPROX_NONE,//轮廓近似的方法,具体见百度百科- -
 cvPoint(0,0));
 
 /*直接用CONTOUR中的矩形来画轮廓*/
 /*遍历seq序列*/
 for(;seq;seq=seq->h_next)
 {
 /*直接使用轮廓的矩形,调取rect会得到与x、y轴平行的矩形,并非最小矩形*/
 CvRect r=((CvContour*)cont)->rect;//将序列类型转换成轮廓类型的指针?
 /*矩形的面积小于轮廓面积的话,舍弃;矩形面积也不能过小*/
 if((r.height*r.width>CONTOUR_MAX_AERA)&&(r.height*r.width>2560))
 {
  /*cvRectangle函数通过对角线两个顶点,绘制矩形*/
  cvRectangle(img,//图像
  cvPoint(r.x,r.y),//一个顶点
  cvPoint(r.x + r.width, r.y + r.height),//另一个顶点
  CV_RGB(255,0,0),//线条颜色
  1,//线条粗细程度
  CV_AA,//线条类型
  0); //坐标点的小数点位数
 }
 }
 
 /*函数调用完毕,释放内存*/
 cvReleaseMemStorage(&stor);
 cvReleaseImage(&pyr);
}
 
/处理视频,主函数/
int main(int argc,char**argv)
{
 IplImage *motion=0;
 CvCapture *capture=0;
 /*读取视频帧*/
 capture=cvCaptureFromFile("D:\\视频\\01.mp4");
 if(capture)
 {
 cvNamedWindow("Motion",1);
 for(;;)
 {
  IplImage *image;
  /*使用cvGrabFrame函数抓取帧*/
  if(!cvGrabFrame(capture))
  break;
  /*使用cvRetrieveFrame函数取回被cvGrabFrame抓取的帧*/
  image=cvRetrieveFrame(capture);
  if(image)
  {
  /*如果motion并未初始化,说明这是第一帧。我们将motion初始化*/
  if(!motion)
  {
   motion=cvCreateImage(cvSize(image->width,image->height),8,1);
   cvZero(motion);
   /*需要保证内存存储的顺序和取出的帧相同*/
   motion->origin=image->origin;
  }
  }
  /*若取出了新的一帧,而且motion不为空,则更新画面*/
  update(image,motion,10);
  /*显示处理过的图像*/
  cvShowImage("Motion",image);
 
  /*10ms内检测到用户按了任意键,均退出*/
  if(cvWaitKey(10)>=0)
  break;
 }
 /*当上面这个for循环执行结束时,说明视频已经处理完成或者用户停止处理视频了*/
 cvReleaseCapture(&capture);
 cvDestroyWindow("Motion");
 }
 return 0;
}

经过测试,这个程序能够成功检测并用红色方框圈出移动的车辆和行人。

待改进的地方有:

①视频处理速度慢,导致视频处理速度只有视频正常播放速度的二分之一。

②对于行人的检测,画出的红色方框不稳定,不是将整个行人框出,经常会分别框出一个人的几个不同部位orz。

③当两个物体稍有重叠时,会将重叠物体当作一个物体圈出。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/sinat_34604992/article/details/52254318