2013年11月13日 星期三

EmguCV Image Process: Process Video Sequences part 5


EmguCV Image Process: Process Video Sequences

參考OpenCV 2 Computer Vision Application Programming Cookbook第十章

介紹內容如下:

Reading video sequences

Processing the video frames

Writing video sequences

Tracking feature points in video

Extracting the foreground objects in video

上一篇介紹了透過OpticalFlow來做特徵追蹤

本篇將介紹簡單的方法實作前景偵測

前景偵測所感興趣的物件,就是在畫面中移動的物件、區塊

稱之為前景

反之的部分則為背景

要實作前景偵測就必須要建立背景模型

而最簡單的方式

就是以擷取的第一張畫面作為背景模型

而前景的影像 = 背景模型 與 當前影像 的差值

這裡利用AbsDiff這個方法來實作

計算出的差值差異達到某種程度,就認之為前景


但這方法有一些限制

大環境的變化就會影響前景偵測的效果

例如:光線的變化

光線的變化對於人眼來說算是背景的變化

但若是變化過於劇烈,則會被程式誤判為前景

因此需要一些動態的方式來建立背景模型

若是利用一段時間的影像利用平均值來建立背景模型似乎可行

但這會遇到幾個問題:

這樣勢必得儲存許多張的影像來建立背景

在計算平均值的過程無法做前景偵測

更重要的問題是,到底要多少張影像才夠製作背景模型?


而比較好的方法就是不斷的去更新背景模型

這個方法被稱之為:running average (moving average)

就是不斷的利用新的一張影像來更新背景模型

而每次的計算都給予一定程度的百分比來更新

例:當前的平均值 = (1-a)*前一張平均值 + a*當前的值

a則稱之為learning rate

learning rate的值越大

背景更新的速度就越快

延續前幾篇的實作模式

這裡來實作一個背景/前景切割類別:

public class BGFGSegmentor
{
    //Current gray-level image
    Image<Gray, Byte> gray;
    //accumulated background
    Image<Gray, float> background;
    //background image
    Image<Gray, Byte> backImage;
    //foreground image
    Image<Gray, Byte> foreground;
    //learning rate in background accumulation
    double learningRate;
    //threshold for foreground extraction
    int threshold;

    public BGFGSegmentor()
    {
        this.threshold = 10;
        this.learningRate = 0.01;
    }

    public IImage Process(IImage image)
    {
        Image<Gray, Byte> output;
        if (image.NumberOfChannels != 1)
        {
            //convert to gray-level image
            this.gray = ((Image<Bgr, Byte>)image).Convert<Gray, Byte>();
        }
        else
        {
            this.gray = ((Image<Gray, Byte>)image).Copy();
        }
        output = this.gray.Copy();
        
        //initialize background to 1st frame
        if (this.background == null)
        {
            this.background = this.gray.Convert<Gray,float>();
        }
        //convert background to 8U
        this.backImage = this.background.Convert<Gray, Byte>();

        //compute difference between image and background
        this.foreground = this.gray.AbsDiff(this.backImage);

        //apply threshold to foreground image
        output = this.foreground.ThresholdBinaryInv(new Gray(this.threshold), new Gray(255));

        //accumulate background
        CvInvoke.cvRunningAvg(this.gray, this.background, this.learningRate, output);

        return output;
    }
}

此類別需要用到當前的灰階影像 gray

背景模型影像(32bits) background

背景模型影像(8bits) backImage

前景影像 foreground

背景更新率 learningRate

前景擷取閥值 threshold

這幾個變數來實作


一開始先取得傳入影像的灰階影像

並以第一張影像作為背景模型影像

而前景影像就是當前影像與背景影像的差值:

this.foreground = this.gray.AbsDiff(this.backImage);

將前景影像以前景偵測閥值做二值化處理後,則為結果output

output = this.foreground.ThresholdBinaryInv(new Gray(this.threshold), new Gray(255));

最後就是更新背景模型影像

CvInvoke.cvRunningAvg(this.gray, this.background, this.learningRate, output);

利用二值化影像output作為遮罩

將當前影像與背景模型影像做更新率的計算


最後實作的方式如同過去一般:

//Create video processor instance
VideoProcessor processor = new VideoProcessor();
//Create background/foreground segmentor
BGFGSegmentor segmentor = new BGFGSegmentor();
//Open the video file
processor.SetInput(@"tracking.avi");
//Declare a window to display the video
processor.DisplayInput("Current frame");
processor.DisplayOutput("Output frame");
//Play the video at the original frame rate
processor.SetDelay((int)(1000 / processor.GetFrameRate()));
//Set the frame processor callback function
processor.SetFrameProcessor(segmentor.Process);
//Start the process
processor.Run();

執行結果如下:

 前景的部分以黑色來呈現

光影變化的部分像是影子也會被誤認為前景

 與背景色差異越明顯的部分就會偵測的越明顯

這一張有發現哪裡怪怪的嗎??

與前一張相同的問題,發現了嗎?


最後兩張可以清楚地發現

明明當前影像(上面彩色影像)只有一個人或兩個人

但前景影像(下面黑白影像)在左上方卻有很多人影

這是怎麼回事??


由於此影片的第一張影像

就不是一個乾淨的背景影像


因此在第一張背景模型建立的時候,程式就已經認為那幾個人影是背景

因此當這幾個人離開畫面後

程式就會誤認那個幾個區塊變成了前景

也因為色階的差異過大,在計算cvRunningAvg時所帶入的前景遮罩

使得這個區塊一直無法被更新成背景

因此這幾個黑影就成了永遠的前景


在前景偵測這個領域

就不得不提到混合高斯模型(mixture of Gaussian)

其修正了背景更新的計算方式

使其不容易發生上述的問題

對於光影變化的雜訊控制也比較好

而在EmguCV 2.4板之後提供了新的類別:BackgroundSubstractorMOG2

甚至還提供了陰影偵測

偵測的結果如下:




左上方的人影非但不見了

連人的影子都消去了

是不是很神奇呢!!

而BackgroundSubstractorMOG2這個類別的實作方式非常簡單

在建構式時帶入參數:

this.substractor = new BackgroundSubstractorMOG2(0, 50f, true);

並透過

this.substractor.Update(image as Image<Bgr, byte>);

即可取得 this.substractor.ForgroundMask

自行實作看看嘍~!!


沒有留言:

張貼留言