OpenCV ——背景建模之CodeBook(1)


1,CodeBook算法流程介紹

  CodeBook算法的基本思想是得到每個像素的時間序列模型。這種模型能很好地處理時間起伏,缺點是需要消耗大量的內存。CodeBook算法為當前圖像的每一個像素建立一個CodeBook(CB)結構,每個CodeBook結構又由多個CodeWord(CW)組成。
  CB和CW的形式如下:
  CB={CW1,CW2,…CWn,t}
  CW={lHigh,lLow,max,min,t_last,stale}
  其中n為一個CB中所包含的CW的數目,當n太小時,退化為簡單背景,當n較大時可以對復雜背景進行建模;t為CB更新的次數。CW是一個6元組,其中IHigh和ILow作為更新時的學習上下界,max和min記錄當前像素的最大值和最小值。上次更新的時間t_last和陳舊時間stale(記錄該CW多久未被訪問)用來刪除很少使用的CodeWord。
假設當前訓練圖像I中某一像素為I(x,y),該像素的CB的更新算法如下,另外記背景閾值的增長判定閾值為Bounds:
  (1) CB的訪問次數加1;
  (2) 遍歷CB中的每個CW,如果存在一個CW中的IHigh,ILow滿足ILow≤I(x,y)≤ IHigh,則轉(4);
  (3) 創建一個新的碼字CWnew加入到CB中, CWnew的max與min都賦值為I(x,y), IHigh <- I(x,y) + Bounds,ILow <- I(x,y) – Bounds,並且轉(6);
  (4) 更新該碼字的t_last,若當前像素值I(x,y)大於該碼字的max,則max <- I(x,y),若 I(x,y)小於該碼字的min,則min <- I(x,y);
  (5) 更新該碼字的學習上下界,以增加背景模型對於復雜背景的適應能力,具體做法是: 若IHigh < I(x,y) + Bounds,則IHigh 增長1,若ILow > I(x,y) – Bounds,則ILow 減少1;
  (6) 更新CB中每個CW的stale。
  使用已建立好的CB進行運動目標檢測的方法很簡單,記判斷前景的范圍上下界為minMod和maxMod,對於當前待檢測圖像上的某一像素I(x,y),遍歷它對應像素背景模型CB中的每一個碼字CW,若存在一個CW,使得I(x,y) < max + maxMod並且I(x,y) > min – minMod,則I(x,y)被判斷為背景,否則被判斷為前景。
  在實際使用CodeBook進行運動檢測時,除了要隔一定的時間對CB進行更新的同時,需要對CB進行一個時間濾波,目的是去除很少被訪問到的CW,其方法是訪問每個CW的stale,若stale大於一個閾值(通常設置為總更新次數的一半),移除該CW。
  綜上所述,CodeBook算法檢測運動目標的流程如下:
  (1) 選擇一幀到多幀使用更新算法建立CodeBook背景模型;
  (2) 按上面所述方法檢測前景(運動目標);
  (3) 間隔一定時間使用更新算法更新CodeBook模型,並對CodeBook進行時間濾波;
  (4) 若檢測繼續,轉(2),否則結束。

2,基於OpenCV2.x的代碼

  codebookWithOpenCV2.h

#ifndef ___CODEBOOKWITHOPENCV2_H___
#define ___CODEBOOKWITHOPENCV2_H___ 

#include <iostream>
#include <list>
#include <opencv2/opencv.hpp>

#define CHANNELS 3

using namespace std;
using namespace cv;

class CodeWord
{
public:
    uchar    learnHigh[CHANNELS];    // High side threshold for learning
    // 此碼元各通道的閥值上限(背景學習建模界限)
    uchar    learnLow[CHANNELS];        // Low side threshold for learning
    // 此碼元各通道的閥值下限()
    // 學習過程中如果一個新像素各通道值x[i],均有 learnLow[i]<=x[i]<=learnHigh[i],則該像素可合並於此碼元
    uchar    max[CHANNELS];            // High side of box boundary  實際最大值
    // 屬於此碼元的像素中各通道的最大值(判斷過程的界限)
    uchar    min[CHANNELS];            // Low side of box boundary   實際最小值
    // 屬於此碼元的像素中各通道的最小值
    int        t_last_update;            // This is book keeping to allow us to kill stale entries
    // 此碼元最后一次更新的時間,每一幀為一個單位時間,用於計算stale
    int        stale;                    // max negative run (biggest period of inactivity)
    // 此碼元最長不更新時間,用於刪除規定時間不更新的碼元,精簡碼本
                 
};

class CodeBook
{
public:
    list<CodeWord> codeElement;
    //考慮到方便的刪除舊碼元節點,同時訪問碼元的時候只需要順序遍歷,選用STL中的List,避免用指針。
    int                numEntries;
    // 此碼本中碼元的數目
    int                t;                // count every access
    // 此碼本現在的時間,一幀為一個時間單位                    // 碼本的數據結構
};


class BackgroundSubtractorCodeBook
{
public:

    BackgroundSubtractorCodeBook();
    void updateCodeBook(const Mat &inputImage);//背景建模
    void initialize(const Mat &inputImagee,Mat &outputImage);//獲取第一幀,進行初始化
    void clearStaleEntries();//清除舊的碼元
    void backgroudDiff(const Mat &inputImage,Mat &outputImage);//背景前景判斷
    ~BackgroundSubtractorCodeBook();

private:

    Mat         yuvImage;
    Mat         maskImage;
    CodeBook*   codebookVec;    //本來想用Vector,不用數組指針,但是codebook定長,不用增減,vector也不好初始化。
    unsigned    cbBounds[CHANNELS]; //背景建模時,用於建立學習的上下界限
    uchar*        pColor; //yuvImage pointer  通道指針
    uchar*      pMask;// maskImage pointer
    int            imageSize; 
    int            nChannels;
    int            minMod[CHANNELS];  //判斷背景或者前景所用調節閾值
    int            maxMod[CHANNELS];


    uchar maskPixelCodeBook;
    void _updateCodeBookPerPixel(int pixelIndex);
    void _clearStaleEntriesPerPixel(int pixelIndex);
    uchar _backgroundDiff(int pixelIndex);
};

#endif

  codebookWithOpenCV2.cpp

#include "codebookWithOpenCV2.h"

BackgroundSubtractorCodeBook::BackgroundSubtractorCodeBook()
{
  nChannels=CHANNELS; }
//初始化maskImage,codebook數組,將每個像素的codebook的碼元個數置0,初始化建模閾值和分割閾值 void BackgroundSubtractorCodeBook::initialize(const Mat &inputRGBImage,Mat &outputImage) { if (inputRGBImage.empty()) { return ; } if (yuvImage.empty()) { yuvImage.create(inputRGBImage.size(),inputRGBImage.type()); } if (maskImage.empty()) { maskImage.create(inputRGBImage.size(),CV_8UC1); Mat temp(inputRGBImage.rows,inputRGBImage.cols,CV_8UC1,Scalar::all(255)); maskImage=temp; } imageSize=inputRGBImage.cols*inputRGBImage.rows; codebookVec=new CodeBook[imageSize]; for (int i=0;i<imageSize;++i) { codebookVec[i].numEntries=0; }//初始化碼元個數 for (int i=0; i<nChannels; i++) { cbBounds[i] = 10; // 用於確定碼元各通道的建模閥值 minMod[i] = 20; // 用於背景差分函數中 maxMod[i] = 20; // 調整其值以達到最好的分割 } outputImage=maskImage; } //逐個像素建模 void BackgroundSubtractorCodeBook::updateCodeBook(const Mat &inputImage) { cvtColor(inputImage,yuvImage,CV_RGB2YCrCb); pColor=yuvImage.data; for (int c=0;c<imageSize;++c) { _updateCodeBookPerPixel(c); pColor+=3; } } //單個像素建模實現函數,遍歷所有碼元,分三通道匹配,若滿足,則更新該碼元的時間,最大,最小值 //若不匹配,則創建新的碼元。 void BackgroundSubtractorCodeBook::_updateCodeBookPerPixel(int pixelIndex) { if (codebookVec[pixelIndex].numEntries==0) { codebookVec[pixelIndex].t=0; } codebookVec[pixelIndex].t+=1; int n; unsigned int high[3],low[3]; for (n=0; n<nChannels; n++) //處理三通道的三個像素 { high[n] = *(pColor+n) + *(cbBounds+n); // *(p+n) 和 p[n] 結果等價,經試驗*(p+n) 速度更快 if(high[n] > 255) high[n] = 255; low[n] = *(pColor+n)-*(cbBounds+n); if(low[n] < 0) low[n] = 0; // 用p 所指像素通道數據,加減cbBonds中數值,作為此像素閥值的上下限 } int matchChannel; list<CodeWord>::iterator jList; list<CodeWord>::iterator jListAfterPush; for (jList=codebookVec[pixelIndex].codeElement.begin();jList!=codebookVec[pixelIndex].codeElement.end();++jList) { // 遍歷此碼本每個碼元,測試p像素是否滿足其中之一 matchChannel = 0; for (n=0; n<nChannels; n++) //遍歷每個通道 { if(((*jList).learnLow[n]<= *(pColor+n)) && (*(pColor+n) <= (*jList).learnHigh[n])) //Found an entry for this channel // 如果p 像素通道數據在該碼元閥值上下限之間 { matchChannel++; } } if (matchChannel == nChannels) // If an entry was found over all channels // 如果p 像素各通道都滿足上面條件 { (*jList).t_last_update = codebookVec[pixelIndex].t; // 更新該碼元時間為當前時間 // adjust this codeword for the first channel for (n=0; n<nChannels; n++) //調整該碼元各通道最大最小值 { if ((*jList).max[n] < *(pColor+n)) (*jList).max[n] = *(pColor+n); else if ((*jList).min[n] > *(pColor+n)) (*jList).min[n] = *(pColor+n); } break;//如果滿足其中一個碼元,則退出循環。 } } // ENTER A NEW CODE WORD IF NEEDED if(jList==codebookVec[pixelIndex].codeElement.end()) // No existing code word found, make a new one // p 像素不滿足此碼本中任何一個碼元,下面創建一個新碼元 { CodeWord newElement; for(n=0; n<nChannels; n++) // 更新新碼元各通道數據 { newElement.learnHigh[n] = high[n]; newElement.learnLow[n] = low[n]; newElement.max[n] = *(pColor+n); newElement.min[n] = *(pColor+n); } newElement.t_last_update = codebookVec[pixelIndex].t; newElement.stale = 0; codebookVec[pixelIndex].numEntries += 1; codebookVec[pixelIndex].codeElement.push_back(newElement);//新的碼元加入鏈表的最后。 } // OVERHEAD TO TRACK POTENTIAL STALE ENTRIES for (jListAfterPush=codebookVec[pixelIndex].codeElement.begin(); jListAfterPush!=codebookVec[pixelIndex].codeElement.end();++jListAfterPush) { // This garbage is to track which codebook entries are going stale int negRun = codebookVec[pixelIndex].t - (*jListAfterPush).t_last_update; // 計算該碼元的不更新時間 if((*jListAfterPush).stale < negRun) (*jListAfterPush).stale = negRun; } // SLOWLY ADJUST LEARNING BOUNDS //對符合的碼元進行更新,剛建立的碼元肯定不滿足條件,不用考慮 for(n=0; n<nChannels; n++) // 如果像素通道數據在高低閥值范圍內,但在碼元閥值之外,則緩慢調整此碼元學習界限 { if((*jList).learnHigh[n] < high[n]) (*jList).learnHigh[n] += 1;//+1是什么意思?緩慢調整? if((*jList).learnLow[n] > low[n]) (*jList).learnLow[n] -= 1; } return; } void BackgroundSubtractorCodeBook::clearStaleEntries() { for (int i=0;i<imageSize;++i) { _clearStaleEntriesPerPixel(i); } } void BackgroundSubtractorCodeBook::_clearStaleEntriesPerPixel(int pixelIndex) { int staleThresh=codebookVec[pixelIndex].t; for (list<CodeWord>::iterator itor=codebookVec[pixelIndex].codeElement.begin(); itor!=codebookVec[pixelIndex].codeElement.end();) { if ((*itor).stale>staleThresh) { itor=codebookVec[pixelIndex].codeElement.erase(itor);//erase之后返回被刪的下一個元素的位置 } else { (*itor).stale=0; (*itor).t_last_update=0; itor++; } } codebookVec[pixelIndex].t=0;//碼本時間清零; codebookVec[pixelIndex].numEntries=(int)codebookVec[pixelIndex].codeElement.size(); return; } void BackgroundSubtractorCodeBook:: backgroudDiff(const Mat &inputImage,Mat &outputImage) { cvtColor(inputImage,yuvImage,CV_RGB2YCrCb); pColor=yuvImage.data; pMask =maskImage.data; for(int c=0; c<imageSize; c++) { maskPixelCodeBook = _backgroundDiff(c); *pMask++ = maskPixelCodeBook; pColor += 3; // pColor 指向的是3通道圖像 } outputImage=maskImage.clone(); } uchar BackgroundSubtractorCodeBook::_backgroundDiff(int pixelIndex) { int matchChannels; list<CodeWord>::iterator itor; for (itor=codebookVec[pixelIndex].codeElement.begin(); itor!=codebookVec[pixelIndex].codeElement.end();++itor) { matchChannels=0; for (int n=0;n<nChannels;++n) { if (((*itor).min[n] - minMod[n] <= *(pColor+n)) && (*(pColor+n) <=(*itor).max[n] + maxMod[n])) //相對於背景學習,這里是與碼元中的最大最小值比較,並加入了余量minMod,maxMod; matchChannels++; //Found an entry for this channel else break;//一個通道沒匹配,直接退出 } if (matchChannels == nChannels) break; //Found an entry that matched all channels,確定是背景像素 返回0 黑色 } if (itor==codebookVec[pixelIndex].codeElement.end()) { return(255); } return (0); } BackgroundSubtractorCodeBook::~BackgroundSubtractorCodeBook() { delete [] codebookVec; }

  codebookTest.cpp

#include "codebookWithOpenCV2.h"

int main()
{
    VideoCapture cap;
    cap.open("people.avi");
    if( !cap.isOpened() )
    {
        printf("can not open video file\n");
        return -1;
    }

    namedWindow("image", WINDOW_NORMAL);
    namedWindow("foreground mask", WINDOW_NORMAL);

    

    BackgroundSubtractorCodeBook bgcbModel;
    Mat inputImage,outputMaskCodebook;

    for(int i=0;;++i)
    {
        cap>>inputImage;
        if( inputImage.empty() )
            break;
        if(i==0)
        {
            bgcbModel.initialize(inputImage,outputMaskCodebook);
            
        }
        else if (i<=30&&i>0)
        {
            bgcbModel.updateCodeBook(inputImage);
            if (i==30)
            {
                bgcbModel.clearStaleEntries();
            }
        }
        else
        {
            bgcbModel.backgroudDiff(inputImage,outputMaskCodebook);
        }
        imshow("image",inputImage);
        imshow("foreground mask",outputMaskCodebook);

        int c = waitKey(30);
        if (c == 'q' || c == 'Q' || (c & 255) == 27)
            break;
    }

    return 0;
}

  代碼是參考如下翻譯過來的。

/************************************************************************/
/*            A few more thoughts on codebook models
In general, the codebook method works quite well across a wide number of conditions, 
and it is relatively quick to train and to run. It doesn’t deal well with varying patterns of 
light — such as morning, noon, and evening sunshine — or with someone turning lights 
on or off indoors. This type of global variability can be taken into account by using 
several different codebook models, one for each condition, and then allowing the condition 
to control which model is active.                                        */
/************************************************************************/

#include "stdafx.h"
#include <cv.h>            
#include <highgui.h>
#include <cxcore.h>

#define CHANNELS 3        
// 設置處理的圖像通道數,要求小於等於圖像本身的通道數

///////////////////////////////////////////////////////////////////////////
// 下面為碼本碼元的數據結構
// 處理圖像時每個像素對應一個碼本,每個碼本中可有若干個碼元
// 當涉及一個新領域,通常會遇到一些奇怪的名詞,不要被這些名詞嚇壞,其實思路都是簡單的
typedef struct ce {
    uchar    learnHigh[CHANNELS];    // High side threshold for learning
    // 此碼元各通道的閥值上限(學習界限)
    uchar    learnLow[CHANNELS];        // Low side threshold for learning
    // 此碼元各通道的閥值下限
    // 學習過程中如果一個新像素各通道值x[i],均有 learnLow[i]<=x[i]<=learnHigh[i],則該像素可合並於此碼元
    uchar    max[CHANNELS];            // High side of box boundary
    // 屬於此碼元的像素中各通道的最大值
    uchar    min[CHANNELS];            // Low side of box boundary
    // 屬於此碼元的像素中各通道的最小值
    int        t_last_update;            // This is book keeping to allow us to kill stale entries
    // 此碼元最后一次更新的時間,每一幀為一個單位時間,用於計算stale
    int        stale;                    // max negative run (biggest period of inactivity)
    // 此碼元最長不更新時間,用於刪除規定時間不更新的碼元,精簡碼本
} code_element;                        // 碼元的數據結構

typedef struct code_book {
    code_element    **cb;
    // 碼元的二維指針,理解為指向碼元指針數組的指針,使得添加碼元時不需要來回復制碼元,只需要簡單的指針賦值即可
    int                numEntries;
    // 此碼本中碼元的數目
    int                t;                // count every access
    // 此碼本現在的時間,一幀為一個時間單位
} codeBook;                            // 碼本的數據結構


///////////////////////////////////////////////////////////////////////////////////
// int updateCodeBook(uchar *p, codeBook &c, unsigned cbBounds)
// Updates the codebook entry with a new data point
//
// p            Pointer to a YUV pixel
// c            Codebook for this pixel
// cbBounds        Learning bounds for codebook (Rule of thumb: 10)
// numChannels    Number of color channels we're learning
//
// NOTES:
//        cvBounds must be of size cvBounds[numChannels]
//
// RETURN
//    codebook index
int cvupdateCodeBook(uchar *p, codeBook &c, unsigned *cbBounds, int numChannels)
{
    if(c.numEntries == 0) c.t = 0;
    // 碼本中碼元為零時初始化時間為0
    c.t += 1;    // Record learning event
    // 每調用一次加一,即每一幀圖像加一
    
    //SET HIGH AND LOW BOUNDS
    int n;
    unsigned int high[3],low[3];
    for (n=0; n<numChannels; n++)
    {
        high[n] = *(p+n) + *(cbBounds+n);
        // *(p+n) 和 p[n] 結果等價,經試驗*(p+n) 速度更快
        if(high[n] > 255) high[n] = 255;
        low[n] = *(p+n)-*(cbBounds+n);
        if(low[n] < 0) low[n] = 0;
        // 用p 所指像素通道數據,加減cbBonds中數值,作為此像素閥值的上下限
    }

    //SEE IF THIS FITS AN EXISTING CODEWORD
    int matchChannel;    
    int i;
    for (i=0; i<c.numEntries; i++)
    {
        // 遍歷此碼本每個碼元,測試p像素是否滿足其中之一
        matchChannel = 0;
        for (n=0; n<numChannels; n++)
            //遍歷每個通道
        {
            if((c.cb[i]->learnLow[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->learnHigh[n])) //Found an entry for this channel
            // 如果p 像素通道數據在該碼元閥值上下限之間
            {    
                matchChannel++;
            }
        }
        if (matchChannel == numChannels)        // If an entry was found over all channels
            // 如果p 像素各通道都滿足上面條件
        {
            c.cb[i]->t_last_update = c.t;
            // 更新該碼元時間為當前時間
            // adjust this codeword for the first channel
            for (n=0; n<numChannels; n++)
                //調整該碼元各通道最大最小值
            {
                if (c.cb[i]->max[n] < *(p+n))
                    c.cb[i]->max[n] = *(p+n);
                else if (c.cb[i]->min[n] > *(p+n))
                    c.cb[i]->min[n] = *(p+n);
            }
            break;
        }
    }

    // ENTER A NEW CODE WORD IF NEEDED
    if(i == c.numEntries)  // No existing code word found, make a new one
    // p 像素不滿足此碼本中任何一個碼元,下面創建一個新碼元
    {
        code_element **foo = new code_element* [c.numEntries+1];
        // 申請c.numEntries+1 個指向碼元的指針
        for(int ii=0; ii<c.numEntries; ii++)
            // 將前c.numEntries 個指針指向已存在的每個碼元
            foo[ii] = c.cb[ii];
        
        foo[c.numEntries] = new code_element;
        // 申請一個新的碼元
        if(c.numEntries) delete [] c.cb;
        // 刪除c.cb 指針數組
        c.cb = foo;
        // 把foo 頭指針賦給c.cb
        for(n=0; n<numChannels; n++)
            // 更新新碼元各通道數據
        {
            c.cb[c.numEntries]->learnHigh[n] = high[n];
            c.cb[c.numEntries]->learnLow[n] = low[n];
            c.cb[c.numEntries]->max[n] = *(p+n);
            c.cb[c.numEntries]->min[n] = *(p+n);
        }
        c.cb[c.numEntries]->t_last_update = c.t;
        c.cb[c.numEntries]->stale = 0;
        c.numEntries += 1;
    }

    // OVERHEAD TO TRACK POTENTIAL STALE ENTRIES
    for(int s=0; s<c.numEntries; s++)
    {
        // This garbage is to track which codebook entries are going stale
        int negRun = c.t - c.cb[s]->t_last_update;
        // 計算該碼元的不更新時間
        if(c.cb[s]->stale < negRun) 
            c.cb[s]->stale = negRun;
    }

    // SLOWLY ADJUST LEARNING BOUNDS
    for(n=0; n<numChannels; n++)
        // 如果像素通道數據在高低閥值范圍內,但在碼元閥值之外,則緩慢調整此碼元學習界限
    {
        if(c.cb[i]->learnHigh[n] < high[n]) 
            c.cb[i]->learnHigh[n] += 1;
        if(c.cb[i]->learnLow[n] > low[n]) 
            c.cb[i]->learnLow[n] -= 1;
    }

    return(i);
}

///////////////////////////////////////////////////////////////////////////////////
// uchar cvbackgroundDiff(uchar *p, codeBook &c, int minMod, int maxMod)
// Given a pixel and a code book, determine if the pixel is covered by the codebook
//
// p        pixel pointer (YUV interleaved)
// c        codebook reference
// numChannels  Number of channels we are testing
// maxMod    Add this (possibly negative) number onto max level when code_element determining if new pixel is foreground
// minMod    Subract this (possible negative) number from min level code_element when determining if pixel is foreground
//
// NOTES:
// minMod and maxMod must have length numChannels, e.g. 3 channels => minMod[3], maxMod[3].
//
// Return
// 0 => background, 255 => foreground
uchar cvbackgroundDiff(uchar *p, codeBook &c, int numChannels, int *minMod, int *maxMod)
{
    // 下面步驟和背景學習中查找碼元如出一轍
    int matchChannel;
    //SEE IF THIS FITS AN EXISTING CODEWORD
    int i;
    for (i=0; i<c.numEntries; i++)
    {
        matchChannel = 0;
        for (int n=0; n<numChannels; n++)
        {
            if ((c.cb[i]->min[n] - minMod[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->max[n] + maxMod[n]))
                matchChannel++; //Found an entry for this channel
            else
                break;
        }
        if (matchChannel == numChannels)
            break; //Found an entry that matched all channels
    }
    if(i == c.numEntries) 
        // p像素各通道值滿足碼本中其中一個碼元,則返回白色
        return(255);

    return(0);
}


//UTILITES/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
//int clearStaleEntries(codeBook &c)
// After you've learned for some period of time, periodically call this to clear out stale codebook entries
//
//c        Codebook to clean up
//
// Return
// number of entries cleared
int cvclearStaleEntries(codeBook &c)
{
    int staleThresh = c.t >> 1;            // 設定刷新時間
    int *keep = new int [c.numEntries];    // 申請一個標記數組
    int keepCnt = 0;                    // 記錄不刪除碼元數目
    //SEE WHICH CODEBOOK ENTRIES ARE TOO STALE
    for (int i=0; i<c.numEntries; i++)
        // 遍歷碼本中每個碼元
    {
        if (c.cb[i]->stale > staleThresh)    
            // 如碼元中的不更新時間大於設定的刷新時間,則標記為刪除
            keep[i] = 0; //Mark for destruction
        else
        {
            keep[i] = 1; //Mark to keep
            keepCnt += 1;
        }
    }

    // KEEP ONLY THE GOOD
    c.t = 0;                        //Full reset on stale tracking
    // 碼本時間清零
    code_element **foo = new code_element* [keepCnt];
    // 申請大小為keepCnt 的碼元指針數組
    int k=0;
    for(int ii=0; ii<c.numEntries; ii++)
    {
        if(keep[ii])
        {
            foo[k] = c.cb[ii];
            foo[k]->stale = 0;        //We have to refresh these entries for next clearStale
            foo[k]->t_last_update = 0;
            k++;
        }
    }
    //CLEAN UP
    delete [] keep;
    delete [] c.cb;
    c.cb = foo;
    // 把foo 頭指針地址賦給c.cb 
    int numCleared = c.numEntries - keepCnt;
    // 被清理的碼元個數
    c.numEntries = keepCnt;
    // 剩余的碼元地址
    return(numCleared);
}



int main()
{
    ///////////////////////////////////////
    // 需要使用的變量
    CvCapture*    capture;
    IplImage*    rawImage;
    IplImage*    yuvImage;
    IplImage*    ImaskCodeBook;
    codeBook*    cB;
    unsigned    cbBounds[CHANNELS];
    uchar*        pColor; //YUV pointer
    int            imageLen;
    int            nChannels = CHANNELS;
    int            minMod[CHANNELS];
    int            maxMod[CHANNELS];
    
    //////////////////////////////////////////////////////////////////////////
    // 初始化各變量
    cvNamedWindow("Raw");
    cvNamedWindow("CodeBook");

    capture = cvCreateFileCapture("tree.avi");
    if (!capture)
    {
        printf("Couldn't open the capture!");
        return -1;
    }

    rawImage = cvQueryFrame(capture);
    yuvImage = cvCreateImage(cvGetSize(rawImage), 8, 3);    
    // 給yuvImage 分配一個和rawImage 尺寸相同,8位3通道圖像
    ImaskCodeBook = cvCreateImage(cvGetSize(rawImage), IPL_DEPTH_8U, 1);
    // 為ImaskCodeBook 分配一個和rawImage 尺寸相同,8位單通道圖像
    cvSet(ImaskCodeBook, cvScalar(255));
    // 設置單通道數組所有元素為255,即初始化為白色圖像
    
    imageLen = rawImage->width * rawImage->height;
    cB = new codeBook[imageLen];
    // 得到與圖像像素數目長度一樣的一組碼本,以便對每個像素進行處理
    
    for (int i=0; i<imageLen; i++)
        // 初始化每個碼元數目為0
        cB[i].numEntries = 0;
    for (int i=0; i<nChannels; i++)
    {
        cbBounds[i] = 10;    // 用於確定碼元各通道的閥值

        minMod[i]    = 20;    // 用於背景差分函數中
        maxMod[i]    = 20;    // 調整其值以達到最好的分割
    }
        
    
    //////////////////////////////////////////////////////////////////////////
    // 開始處理視頻每一幀圖像
    for (int i=0;;i++)
    {
        cvCvtColor(rawImage, yuvImage, CV_BGR2YCrCb);
        // 色彩空間轉換,將rawImage 轉換到YUV色彩空間,輸出到yuvImage
        // 即使不轉換效果依然很好
        // yuvImage = cvCloneImage(rawImage);

        if (i <= 30)
            // 30幀內進行背景學習
        {
            pColor = (uchar *)(yuvImage->imageData);
            // 指向yuvImage 圖像的通道數據
            for (int c=0; c<imageLen; c++)
            {
                cvupdateCodeBook(pColor, cB[c], cbBounds, nChannels);
                // 對每個像素,調用此函數,捕捉背景中相關變化圖像
                pColor += 3;
                // 3 通道圖像, 指向下一個像素通道數據
            }
            if (i == 30)
                // 到30 幀時調用下面函數,刪除碼本中陳舊的碼元
            {
                for (int c=0; c<imageLen; c++)
                    cvclearStaleEntries(cB[c]);
            }
        }
        else
        {
            uchar maskPixelCodeBook;
            pColor = (uchar *)((yuvImage)->imageData); //3 channel yuv image
            uchar *pMask = (uchar *)((ImaskCodeBook)->imageData); //1 channel image
            // 指向ImaskCodeBook 通道數據序列的首元素
            for(int c=0; c<imageLen; c++)
            {
                maskPixelCodeBook = cvbackgroundDiff(pColor, cB[c], nChannels, minMod, maxMod);
                // 我看到這兒時豁然開朗,開始理解了codeBook 呵呵
                *pMask++ = maskPixelCodeBook;
                pColor += 3;
                // pColor 指向的是3通道圖像
            }
        }
        if (!(rawImage = cvQueryFrame(capture)))
            break;
        cvShowImage("Raw", rawImage);
        cvShowImage("CodeBook", ImaskCodeBook);

        if (cvWaitKey(30) == 27)
            break;
        if (i == 56 || i == 63)
            cvWaitKey();
    }    
    
    cvReleaseCapture(&capture);
    if (yuvImage)
        cvReleaseImage(&yuvImage);
    if(ImaskCodeBook) 
        cvReleaseImage(&ImaskCodeBook);
    cvDestroyAllWindows();
    delete [] cB;

    return 0;
}

  另外,在stackoverflow上找到的代碼,未測試,可參考。

  bgfg_cb.h

#ifndef __bgfg_cb_h__
#define __bgfg_cb_h__


//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.148.9778&rep=rep1&type=pdf
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <time.h>
using namespace cv;
using namespace std;


struct codeword {
    float min;
    float max; 
    float f;
    float l;
    int first;
    int last;
    bool isStale;
};
extern int alpha ;
extern float beta ;
extern int Tdel ,Tadd , Th;


void initializeCodebook(int w,int h);
void update_cb(Mat& frame);
void fg_cb(Mat& frame,Mat& fg);
#endif

bgfg_cb.cpp

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <time.h>
#include "bgfg_cb.h"
using namespace cv;
using namespace std;

vector<codeword> **cbMain;
vector<codeword> **cbCache;
int t=0;
int alpha = 10;//knob
float beta =1;
int Tdel = 200,Tadd = 150,  Th= 200;//knobs

void initializeCodebook(int w,int h)
{
    cbMain = new vector<codeword>*[w];
    for(int i = 0; i < w; ++i)
        cbMain[i] = new vector<codeword>[h];

    cbCache = new vector<codeword>*[w];
    for(int i = 0; i < w; ++i)
        cbCache[i] = new vector<codeword>[h];
}

void update_cb(Mat& frame)
{
    if(t>10) return;
    for(int i=0;i<frame.rows;i++)
    {
        for(int j=0;j<frame.cols;j++)
        {
            int pix =  frame.at<uchar>(i,j);
            vector<codeword>& cm =cbMain[i][j];         
            bool found = false;
            for(int k=0;k<cm.size();k++)
            {
                if(cm[k].min<=pix && pix<=cm[k].max && !found)
                {
                    found=true;
                    cm[k].min = ((pix-alpha)+(cm[k].f*cm[k].min))/(cm[k].f+1);
                    cm[k].max = ((pix+alpha)+(cm[k].f*cm[k].max))/(cm[k].f+1);
                    cm[k].l=0;
                    cm[k].last=t;
                    cm[k].f++;
                }else
                {
                    cm[k].l++;
                }
            }
            if(!found)
            {
                codeword n={};
                n.min=max(0,pix-alpha);
                n.max=min(255,pix+alpha);
                n.f=1;
                n.l=0;
                n.first=t;
                n.last=t;
                cm.push_back(n);
            }
        }
    }
    t++;
}

void fg_cb(Mat& frame,Mat& fg)
{
    fg=Mat::zeros(frame.size(),CV_8UC1);
    if(cbMain==0) initializeCodebook(frame.rows,frame.cols);
    if(t<10)
    {
        update_cb(frame);
        return;
    }

    for(int i=0;i<frame.rows;i++)
    {
        for(int j=0;j<frame.cols;j++)
        {
            int pix = frame.at<uchar>(i,j);
            vector<codeword>& cm = cbMain[i][j];    
            bool found = false;
            for(int k=0;k<cm.size();k++)
            {
                if(cm[k].min<=pix && pix<=cm[k].max && !found)
                {
                    cm[k].min = ((1-beta)*(pix-alpha)) + (beta*cm[k].min);
                    cm[k].max = ((1-beta)*(pix+alpha)) + (beta*cm[k].max);
                    cm[k].l=0;
                    cm[k].first=t;
                    cm[k].f++;
                    found=true;
                }else
                {
                    cm[k].l++;
                }
            }
            cm.erase( remove_if(cm.begin(), cm.end(), [](codeword& c) { return c.l>=Tdel;} ), cm.end() );
            fg.at<uchar>(i,j) = found?0:255;
            if(found) continue;
            found = false;
            vector<codeword>& cc = cbCache[i][j];   
            for(int k=0;k<cc.size();k++)
            {
                if(cc[k].min<=pix && pix<=cc[k].max && !found)
                {
                    cc[k].min = ((1-beta)*(pix-alpha)) + (beta*cc[k].min);
                    cc[k].max = ((1-beta)*(pix+alpha)) + (beta*cc[k].max);
                    cc[k].l=0;
                    cc[k].first=t;
                    cc[k].f++;
                    found=true;
                }else
                {
                    cc[k].l++;
                }
            }

            if(!found)
            {
                codeword n={};
                n.min=max(0,pix-alpha);
                n.max=min(255,pix+alpha);
                n.f=1;
                n.l=0;
                n.first=t;
                n.last=t;   
                cc.push_back(n);
            }

            cc.erase( remove_if(cc.begin(), cc.end(), [](codeword& c) { return c.l>=Th;} ), cc.end() );
            for(vector<codeword>::iterator it=cc.begin();it!=cc.end();it++)
            {
                if(it->f>Tadd)
                {
                    cm.push_back(*it);
                }
            }

            cc.erase( remove_if(cc.begin(), cc.end(), [](codeword& c) { return c.f>Tadd;} ), cc.end() );
        }
    }
}

main.cpp

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "bgfg_cb.h"
#include <iostream>
using namespace cv;
using namespace std;
void proc()
{
    Mat frame,fg,gray;
    VideoCapture cap("C:/Downloads/S2_L1.tar/S2_L1/Crowd_PETS09/S2/L1/Time_12-34/View_001/frame_%04d.jpg");
    cap >> frame;
    initializeCodebook(frame.rows,frame.cols);
    for(;;)
    {
        cap>>frame; 
        cvtColor(frame,gray,CV_BGR2GRAY);
        fg_cb(gray,fg);
        Mat cc;
        imshow("fg",fg);
        waitKey(1);
    }
}

int main(int argc, char ** argv)
{
    proc(); 
    cin.ignore(1);
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM