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

Opencv學習之——二幀差法運動目標檢測與輪廓提取
這是我的第一篇CSDN博文。
代碼是從網上摘抄學習的,加了好多註釋,感覺就像邊看書邊做筆記一樣,給人以滿足的享受。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,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。
③當兩個物體稍有重疊時,會將重疊物體當作一個物體圈出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章