Python基于OpenCV的視頻圖像處理詳解
初識(shí)OpenCV
OpenCV是一個(gè)開源的,跨平臺(tái)的計(jì)算機(jī)視覺(jué)庫(kù),它采用優(yōu)化的C/C++代碼編寫,能夠充分利用多核處理器的優(yōu)勢(shì),提供了Python,Ruby,MATPLOAB以及其他高級(jí)語(yǔ)言接口。
OpenCV的設(shè)計(jì)目標(biāo)是執(zhí)行速度盡量快,主要面向?qū)崟r(shí)應(yīng)用,是視頻信號(hào)處理的主要工具之一,它封裝了豐富的視頻處理相關(guān)的工具包。視頻信號(hào)是重要的視覺(jué)信息來(lái)源,其中包含的信息要遠(yuǎn)大于圖像,對(duì)視頻的分析也是計(jì)算機(jī)視覺(jué)領(lǐng)域的重要研究方向之一。視頻在本質(zhì)上由連鎖的多幀圖像構(gòu)成,因此,視頻信號(hào)處理最終仍歸屬圖像處理范疇。但在視頻中,其時(shí)間維度也包含了許多有用的信息。
視頻讀寫處理
視頻一般有兩種來(lái)源,一種是從本地磁盤加載,另一種是從攝像頭等設(shè)備實(shí)時(shí)獲取。上述兩種視頻獲取方式分別對(duì)應(yīng)著OpenCV2的兩個(gè)函數(shù)CaptureFromFile()和CaptureFromCAM().在OpenCV3中則統(tǒng)一為一個(gè)用于處理視頻源載入的函數(shù)VideoCapture()。
下示例代碼展示了如何從本地載入一個(gè)視頻文件,然后將其轉(zhuǎn)化為灰度圖像連續(xù)幀并播放:
import cv2 cap=cv2.VideoCapture('D:\Image\Funny.mp4') while(cap.isOpened()): ret,frame=cap.read() #循環(huán)播放視頻中每幀圖像 gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #將原幀圖像轉(zhuǎn)化為灰度圖像 cv2.imshow('視頻捕捉',gray) #顯示處理后的圖像 if cv2.waitKey(1) & 0xFF==ord('q'): #按q鍵退出程序 break cap.release() #處理完成,釋放視頻捕捉 cv2.destroyAllWindows() #關(guān)閉窗口釋放資源
由于視頻過(guò)長(zhǎng),原視頻放在了主頁(yè)。
下列代碼則展示了如何從攝像頭獲取視頻:
#攝像頭獲取視頻 import cv2 cap=cv2.VideoCapture(1) #打開攝像頭獲取視頻 while(True): ret,frame=cap.read() #循環(huán)播放視頻中每幀圖像 gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #顯示結(jié)果 cv2.imshow('攝像頭拍照',gray) if cv2.waitKey(1) & 0xFF==ord('q'): break cap.release()#處理完成,釋放視頻捕捉 cv2.destroyAllWindows()
下列代碼展示了將攝像頭獲得的視頻寫入存儲(chǔ)文件的過(guò)程。其中,VideoWriter_fourcc類用于定義視頻文件的寫入格式,其參數(shù)有多種格式可選,如下:
- ①.VideoWriter_fourcc('T','4','2','0'),該選項(xiàng)為一個(gè)未壓縮的YUV顏色編碼類型,是4:2:0色度子采樣。改編碼有著很好的兼容性,但是會(huì)產(chǎn)生較大的文件,文件拓展名為:“.avi”
- ②.VideoWriter_fourcc('P','T','M','1'):該選項(xiàng)為“MPEG-1”編碼類型,文件拓展名為“.mpeg”
- ③.VideoWrite_fourcc('X','V','T','D'):該選項(xiàng)是“MPEG-4”編碼類型,如果希望得到的視頻大小為平均值,推薦使用該選項(xiàng),文件拓展名為:“.mp4”
- ④.VideWriter_fourcc('T','H','E','O'):該選項(xiàng)是“Ogg Vorbis”編碼類型,文件拓展名為“.ogv”。
- ⑤.VideWriter_foucc('F','L','V','T'):該選項(xiàng)是Flash編碼類型,文件拓展名是“.flv”。
定義好輸出視頻的格式后,用VideWriter類進(jìn)行寫入的時(shí)候,需要指定幀速率和幀大小,因此需要從另一個(gè)視頻文件復(fù)制視頻幀,這些屬性可以通過(guò)VideoCapture類的get()函數(shù)得到。
通過(guò)OpenCV獲取視頻并寫入文件的示例代碼:
#通過(guò)OpenCv獲取視頻并寫入 import cv2 cap2=cv2.VideoCapture('D:\Image\Funny.mp4') cap=cv2.VideoCapture(1)#打開攝像頭并獲取視頻 #對(duì)視頻幀率fps進(jìn)行賦值 fps=24 #過(guò)去視頻幀的大小 size=(int(cap2.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cap2.get(cv2.CAP_PROP_FRAME_HEIGHT))) fourcc=cv2.VideoWriter_fourcc('X','V','I','D') #MP4文件格式 out=cv2.VideoWriter('D:\Image\output.avi',fourcc,fps,size)#定義視頻文件寫入對(duì)象 while(cap.isOpened()): ret,frame=cap.read() if ret==True: ''' 獲取幀圖像并翻轉(zhuǎn),cv2.flip()的第二個(gè)參數(shù)表示翻轉(zhuǎn)方式:0代表垂直翻轉(zhuǎn),1代表水平翻轉(zhuǎn),-1代表水平垂直翻轉(zhuǎn) ''' frame=cv2.flip(frame,1) out.write(frame)#將翻轉(zhuǎn)后的幀圖像寫入文件 cv2.imshow('幀圖像處理',frame) if cv2.waitKey(1) & 0xFF==ord('q'): break else: break cap.release() out.release() cv2.destroyAllWindows()
運(yùn)動(dòng)軌跡標(biāo)記
運(yùn)動(dòng)捕捉(Motion Capture)技術(shù)可對(duì)運(yùn)動(dòng)物體或其特征點(diǎn)在三維空間中的運(yùn)動(dòng)軌跡進(jìn)行實(shí)時(shí),精確,定量地連續(xù)測(cè)量,跟蹤和記錄。運(yùn)動(dòng)軌跡則是從物體開始位置運(yùn)動(dòng)結(jié)束位置所經(jīng)過(guò)的路線組成的空間特征?;谟?jì)算機(jī)視覺(jué)圖像處理技術(shù)的運(yùn)動(dòng)捕捉方案在動(dòng)畫及游戲制作,仿真訓(xùn)練等領(lǐng)域有著廣泛的應(yīng)用。
光流(Optical Flow)是圖像亮度的運(yùn)動(dòng)信息描述,是空間運(yùn)動(dòng)物體在觀測(cè)成像面上的像素運(yùn)動(dòng)的瞬時(shí)速度,也是對(duì)視頻中運(yùn)動(dòng)對(duì)象軌跡進(jìn)行標(biāo)記的一種常用方法。光流由場(chǎng)景中前景目標(biāo)本身的運(yùn)動(dòng),攝像機(jī)的運(yùn)動(dòng),或者兩者的共同運(yùn)動(dòng)產(chǎn)生。當(dāng)人通過(guò)眼睛觀察運(yùn)動(dòng)物體時(shí),物體的景象在人眼的視網(wǎng)膜上形成一系列連續(xù)變化的圖像,這一系列連續(xù)變化的信息不斷“流過(guò)”視網(wǎng)膜,猶如光在平面中的流動(dòng),故稱之為“光流”。光流的概念在20世紀(jì)40年代首次被提出,該方法利用圖像序列中的像素在時(shí)域上的變化,相鄰幀之間的相關(guān)性來(lái)找到前一幀與當(dāng)前幀之間存在的對(duì)應(yīng)關(guān)系,從而計(jì)算出相鄰幀之間物體的運(yùn)動(dòng)信息。光流表達(dá)了圖像的變化,由于它包含了目標(biāo)運(yùn)動(dòng)的信息,因此可被觀察者用于確定目標(biāo)的運(yùn)動(dòng)情況。
在真實(shí)的三維空間中,描述物體運(yùn)動(dòng)狀態(tài)的物理概念是運(yùn)動(dòng)場(chǎng)(Motion Field)。三維空間中的每一個(gè)點(diǎn),經(jīng)過(guò)某段時(shí)間的運(yùn)動(dòng)之后會(huì)到達(dá)一個(gè)新的位置,而這個(gè)位移過(guò)程可以用運(yùn)動(dòng)場(chǎng)來(lái)描述,運(yùn)動(dòng)場(chǎng)的實(shí)質(zhì)上就是物體在三維真實(shí)世界的運(yùn)動(dòng),而時(shí)光流暢(Optical Flow Field)是指圖像中所有像素點(diǎn)構(gòu)成的一種二維瞬時(shí)速度場(chǎng)它是一個(gè)二維矢量場(chǎng)。
三維空間運(yùn)動(dòng)到二維平面的投影所形成的光流,當(dāng)描述部分像素時(shí),稱為稀疏光流,當(dāng)描述全部像素時(shí),則稱為稠密光流。
OpenCV實(shí)現(xiàn)了不少光流算法,其中,Lucas-Kanade(L-K)是一種廣泛使用的光流估計(jì)差分算法,它由布魯斯·D.盧卡斯(Bruce D.Lucas)和金出武雄(Takeo Kanade)提出,L-K算法假設(shè)光流在像素點(diǎn)的領(lǐng)域是一個(gè)常數(shù),然后使用最小二乘法對(duì)領(lǐng)域中的所有像素點(diǎn)求解基本的光流方程。通過(guò)結(jié)合幾個(gè)鄰近像素點(diǎn)的信息,L-K算法通常能夠消除光流方程中的多義性。而且與逐點(diǎn)計(jì)算的方法比,L-K算法對(duì)圖像噪聲不敏感。對(duì)于L-K算法,低速度,亮度不變以及區(qū)域一致性都是較強(qiáng)的假設(shè),但是這些條件并不容易滿足。當(dāng)運(yùn)動(dòng)速度過(guò)快時(shí),這種假設(shè)不成立,使得最終求出的光流值有較大的誤差。吉思——伊卡斯·布格(Jean—Yves Bouguent)提出了一種基于金字塔分層的算法,針對(duì)仿射變換的改進(jìn)L-K算法,該算法最明顯的優(yōu)勢(shì)在于,對(duì)于每一層的光流都會(huì)保持很小,但最終計(jì)算的光流可進(jìn)行累積,從而可有效地跟蹤特征點(diǎn)。L-K算法現(xiàn)已逐漸發(fā)展成為計(jì)算圖像稀疏光流的重要方法。通過(guò)金字塔L-K算法計(jì)算稀疏光流的示例代碼:
#L-K算法 import numpy as np import cv2 #設(shè)置L-K算法參數(shù) lk_params=dict(winSize=(15,15), #搜索窗口的大小 maxLevel=2,#最大金字塔層數(shù) #迭代算法終止條件(迭代次數(shù)或迭代閾值) criteria=(cv2.TERM_CRITERIA_EPS|cv2.TERM_CRITERIA_COUNT,10,0.03)) feature_params=dict(maxCorners=500, #設(shè)置最多返回的關(guān)鍵點(diǎn)(角點(diǎn))數(shù) qualityLevel=0.3, #角點(diǎn)閾值:反映一個(gè)像素點(diǎn)對(duì)強(qiáng)才算一個(gè)角點(diǎn) minDistance=7, #角點(diǎn)之間的最小像素點(diǎn)(歐氏距離) blockSize=7) #計(jì)算一個(gè)像素點(diǎn)是否為關(guān)鍵點(diǎn)時(shí)所取區(qū)域的大小 class App: #構(gòu)造方法,初始化一些參數(shù)和視頻路徑 def __init__(self,video_src): self.track_len=10 #光流標(biāo)記長(zhǎng)度 self.detect_interval=5#幀檢測(cè)間隔 #跟蹤點(diǎn)幾何初始化,self.tracks中值的格式時(shí):(前一幀角點(diǎn)) self.tracks=[] self.cam=cv2.VideoCapture(video_src) #視頻源 self.frame_idx=0 #幀序列號(hào)初始化 def run(self): #運(yùn)行光流方法 while True: ret,frame=self.cam.read() if ret ==True: frame_gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) vis=frame.copy() #檢測(cè)到角點(diǎn)后光流跟蹤 if len(self.tracks)>0: img0,img1=self.prev_gray,frame_gray p0=np.float32([tr[-1] for tr in self.tracks]).reshape(-1,1,2) #將前一幀的角點(diǎn)和當(dāng)前幀的圖像作為輸入的角點(diǎn)在當(dāng)前幀的位置 p1,st,err=cv2.calcOpticalFlowPyrLK(img0,img1,p0,None,**lk_params) #將當(dāng)前幀跟蹤到的角點(diǎn)以及圖像和前一幀的圖像作為輸入得到前一幀的角點(diǎn)檢測(cè) p0r,st,err=cv2.calcOpticalFlowPyrLK(img1,img0,p1,None,**lk_params) #得到角點(diǎn)回溯與前一幀實(shí)際角點(diǎn)的位置變化系 d=abs(p0-p0r).reshape(-1,2).max(-1) good=d<1 #判斷d的值是否小于1 new_tracks=[] #將跟蹤的點(diǎn)列為成功跟蹤點(diǎn) for tr,(x,y),good_flag in zip(self.tracks,p1.reshape(-1,2),good): if not good_flag: continue tr.append((x,y)) if (len(tr)>self.track_len): del tr[0] new_tracks.append(tr) cv2.circle(vis,(x,y),2,(0,255,0),-1) self.tracks=new_tracks #以前一幀角點(diǎn)為初始點(diǎn),以當(dāng)前幀跟蹤到的點(diǎn)為終點(diǎn)劃線,開始軌跡標(biāo)記 cv2.polylines(vis,[np.int32(tr) for tr in self.tracks],False,(0,255,0)) #每五幀檢測(cè)一次 if (self.frame_idx % self.detect_interval==0): mask=np.zeros_like(frame_gray)#初始化和視頻尺寸大小相同的圖像 mask[:]=255 #計(jì)算全部圖像的角點(diǎn) for x,y in [np.int32(tr[-1]) for tr in self.tracks]: cv2.circle(mask,(x,y),5,0,-1) #利用goodFeaturesToTrack進(jìn)行角點(diǎn)檢測(cè) p=cv2.goodFeaturesToTrack(frame_gray,mask=mask,**feature_params) if p is not None: for x,y in np.float32(p).reshape(-1,2): self.tracks.append([(x,y)]) #將檢測(cè)到的角點(diǎn)放在預(yù)跟蹤序列 self.frame_idx+=1 self.prev_gray=frame_gray cv2.imshow('Lucas-Kanade光流算法',vis) ch=0xFF & cv2.waitKey(1) if ch==ord('q'):#按q鍵退出 break def main(): import sys video_src='D:\Image\haha.mp4' App(video_src).run() #運(yùn)行主程序 cv2.destroyAllWindows() if __name__=='__main__': main()
由于視頻是本地文件,不能展示出來(lái),這里就直接呈現(xiàn)代碼;通過(guò)運(yùn)行結(jié)果程序可知,稠密光流算法是一種對(duì)圖像進(jìn)行逐點(diǎn)匹配的圖像配準(zhǔn)算法。不同于稀疏光流算法只針對(duì)圖像中若干個(gè)特征點(diǎn),稠密光流算法計(jì)算圖像上所有的點(diǎn)的偏移量,從而形成一個(gè)稠密的光流場(chǎng)。通過(guò)這個(gè)稠密的光流場(chǎng),可以進(jìn)行像素級(jí)別的圖像配準(zhǔn),因此,其配準(zhǔn)后的效果也明顯優(yōu)于稀疏光流算法配準(zhǔn)的效果。但是其副作用也非常明顯,由于要計(jì)算每個(gè)點(diǎn)的偏移量,其計(jì)算量也明顯大于稀疏光流算法。
CalOpticalFlowFraneback()算法
在OpenCV中,CalOpticalFlowFraneback()函數(shù)利用Gunnar Farneback算法進(jìn)行全局性稠密光流算法,其參數(shù)說(shuō)明如下:
- (1)prevImg:輸入的8bit單通道前一幀圖像。
- (2)nextImg:輸入的8bit單通道當(dāng)前幀圖像。
- (3)pyr_scale:金字塔參數(shù),0.5為經(jīng)典參數(shù),每一層是下一層尺度的一般。
- (4)levels:金字塔的層數(shù)。
- (5)winsize:窗口大小。
- (6)iterations:迭代次數(shù)。
- (7)poly_n:像素領(lǐng)域的大小,如果值比較大則表示圖像整體比較平滑。
- (8)poly_sigma:高斯標(biāo)準(zhǔn)差。
- (9)flags:可以為這些組合——OPTFLOW_USE_INITIAL_FLOW,OPTFLOW_FARNEBACK_GAUSSIAN,返回值為每一個(gè)像素點(diǎn)的位移。
基于稠密光流算法的運(yùn)動(dòng)軌跡標(biāo)記的代碼:
#基于稠密光流算法軌跡標(biāo)記 from numpy import * import cv2 #定義光流跟蹤標(biāo)記函數(shù) def draw_flow(im,flow,step=16): h,w=im.shape[:2] y,x=mgrid[step/2:h:step,step/2:w:step].reshape(2,-1) fx,fy=flow[y.astype(int),x.astype(int)].T #創(chuàng)建標(biāo)記線條端點(diǎn) lines=vstack([x,y,x+fx,y+fy]).T.reshape(-1,2,2) lines=int32(lines) #創(chuàng)建圖像和進(jìn)行線條標(biāo)記 vis=cv2.cvtColor(im,cv2.COLOR_GRAY2BGR) for (x1,y1),(x2,y2) in lines: cv2.line(vis,(x1,y1),(x2,y2),(0,255,0),1) cv2.circle(vis,(x1,y1),1,(0,255,0),-1)#畫圓 return vis cap=cv2.VideoCapture(1)#開啟攝像頭 #讀取視頻幀 ret,im=cap.read() #轉(zhuǎn)化為灰度圖像 prev_gray=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY) while True: #讀取視頻幀 ret,im=cap.read() #轉(zhuǎn)換為灰度圖像 gray=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY) #光流計(jì)算 flow=cv2.calcOpticalFlowFarneback(prev_gray,gray,None,0.5,3,15,3,5,1.2,0) prev_gray=gray #繪制光流軌跡 cv2.imshow('稠密光流算法',draw_flow(gray,flow)) if cv2.waitKey(1)&0xFF==ord('q'): #按q鍵結(jié)束 break im.release() cv2.destroyAllWindows()
運(yùn)動(dòng)檢測(cè)
運(yùn)動(dòng)檢測(cè)(Motion Detection)是計(jì)算機(jī)視覺(jué)和視頻處理中常用的預(yù)處理步驟,是指從視頻中識(shí)別發(fā)生變化或移動(dòng)的區(qū)域。運(yùn)動(dòng)檢測(cè)最常見(jiàn)的應(yīng)用場(chǎng)景是運(yùn)動(dòng)目標(biāo)檢測(cè),也就是對(duì)攝像頭記錄的視頻移動(dòng)目標(biāo)進(jìn)行定位到過(guò)程,有著非常廣泛的應(yīng)用。實(shí)時(shí)目標(biāo)檢測(cè)是許多計(jì)算機(jī)視覺(jué)的重要任務(wù),例如安全監(jiān)控,增強(qiáng)現(xiàn)實(shí)應(yīng)用,基于對(duì)象的視頻壓縮,基于感知的用戶界面及輔助駕駛等。
運(yùn)動(dòng)目標(biāo)檢測(cè)算法根據(jù)目標(biāo)與攝像機(jī)之間的關(guān)系可以分為靜態(tài)背景下的運(yùn)動(dòng)目標(biāo)檢測(cè)和動(dòng)態(tài)背景下的運(yùn)動(dòng)目標(biāo)檢測(cè)。靜態(tài)背景下的運(yùn)動(dòng)目標(biāo)檢測(cè),就是從序列圖像中將實(shí)際的的變化區(qū)域與背景分開。在背景靜止的大前提下進(jìn)行運(yùn)動(dòng)目標(biāo)檢測(cè)的方法有很多,大多側(cè)重于背景擾動(dòng)小噪聲的消除,如背景差分法(Background Difference Nethod ,BDM),幀間差分法(Inter-Frame Difference Method,IFDM),光流法,高斯混合模型(Gaussion Mixed Model,GMM),碼本(Codebook),自組織背景減除(Self-Organizing Background Subtraction,SOBS),視覺(jué)背景提取(Visual Background Extractor,VIBE)以及這些方法的變種,如三幀差分法,五幀差分法,或者這些方法的結(jié)合。動(dòng)態(tài)背景下的運(yùn)動(dòng)目標(biāo)檢測(cè),相對(duì)于靜態(tài)背景而言,算法的思路有所不同,一般更側(cè)重于匹配,需要進(jìn)行圖像的全局運(yùn)動(dòng)估計(jì)與補(bǔ)償,因?yàn)樵谀繕?biāo)和背景同時(shí)運(yùn)動(dòng)的情況下,無(wú)法簡(jiǎn)單的根據(jù)運(yùn)動(dòng)來(lái)判斷。動(dòng)態(tài)背景下的運(yùn)動(dòng)目標(biāo)檢測(cè)算法也有很多,例如塊匹配(Block Matching,BM)和光流估計(jì)(Optical Flow Estimation,OFE)等。
幀間差分法是一種常用的運(yùn)動(dòng)目標(biāo)檢測(cè)算法,其基本原理是觀測(cè)視頻圖像相鄰幀之間的細(xì)微變化來(lái)判斷物體是否在運(yùn)動(dòng)。攝像機(jī)采集的視頻序列具有連續(xù)性,如果場(chǎng)景內(nèi)沒(méi)有運(yùn)動(dòng)目標(biāo),則連續(xù)幀的變化很??;如果存在運(yùn)動(dòng)目標(biāo),由于場(chǎng)景中的目標(biāo)的運(yùn)動(dòng),目標(biāo)的影像在不同圖像幀之間的位置會(huì)不同,從而導(dǎo)致連續(xù)幀之間有顯著變化。該算法通過(guò)對(duì)時(shí)間上連續(xù)的兩幀或三幀圖像進(jìn)行差分運(yùn)算,對(duì)不同幀對(duì)應(yīng)的的像素點(diǎn)灰度值想減,來(lái)判斷灰度值的絕對(duì)值,當(dāng)絕對(duì)值超過(guò)一定閾值時(shí),則可判斷其為運(yùn)動(dòng)目標(biāo),從而實(shí)現(xiàn)目標(biāo)的檢測(cè)功能。
(二幀差分原理圖)
(三幀差分原理圖)
在OpebCV中用absdiff()函數(shù)實(shí)現(xiàn)。
三幀差分法的運(yùn)算過(guò)程如上圖所示。記視頻序列中第n+1幀、第n幀和第n-1幀的圖像分別為fn+1、fn和fn-1,三幀對(duì)應(yīng)像素點(diǎn)的灰度值記為fn+1(x,y)、fn(x,y)和fn-1(x,y),分別得到差分圖像Dn和Dn+1,對(duì)差分圖像Dn和Dn+1按照下式進(jìn)行計(jì)算,得到圖像Dn',然后再進(jìn)行閾值處理、連通性分析,最終提取出運(yùn)動(dòng)目標(biāo)。
在幀間差分法中,閾值T的選擇非常重要。如果閾值T選取的值太小,則無(wú)法抑制差分圖像中的噪聲;如果閾值T選取的值太大,又有可能掩蓋差分圖像中目標(biāo)的部分信息;而且固定的閾值T無(wú)法適應(yīng)場(chǎng)景中光線變化等情況。為此,有人提出了在判決條件中加入對(duì)整體光照敏感的添加項(xiàng)的方法,將判決條件修改為:
其中,NA為待檢測(cè)區(qū)域中像素的總數(shù)目,λ為光照的抑制系數(shù),A可設(shè)為整幀圖像。紅框中的添加項(xiàng)表達(dá)了整幀圖像中光照的變化情況。如果場(chǎng)景中的光照變化較小,則該項(xiàng)的值趨向于零;如果場(chǎng)景中的光照變化明顯,則該項(xiàng)的值明顯增大,導(dǎo)致上式右側(cè)判決條件自適應(yīng)地增大,最終的判決結(jié)果為沒(méi)有運(yùn)動(dòng)目標(biāo),這樣就有效地抑制了光線變化對(duì)運(yùn)動(dòng)目標(biāo)檢測(cè)結(jié)果的影響。
下圖中左圖是采用幀間差分法進(jìn)行運(yùn)動(dòng)目標(biāo)檢測(cè)的實(shí)驗(yàn)結(jié)果,(b)圖是采用兩幀差分法的檢測(cè)結(jié)果,(c)圖是采用三幀差分法的檢測(cè)結(jié)果。視頻序列中的目標(biāo)運(yùn)動(dòng)較快,在這種情況下,運(yùn)動(dòng)目標(biāo)在不同圖像幀內(nèi)的位置明顯不同,采用兩幀差分法檢測(cè)出的目標(biāo)會(huì)出現(xiàn)“重影”的現(xiàn)象,采用三幀差分法,可以檢測(cè)出較為完整的運(yùn)動(dòng)目標(biāo)。
綜上所述,幀間差分法的原理簡(jiǎn)單,計(jì)算量小,能夠快速檢測(cè)出場(chǎng)景中的運(yùn)動(dòng)目標(biāo)。但由實(shí)驗(yàn)結(jié)果可以看出,幀間差分法不能提取出對(duì)象的完整區(qū)域,只能提取出邊界。同時(shí)依賴于選擇的幀間時(shí)間間隔,對(duì)快速運(yùn)動(dòng)的物體,需要選擇較小的時(shí)間間隔,如果選擇不合適,當(dāng)物體在前后兩幀中沒(méi)有重疊時(shí),會(huì)被檢測(cè)為兩個(gè)分開的物體,而對(duì)慢速運(yùn)動(dòng)的物體,應(yīng)該選擇較大的時(shí)
間差,如果時(shí)間選擇不適當(dāng),當(dāng)物體在前后兩幀中幾乎完全重疊時(shí),則檢測(cè)不到物體。
因此幀間差分法通常不單獨(dú)用在目標(biāo)檢測(cè)中,往往與其它的檢測(cè)算法結(jié)合使用。常見(jiàn)的是結(jié)合背景差分法和幀間差分法的優(yōu)缺點(diǎn),使它們優(yōu)勢(shì)互補(bǔ),從而克服相互的弱點(diǎn),提高運(yùn)動(dòng)檢測(cè)的效果。例如在實(shí)際的場(chǎng)景中,即便是室內(nèi)環(huán)境,也存在光線等各種變化造成的干擾,或者人為造成的開燈等光線的強(qiáng)烈變化。所以在背景差分法的實(shí)現(xiàn)中,它的固定背景不能一成不變。如果不進(jìn)行重新初始化,錯(cuò)誤的檢測(cè)結(jié)果將隨時(shí)間不斷累計(jì),造成惡性循環(huán),從而造成監(jiān)控失效。
利用幀間差分法示例代碼:
#利用幀間差分法進(jìn)行目標(biāo)檢測(cè) import time import cv2 import argparse #用于解析參數(shù) import datetime #用于時(shí)間和日期相關(guān)處理 import imutils #用于圖像相關(guān)處理 import argparse #創(chuàng)建參數(shù)解析器并解析參數(shù) ap=argparse.ArgumentParser() ap.add_argument('-v','--video',help='path to the video file') ap.add_argument('-a','--min-area',type=int,default=500,help='minimum area size') args=vars(ap.parse_args(args=[])) #這里有兩種表達(dá)式,JUPYTER只能使用這種,表示默認(rèn)參數(shù),pycharme可以用另一種 #如果video參數(shù)為None,那么我們從攝像頭讀取數(shù)據(jù) if args.get('video',None) is None: camera=cv2.VideoCapture(1) if camera is None: print("請(qǐng)檢查攝像頭連接") exit() time.sleep(0.25) else: #讀取一個(gè)視頻 camera=cv2.VideoCapture('D:\Image\haha.mp4') ''' 初始化視頻流的第一幀。一般情況下,視頻第一幀不會(huì)包含運(yùn)動(dòng)而僅僅是背景 ''' firstFrame=None #遍歷視頻的每一幀 while True: ''' 獲取當(dāng)前視幀并初始化顯示文本,調(diào)用camera.read()將返回一個(gè)二元組,元組的第一個(gè)值是True和False,表明是否成功從緩沖中讀取幀圖像,元組的第二個(gè)值就是獲取的當(dāng)前幀圖像的值。 ''' (grabbed,frame)=camera.read() text='No Motion Detected' #如果不能獲取到幀,說(shuō)明到了視頻的結(jié)尾 if not grabbed: break frame=imutils.resize(frame,width=500) #調(diào)整幀圖像大小 gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #轉(zhuǎn)換為灰度圖像 gray=cv2.GaussianBlur(gray,(21,21),0)#高斯模糊處理 #如果第一幀是None,對(duì)其進(jìn)行初始化 if firstFrame is None: firstFrame=gray continue #將當(dāng)前幀和第一幀圖像對(duì)應(yīng)的像素點(diǎn)的灰度值相減并求絕對(duì)值來(lái)計(jì)算兩幀的不同 frameDelta=cv2.absdiff(firstFrame,gray) #對(duì)差分圖像進(jìn)行閾值處理來(lái)顯示圖像中像素點(diǎn)的灰度值有所變化的區(qū)域 thresh=cv2.threshold(frameDelta,25,255,cv2.THRESH_BINARY)[1] #擴(kuò)展閾值圖像填充空洞,然后找到閾值圖像中的輪廓 thresh=cv2.dilate(thresh,None,iterations=2) ''' cv2.findContours()函數(shù)返回3個(gè)值,第一個(gè)是所處理的圖像,第二個(gè)是輪廓,第三個(gè)是每個(gè)輪廓對(duì)應(yīng)的屬性 ''' contours,hierrarchy=cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #老版返回3個(gè)接受參數(shù),新版返回兩個(gè); #遍歷輪廓 for c in contours: #過(guò)濾小的,不相關(guān)的輪廓,如果輪廓面積大于min_area,則在前景和移動(dòng)區(qū)域畫邊框 if cv2.contourArea(c)<args['min_area']: continue #計(jì)算輪廓的邊界框,在當(dāng)前幀中畫出該框并更新相應(yīng)文本 (x,y,w,h)=cv2.boundingRect(c) cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2) text='Motion Detected' #在當(dāng)前幀上寫文本及時(shí)間戳 cv2.putText(frame,'Room Status:{}'.format(text),(10,20),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,255),2) cv2.putText(frame,datetime.datetime.now().strftime('%A %%d %B %Y %I:%M:%S%p'),(10,frame.shape[0]-10),cv2.FONT_HERSHEY_SIMPLEX,0.35,(0,0,255),1) #顯示當(dāng)前幀并記錄用戶是否按了鍵 cv2.imshow('視頻監(jiān)控演示',frame) cv2.imshow('閾值輪廓圖像',thresh) cv2.imshow('幀差分圖像',frameDelta) key=cv2.waitKey(1) if key==ord('q'): break #跳出循環(huán) #釋放攝像機(jī)資源并關(guān)閉打開的窗口 camera.release() cv2.destroyAllWindows()
由于視頻不太容易上傳,這里就使用截屏作為結(jié)果展示。
運(yùn)動(dòng)方向檢測(cè)
在某些應(yīng)用場(chǎng)合,檢測(cè)出運(yùn)動(dòng)的物體之后,我們還需知道物體的運(yùn)動(dòng)方向,判斷其是否進(jìn)入或離開檢測(cè)區(qū)域。對(duì)于運(yùn)動(dòng)方向的檢測(cè),一般通過(guò)檢測(cè)圖像的光流場(chǎng)估算圖像的運(yùn)動(dòng)場(chǎng)來(lái)實(shí)現(xiàn)。根據(jù)傳統(tǒng)估算方法,需要對(duì)圖像中的每一個(gè)像素進(jìn)行計(jì)算,算出圖像每一點(diǎn)的運(yùn)動(dòng)場(chǎng),然后得到整幅圖像的運(yùn)動(dòng)場(chǎng)。
檢測(cè)物體的運(yùn)動(dòng)方向,理論上也可以使在幀間差分法的基礎(chǔ)上通過(guò)計(jì)算幀圖像輪廓中點(diǎn)的變化來(lái)實(shí)現(xiàn),但因每次檢測(cè)出的輪廓數(shù)量不穩(wěn)定,所以該方式會(huì)使得誤差不可控。不過(guò),OpenCv中的goodFeaturesToTrack()函數(shù)可用于獲取圖像最大特征只的角點(diǎn)。我們可以此為契機(jī)重新設(shè)計(jì)物體運(yùn)動(dòng)方向檢測(cè)算法,步驟如下:
- (1) 對(duì)相鄰兩幀圖像所有像素點(diǎn)通過(guò)absdiff()函數(shù)進(jìn)行差分運(yùn)算得到差分圖像。
- (2) 將差分圖像轉(zhuǎn)化成灰度圖像并進(jìn)行二值化處理。
- (3) 利用goodFeaturesToTrack()函數(shù)獲得最大特征值的角點(diǎn)。
- (4) 計(jì)算角點(diǎn)的平均特征值,寫入隊(duì)列。
- (5) 維護(hù)一個(gè)長(zhǎng)度為10的隊(duì)列,隊(duì)列滿時(shí)計(jì)算隊(duì)列中元素的增減情況,并以此來(lái)確定目標(biāo)的運(yùn)動(dòng)方向。
利用上述改進(jìn)算法進(jìn)行物體運(yùn)動(dòng)方向檢測(cè)的示例代碼:
#運(yùn)動(dòng)方向檢測(cè) import cv2 import numpy as np import queue #導(dǎo)入庫(kù)主要用于隊(duì)列處理 camera=cv2.VideoCapture(1) if camera is None: print('請(qǐng)檢查攝像頭鏈接') exit() width=int(camera.get(3)) height=int(camera.get(4)) #參數(shù)初始化 firstFrame=None lastDec=None firstThresh=None feature_params=dict(maxCorners=100, #設(shè)置最多返回的關(guān)鍵點(diǎn)數(shù) qualityLevel=0.3, #角點(diǎn)閾值:響應(yīng)最大值 minDistance=7, #角點(diǎn)之間最少像素點(diǎn)(歐氏距離) blockSize=7) #計(jì)算一個(gè)像素點(diǎn)是否為關(guān)鍵點(diǎn)時(shí)所取區(qū)域 #Lucas-Kanade 光流算法參數(shù)設(shè)置 lk_params=dict(winSize=(15,15),#搜索窗口的大小 maxLevel=2,#最大金字塔層數(shù) criteria=(cv2.TermCriteria_EPS|cv2.TERM_CRITERIA_COUNT,10,0.03)) color=np.random.randint(0,255,(100,3)) num=0 #隊(duì)列初始化 q_x=queue.Queue(maxsize=10) q_y=queue.Queue(maxsize=10) while True: #獲取視頻幀并轉(zhuǎn)化為灰度圖像 (grabbed,frame)=camera.read() gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) gray=cv2.GaussianBlur(gray,(21,21),0) if firstFrame is None: firstFrame=gray continue frameDelta=cv2.absdiff(firstFrame,gray) #對(duì)圖像進(jìn)行閾值二值化 thresh=cv2.threshold(frameDelta,25,255,cv2.THRESH_BINARY)[1] p0=cv2.goodFeaturesToTrack(thresh,mask=None,**feature_params) if p0 is not None: x_sum=0 y_sum=0 for i,old in enumerate(p0): x,y=old.ravel() x_sum+=x y_sum+=y x_avg=x_sum/len(p0) y_avg=y_sum/len(p0) if q_x.full(): qx_list=list(q_x.queue) key=0 diffx_sum=0 for item_x in qx_list: key+=1 if key<10: diff_x=item_x-qx_list[key] diffx_sum+=diff_x if diffx_sum<0 and x_avg<500:#表明隊(duì)列在增加 print('Left') cv2.putText(frame,'Left Motion Detected',(100,100),0,0.5,(0,0,255),2) else: print("Right") cv2.putText(frame,"Right Motion Detected",(300,100),0,0.5,(255,0,255),2) q_x.get() q_x.put(x_avg) cv2.putText(frame,str(x_avg),(300,100),0,0.5,(0,0,255),2) frame=cv2.circle(frame,(int(x_avg),int(y_avg)),5,color[i].tolist(),-1) cv2.imshow('運(yùn)動(dòng)方向檢測(cè)',frame) firstFrame=gray.copy() key=cv2.waitKey(1)&0xFF if key==ord('q'): break camera.release() cv2.destroyAllWindows()
以上就是Python基于OpenCV的視頻圖像處理詳解的詳細(xì)內(nèi)容,更多關(guān)于Python OpenCV視頻圖像處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Python實(shí)現(xiàn)七大排序算法的代碼實(shí)例
這篇文章主要介紹了使用Python實(shí)現(xiàn)七大排序算法的代碼實(shí)例,所謂排序,就是使一串記錄,按照其中的某個(gè)或某些關(guān)鍵字的大小,遞增或遞減的排列起來(lái)的操作,需要的朋友可以參考下2023-07-07配置python連接oracle讀取excel數(shù)據(jù)寫入數(shù)據(jù)庫(kù)的操作流程
這篇文章主要介紹了配置python連接oracle,讀取excel數(shù)據(jù)寫入數(shù)據(jù)庫(kù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03python3+openCV 獲取圖片中文本區(qū)域的最小外接矩形實(shí)例
這篇文章主要介紹了python3+openCV 獲取圖片中文本區(qū)域的最小外接矩形實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-06-06一起來(lái)學(xué)習(xí)Python的元組和列表
這篇文章主要為大家詳細(xì)介紹了Python元組和列表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03基于Python實(shí)現(xiàn)一個(gè)簡(jiǎn)單的學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了如何利用python實(shí)現(xiàn)簡(jiǎn)單的學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-12-12Python Sqlite3以字典形式返回查詢結(jié)果的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇Python Sqlite3以字典形式返回查詢結(jié)果的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10Python matplotlib學(xué)習(xí)筆記之坐標(biāo)軸范圍
這篇文章主要介紹了Python matplotlib學(xué)習(xí)筆記之坐標(biāo)軸范圍,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06