《Learning OpenCV》中的第9中開篇介紹了一種建立背景模型的方法——平均背景法。 但書上只是簡單地介紹了一下這種方法的思想,更多的原理蘊含在它給出的代碼當中。
平均背景法是一種建立背景模型的方法。簡單地說,就是從視頻或攝像頭中獲取一系列的幀,然後取這些幀中的平均像素值來表徵背景。然後再給這些平均像素值加上一定的閾值範圍,這就構成了背景模型。在新加入的圖像中,如果對應位置的像素超出了這個背景模型中對應位置像素的閾值範圍,就把它當作前景。
應用的例子:舉一個例子來說明背景模型的作用,比如說深夜裏,攝像頭檢測是否有可疑人物進去,一旦建立了背景模型,攝像頭所拍攝的圖像將會與背景模型圖像進行比較,如果圖像中的像素變動很大,則說明前景發生了變化。如果有可疑人物進入攝像頭的範圍,這種變化就可以被捕捉到,可以進行報警等操作。
參考後面附上的代碼,下面介紹平均背景法的建模和分割的步驟:
步驟1: 累積背景
從視頻或攝像頭中連續獲取301幀圖像,把這301幀圖像分別標爲I1、I2 ... I300、 I301
l 從第2幀圖像開始,把後面的300幀圖像累加起來(這種累加是矩陣加法,下同)I2+…+ I300+I301 (因爲考慮到後面的幀差需要從第2幀開始計算,所以這裏也是從第2幀開始),得到圖像IavgF
定義:幀差 = In-In-1
l 把300幀圖像裏面的每一個幀差累加起來[I2-I1+I3-I4+…+I299-I300+(I300-I301)]得到圖像 Iscratch
步驟2: 創建基於統計學的背景模型
由步驟1得到了兩個圖像,IavgF 和Iscratch
l 計算出這300幀中每幀的平均值IavgF * (1.0/300) --> IavgF
l 從這300幀的幀差和中計算出平均幀差IdiffF * (1.0/300) --> IdiffF
l 計算閾值的上限:(這裏以7.0作爲一個計算閾值上限的參數)
IavgF + [(IdiffF + 1.0) * 7.0] --> IhiF
把IhiF這個3通道圖分割成3個單通道圖Ihi1, Ihi2, Ihi3分別代表B、G、R通道
l 計算閾值的下限:(這裏以6.0作爲一個計算閾值下限的參數)
IavgF - [(IdiffF + 1.0) * 6.0] --> IlowF
把IhiF這個3通道圖分割成3個單通道圖Ilow1, Ilow2, Ilow3分別代表B、G、R通道
注意:幀差越大的地方,計算出來的閾值範圍就越大。最直觀的感受是,變化得比較厲害的那些位置,擁有的閾值範圍較大。
步驟3: 利用背景模型分割背景
繼續從視頻或攝像頭中獲取圖像幀,把獲取到的圖像幀標爲I
l 把I這個3通道圖分割成3個單通道圖Igray1, Igray2, Igray3分別代表B、G、R通道
l 對Igray1, Igray2,Igray3進行閾值處理:
對於Igray1,如果圖像中的像素值在(low1, lhi1)的範圍之內,則把該像素值設置爲255,否則把該像素值設爲0。對Igray2和Igray3作同樣的處理。然後合併Igray1, Igray2, Igray3這3個單通道圖爲1個3通道圖Imask。
l 作最後處理: 255 – Imask à Imask
結果是:
l 在建立背景模型Imask的過程中,像素值不常變化的地方,在最後得到的背景模型當中閾值範圍較小,只要圖像幀I中的像素在這些閾值範圍較小的敏感地方,對應於背景模型圖像Imask中的像素稍微出現一些變動,結果圖像中那些變動的位置就會呈現出白色(因爲通過計算所得到的像素值爲255),而沒有變動的地方則呈現黑色(因爲通過計算所得到的像素值爲0)。
l 然而,在建立背景模型Imask的過程中,在像素值經常變化的地方,在最後得到的背景模型當中閾值範圍較大,所以這些區域就不那麼敏感了,需要變化較大時纔會顯現白色。
參考代碼
http://hi.baidu.com/lin65505578/item/3e2faa42d4dd791c896d10d2這篇博文對《Learning OpenCV》一書當中的代碼進行了一點擴充,而且給出了一些有關背景模型的資料,我在學習這種方法的時候也是參考了這篇博文,通過代碼來理解平均背景法的原理。這裏引用一下博文裏給出的代碼,爲了幫助理解代碼,我同時給出了一個幫助理解這些代碼的僞代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
#include
<cv.h> #include
<highgui.h> #include
<stdio.h> IplImage
*IavgF,*IdiffF,*IprevF,*IhiF,*IlowF; IplImage
*Iscratch,*Iscratch2; IplImage
* Igray1,*Igray2,*Igray3; IplImage
*Ilow1,*Ilow2 ,*Ilow3; IplImage
*Ihi1,*Ihi2,*Ihi3; IplImage
*Imaskt; IplImage
*Imask; float
Icount; void
AllocateImages(IplImage *I); void
accumulateBackground(IplImage * I); void
createModelsfromStats(); void
setHighThreshold( float
scale); void
setLowThreshold( float
scale); void
backgroundDiff(IplImage *I,IplImage * Imask); void
DeallocateImages(); int
main( int
argc, char
** argv) { CvCapture
*capture=cvCreateCameraCapture(0); if
(NULL==capture) { printf ( "沒有找到攝像頭裝置!\n" ); return
-1; } IplImage
* Img=cvQueryFrame(capture); cvNamedWindow( "原圖" ,0); cvNamedWindow( "檢測圖" ,0); cvShowImage( "原圖" ,Img); AllocateImages(Img); //初始化 printf ( "開始統計背景模型\n" ); while
(Icount<300) //統計背景模型 { accumulateBackground(Img); Img=cvQueryFrame(capture); cvWaitKey(100); cvShowImage( "原圖" ,Img); printf ( "." ); } createModelsfromStats(); //就算每一個像素的均值和方差觀測 printf ( "\n統計背景模型結束.....\n" ); printf ( "按任意鍵開始分割圖像.....\n" ); cvWaitKey(NULL); printf ( "開始分割圖像.....\n" ); while
(1) { Img=cvQueryFrame(capture); backgroundDiff(Img,Imask); cvShowImage( "原圖" ,Img); cvShowImage( "檢測圖" ,Imask); if
(27==cvWaitKey(100)) break ;
} cvDestroyAllWindows(); //釋放內存 DeallocateImages(); cvReleaseCapture(&capture); return
0; } void
AllocateImages(IplImage *I) { CvSize
sz=cvGetSize(I); IavgF=cvCreateImage(sz,IPL_DEPTH_32F,3); IdiffF=cvCreateImage(sz,IPL_DEPTH_32F,3); IprevF=cvCreateImage(sz,IPL_DEPTH_32F,3); IhiF=cvCreateImage(sz,IPL_DEPTH_32F,3); IlowF=cvCreateImage(sz,IPL_DEPTH_32F,3); Ilow1=cvCreateImage(sz,IPL_DEPTH_32F,1); Ilow2=cvCreateImage(sz,IPL_DEPTH_32F,1); Ilow3=cvCreateImage(sz,IPL_DEPTH_32F,1); Ihi1=cvCreateImage(sz,IPL_DEPTH_32F,1); Ihi2=cvCreateImage(sz,IPL_DEPTH_32F,1); Ihi3=cvCreateImage(sz,IPL_DEPTH_32F,1); cvZero(IavgF); cvZero(IdiffF); cvZero(IprevF); cvZero(IhiF); cvZero(IlowF); Icount=0.00001; Iscratch=cvCreateImage(sz,IPL_DEPTH_32F,3); Iscratch2=cvCreateImage(sz,IPL_DEPTH_32F,3); Igray1=cvCreateImage(sz,IPL_DEPTH_32F,1); Igray2=cvCreateImage(sz,IPL_DEPTH_32F,1); Igray3=cvCreateImage(sz,IPL_DEPTH_32F,1); Imaskt=cvCreateImage(sz,IPL_DEPTH_8U,1); Imask=cvCreateImage(sz,IPL_DEPTH_8U,1); cvZero(Iscratch); cvZero(Iscratch2); cvZero(Imask); cvZero(Imaskt); } void
accumulateBackground(IplImage * I) { static
int
first=1; cvCvtScale(I,Iscratch,1,0); if
(!first) { cvAcc(Iscratch,IavgF); cvAbsDiff(Iscratch,IprevF,Iscratch2); cvAcc(Iscratch2,IdiffF); Icount+=1.0; } first=0; cvCopy(Iscratch,IprevF); } void
createModelsfromStats() { //通過除以輸入圖像積累的數目計算平均原始圖像和絕對差分圖像 cvConvertScale(IavgF,IavgF,( double )(1.0/Icount)); cvConvertScale(IdiffF,IdiffF,( double )(1.0/Icount)); //make
sure diff is slways something cvAddS(IdiffF,cvScalar(1.0,1.0,1.0),IdiffF); //確保平均差分圖像的值最小是1 setHighThreshold(7.0); //當計算前景和背景閾值以及避免前景閾值和背景閾值相等而出現的退化情況時,我們要縮放這個因素 setLowThreshold(6.0); } void
setHighThreshold( float
scale) { cvConvertScale(IdiffF,Iscratch,scale); cvAdd(Iscratch,IavgF,IhiF); cvSplit(IhiF,Ihi1,Ihi2,Ihi3,0); } void
setLowThreshold( float
scale) { cvConvertScale(IdiffF,Iscratch,scale); cvSub(IavgF,Iscratch,IlowF); cvSplit(IlowF,Ilow1,Ilow2,Ilow3,0); } void
backgroundDiff(IplImage *I,IplImage * Imask) { cvConvertScale(I,Iscratch,1,0); cvSplit(Iscratch,Igray1,Igray2,Igray3,0); cvInRange(Igray1,Ilow1,Ihi1,Imask); cvInRange(Igray2,Ilow2,Ihi2,Imaskt); cvOr(Imask,Imaskt,Imask); cvInRange(Igray3,Ilow3,Ihi3,Imask); cvOr(Imask,Imaskt,Imask); cvSubRS(Imask,cvScalar(255),Imask); } void
DeallocateImages() { cvReleaseImage(&IavgF); cvReleaseImage(&IdiffF); cvReleaseImage(&IprevF); cvReleaseImage(&IhiF); cvReleaseImage(&IlowF); cvReleaseImage(&Ilow1); cvReleaseImage(&Ilow2); cvReleaseImage(&Ilow3); cvReleaseImage(&Ihi1); cvReleaseImage(&Ihi2); cvReleaseImage(&Ihi3); cvReleaseImage(&Iscratch); cvReleaseImage(&Iscratch2); cvReleaseImage(&Igray1); cvReleaseImage(&Igray2); cvReleaseImage(&Igray3); cvReleaseImage(&Imaskt); cvReleaseImage(&Imask); } |
對應的僞代碼
Step1 : Accumulate backgroundIcount = 300 times
convert to float
I(a frame) ------------------> Iscratch + IavgF--> IavgF (do the loop for 300 times)
-
IprevF
=
Iscratch2
Iscratch2 + IdiffF --> IdiffF
Step2: Create model from statistics
Icount = 300
IavgF * (1.0/Icount) --> IavgF
IdiffF * (1.0/Icount) --> IdiffF
IavgF + [(IdiffF + 1.0) * 7.0] --> IhiF --> Ihi1, Ihi2, Ihi3
IavgF - [(IdiffF + 1.0) * 6.0] --> IlowF --> Ilow1, Ilow2, Ilow3
Step3: Segmentation
convert to float
I(a frame) ------------------> Iscratch --> Igray1, Igray2, Igray3
(Ilow1,Ihi1)-> 255 otherwise -> 0
Igray1 ------------------------------------> Imask
(Ilow1,Ihi1)-> 255 otherwise -> 0
Igray2 ------------------------------------> Imaskt
Imask | Imaskt --> Imask
(Ilow1,Ihi1)-> 255 otherwise -> 0
Igray3 -------------------------------------> Imask
Imask | Imaskt --> Imask
255 - Imask --> Imask