1. 程式人生 > 程式設計 >Opencv二幀差法檢測運動目標與提取輪廓

Opencv二幀差法檢測運動目標與提取輪廓

Opencv學習之二幀差法運動目標檢測與輪廓提取 ,供大家參考,具體內容如下

程式碼是從網上摘抄學習的,加了好多註釋,感覺就像邊看書邊做筆記一樣,給人以滿足的享受。Let's do this!

#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,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);
 cvPyrDown(dst,pyr,CV_GAUSSIAN_5x5);
 cvDilate(pyr,1);
 cvPyrUp(pyr,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),//線條顏色
   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),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。

③當兩個物體稍有重疊時,會將重疊物體當作一個物體圈出。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。