影象區域性與部分分割--opencv
背景減除一旦背景模型建立,將背景模型和當前的影象進行比較,然後減去這些已知的背景資訊,則剩下的目標物大致就是所求的前景目標了缺點 —— 該方法基於一個不長成立的假設:所有畫素點是獨立的場景建模新的前景(物體移動的新位置) —— 舊的前景 (物體離開後留下的“空洞”)—— 背景cvInitLineIterator() 和 CV_NEXT_LINE_POINT() 對任意直線上的畫素進行取樣 從視訊的一行中讀出所有畫素的RGB值,收集這些數值並將其分成三個檔案 #include <cv.h> #include <highgui.h> int main(int argc,char** argv) { CvCapture* capture=cvCreateFileCapture(""); int max_buffer; IplImage *rowImage; int r[10000],g[10000],b[10000]; CvLineIterator iterator; FILE *fptrb=fopen("blines.csv","w"); // store the data here FILE *fptrg=fopen("glines.csv","w"); FILE *fptrr=fopen("rlines.csv","w"); CvPoint pt1,pt2; for (;;) { if(!cvGrabFrame(capture)) break; rowImage=cvRetrieveFrame(capture); max_buffer=cvInitLineIterator(rowImage,pt1,pt2,&iterator,8,0); for (int j=0;j<max_buffer;j++) { fprintf(fptrb,"%d",iterator.ptr[0]); fprintf(fptrg,"%d",iterator.ptr[1]); fprintf(fptrr,"%d",iterator.ptr[2]); iterator.ptr[2]=255; // mark this sample in red CV_NEXT_LINE_POINT(iterator); fprintf(fptrb,"\n"); fprintf(fptrg,"\n"); fprintf(fptrr,"\n"); } } cvReleaseCapture(capture); fclose(fptrb); fclose(fptrg); fclose(fptrr); return 0; }
幀差 —— 用一幀減去另一幀,然後將足夠大的差別標為前景,這種方法往往能捕捉運動目標的邊緣
cvAbsDiff cvThreshold (忽略很小的差異——比如小於15,標識其餘的作為較大的差別)
可以用cvErode() 函式或者用連通域去噪
對於彩色影象,我們用相同的程式碼對每個顏色通道分別處理,之後在呼叫cvOr() 函式將所有的通道拼接在一起
平均背景法
—— 計算每個畫素的平均值和標準差作為他的背景模型
平均背景法使用四個OpenCV函式 :
cvAcc 累計影象
cvAbsDiff 計算一定時間內的每幀影象之差
cvInRange 將影象分割成前景區和背景區域 (背景模型在已經學習的情況下)
cvOr 將不同的彩色通道影象中合成一個掩碼影象
// 背景法 --- 只能用於背景場景中不包含運動的部分 // 為需要的不同臨時影象和統計屬性影象建立指標 IplImage *IavgF,IdiffF,*IprevF,*IhiF,*IlowF; IplImage *Iscratch,*Iscratch2; IplImage *Igray1,*Igray2,*Igray3; IplImage *Ilow1,*Ilow2,*Ilow3; IplImage *Ihi1,*Ihi2,*Ihi3; IplImage *Imaskt; float Icount; // couts number of images learned for averaging later // 建立一個函式給需要的所有臨時影象分配記憶體 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; // Protect against divide by zero 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_32F,1); cvZero(Iscratch); cvZero(Iscratch2); } // 學習積累背景影象和每一幀影象差值的絕對值 void accumulateBackground(IplImage* I) { static int first=1; cvCvtScale(I,Iscratch,1,0); if (!first) { cvAcc(Iscratch,IavgF); cvAbsDiff(Iscratch,IprevF,Iscratch2); cvAcc(Iscratch,IdiffF); Icount+=1.0; } first=0; cvCopy(Iscratch,IprevF); } 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 createModelsfromStats() { cvConvertScale(IavgF,IavgF,(double)(1.0/Icount)); cvConvertScale(IdiffF,IdiffF,(double)(1.0/Icount)); // make sure diff is always something cvAddS(IdiffF,cvScalar(1.0,1.0,1.0),IdiffF); setHighThreshold(7.0); setLowThreshold(6.0); // 對每一幀影象的絕對差大於平均值7倍的畫素都認為是前景 } // 有了背景模型,同時給出了高低閾值,就可以用它將影象分割成前景(不能被背景模型解釋的影象部分)和背景(在背景模型中,任何高低閾值之間的影象部分) void backgroundDiff(IplImage* I,IplImage* Imask) { cvCvtScale(I,Iscratch,1,0); // To float cvSplit(Iscratch,Igray1,Igray2,Igray3); // channel 1 cvInRange(Igray1,Ilow1,Ihi1,Imask); // 是否在高低閾值之間 // channel 2 cvInRange(Igray2,Ilow2,Ihi2,Imask); // channel 3 cvInRange(Igray3,Ilow3,Ihi3,Imask); cvOr(Imask,Imaskt,Imask); // finally , invert the result cvSubRS(Imask,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); }
累計均值,方差和協方差
均值漂移
cvRunningAvg —— 更新時,源影象佔一定權重 —— 也稱為,跟蹤器(前一幀影象褪色的影響,引數a本質上上是設定所需要的時間)
計算方差 —— cvSquareAcc —— 單個畫素的方差正好是平方的均值減去均值的平方
計算協方差 —— cvMultiplyAcc
高階背景模型
複雜的運動目標 —— 得到每個畫素或一組畫素的時間序列模型 ,這種模型能夠很好的處理時間起伏,缺點是需要消耗大量的記憶體
codebook (編碼本) —— 將一個畫素現在的觀測值和先前的觀測值作比較。如果兩個值很接近,它被建模為那種顏色下的擾動,如果兩個值不接近,它可以產生與該畫素相關的一組色彩。
從經驗角度看絕大部分背景中的變化傾向於沿著亮度軸,而不是顏色軸
在背景學習模型的codebook方法中,在每一個三顏色軸上,每一個box用兩個閾值(最大和最小)定義。如果新的背景模型落到學習的閾值(learnHigh 和 learnLow 之間,這些box的邊界將膨脹 (最大閾值變大,最小閾值變小)。如果新的背景樣本在box和學習閾值外,將開始生成一個新的box,在背景差分模型中,也能容納maxMod和minMod閾值。使用這些閾值。可以說,如果一個畫素和box邊界最大值和最小值非常接近,我們就認為它在box裡面。再次調整閾值,允許模型適應特殊情形)
codebook box 容納呈現多維不連續分佈的畫素,所以能更好的模擬畫素的不連續分佈
使用codebook 背景模型
1,使用 函式 update_codebook 在幾秒鐘或幾分鐘時間內訓練一個基本的背景模型
2,使用 clear_stale_entries 清除stale索引
3,調整閾值 minMod 和 maxMod ui已知前景達到最好的分割
4,保持一個更高級別的場景模型
5,通過 background_diff 使用訓練好的模型將前景從背景中分割出來
6,定期更新學習的背景畫素
7,在一個頻率較慢的情況下,使用函式 clear_stale_entries 定期清理 stale 的codebook 索引
部分程式碼:
#include <cv.h> #include <highgui.h> #define CHANNELS 3 typedef struct ce { uchar learnHigh[CHANNELS]; uchar learnLow[CHANNELS]; uchar max[CHANNELS]; // High side of box boundary uchar min[CHANNELS]; int t_last_update; // allow us to kill stale entries int stale; // max negative run }code_element; typedef struct code_book{ code_element **cb; int numEntries; int t; // count every access }codeBook; // 如果一個畫素值的美國通道都不在 min - learnLow 和 max + learnHigh 之間,就會生成一個新的碼元。距離上次更新和陳舊的時間(t_last_update)用於刪除過程中學習的很少使用的碼本條目 // 背景學習 int update_codebook(uchar* p,codeBook& c,unsigned* cbBounds,int numChannels) { int n; unsigned int high[3],low[3]; for (n=0;n<numChannels;n++) { high[n]=*(p+n)+*(cbBounds+n); if(high[n]>255) high[n]=255; low[n]=*(p+n)-*(cbBounds+n); if(low[n]<0) low[n]=0; } // see if this fits an existing codeword int matchChannel; for (int i=0;i<c.numEntries;i++) { matchChannel=0; for (n=0;n<numChannels;n++) { if((c.cb[i]->learnLow<=*(p+n))&&(*(p+n)<=c.cb[i]->learnHigh[n])) matchChannel++; if (matchChannel==numChannels) // if an entry war found { 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; } } } // overhead to track potential stale entries for (int s=0;s<c.numEntries;s++) { int negRun=c.t-c.cb[s]->t_last_update; if (c.cb[s]->stale<negRun) { c.cb[s]->stale=negRun; } } // enter a new codeword if needed } // 學習有移動前景目標的背景 // 背景差分,尋找前景目標
用於前景清除的連通部分
包含噪聲輸入掩模影象,然後利用形態學“開”操作將小的噪聲縮小至0,接著用“閉”操作重建由於開操作丟失的邊緣部分
沒有任何理由相信噪聲有很大的空間相關性,這些訊號又大量的非常小的區域來描述
一個功能強大的在背景中減去噪聲的技術