原文:http://www.cnblogs.com/tornadomeet/archive/2012/04/08/2438158.html
前景檢測演算法_1(codebook和平均背景法)
前景分割中一個非常重要的研究方向就是背景減圖法,因為背景減圖的方法簡單,原理容易被想到,且在智能視頻監控領域中,攝像機很多情況下是固定的,且背景也是基本不變或者是緩慢變換的,在這種場合背景減圖法的應用驅使了其不少科研人員去研究它。
但是背景減圖獲得前景映像的方法缺點也很多:比如說光照因素,遮擋因素,動態周期背景,且背景非周期背景,且一般情況下我們考慮的是每個像素點之間獨立,這對實際應用留下了很大的隱患。
這一小講主要是講簡單背景減圖法和codebook法。
一、簡單背景減圖法的工作原理。
在視頻對背景進行建模的過程中,每2幀映像之間對應像素點灰階值算出一個誤差值,在背景建模時間內算出該像素點的平均值,誤差平均值,然後在平均差值的基礎上+-誤差平均值的常數(這個係數需要手動調整)倍作為背景映像的閾值範圍,所以當進行前景檢測時,當相應點位置來了一個像素時,如果來的這個像素的每個通道的灰階值都在這個閾值範圍內,則認為是背景用0表示,否則認為是前景用255表示。
下面的一個工程是learning opencv一書中作者提供的原始碼,關於簡單背景減圖的代碼和注釋如下:
avg_background.h檔案:
1 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 // Accumulate average and ~std (really absolute difference) image and use this to detect background and foreground 3 // 4 // Typical way of using this is to: 5 // AllocateImages(); 6 ////loop for N images to accumulate background differences 7 // accumulateBackground(); 8 ////When done, turn this into our avg and std model with high and low bounds 9 // createModelsfromStats();10 ////Then use the function to return background in a mask (255 == foreground, 0 == background)11 // backgroundDiff(IplImage *I,IplImage *Imask, int num);12 ////Then tune the high and low difference from average image background acceptance thresholds13 // float scalehigh,scalelow; //Set these, defaults are 7 and 6. Note: scalelow is how many average differences below average14 // scaleHigh(scalehigh);15 // scaleLow(scalelow);16 ////That is, change the scale high and low bounds for what should be background to make it work.17 ////Then continue detecting foreground in the mask image18 // backgroundDiff(IplImage *I,IplImage *Imask, int num);19 //20 //NOTES: num is camera number which varies from 0 ... NUM_CAMERAS - 1. Typically you only have one camera, but this routine allows21 // you to index many.22 //23 #ifndef AVGSEG_24 #define AVGSEG_25 26 27 #include "cv.h" // define all of the opencv classes etc.28 #include "highgui.h"29 #include "cxcore.h"30 31 //IMPORTANT DEFINES:32 #define NUM_CAMERAS 1 //This function can handle an array of cameras33 #define HIGH_SCALE_NUM 7.0 //How many average differences from average image on the high side == background34 #define LOW_SCALE_NUM 6.0 //How many average differences from average image on the low side == background35 36 void AllocateImages(IplImage *I);37 void DeallocateImages();38 void accumulateBackground(IplImage *I, int number=0);39 void scaleHigh(float scale = HIGH_SCALE_NUM, int num = 0);40 void scaleLow(float scale = LOW_SCALE_NUM, int num = 0);41 void createModelsfromStats();42 void backgroundDiff(IplImage *I,IplImage *Imask, int num = 0);43 44 #endif
avg_background.cpp檔案:
1 // avg_background.cpp : 定義控制台應用程式的進入點。 2 // 3 4 #include "stdafx.h" 5 #include "avg_background.h" 6 7 8 //GLOBALS 9 10 IplImage *IavgF[NUM_CAMERAS],*IdiffF[NUM_CAMERAS], *IprevF[NUM_CAMERAS], *IhiF[NUM_CAMERAS], *IlowF[NUM_CAMERAS]; 11 IplImage *Iscratch,*Iscratch2,*Igray1,*Igray2,*Igray3,*Imaskt; 12 IplImage *Ilow1[NUM_CAMERAS],*Ilow2[NUM_CAMERAS],*Ilow3[NUM_CAMERAS],*Ihi1[NUM_CAMERAS],*Ihi2[NUM_CAMERAS],*Ihi3[NUM_CAMERAS]; 13 14 float Icount[NUM_CAMERAS]; 15 16 void AllocateImages(IplImage *I) //I is just a sample for allocation purposes 17 { 18 for(int i = 0; i<NUM_CAMERAS; i++){ 19 IavgF[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 3 ); 20 IdiffF[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 3 ); 21 IprevF[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 3 ); 22 IhiF[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 3 ); 23 IlowF[i] = cvCreateImage(cvGetSize(I), IPL_DEPTH_32F, 3 ); 24 Ilow1[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 25 Ilow2[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 26 Ilow3[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 27 Ihi1[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 28 Ihi2[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 29 Ihi3[i] = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 30 cvZero(IavgF[i] ); 31 cvZero(IdiffF[i] ); 32 cvZero(IprevF[i] ); 33 cvZero(IhiF[i] ); 34 cvZero(IlowF[i] ); 35 Icount[i] = 0.00001; //Protect against divide by zero 36 } 37 Iscratch = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 3 ); 38 Iscratch2 = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 3 ); 39 Igray1 = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 40 Igray2 = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 41 Igray3 = cvCreateImage( cvGetSize(I), IPL_DEPTH_32F, 1 ); 42 Imaskt = cvCreateImage( cvGetSize(I), IPL_DEPTH_8U, 1 ); 43 44 cvZero(Iscratch); 45 cvZero(Iscratch2 ); 46 } 47 48 void DeallocateImages() 49 { 50 for(int i=0; i<NUM_CAMERAS; i++){ 51 cvReleaseImage(&IavgF[i]); 52 cvReleaseImage(&IdiffF[i] ); 53 cvReleaseImage(&IprevF[i] ); 54 cvReleaseImage(&IhiF[i] ); 55 cvReleaseImage(&IlowF[i] ); 56 cvReleaseImage(&Ilow1[i] ); 57 cvReleaseImage(&Ilow2[i] ); 58 cvReleaseImage(&Ilow3[i] ); 59 cvReleaseImage(&Ihi1[i] ); 60 cvReleaseImage(&Ihi2[i] ); 61 cvReleaseImage(&Ihi3[i] ); 62 } 63 cvReleaseImage(&Iscratch); 64 cvReleaseImage(&Iscratch2); 65 66 cvReleaseImage(&Igray1 ); 67 cvReleaseImage(&Igray2 ); 68 cvReleaseImage(&Igray3 ); 69 70 cvReleaseImage(&Imaskt); 71 } 72 73 // Accumulate the background statistics for one more frame 74 // We accumulate the images, the image differences and the count of images for the 75 // the routine createModelsfromStats() to work on after we're done accumulating N frames. 76 // I Background image, 3 channel, 8u 77 // number Camera number 78 void accumulateBackground(IplImage *I, int number) 79 { 80 static int first = 1; 81 cvCvtScale(I,Iscratch,1,0); //To float;#define cvCvtScale cvConvertScale #define cvScale cvConvertScale 82 if (!first){ 83 cvAcc(Iscratch,IavgF[number]);//將2幅映像相加:IavgF[number]=IavgF[number]+Iscratch,IavgF[]裡面裝的是時間順序圖表片的累加 84 cvAbsDiff(Iscratch,IprevF[number],Iscratch2);//將2幅映像相減:Iscratch2=abs(Iscratch-IprevF[number]); 85 cvAcc(Iscratch2,IdiffF[number]);//IdiffF[]裡面裝的是映像差的累積和 86 Icount[number] += 1.0;//累積的圖片幀數計數 87 } 88 first = 0; 89 cvCopy(Iscratch,IprevF[number]);//執行完該函數後,將當前幀資料儲存為前一幀資料 90 } 91 92 // Scale the average difference from the average image high acceptance threshold 93 void scaleHigh(float scale, int num)//設定背景建模時的高閾值函數 94 { 95 cvConvertScale(IdiffF[num],Iscratch,scale); //Converts with rounding and saturation 96 cvAdd(Iscratch,IavgF[num],IhiF[num]);//將平均累積映像與誤差累積映像縮放scale倍然後再相加 97 cvCvtPixToPlane( IhiF[num], Ihi1[num],Ihi2[num],Ihi3[num], 0 );//#define cvCvtPixToPlane cvSplit,且cvSplit是將一個多通道矩陣轉換為幾個單通道矩陣 98 } 99 100 // Scale the average difference from the average image low acceptance threshold101 void scaleLow(float scale, int num)//設定背景建模時的低閾值函數102 {103 cvConvertScale(IdiffF[num],Iscratch,scale); //Converts with rounding and saturation104 cvSub(IavgF[num],Iscratch,IlowF[num]);//將平均累積映像與誤差累積映像縮放scale倍然後再相減105 cvCvtPixToPlane( IlowF[num], Ilow1[num],Ilow2[num],Ilow3[num], 0 );106 }107 108 //Once you've learned the background long enough, turn it into a background model109 void createModelsfromStats()110 {111 for(int i=0; i<NUM_CAMERAS; i++)112 {113 cvConvertScale(IavgF[i],IavgF[i],(double)(1.0/Icount[i]));//此處為求出累積求和映像的平均值114 cvConvertScale(IdiffF[i],IdiffF[i],(double)(1.0/Icount[i]));//此處為求出累計誤差映像的平均值115 cvAddS(IdiffF[i],cvScalar(1.0,1.0,1.0),IdiffF[i]); //Make sure diff is always something,cvAddS是用於一個數值和一個標量相加116 scaleHigh(HIGH_SCALE_NUM,i);//HIGH_SCALE_NUM初始定義為7,其實就是一個倍數117 scaleLow(LOW_SCALE_NUM,i);//LOW_SCALE_NUM初始定義為6118 }119 }120 121 // Create a binary: 0,255 mask where 255 means forground pixel122 // I Input image, 3 channel, 8u123 // Imask mask image to be created, 1 channel 8u124 // num camera number.125 //126 void backgroundDiff(IplImage *I,IplImage *Imask, int num) //Mask should be grayscale127 {128 cvCvtScale(I,Iscratch,1,0); //To float;129 //Channel 1130 cvCvtPixToPlane( Iscratch, Igray1,Igray2,Igray3, 0 );131 cvInRange(Igray1,Ilow1[num],Ihi1[num],Imask);//Igray1[]中相應的點在Ilow1[]和Ihi1[]之間時,Imask中相應的點為255(背景符合)132 //Channel 2133 cvInRange(Igray2,Ilow2[num],Ihi2[num],Imaskt);//也就是說對於每一幅映像的絕對值差小於絕對值差平均值的6倍或者大於絕對值差平均值的7倍被認為是前景映像134 cvOr(Imask,Imaskt,Imask);135 //Channel 3136 cvInRange(Igray3,Ilow3[num],Ihi3[num],Imaskt);//這裡的固定閾值6和7太不合理了,還好工程後面可以根據實際情況手動調整。137 cvOr(Imask,Imaskt,Imask);138 //Finally, invert the results139 cvSubRS( Imask, cvScalar(255), Imask);//前景用255表示了,背景是用0表示140 }
二、codebook演算法工作原理
考慮到簡單背景減圖法無法對動態背景建模,有學者就提出了codebook演算法。
該演算法為映像中每一個像素點建立一個碼本,每個碼本可以包括多個碼元,每個碼元有它的學習時最大最小閾值,檢測時的最大最小閾值等成員。在背景建模期間,每當來了一幅新圖片,對每個像素點進行碼本匹配,也就是說如果該像素值在碼本中某個碼元的學習閾值內,則認為它離過去該對應點出現過的曆史情況偏離不大,通過一定的像素值比較,如果滿足條件,此時還可以更新對應點的學習閾值和檢測閾值。如果新來的像素值對碼本中每個碼元都不匹配,則有可能是由於背景是動態,所以我們需要為其建立一個新的碼元,並且設定相應的碼元成員變數。因此,在背景學習的過程中,每個像素點可以對應多個碼元,這樣就可以學到複雜的動態背景。
關於codebook演算法的代碼和注釋如下:
cv_yuv_codebook.h檔案:
1 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 // Accumulate average and ~std (really absolute difference) image and use