C++利用幀差法背景差分實現運動目標檢測(opencv)
幀差法、光流法、背景減除法
運動目標檢測是指在序列影象中檢測出變化區域並將運動目標從背景影象中提取出來。通常情況下,目標分類、跟蹤和行為理解等後處理過程僅僅考慮影象中對應於運動目標的畫素區域,因此運動目標的正確檢測與分割對於後期處理非常重要然而,由於場景的動態變化,如天氣、光照、陰影及雜亂背景干擾等的影響,使得運動目標的檢測與分割變得相當困難。根據攝像頭是否保持靜止,運動檢測分為靜態背景和運運動目標檢測是指在序列影象中檢測出變化區域並將運動目標從背景影象中提取出來。通常情況下,目標分類、跟蹤和行為理解等後處理過程僅僅考慮影象中對應於運動目標的畫素區域,因此運動目標的正確檢測與分割對於後期處理非常重要然而,由於場景的動態變化,如天氣、光照、陰影及雜亂背景干擾等的影響,使得運動目標的檢測與分割變得相當困難。根據攝像頭是否保持靜止,運動檢測分為靜態背景和運動背景兩類。大多數視訊監控系統是攝像頭固定的,因此靜態背景下運動目標檢測演算法受到廣泛關注,常用的方法有幀差法、光流法、背景減除法等。
(l)幀差法
幀差法是最為常用的運動目標檢測和分割方法之一,基本原理就是在影象序列相鄰兩幀或三幀間採用基於畫素的時間差分通過閉值化來提取出影象中的運動區域。首先,將相鄰幀影象對應畫素值相減得到差分影象,然後對差分影象二值化,在環境亮度變化不大的情況下,如果對應畫素值變化小於事先確定的閡值時,可以認為此處為背景畫素:如果影象區域的畫素值變化很大,可以認為這是由於影象中運動物體引起的,將這些區域標記為前景畫素,利用標記的畫素區域可以確定運動目標在影象中的位置。由於相鄰兩幀間的時間間隔非常短,用前一幀影象作為當前幀的背景模型具有較好的實時性,其背景不積累,且更新速度快、演算法簡單、計算量小。演算法的不足在於對環境噪聲較為敏感,閩值的選擇相當關鍵,選擇過低不足以抑制影象中的噪聲,過高則忽略了影象中有用的變化。對於比較大的、顏色一致的運動目標,有可能在目標內部產生空洞,無法完整地提取運動目標。
(2)光流法
光流法的主要任務就是計算光流場,即在適當的平滑性約束條件下,根據影象序列的時空梯度估算運動場,通過分析運動場的變化對運動目標和場景進行檢測與分割。通常有基於全域性光流場和特徵點光流場兩種方法。最經典的全域性光流場計算方法是L-K(Lueas&Kanada)法和H-S(Hom&Schunck)法,得到全域性光流場後通過比較運動目標與背景之間的運動差異對運動目標進行光流分割,缺點是計算量大。特徵點光流法通過特徵匹配求特徵點處的流速,具有計算量小、快速靈活的特點,但稀疏的光流場很難精確地提取運動目標的形狀。總的來說,光流法不需要預先知道場景的任何資訊,就能夠檢測到運動物件,可處理背景運動的情況,但噪聲、多光源、陰影和遮擋等因素會對光流場分佈的計算結果造成嚴重影響;而且光流法計算複雜,很難實現實時處理。
(3)背景減除法
背景減除法是一種有效的運動物件檢測演算法,基本思想是利用背景的引數模型來近似背景影象的畫素值,將當前幀與背景影象進行差分比較實現對運動區域的檢測,其中區別較大的畫素區域被認為是運動區域,而區別較小的畫素區域被認為是背景區域。背景減除法必須要有背景影象,並且背景影象必須是隨著光照或外部環境的變化而實時更新的,因此背景減除法的關鍵是背景建模及其更新。針對如何建立對於不同場景的動態變化均具有自適應性的背景模型,減少動態場景變化對運動分割的影響,研究人員已提出了許多背景建模演算法,但總的來講可以概括為非迴歸遞推和迴歸遞推兩類。非迴歸背景建模演算法是動態的利用從某一時刻開始到當前一段時間記憶體儲的新近觀測資料作為樣本來進行背景建模。非迴歸背景建模方法有最簡單的幀間差分、中值濾波方法、Toyama等利用快取的樣本畫素來估計背景模型的線性濾波器、Elg~al等提出的利用一段時間的歷史資料來計算背景畫素密度的非引數模型等。迴歸演算法在背景估計中無需維持儲存背景估計幀的緩衝區,它們是通過迴歸的方式基於輸入的每一幀影象來更新某個時刻的背景模型。這類方法包括廣泛應用的線性卡爾曼濾波法、Stauffe:與Grimson提出的混合高斯模型等
在這裡我們實現以下幀差法,即利用當前幀與下一幀之差來檢測運動目標
///運動物體檢測——幀差法
#include"opencv2/opencv.hpp"
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
#include <iostream>
using namespace std;
//運動物體檢測函式宣告
Mat MoveDetect(Mat temp, Mat frame);
int main()
{
VideoCapture video(0);
if(!video.isOpened())
return -1;
// VideoCapture video(0);//定義VideoCapture類video
// if (!video.isOpened()) //對video進行異常檢測
// {
// cout << "video open error!" << endl;
// return 0;
// }
while(1)
{
int frameCount = video.get(CV_CAP_PROP_FRAME_COUNT);//獲取幀數
double FPS = video.get(CV_CAP_PROP_FPS);//獲取FPS
Mat frame;//儲存幀
Mat temp;//儲存前一幀影象
Mat result;//儲存結果影象
for (int i = 0; i < frameCount; i++)
{
video >> frame;//讀幀進frame
imshow("frame", frame);
if (frame.empty())//對幀進行異常檢測
{
cout << "frame is empty!" << endl;
break;
}
if (i == 0)//如果為第一幀(temp還為空)
{
result = MoveDetect(frame, frame);//呼叫MoveDetect()進行運動物體檢測,返回值存入result
}
else//若不是第一幀(temp有值了)
{
result = MoveDetect(temp, frame);//呼叫MoveDetect()進行運動物體檢測,返回值存入result
}
imshow("result", result);
if (waitKey(1000.0 / FPS) == 27)//按原FPS顯示
{
cout << "ESC退出!" << endl;
break;
}
temp = frame.clone();
}
}
return 0;
}
Mat MoveDetect(Mat temp, Mat frame)
{
Mat result = frame.clone();
//1.將background和frame轉為灰度圖
Mat gray1, gray2;
cvtColor(temp, gray1, CV_BGR2GRAY);
cvtColor(frame, gray2, CV_BGR2GRAY);
//2.將background和frame做差
Mat diff;
absdiff(gray1, gray2, diff);
imshow("diff", diff);
//3.對差值圖diff_thresh進行閾值化處理
Mat diff_thresh;
threshold(diff, diff_thresh, 50, 255, CV_THRESH_BINARY);
imshow("diff_thresh", diff_thresh);
//4.腐蝕
Mat kernel_erode = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat kernel_dilate = getStructuringElement(MORPH_RECT, Size(18, 18));
erode(diff_thresh, diff_thresh, kernel_erode);
imshow("erode", diff_thresh);
//5.膨脹
dilate(diff_thresh, diff_thresh, kernel_dilate);
imshow("dilate", diff_thresh);
//6.查詢輪廓並繪製輪廓
vector<vector<Point> > contours;
findContours(diff_thresh, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
drawContours(result, contours, -1, Scalar(0, 0, 255), 2);//在result上繪製輪廓
//7.查詢正外接矩形
vector<Rect> boundRect(contours.size());
for (int i = 0; i < contours.size(); i++)
{
boundRect[i] = boundingRect(contours[i]);
rectangle(result, boundRect[i], Scalar(0, 255, 0), 2);//在result上繪製正外接矩形
}
return result;//返回result
}
背景差分實現運動目標檢測
//最簡單背景差分法
#include <stdio.h>
#include <cv.h>
#include <highgui.h>
int main( int argc, char** argv )
{
//宣告IplImage指標
IplImage* pFrame = NULL;
IplImage* pFrImg = NULL;
IplImage* pBkImg = NULL;
CvMat* pFrameMat = NULL;
CvMat* pFrMat = NULL;
CvMat* pBkMat = NULL;
CvCapture* pCapture = NULL;
int nFrmNum = 0;
//建立視窗
cvNamedWindow("video", 1);
cvNamedWindow("background",1);
cvNamedWindow("foreground",1);
//使視窗有序排列
cvMoveWindow("video", 30, 0);
cvMoveWindow("background", 360, 0);
cvMoveWindow("foreground", 690, 0);
pCapture = cvCaptureFromCAM(0);//從攝像頭讀入
//逐幀讀取視訊
while(pFrame = cvQueryFrame( pCapture ))
{
nFrmNum++;
//如果是第一幀,需要申請記憶體,並初始化
if(nFrmNum == 1)
{
pBkImg = cvCreateImage(cvSize(pFrame->width, pFrame->height),
IPL_DEPTH_8U,1);
pFrImg = cvCreateImage(cvSize(pFrame->width, pFrame->height),
IPL_DEPTH_8U,1);
pBkMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);
pFrMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);
pFrameMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);
//轉化成單通道影象再處理
cvCvtColor(pFrame, pBkImg, CV_BGR2GRAY);
cvCvtColor(pFrame, pFrImg, CV_BGR2GRAY);
cvConvert(pFrImg, pFrameMat);
cvConvert(pFrImg, pFrMat);
cvConvert(pFrImg, pBkMat);
}
else
{
cvCvtColor(pFrame, pFrImg, CV_BGR2GRAY);
cvConvert(pFrImg, pFrameMat);
//先做高斯濾波,以平滑影象
//cvSmooth(pFrameMat, pFrameMat, CV_GAUSSIAN, 3, 0, 0);
//當前幀跟背景圖相減
cvAbsDiff(pFrameMat, pBkMat, pFrMat);
//二值化前景圖
cvThreshold(pFrMat, pFrImg, 60, 255.0, CV_THRESH_BINARY);
//進行形態學濾波,去掉噪音
//cvErode(pFrImg, pFrImg, 0, 1);
//cvDilate(pFrImg, pFrImg, 0, 1);
//更新背景
cvRunningAvg(pFrameMat, pBkMat, 0.003, 0);
//將背景轉化為影象格式,用以顯示
cvConvert(pBkMat, pBkImg);
//顯示影象
// pFrImg->origin=1; // 根據網友意見整改
// pBkImg->origin=1;
//cvFlip(pBkImg,NULL,0); // 也可
cvShowImage("video", pFrame);
cvShowImage("background", pBkImg);
cvShowImage("foreground", pFrImg);
//如果有按鍵事件,則跳出迴圈
//此等待也為cvShowImage函式提供時間完成顯示
//等待時間可以根據CPU速度調整
if( cvWaitKey(2) >= 0 )
break;
} // end of if-else
} // end of while-loop
//銷燬視窗
cvDestroyWindow("video");
cvDestroyWindow("background");
cvDestroyWindow("foreground");
//釋放影象和矩陣
cvReleaseImage(&pFrImg);
cvReleaseImage(&pBkImg);
cvReleaseMat(&pFrameMat);
cvReleaseMat(&pFrMat);
cvReleaseMat(&pBkMat);
cvReleaseCapture(&pCapture);
return 0;
}