OpenCV4 實(shí)現(xiàn)背景分離的詳細(xì)步驟(背景減法模型)
定義:
背景分離,又稱背景減法模型。
背景分離(BS)是一種通過使用靜態(tài)相機(jī)來生成前景掩碼(即包含屬于場(chǎng)景中的移動(dòng)對(duì)象像素的二進(jìn)制圖像)的常用技術(shù)。
顧名思義,BS計(jì)算前景掩碼,在當(dāng)前幀與背景模型之間執(zhí)行減法運(yùn)算,其中包含場(chǎng)景的靜態(tài)部分,或者更一般而言,考慮到所觀察場(chǎng)景的特征,可以將其視為背景的所有內(nèi)容。
背景建模包括兩個(gè)主要步驟:
1. 背景初始化;
2. 背景更新。
初步,計(jì)算背景的初始模型,而在第二步中,更新模型以適應(yīng)場(chǎng)景中可能的變化。
OpenCV中三個(gè)背景分離的重要函數(shù)
BackgroundSubtractorMOG()
這是一個(gè)以混合高斯模型為基礎(chǔ)的前景/背景分割算法。
它使用 K(K=3 或 5)個(gè)高斯分布混合對(duì)背景像素進(jìn)行建模。使用這些顏色(在整個(gè)視頻中)存在時(shí)間的長(zhǎng)短作為混合的權(quán)重。背景的顏色一般持續(xù)的時(shí)間最長(zhǎng),而且更加靜止。
在 x,y平面上一個(gè)像素就是一個(gè)像素,沒有分布,但是背景建模是基于時(shí)間序列的,因此每一個(gè)像素點(diǎn)所在的位置在整個(gè)時(shí)間序列中就會(huì)有很多值,從而構(gòu)成一個(gè)分布
使用函數(shù)時(shí)先用函數(shù):CV2.createBackgroundSubtractorMOG() 創(chuàng)建一個(gè)背景對(duì)象。這個(gè)函數(shù)有些可選參數(shù),比如要進(jìn)行建模場(chǎng)景的時(shí)間長(zhǎng)度,高斯混合成分的數(shù)量,閾值等。將他們?nèi)吭O(shè)置為默認(rèn)值。然后在整個(gè)視頻中我們是需要使用backgroundsubtractor.apply() 就可得到前景的掩模了,移動(dòng)的物體會(huì)被標(biāo)記為白色,背景會(huì)被標(biāo)記為黑色的,前景的掩模就是白色的了。
不過目前這個(gè)方法已經(jīng)被棄用了,OpenCV中也沒有了相關(guān)函數(shù)的API。
BackgroundSubtractorMOG2
這個(gè)也是以高斯混合模型為基礎(chǔ)的背景/前景分割算法。這個(gè)算法的一個(gè)特點(diǎn)是它為每 一個(gè)像素選擇一個(gè)合適數(shù)目的高斯分布。(上一個(gè)方法中我們使用是 K 高斯分布),這樣就會(huì)對(duì)由于亮度等發(fā)生變化引起的場(chǎng)景變化產(chǎn)生更好的適應(yīng)。
和前面一樣我們需要?jiǎng)?chuàng)建一個(gè)背景對(duì)象。但在這里我們我們可以選擇是否檢測(cè)陰影。如果 detectShadows = True(默認(rèn)值),它就會(huì)檢測(cè)并將影子標(biāo)記出來,但是這樣做會(huì)降低處理速度。影子會(huì)被標(biāo)記為灰色。
BackgroundSubtractorMOG2算法的兩個(gè)改進(jìn)點(diǎn):
- -陰影檢測(cè)
- -速度快了一倍
BackgroundSubtractorGMG
此算法結(jié)合了靜態(tài)背景圖像估計(jì)和每個(gè)像素的貝葉斯分割。它使用前面很少的圖像(默認(rèn)為前 120 幀)進(jìn)行背景建模。使用了概率前景估計(jì)算法(使用貝葉斯估計(jì)鑒定前景)。這是一種自適應(yīng)的估計(jì),新觀察到的 對(duì)象比舊的對(duì)象具有更高的權(quán)重,從而對(duì)光照變化產(chǎn)生適應(yīng)。一些形態(tài)學(xué)操作 如開運(yùn)算閉運(yùn)算等被用來除去不需要的噪音,在前幾幀圖像中你會(huì)得到一個(gè)黑色窗口,對(duì)結(jié)果進(jìn)行形態(tài)學(xué)開運(yùn)算對(duì)與去除噪聲很有幫助。
不過同樣的,這個(gè)方法目前也是不再用了。
BackgroundSubtractorKNN
KNN作為大名鼎鼎的機(jī)器學(xué)習(xí)算法,其用在背景分離應(yīng)用中也是得心應(yīng)手,但是在此不對(duì)KNN作過多的解釋。
C++實(shí)現(xiàn):
#include "opencv2/imgcodecs.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/videoio.hpp" #include <opencv2/highgui.hpp> #include <opencv2/video.hpp> #include <iostream> #include <sstream> using namespace cv; using namespace std; const int HISTORY_NUM = 7;// 14;// 歷史信息幀數(shù) const int nKNN = 3;// KNN聚類后判斷為背景的閾值 const float defaultDist2Threshold = 20.0f;// 灰度聚類閾值 struct PixelHistory { unsigned char* gray;// 歷史灰度值 unsigned char* IsBG;// 對(duì)應(yīng)灰度值的前景/背景判斷,1代表判斷為背景,0代表判斷為前景 }; int main() { PixelHistory* framePixelHistory = NULL;// 記錄一幀圖像中每個(gè)像素點(diǎn)的歷史信息 cv::Mat frame, FGMask, FGMask_KNN; int keyboard = 0; int rows, cols; rows = cols = 0; bool InitFlag = false; int frameCnt = 0; int gray = 0; VideoCapture capture("768X576.avi"); Ptr<BackgroundSubtractorKNN> pBackgroundKnn = createBackgroundSubtractorKNN(); pBackgroundKnn->setHistory(200); pBackgroundKnn->setDist2Threshold(600); pBackgroundKnn->setShadowThreshold(0.5); while ((char)keyboard != 'q' && (char)keyboard != 27) { // 讀取當(dāng)前幀 if (!capture.read(frame)) exit(EXIT_FAILURE); cvtColor(frame, frame, COLOR_BGR2GRAY); if (!InitFlag) { // 初始化一些變量 rows = frame.rows; cols = frame.cols; FGMask.create(rows, cols, CV_8UC1);// 輸出圖像初始化 // framePixelHistory分配空間 framePixelHistory = (PixelHistory*)malloc(rows * cols * sizeof(PixelHistory)); for (int i = 0; i < rows * cols; i++) { framePixelHistory[i].gray = (unsigned char*)malloc(HISTORY_NUM * sizeof(unsigned char)); framePixelHistory[i].IsBG = (unsigned char*)malloc(HISTORY_NUM * sizeof(unsigned char)); memset(framePixelHistory[i].gray, 0, HISTORY_NUM * sizeof(unsigned char)); memset(framePixelHistory[i].IsBG, 0, HISTORY_NUM * sizeof(unsigned char)); } InitFlag = true; } if (InitFlag) { FGMask.setTo(Scalar(255)); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { gray = frame.at<unsigned char>(i, j); int fit = 0; int fit_bg = 0; // 比較確定前景/背景 for (int n = 0; n < HISTORY_NUM; n++) { if (fabs(gray - framePixelHistory[i * cols + j].gray[n]) < defaultDist2Threshold)// 灰度差別是否位于設(shè)定閾值內(nèi) { fit++; if (framePixelHistory[i * cols + j].IsBG[n])// 歷史信息對(duì)應(yīng)點(diǎn)之前被判斷為背景 { fit_bg++; } } } if (fit_bg >= nKNN)// 當(dāng)前點(diǎn)判斷為背景 { FGMask.at<unsigned char>(i, j) = 0; } // 更新歷史值 int index = frameCnt % HISTORY_NUM; framePixelHistory[i * cols + j].gray[index] = gray; framePixelHistory[i * cols + j].IsBG[index] = fit >= nKNN ? 1 : 0;// 當(dāng)前點(diǎn)作為背景點(diǎn)存入歷史信息 } } } pBackgroundKnn->apply(frame, FGMask_KNN); imshow("Frame", frame); imshow("FGMask", FGMask); imshow("FGMask_KNN", FGMask_KNN); keyboard = waitKey(30); frameCnt++; } capture.release(); return 0; }
python實(shí)現(xiàn):
import cv2 cap=cv2.VideoCapture('./768x576.avi') fgbg = cv2.createBackgroundSubtractorKNN() while (1): ret, frame = cap.read() fgmask = fgbg.apply(frame) cv2.imshow('frame', fgmask) k = cv2.waitKey(100) & 0xff if k == 27: break cap.release() cv2.destroyAllWindows()
利用圖像減法函數(shù)實(shí)現(xiàn)(python版本):
import cv2 import time """ 背景減法 """ cap = cv2.VideoCapture("./768x576.avi") _, first_frame = cap.read() first_gray = cv2.cvtColor(first_frame, cv2.COLOR_BGR2GRAY) first_gray = cv2.GaussianBlur(first_gray, (5, 5), 0) cv2.imshow("First frame", first_frame) cv2.imwrite('first_frame.jpg', first_frame) count_frame = 0 start = time.time() while True: count_frame += 1 _, frame = cap.read() gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray_frame = cv2.GaussianBlur(gray_frame, (5, 5), 0) difference = cv2.absdiff(first_gray, gray_frame) _, difference = cv2.threshold(difference, 25, 255, cv2.THRESH_BINARY) cv2.imshow("Frame", frame) cv2.imshow("difference", difference) key = cv2.waitKey(30) if key == 27: break end = time.time() print("time %.2f s" % (end-start)) print(count_frame) cap.release() cv2.destroyAllWindows()
到此這篇關(guān)于OpenCV4 實(shí)現(xiàn)背景分離、背景減法模型的文章就介紹到這了,更多相關(guān)OpenCV4 實(shí)現(xiàn)背景分離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
簡(jiǎn)述C語(yǔ)言中system()函數(shù)與vfork()函數(shù)的使用方法
這篇文章主要介紹了簡(jiǎn)述C語(yǔ)言中system()函數(shù)與vfork()函數(shù)的使用方法,是C語(yǔ)言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08C++?LeetCode1827題解最少操作使數(shù)組遞增
這篇文章主要為大家介紹了C++?LeetCode1827題解最少操作使數(shù)組遞增示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Matlab實(shí)現(xiàn)帶豎線散點(diǎn)的核密度圖的繪制
核密度估計(jì)是用于估計(jì)隨機(jī)變量概率密度函數(shù)的一種非參數(shù)方法。核密度圖不失為一種用來觀察連續(xù)型變量分布的有效方法。本文將用Matlab實(shí)現(xiàn)帶豎線散點(diǎn)的核密度圖的繪制,感興趣的可以了解一下2022-08-08C語(yǔ)言驅(qū)動(dòng)開發(fā)之內(nèi)核通過PEB獲取進(jìn)程參數(shù)
PEB結(jié)構(gòu)(Process Envirorment Block Structure)其中文名是進(jìn)程環(huán)境塊信息。本文將通過PEB實(shí)現(xiàn)獲取進(jìn)程參數(shù),感興趣的小伙伴可以了解一下2022-10-10