opencv_MHI_深入研究








MHI 最初是由 Bobick 和 Davis 提出的,
在此之前, Bobick 和 Davis 首先提出了二值的運動能量圖
( Motion Energy Image , MEI ),通過描述物體如何移動和運動在空間中發生的位置,
來進行基於運動的物體識別。

運動能量圖顯示了運動的輪廓和能量的空間分佈。
在運動能量圖的基礎上產生了運動歷史圖( Motion History Image , MHI )。
運動歷史圖是一種基於視覺的模板方法,通過計算時間段內同一位置的像素變化,
將目標運動情況以圖像亮度的形式表現出來。

其每個像素的灰度值表示了在一組視頻序列中該位置像素的最近的運動情況。
最後運動的時刻越接近當前幀,該像素的灰度值越高。

因此, MHI 圖像可以表徵人體在一個動作過程中最近的動作情況,
這使得 MHI 被廣 ​​泛應用於動作識別領域。





設 H 為運動歷史圖像素的強度值, H (x, y, t) 可以由更新函數計算得出:



式中, (x, y) 和 t 為像素點的位置及時間;

(tau念法:ʹtau 】: τ(小寫)為持續時間,
代表 「一個時間區間」,從幀數的角度決定了​​運動的時間範圍

Ψ(psi) /【念法:ʹpsaɪΨ(x, y, t)為更新函數,
可由幀間差(Frame Difference)或光流(Optical Flow)等多種方法定義

δ(delta)  / 念法: ʹdɛltə  : 為衰退參數






(1)    mhi(x,y) = timestamp if silhouette(x,y)!=0.
即當運動輪廓圖像在坐標(x,y)處不為零時,那麼將mhi(x,y)處打上時間標記timestamp.

(2)    mhi(x,y) = 0 if silhouette(x, y) = 0 and mhi(x,y) < (timestamp - duration).
由於timestamp是在不斷隨著時間增長的,所以發生過運動的像素點維持的 
最長時間為duration.
mhi(x,y)保持的時間超過duration後,若在(x,y)處沒有發生新的運動,就令mhi(x,y)為零。

(3)    mhi(x,y) = mhi(x,y) otherwise.

只要發生過運動的像素點維持的時間沒有超過duration,那麼令mhi(x,y)的大小維持發生運動的那個時刻的時間標記。



式中, I (x, y, t) 為視頻圖像序列第 t 幀坐標 (x, y) 像素點的強度值, delta 為幀間距離, 






實踐的程式碼



#include "opencv2/video/tracking.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc_c.h"
#include <time.h> // for CLOCKS_PER_SEC 及 clock()
#include <stdio.h>
#include <ctype.h>

// various tracking parameters (in seconds)
const double MHI_DURATION = 3;//1s 為運動跟踪的最大持續時間
const double MAX_TIME_DELTA = 0.5;//最大時間增量為0.5s 
const double MIN_TIME_DELTA = 0.05;//最小時間增量0.05s 

// number of cyclic frame buffer used for motion detection
// (should, probably, depend on FPS)
const int N = 5;//frame buffer當中你要存取的frame數

// ring image buffer
IplImage **buf = 0; // 創建 frame buffer , 型態為IplImage * buf 的 Array
int last = 0;

// temporary images
IplImage *mhi = 0; // MHI
IplImage *orient = 0; // orientation
IplImage *mask = 0; // valid orientation mask
IplImage *segmask = NULL; // motion segmentation map
CvMemStorage* storage = NULL; // temporary storage
//用來建立一個指定大小的記憶體區塊,若為0,
//則建立的記憶體區塊大小依照預設值為64k

// parameters:
//  img - input video frame
//  dst - resultant motion picture
//  args - optional parameters
static void  update_mhi(IplImage* img, IplImage* dst, int diff_threshold)
{
    double timestamp = (double)clock() / CLOCKS_PER_SEC; // 得到當前時間(以毫秒為單位)
    //
    CvSize size = cvSize(img->width, img->height); // //獲取目前攝像頭捕捉到的frame size
    int i, idx1 , idx2;
    IplImage* silh; //創建一個用來存輪廓(剪影)的圖像空間
    CvSeq* seq; // 創建一序列                
        
    
    
    if (!mhi || mhi->width != size.width || mhi->height != size.height) 
    {
        //如果frame的大小有所改變,就重新分配他們到緩衝區中    

        if (buf == 0) //若尚沒有初始化則分配內存給他
        {
            buf = (IplImage**)malloc(N*sizeof(buf[0]));
//void *malloc(long NumBytes)   該函數分配了NumBytes個字節,並返回了指向這塊內存的指針。
//用sizeof傳回變數的位元組大小 
            memset(buf, 0, N*sizeof(buf[0]));//用來對一段內存空間全部設置為某個字符
            //把buffer所指內存區域的前count個字節設置成字符c ,返回指向buffer的指針
        }

        for (i = 0; i < N; i++) {
            cvReleaseImage(&buf[i]);
            buf[i] = cvCreateImage(size, IPL_DEPTH_8U, 1);
            cvZero(buf[i]);
        }
        cvReleaseImage(&mhi);
        cvReleaseImage(&orient);
        cvReleaseImage(&segmask);
        cvReleaseImage(&mask);

        mhi = cvCreateImage(size, IPL_DEPTH_32F, 1);
        cvZero(mhi); // clear MHI at the beginning
        orient = cvCreateImage(size, IPL_DEPTH_32F, 1);
        segmask = cvCreateImage(size, IPL_DEPTH_32F, 1);
        mask = cvCreateImage(size, IPL_DEPTH_8U, 1);
    }

    cvCvtColor(img, buf[last], CV_BGR2GRAY); // convert frame to grayscale
    //第一張 視訊影像在剛載入時先做了彩色轉灰階的功能
    //並存放在frame buffer的IplImage *的指針(陣列中)
    //buf[last] 即 buf[0] 第一個位置

    //設定frame的索引編號
    idx1 = last;
    //將idx1設為編號0 (最近進來的視訊frame索引編號)
    //執行第一輪 更新index到 0
    //執行第二輪 更新index到 1

    idx2 = (last + 1) % N; 
    // index of (last - (N-1))th frame 
    //idx2 代表 第 最新進來的視訊frame編號
    //執行第一輪 更新index到 1
    //執行第二輪 更新index到 2

    last = idx2;//last以更新到index 1 之後
    //執行第一輪 更新index到 1
    //執行第二輪 更新index到 2

    //做幀差(Frame Difference)
    silh = buf[idx2];//silh首先會在此接收到來自緩衝區buf[1]的一張單通灰階影像
    cvAbsDiff(buf[idx1], buf[idx2], silh);// get difference between frames 
    //執行第一輪 buffer 當中 index 0 - index 1 存給silh
    //執行第二輪 buffer 當中 index 1 - index 2 存給silh

    //對輪廓做二值化
    cvThreshold(silh, silh, diff_threshold, 1, CV_THRESH_BINARY); // and threshold it


    cvUpdateMotionHistory(silh, mhi, timestamp, MHI_DURATION); // update MHI
    //第一個參數 silhouette : 由幀間差分(Frame difference)得到的運動輪廓圖像。
    //第二個參數 mhi : motion histoty image的縮寫,表示運動歷史圖像。
    //第三個參數 timestamp : 時間標記。
    //第四個參數 duration : 發生過運動的像素所能保持的最長時間。

    // convert MHI to  8u image(哪種通道混和效果由自己取決)
    cvCvtScale(mhi, mask, 255. / MHI_DURATION,(MHI_DURATION - timestamp)*255. / MHI_DURATION);

    cvZero(dst);//若預設通道合併處填為藍色就會在一開始顯示為藍色
    //若我在此把cvZero(dst);註解掉你就會看到反色
    //以藍色為例你看到的就會是黃背白前景
    //這裡主要是設計用來將輪廓(剪影)以藍色通道來做顏色強弱顯示

    cvMerge(mask, mask, 0, 0, dst); //合併單通道矩陣為成為多通道的圖形
    //cvMerge(B,G,R,A,dst)    
    //前四個引數為單通道uchar型別的資料結構,
    //第五個引數為輸出IplImage資料結構或CvMat結構圖形

    // calculate motion gradient orientation and valid orientation mask
    //計算運動歷史圖像的梯度方向
    cvCalcMotionGradient(mhi, mask, orient, MAX_TIME_DELTA, MIN_TIME_DELTA, 3);
    //cvCalcMotionGradient(mhi, mask, orient, MIN_TIME_DELTA, MAX_TIME_DELTA, 3);
    //第一個參數 : 運動歷史圖像(單通道)
    //第二個參數 : Mask 圖像;用來標註運動梯度數據正確的點,單通道8bits,為輸出參數
    //第三個參數 : 運動梯度的方向圖像,包含從0 到360 角度
    //第四個參數 : 函數在每個像素點(x,y) 鄰域尋找MHI 的最小值(m(x,y)) 
    //第五個參數 : 函數在每個像素點(x,y) 鄰域尋找MHI 的最大值(m(x,y)) 
    //min(delta1,delta2) <= M(x,y)-m(x,y) <= max(delta1,delta2)
    //第六個參數 : Aperture size of sobel operator
    //索貝爾運算子的光圈大小(函數所用微分算子的開孔尺寸CV_SCHARR, 1, 3, 5 or 7 (見cvSobel))    
    //Both inDelta1 and inDelta2 must be greater than 0.0 in cvCalcMotionGradient.
    //History image must be REAL single-channel in cvCalcMotionGradient.

    if (!storage)
        storage = cvCreateMemStorage(0);//建立的記憶體區塊大小依照預設值為64k
    else
        cvClearMemStorage(storage);

    // segment motion: get sequence of motion components
    // segmask is marked motion components map. It is not used further
    seq = cvSegmentMotion(mhi, segmask, storage, timestamp, MAX_TIME_DELTA);
    //將整個運動分割為獨立的運動部分
    //第一個參數 : 運動歷史圖像(單通道)
    //第二個參數 : 發現應當存儲的mask圖像, 單通道32bits浮點數,用不同的單獨數字(1,2,...)標識它們
    //第三個參數 : 包含運動連通域序列的內存存儲倉區塊
    //第四個參數 :當前時間,以毫秒為單位
    //第五個參數 : 分割閾值,推薦等於或大於運動歷史"每步"之間的間隔    
}


int main(int argc, char** argv)
{
    IplImage* motion = 0;
    CvCapture* capture = 0;
    capture = cvCaptureFromCAM(0);    // 攝影機開啟
    if (capture)
    {
        cvNamedWindow("Motion", 1);
        for (;;)
        {
            IplImage* image = cvQueryFrame(capture);
            if (!image)//判斷使否沒取到影格
                break;

            if (!motion)//如果motion圖像的資料結構為空值
            {
                motion = cvCreateImage(cvSize(image->width, image->height), 8, 3);
                //就先產生3通道,大小跟攝影機捕捉到的影像相同的空間
                //cvZero(motion);//設值為0---->
                //若預設通道合併處填為藍色就會在一開始顯示為藍色
                motion->origin = image->origin;
            }

            update_mhi(image, motion, 30);
            //自定義的update_mhi函數(攝影機捕獲到的影像 , 效果輸出影像 , 門檻值)
            cvShowImage("Motion", motion);

            if (cvWaitKey(10) >= 0)
                break;
        }
        cvReleaseCapture(&capture);
        cvDestroyWindow("Motion");
    }
    return 0;
}


這裡我在此利用靜止的攝像機(位置不動的概念)

使用幀間差(Frame Difference)得到物體的運動邊緣(輪廓),

藉此資訊就足夠讓運動模板(motion template) 產生效果
這裡若改成 三通道合併

輪廓剪影區域就會以 白光來顯示

cvMerge(mask, mask, mask, 0, dst); //合併單通道矩陣為成為多通道的圖形



整理過後的程式碼


#include "opencv2/video/tracking.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc_c.h"
#include <time.h> // for CLOCKS_PER_SEC 及 clock()
#include <stdio.h>
#include <ctype.h>

// various tracking parameters (in seconds)
const double MHI_DURATION = 3;//3s 為運動跟踪的最大持續時間
const double MAX_TIME_DELTA = 0.5;//最大時間增量為0.5s 
const double MIN_TIME_DELTA = 0.05;//最小時間增量0.05s 

// number of cyclic frame buffer used for motion detection
// (should, probably, depend on FPS)
const int N = 5;//frame buffer當中你要存取的frame數

// ring image buffer
IplImage **buf = 0; // 創建 frame buffer , 型態為IplImage * buf 的 Array
int last = 0;

// temporary images
IplImage *mhi = 0; // MHI
IplImage *orient = 0; // orientation
IplImage *mask = 0; // valid orientation mask
IplImage *segmask = NULL; // motion segmentation map
CvMemStorage* storage = NULL; // temporary storage
//用來建立一個指定大小的記憶體區塊,若為0,
//則建立的記憶體區塊大小依照預設值為64k

// parameters:
//  img - input video frame
//  dst - resultant motion picture
//  args - optional parameters
static void  update_mhi(IplImage* img, IplImage* dst, int diff_threshold)
{
    double timestamp = (double)clock() / CLOCKS_PER_SEC; // 得到當前時間(以毫秒為單位)
    //
    CvSize size = cvSize(img->width, img->height); // //獲取目前攝像頭捕捉到的frame size
    int i, idx1, idx2;
    IplImage* silh; //創建一個用來存輪廓(剪影)的圖像空間
    CvSeq* seq; // 創建一序列                



    if (!mhi || mhi->width != size.width || mhi->height != size.height)
    {
        //如果frame的大小有所改變,就重新分配他們到緩衝區中    

        if (buf == 0) //若尚沒有初始化則分配內存給他
        {
            buf = (IplImage**)malloc(N*sizeof(buf[0]));
            memset(buf, 0, N*sizeof(buf[0]));//用來對一段內存空間全部設置為某個字符
            //把buffer所指內存區域的前count個字節設置成字符c ,返回指向buffer的指針
        }

        for (i = 0; i < N; i++) {
            cvReleaseImage(&buf[i]);
            buf[i] = cvCreateImage(size, IPL_DEPTH_8U, 1);
            cvZero(buf[i]);
        }

        mhi = cvCreateImage(size, IPL_DEPTH_32F, 1);
        cvZero(mhi); // clear MHI at the beginning                
        mask = cvCreateImage(size, IPL_DEPTH_8U, 1);
    }

    cvCvtColor(img, buf[last], CV_BGR2GRAY); // convert frame to grayscale
    //第一張 視訊影像在剛載入時先做了彩色轉灰階的功能
    //並存放在frame buffer的IplImage *的指針(陣列中)
    //buf[last] 即 buf[0] 第一個位置

    //設定frame的索引編號
    idx1 = last;
    //將idx1設為編號0 (最近進來的視訊frame索引編號)
    //執行第一輪 更新index到 0
    //執行第二輪 更新index到 1

    idx2 = (last + 1) % N;
    // index of (last - (N-1))th frame 
    //idx2 代表 第 最新進來的視訊frame編號
    //執行第一輪 更新index到 1
    //執行第二輪 更新index到 2

    last = idx2;//last以更新到index 1 之後
    //執行第一輪 更新index到 1
    //執行第二輪 更新index到 2

    //做幀差(Frame Difference)
    silh = buf[idx2];//silh首先會在此接收到來自緩衝區buf[1]的一張單通灰階影像
    cvAbsDiff(buf[idx1], buf[idx2], silh);// get difference between frames 
    //執行第一輪 buffer 當中 index 0 - index 1 存給silh
    //執行第二輪 buffer 當中 index 1 - index 2 存給silh

    //對輪廓做二值化
    cvThreshold(silh, silh, diff_threshold, 1, CV_THRESH_BINARY); // and threshold it


    cvUpdateMotionHistory(silh, mhi, timestamp, MHI_DURATION); // update MHI
    //第一個參數 silhouette : 由幀間差分(Frame difference)得到的運動輪廓圖像。
    //第二個參數 mhi : motion histoty image的縮寫,表示運動歷史圖像。
    //第三個參數 timestamp : 時間標記。
    //第四個參數 duration : 發生過運動的像素所能保持的最長時間。

    // convert MHI to  8u image(哪種通道混和效果由自己取決)
    cvCvtScale(mhi, mask, 255. / MHI_DURATION, (MHI_DURATION - timestamp)*255. / MHI_DURATION);

    cvZero(dst);//若預設通道合併處填為藍色就會在一開始顯示為藍色
    //若我在此把cvZero(dst);註解掉你就會看到反色
    //以藍色為例你看到的就會是黃背白前景
    //這裡主要是設計用來將輪廓(剪影)以藍色通道來做顏色強弱顯示

    cvMerge(mask, mask, 0, 0, dst); //合併單通道矩陣為成為多通道的圖形
    //cvMerge(B,G,R,A,dst)    
    //前四個引數為單通道uchar型別的資料結構,
    //第五個引數為輸出IplImage資料結構或CvMat結構圖形    
}


int main(int argc, char** argv)
{
    IplImage* motion = 0;
    CvCapture* capture = 0;
    capture = cvCaptureFromCAM(0);    // 攝影機開啟
    if (capture)
    {
        cvNamedWindow("Motion", 1);
        for (;;)
        {
            IplImage* image = cvQueryFrame(capture);
            if (!image)//判斷使否沒取到影格
                break;

            if (!motion)//如果motion圖像的資料結構為空值
            {
                motion = cvCreateImage(cvSize(image->width, image->height), 8, 3);
                //就先產生3通道,大小跟攝影機捕捉到的影像相同的空間
                //cvZero(motion);//設值為0---->
                //若預設通道合併處填為藍色就會在一開始顯示為藍色
                motion->origin = image->origin;
            }

            update_mhi(image, motion, 30);
            //自定義的update_mhi函數(攝影機捕獲到的影像 , 效果輸出影像 , 門檻值)
            cvShowImage("Motion", motion);

            if (cvWaitKey(10) >= 0)
                break;
        }
        cvReleaseCapture(&capture);
        cvDestroyWindow("Motion");
    }
    return 0;
}












參考資料部落格:

[轉載]Motion History Image

http://blog.sciencenet.cn/blog-55488-951837.html


運動模板跟踪
http://www.lai18.com/content/24572252.html


cvCalcMotionGradient

http://docs.adaptive-vision.com/current/studio/filters/VideoAnalysis/cvCalcMotionGradient.html

Cv運動分析與對象跟踪




OpenCV運動目標檢測

http://blog.csdn.net/wuhaibing_cver/article/details/8473291



[Opencv] CvSeq

http://blog.yam.com/Chocobbboy/article/87472086


openCV中cvSeq的用法說明

http://blog.csdn.net/wqvbjhc/article/details/5497017



[程式]OpenCV學習筆記心得04:簡單的人臉偵測(face detection),使用HaarDetectObjects




【OpenCV】Motion History Image 歷史移動資訊檢測

http://ccw1986.blogspot.tw/2013/09/opencvmotion-history-image.html

資料結構操作與運算-通道的分割,合併與混合


http://yester-place.blogspot.tw/2008/07/blog-post_20.html

C 語言標準函數庫分類導覽 - time.h clock()


http://pydoing.blogspot.tw/2010/07/c-clock.html


[C] 計時器整理

http://edisonx.pixnet.net/blog/post/52113788-%5Bc%5D-%E8%A8%88%E6%99%82%E5%99%A8%E6%95%B4%E7%90%86







留言

這個網誌中的熱門文章

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header

經得起原始碼資安弱點掃描的程式設計習慣培養(三)_7.Cross Site Scripting(XSS)_Stored XSS_Reflected XSS All Clients

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題