視頻前景目標提取(一)

    最近幾天參加了一次比賽,提取監控視頻前景目標,前前後後試了很多的方法,幀差法+GMM,四幀差法,改進幀差法,混合高斯模型,改進的混合高斯模型,ViBe前景目標提取,ViBe+GMM前景目標提取等等。最後也嘗試了模板匹配和HOG+SVM訓練樣本圖片提取視頻目標。大概會用幾篇文章介紹一下這些方法的優缺點和各自的適用情況。編程實現均在opencv2.4.13+VS2015環境。

    首先介紹一下幀差法+GMM,GMM又叫混合高斯模型,在進行前景檢測前,先對背景進行訓練,對圖像中每個背景採用一個混合高斯模型進行模擬,每個背景的混合高斯的個數可以自適應。然後在測試階段,對新來的像素進行GMM匹配,如果該像素值能夠匹配其中一個高斯,則認爲是背景,否則認爲是前景。由於整個過程GMM模型在不斷更新學習中,所以對動態背景有一定的魯棒性。最後通過對一個有樹枝搖擺的動態背景進行前景檢測,取得了較好的效果。

    混合高斯模型最早在計算機視覺中的應用是用來做前景檢測,主要是用於視頻監控領域,這個系統和穩定且有自學能力,能在戶外環境跑16個多月。KaewTraKulPong將GMM的訓練過程做了改進,將訓練過程分爲2步進行,前L幀採用EM算法進行權值,均值,方差更新,後面的過程就採用中的方法進行更新,取得了更好的檢測效果。Zivkovic et al.在中對GMM理論做了全面的論述,使得GMM理論的使用不僅金限於計算機視覺領域。並且該作者將該理論進一步具體到背景減圖的前景檢測中來,即加入了參數估計的先驗知識,取得了很好的效果和穩定性。

實現過程

  1. 首先將每個高斯的均值,方差,權值都設置爲0,即初始化個模型矩陣參數。
  2. 採用視頻中的T幀用來訓練GMM模型。對每一個像素而言,建立其模型個數最大GMM_MAX_COMPONT個高斯的GMM模型。當第一個像素來,單獨爲其在程序中設置好其固定的初始均值,方差,並且權值設置爲1。
  3. 非第一幀訓練過程中,當後面來的像素值時,與前面已有的高斯的均值比較,如果該像素點的值與其模型均值差在3倍的方差內,則任務屬於該高斯。此時用如下方程進行更新:
  4. 當到達訓練的幀數T後,進行不同像素點GMM個數自適應的選擇。首先用權值除以方差對各個高斯進行從大到小排序,然後選取最前面B個高斯,使

  

  這樣就可以很好的消除訓練過程中的噪聲點。

  5. 在測試階段,對新來像素點的值與B個高斯中的每一個均值進行比較,如果其差值在2倍的方差之間的話,則認爲是背景,否則認爲是前景。並且只要其中有一個高斯分量滿足該條件就認爲是前景。前景賦值爲255,背景賦值爲0。這樣就形成了一副前景二值圖。

  6. 由於前景二值圖中含有很多噪聲,所以採用了形態學的開操作將噪聲縮減到0,緊接着用閉操作重建由於開操作丟失的邊緣部分的信息。消除了不連通的小噪聲點。

  上面是該算法實現的大概流程,但是當我們在具體編程時,還是有很多細節的地方需要注意,比如有些參數值的選擇。在這裏,經過試驗將一些常用的參數值聲明如下:

  1. 3個參數的值的更新方差中,取其中的學習率爲0.005。也就是說T等於200。
  2. 定義每個像素的最大混合高斯個數取7。
  3. 取視頻的前1000幀進行訓練。
  4. 取Cf爲0.3。即滿足權值大於0.7的個數爲自適應高斯的個數B。
  5. 訓練過程中,需要新建立一個高斯時,其權值取值設爲與學習率大小值相等,即0.05。
  6. 訓練過程中,需要新建立一個高斯時,取該高斯的均值爲該輸入像素值大小本身。方差爲15。
  7. 訓練過程中,需要新建立一個高斯時,取該高斯的方差15。



程序設計

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2\opencv.hpp>
#include <vector>
using namespace cv;
using namespace std;

int main()
{
	//int argc, char* argv[]
	CvCapture *capture = cvCreateFileCapture("E:\\數學建模\\input2.avi");
	IplImage *img, *img_gray, *img_cny, *back, *fore;//原圖像、灰度圖像、邊緣檢測得到的圖像、背景圖像、前景圖像
	
	CvVideoWriter *writer=NULL;
	double fps = cvGetCaptureProperty(capture, CV_CAP_PROP_FPS);
	CvSize size = cvSize(
		(int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH),
		(int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT));
	writer = cvCreateVideoWriter("E:\\數學建模\\input2out.avi", CV_FOURCC('M','J','P','G'), fps, size, 0);

	int width, height;//圖像的長、寬
	int cnt = 0; //視頻幀數
	img = cvQueryFrame(capture);
	cnt++;
	width = img->width; height = img->height;
	vector< vector<double> > mean(width*height), sd(width*height), w(width*height);
	img_gray = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
	img_cny = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
	back = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
	fore = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
	cvCvtColor(img, img_gray, CV_BGR2GRAY);
	cvCopy(img_gray, back);
	//初始化混合高斯背景模型
	int K = 5, sd_init = 16, T = 200, match = 0;//高斯分佈的個數、標準差的初始值 幀數閾值 匹配參數
	double w_init = 0.005, D = 2.5, alph, p, threold = 0.7;//權重的初始值、置信參數、學習速率、參數更新速率、預定閾值(用於選取背景模型)
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			mean[i*width + j].push_back((uchar)img_gray->imageData[i*width + j]);
			sd[i*width + j].push_back(sd_init);
			w[i*width + j].push_back(1);
		}
	}
	IplImage* logpolar_frame = cvCreateImage(size, IPL_DEPTH_8U, 3);
	while (1)
	{
		img = cvQueryFrame(capture);
		if (!img) break;

		cvCvtColor(img, img_gray, CV_BGR2GRAY);
		cnt++;

		//計算學習速率
		if (cnt < T) alph = (double)1 / (2 * cnt);
		else alph = (double)1 / (2 * T);

		for (int i = 0; i < height; i++)
		{
			for (int j = 0; j < width; j++)
			{
				match = 0;
				int len = mean[i*width + j].size();
				for (int k = 0; k < len; k++)
				{
					//判斷一個像素是否與背景模型匹配
					if (fabs((uchar)img_gray->imageData[i*width + j] - mean[i*width + j][k]) < D*sd[i*width + j][k])
					{
						match = 1;
						//更新權重、均值、標準差
						w[i*width + j][k] = (1 - alph)*w[i*width + j][k] + alph;
						p = alph / w[i*width + j][k];
						mean[i*width + j][k] = (1 - p)*mean[i*width + j][k] + p*(uchar)img_gray->imageData[i*width + j];
						sd[i*width + j][k] = sqrt((1 - p)*sd[i*width + j][k] * sd[i*width + j][k] + p*pow((uchar)img_gray->imageData[i*width + j] - mean[i*width + j][k], 2));
					}
					else
					{
						w[i*width + j][k] = (1 - alph)*w[i*width + j][k];
					}
				}
				if (match == 0)
				{
					//增加新的背景模型
					if (len < K)
					{
						mean[i*width + j].push_back((uchar)img_gray->imageData[i*width + j]);
						sd[i*width + j].push_back(sd_init);
						w[i*width + j].push_back(w_init);
					}
					//更改權重最小的背景模型
					else
					{
						int min = 0;
						double tmp = w[i*width + j][0];
						for (int k = 1; k < len; k++)
						{
							if (tmp >  w[i*width + j][k])
							{
								min = k;
								tmp = w[i*width + j][k];
							}
						}
						mean[i*width + j][min] = (uchar)img_gray->imageData[i*width + j];
						sd[i*width + j][min] = sd_init;
						w[i*width + j][min] = w_init;
					}
				}
				//每隔50幀 刪除權重最小的高斯分佈	
				if (cnt % 50 == 0)
				{
					vector<double>::iterator it_w, it_sd, it_mean;
					it_w = w[i*width + j].begin();
					it_sd = sd[i*width + j].begin();
					it_mean = mean[i*width + j].begin();
					for (; it_w != w[i*width + j].end() && it_sd != sd[i*width + j].end() && it_mean != mean[i*width + j].end();)
					{
						if (*it_w < w_init && (*it_w / *it_sd) < (w_init / sd_init))
						{
							it_w = w[i*width + j].erase(it_w);
							it_sd = sd[i*width + j].erase(it_sd);
							it_mean = mean[i*width + j].erase(it_mean);
						}
						else
						{
							it_w++;
							it_sd++;
							it_mean++;
						}
					}
				}
				//權重歸一化
				len = mean[i*width + j].size();
				double tmp = 0;
				for (int k = 0; k < len; k++)
				{
					tmp += w[i*width + j][k];
				}
				for (int k = 0; k < len; k++)
				{
					w[i*width + j][k] /= tmp;
				}
				//計算各背景模型的優先級,並以從大到小的順序排序
				double *rand;//優先級
				int *rand_ind;//排序後的優先級索引
				rand = (double *)malloc(sizeof(double)*len);
				rand_ind = (int *)malloc(sizeof(int)*len);
				for (int k = 0; k < len; k++)
				{
					rand[k] = w[i*width + j][k] / sd[i*width + j][k];
					rand_ind[k] = k;
				}
				for (int k = 1; k< len; k++)
				{
					for (int l = 0; l < k; l++)
					{
						if (rand[k] > rand[l])
						{
							double rand_tmp = rand[k];
							rand[k] = rand[l];
							rand[l] = rand_tmp;

							double rand_ind_tmp = rand_ind[k];
							rand_ind[k] = rand_ind[l];
							rand_ind[l] = rand_ind_tmp;

						}
					}
				}
				double sum = 0;
				int B;
				for (int k = 0; k < len; k++)
				{
					int temp = rand_ind[k];
					sum += w[i*width + j][temp];
					if (sum>threold)
					{
						B = k;
						break;
					}
				}
				match = 0;
				back->imageData[i*width + j] = 0;
				//求背景模型
				for (int k = 0; k <= B; k++)
				{
					int temp = rand_ind[k];
					back->imageData[i*width + j] += w[i*width + j][temp] * mean[i*width + j][temp];
				}
				/*
				//求前景圖像
				for (int k = 0; k <= B; k++)
				{
				int temp = rand_ind[k];
				if (fabs((uchar)img_gray->imageData[i*width + j] - mean[i*width + j][temp]) < D*sd[i*width + j][temp])
				{
				match = 1;
				break;
				}
				}
				if (match == 1) fore->imageData[i*width + j] = 0;
				else fore->imageData[i*width + j] = (uchar)img_gray->imageData[i*width + j];
				*/
				free(rand);
				free(rand_ind);
			}
		}

		cvAbsDiff(img_gray, back, fore);
		cvCanny(img_gray, img_cny, 40, 100,3);
		cvShowImage("canny", img_cny);
		cvAnd(img_cny, fore, fore);
		
		cvDilate(fore, fore, NULL, 3);
		cvErode(fore, fore, NULL, 3);
		cvThreshold(fore, fore, 55, 255, CV_THRESH_BINARY);
		cvWriteFrame(writer, fore);

		cvShowImage("fore", fore);
		cvShowImage("back", back);
		cvShowImage("src", img);
		char s = cvWaitKey(1);
		if (s == 27) break;
	}
	cvDestroyAllWindows();
	cvReleaseCapture(&capture);
	cvReleaseImage(&img_gray);
	cvReleaseImage(&fore);
	cvReleaseImage(&back);
	cvReleaseImage(&img_cny);
	cvReleaseVideoWriter(&writer);
	return (0);
}

原圖: 背景:前景目標:



總的來說,高斯混合算法在使用形態學處理後還是可以的,但是訓練過程比較滿,實時性不好。
如果使用連通域處理,用多邊形擬合的話,效果還是可以的。




發佈了56 篇原創文章 · 獲贊 72 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章