這篇文章最初發表在http://blog.csdn.net/j56754gefge/article/details/44598381,均是我個人原創,轉載請註明出處!
% input: im1, im2, im3, im4, im5
% output: background image F
F = im1;
for i=1:3
A = cat(3,im1(:,:,i),im2(:,:,i),im3(:,:,i),im4(:,:,i),im5(:,:,i));
F(:,:,i) = median(A,3);
end
figure,imshow(F)
在工程應用中,常常需要對固定相機拍攝的n幅圖像做一個背景建模,然後用圖像減去背景就得到運動變化部分。背景建模的方法很多,常用的有GM,GMM,這些經典算法在openCV裏都集成了,但是速度上還不是特別快。對於類似的情況其實有更簡單的方法建背景--中值背景就是在工程中用得最多,效果最理想的方法之一。注意,這裏說的中值,就是median,不是mean(均值)。
計算中值的過程:舉個例子,對5個數[20 30 32 23 90]計算中值,首選需要對其排序,用冒泡排序算法得到的結果應該是[20 23 30 32 90],然後取中間一個數,即30。
計算中值圖像( F )的時候,就是要對n幅圖像( A1, A2, ..., An )上每個像素位置分別求中值,大概就是:
F = median_background( A1, A2, A3, ..., An )
s.t., F(x,y) = median( [ A1(x,y), A2(x,y), ..., An(x,y) ] )
另外,我們希望算法是增量式的:比如剛纔我已經對A1到An共n幅圖像計算出了一箇中值圖像F,那麼現在我又得到了第n+1幅圖像,我希望更新這個背景:
F_new = median_background( A2, A3, A4, ..., A(n+1) )
如果從頭開始計算F_new,感覺之前的結果沒用上,有點可惜。爲了減少計算量,我希望這個算法是增量的,即F_new可以在F的基礎上得到。那麼我想到了在opencv和matlab裏面都有一個index_filter之類的函數,就是對數組元素進行排序,只保存排序的索引,而不改變數組元素本身。基於這個思想,我寫出了本文代碼。
下面貼代碼:
// 文件 yuMedianBackground.h
#ifndef YU_MEDIAN_BACKGROUND_H
#define YU_MEDIAN_BACKGROUND_H
#include "cv.h"
using namespace cv;
// **********************************
// calculating a median image of a set of images.
// require the image set to be CV_8UC1 type.
// by : Yu Xianguo, Oct.29.2014
// **********************************
class yuMedianBackground
{
public:
yuMedianBackground(){};
Mat calc( const Mat *frames, int num_frames );
Mat calcOn( const Mat &frame );
Mat compounded;
Mat indexes;
Mat background;
};
#endif
// 文件 yuMedianBackground.cpp
#include "yuMedianBackground.h"
using namespace std;
Mat yuMedianBackground::calc( const Mat *frames, int num_frames )
{
assert( num_frames>0 && num_frames<32 && frames->type()==CV_8UC1 );
cv::merge( frames, num_frames, compounded );
int rows = frames[0].rows, cols = frames[0].cols;
indexes.create( rows, cols, CV_8UC(num_frames) );
std::vector<uchar> v(num_frames);
uchar num = num_frames, num1 = num_frames - 1;
for( uchar i=0; i<num; i++ ) v[i]=i;
indexes.setTo(v);
uchar *pt1 = compounded.data, *pt2 = indexes.data;
int gap1 = compounded.step-cols*num_frames;
int gap2 = indexes.step-cols*num_frames;
uchar tmp, *pt3;
for( int y=0; y++<rows; pt1+=gap1, pt2+=gap2 ){
for( int x=0; x++<cols; pt1+=num, pt2+=num ){
for( uchar i=0; i<num1; i++ ){
pt3 = pt2;
for( uchar j=0, nd=num1-i; j<nd; j++ ){
uchar &a = *(pt3++); // pt2[j]
if( pt1[*pt3]<pt1[a] ){
tmp = a; a = *pt3; *pt3 = tmp;
}
//uchar &a = pt2[j], &b = pt2[j+1];
//if( pt1[b]<pt1[a] ){
//tmp = a; a = b; b = tmp; // exchange a & b
//}
}
}
}
}
// extract
background.create( rows, cols, CV_8UC1 );
int mid = (int)( (num_frames-1)/2.f+.5f );
pt1 = compounded.data, pt2 = indexes.data+mid, pt3 = background.data;
int gap3 = background.step-cols;
for( int y=0; y++<rows; pt1+=gap1, pt2+=gap2, pt3+=gap3 )
for( int x=0; x++<cols; pt1+=num, pt2+=num )
*(pt3++) = pt1[*pt2];
return background;
}
Mat yuMedianBackground::calcOn( const Mat &frame )
{
// --------- need calc() to be called beforehand
assert( frame.size()==background.size() && frame.type()==CV_8UC1 );
// --------- define variables
int rows = frame.rows, cols = frame.cols, num_frames = compounded.channels();
uchar *pt1 = compounded.data, *pt2 = indexes.data, *pt3 = frame.data;
int gap1 = compounded.step-cols*num_frames, gap2 = indexes.step-cols*num_frames, gap3 = frame.step-cols;
uchar i, num = num_frames, num1 = num_frames-1, tmp;
// --------- update "compounded" with "frame"
// code 1.
//y = 0; while(y++<rows){
// x = 0; while(x++<cols){
// i = 1; while(i++<num){
// *pt1 = *(pt1+1); // overwrite frame[0]
// pt1++;
// }
// *(pt1++) = *(pt3++);
// }
// pt1 += gap1, pt3 += gap3;
//}
//pt1 = compounded.data, pt3 = frame.data; // recover
// code 2. alternative to code(1), but much faster than
Mat from[] = { compounded, frame };
Mat to[] = { compounded };
int *from_to = new int [2*num_frames];
int *pt4 = from_to;
for( i=0; i<num; i++ ){
*(pt4++) = i+1;
*(pt4++) = i;
}
cv::mixChannels( from, 2, to, 1, from_to, num_frames );
delete [] from_to;
// --------- update "indexes"
for( int y=0; y++<rows; pt1+=gap1, pt2+=gap2, pt3+=gap3 ){
for( int x=0; x++<cols; pt1+=num, pt2+=num ){
uchar *pt4 = pt2, *pt5;
i = 0; while(i++<num){
if( *pt4 )
(*(pt4++))--;
else
*(pt5=pt4++) = num1;
}
tmp = *(pt3++);
// backward
for( pt4=pt5; pt4!=pt2; ){
uchar &a = *(pt4-1); // corresponding index to position (i-1)
if( tmp<pt1[a] ){ // corresponding gray value to index (a)
*(pt4--)= a; a = num1; // exchange index in positions (i-1) & (i)
}
else break;
}
if( pt4!=pt5 )
continue;
// forward
pt4 = pt5;
for( pt5=pt2+num1; pt4!=pt5; ){
uchar &a = *(pt4+1);
if( pt1[a]<tmp ){
*(pt4++) = a; a = num1;
}
else
break;
}
}
}
// --------- extract
int mid = (int)( (num_frames-1)/2.f+.5f );
pt1 = compounded.data, pt2 = indexes.data+mid;
pt3 = background.data, gap3 = background.step-cols;
for( int y=0; y++<rows; pt1+=gap1, pt2+=gap2, pt3+=gap3 )
for( int x=0; x++<cols; pt1+=num, pt2+=num )
*(pt3++) = pt1[*pt2];
return background;
}
1.裏面主要接口是calc()和calcOn()兩個函數,分別是計算F以及在F的基礎上計算F_new的。
2.代碼是我花了一些時間寫的,在工程中也用過,還沒發現bug。如果有誰發現了代碼中存在bug,請一定在回覆裏告訴我,我會修改本文代碼,以惠及後來者。
3.速度上我自信這是你們見過的最快的或接近最快的計算中值圖像的程序了,如果誰有更快的代碼,希望不要吝嗇傳上來給我們看看,可以發csdn資源或博客,然後記得把網址貼在回覆裏,讓我學習一下