Python基於OpenCV的視頻圖像處理詳解

初識OpenCV

OpenCV是一個開源的,跨平臺的計算機視覺庫,它采用優化的C/C++代碼編寫,能夠充分利用多核處理器的優勢,提供瞭Python,Ruby,MATPLOAB以及其他高級語言接口。

OpenCV的設計目標是執行速度盡量快,主要面向實時應用,是視頻信號處理的主要工具之一,它封裝瞭豐富的視頻處理相關的工具包。視頻信號是重要的視覺信息來源,其中包含的信息要遠大於圖像,對視頻的分析也是計算機視覺領域的重要研究方向之一。視頻在本質上由連鎖的多幀圖像構成,因此,視頻信號處理最終仍歸屬圖像處理范疇。但在視頻中,其時間維度也包含瞭許多有用的信息。

視頻讀寫處理

視頻一般有兩種來源,一種是從本地磁盤加載,另一種是從攝像頭等設備實時獲取。上述兩種視頻獲取方式分別對應著OpenCV2的兩個函數CaptureFromFile()和CaptureFromCAM().在OpenCV3中則統一為一個用於處理視頻源載入的函數VideoCapture()。 

下示例代碼展示瞭如何從本地載入一個視頻文件,然後將其轉化為灰度圖像連續幀並播放:

import cv2
cap=cv2.VideoCapture('D:\Image\Funny.mp4')
while(cap.isOpened()):
    ret,frame=cap.read() #循環播放視頻中每幀圖像
    gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #將原幀圖像轉化為灰度圖像
    cv2.imshow('視頻捕捉',gray) #顯示處理後的圖像
    if cv2.waitKey(1) & 0xFF==ord('q'): #按q鍵退出程序
        break
cap.release() #處理完成,釋放視頻捕捉
cv2.destroyAllWindows() #關閉窗口釋放資源

 由於視頻過長,原視頻放在瞭主頁。

下列代碼則展示瞭如何從攝像頭獲取視頻:

#攝像頭獲取視頻
import cv2
cap=cv2.VideoCapture(1) #打開攝像頭獲取視頻
while(True):
    ret,frame=cap.read() #循環播放視頻中每幀圖像
    gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    #顯示結果
    cv2.imshow('攝像頭拍照',gray)
    if cv2.waitKey(1) & 0xFF==ord('q'):
        break
cap.release()#處理完成,釋放視頻捕捉
cv2.destroyAllWindows()

下列代碼展示瞭將攝像頭獲得的視頻寫入存儲文件的過程。其中,VideoWriter_fourcc類用於定義視頻文件的寫入格式,其參數有多種格式可選,如下:

  • ①.VideoWriter_fourcc('T','4','2','0'),該選項為一個未壓縮的YUV顏色編碼類型,是4:2:0色度子采樣。改編碼有著很好的兼容性,但是會產生較大的文件,文件拓展名為:“.avi”
  • ②.VideoWriter_fourcc('P','T','M','1'):該選項為“MPEG-1”編碼類型,文件拓展名為“.mpeg”
  • ③.VideoWrite_fourcc('X','V','T','D'):該選項是“MPEG-4”編碼類型,如果希望得到的視頻大小為平均值,推薦使用該選項,文件拓展名為:“.mp4”
  • ④.VideWriter_fourcc('T','H','E','O'):該選項是“Ogg Vorbis”編碼類型,文件拓展名為“.ogv”。
  • ⑤.VideWriter_foucc('F','L','V','T'):該選項是Flash編碼類型,文件拓展名是“.flv”。

定義好輸出視頻的格式後,用VideWriter類進行寫入的時候,需要指定幀速率和幀大小,因此需要從另一個視頻文件復制視頻幀,這些屬性可以通過VideoCapture類的get()函數得到。

通過OpenCV獲取視頻並寫入文件的示例代碼:

#通過OpenCv獲取視頻並寫入
import cv2
cap2=cv2.VideoCapture('D:\Image\Funny.mp4')
cap=cv2.VideoCapture(1)#打開攝像頭並獲取視頻
#對視頻幀率fps進行賦值
fps=24
#過去視頻幀的大小
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)#定義視頻文件寫入對象
while(cap.isOpened()):
    ret,frame=cap.read()
    if ret==True:
        '''
        獲取幀圖像並翻轉,cv2.flip()的第二個參數表示翻轉方式:0代表垂直翻轉,1代表水平翻轉,-1代表水平垂直翻轉
        '''
        frame=cv2.flip(frame,1)
        out.write(frame)#將翻轉後的幀圖像寫入文件
        cv2.imshow('幀圖像處理',frame)
        if cv2.waitKey(1) & 0xFF==ord('q'):
            break
    else:
        break
cap.release()
out.release()
cv2.destroyAllWindows()

運動軌跡標記

運動捕捉(Motion Capture)技術可對運動物體或其特征點在三維空間中的運動軌跡進行實時,精確,定量地連續測量,跟蹤和記錄。運動軌跡則是從物體開始位置運動結束位置所經過的路線組成的空間特征。基於計算機視覺圖像處理技術的運動捕捉方案在動畫及遊戲制作,仿真訓練等領域有著廣泛的應用。

光流(Optical Flow)是圖像亮度的運動信息描述,是空間運動物體在觀測成像面上的像素運動的瞬時速度,也是對視頻中運動對象軌跡進行標記的一種常用方法。光流由場景中前景目標本身的運動,攝像機的運動,或者兩者的共同運動產生。當人通過眼睛觀察運動物體時,物體的景象在人眼的視網膜上形成一系列連續變化的圖像,這一系列連續變化的信息不斷“流過”視網膜,猶如光在平面中的流動,故稱之為“光流”。光流的概念在20世紀40年代首次被提出,該方法利用圖像序列中的像素在時域上的變化,相鄰幀之間的相關性來找到前一幀與當前幀之間存在的對應關系,從而計算出相鄰幀之間物體的運動信息。光流表達瞭圖像的變化,由於它包含瞭目標運動的信息,因此可被觀察者用於確定目標的運動情況。

在真實的三維空間中,描述物體運動狀態的物理概念是運動場(Motion Field)。三維空間中的每一個點,經過某段時間的運動之後會到達一個新的位置,而這個位移過程可以用運動場來描述,運動場的實質上就是物體在三維真實世界的運動,而時光流暢(Optical Flow Field)是指圖像中所有像素點構成的一種二維瞬時速度場它是一個二維矢量場。

三維空間運動到二維平面的投影所形成的光流,當描述部分像素時,稱為稀疏光流,當描述全部像素時,則稱為稠密光流。

OpenCV實現瞭不少光流算法,其中,Lucas-Kanade(L-K)是一種廣泛使用的光流估計差分算法,它由佈魯斯·D.盧卡斯(Bruce D.Lucas)和金出武雄(Takeo Kanade)提出,L-K算法假設光流在像素點的領域是一個常數,然後使用最小二乘法對領域中的所有像素點求解基本的光流方程。通過結合幾個鄰近像素點的信息,L-K算法通常能夠消除光流方程中的多義性。而且與逐點計算的方法比,L-K算法對圖像噪聲不敏感。對於L-K算法,低速度,亮度不變以及區域一致性都是較強的假設,但是這些條件並不容易滿足。當運動速度過快時,這種假設不成立,使得最終求出的光流值有較大的誤差。吉思——伊卡斯·佈格(Jean—Yves Bouguent)提出瞭一種基於金字塔分層的算法,針對仿射變換的改進L-K算法,該算法最明顯的優勢在於,對於每一層的光流都會保持很小,但最終計算的光流可進行累積,從而可有效地跟蹤特征點。L-K算法現已逐漸發展成為計算圖像稀疏光流的重要方法。通過金字塔L-K算法計算稀疏光流的示例代碼:

#L-K算法
import numpy as np
import cv2
#設置L-K算法參數
lk_params=dict(winSize=(15,15), #搜索窗口的大小
                        maxLevel=2,#最大金字塔層數
                        #迭代算法終止條件(迭代次數或迭代閾值)
                        criteria=(cv2.TERM_CRITERIA_EPS|cv2.TERM_CRITERIA_COUNT,10,0.03))
feature_params=dict(maxCorners=500,   #設置最多返回的關鍵點(角點)數
                    qualityLevel=0.3,  #角點閾值:反映一個像素點對強才算一個角點
                    minDistance=7, #角點之間的最小像素點(歐氏距離)
                    blockSize=7) #計算一個像素點是否為關鍵點時所取區域的大小
class App:
    #構造方法,初始化一些參數和視頻路徑
    def __init__(self,video_src):
        self.track_len=10 #光流標記長度
        self.detect_interval=5#幀檢測間隔
        #跟蹤點幾何初始化,self.tracks中值的格式時:(前一幀角點)
        self.tracks=[]
        self.cam=cv2.VideoCapture(video_src) #視頻源
        self.frame_idx=0 #幀序列號初始化
    def run(self): #運行光流方法
        while True:
            ret,frame=self.cam.read()
            if ret ==True:
                frame_gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
                vis=frame.copy()
                #檢測到角點後光流跟蹤
                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)
                    #將前一幀的角點和當前幀的圖像作為輸入的角點在當前幀的位置
                    p1,st,err=cv2.calcOpticalFlowPyrLK(img0,img1,p0,None,**lk_params)
                    #將當前幀跟蹤到的角點以及圖像和前一幀的圖像作為輸入得到前一幀的角點檢測
                    p0r,st,err=cv2.calcOpticalFlowPyrLK(img1,img0,p1,None,**lk_params)
                    #得到角點回溯與前一幀實際角點的位置變化系
                    d=abs(p0-p0r).reshape(-1,2).max(-1)
                    good=d<1 #判斷d的值是否小於1
                    new_tracks=[]
                    #將跟蹤的點列為成功跟蹤點
                    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
                    #以前一幀角點為初始點,以當前幀跟蹤到的點為終點劃線,開始軌跡標記
                    cv2.polylines(vis,[np.int32(tr) for tr in self.tracks],False,(0,255,0))
                    #每五幀檢測一次
                if (self.frame_idx % self.detect_interval==0):
                    mask=np.zeros_like(frame_gray)#初始化和視頻尺寸大小相同的圖像
                    mask[:]=255 #計算全部圖像的角點
                    for x,y in [np.int32(tr[-1]) for tr in self.tracks]:
                        cv2.circle(mask,(x,y),5,0,-1)
            #利用goodFeaturesToTrack進行角點檢測
                    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)]) #將檢測到的角點放在預跟蹤序列
                    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() #運行主程序
    cv2.destroyAllWindows()
if __name__=='__main__':
    main() 

由於視頻是本地文件,不能展示出來,這裡就直接呈現代碼;通過運行結果程序可知,稠密光流算法是一種對圖像進行逐點匹配的圖像配準算法。不同於稀疏光流算法隻針對圖像中若幹個特征點,稠密光流算法計算圖像上所有的點的偏移量,從而形成一個稠密的光流場。通過這個稠密的光流場,可以進行像素級別的圖像配準,因此,其配準後的效果也明顯優於稀疏光流算法配準的效果。但是其副作用也非常明顯,由於要計算每個點的偏移量,其計算量也明顯大於稀疏光流算法。

CalOpticalFlowFraneback()算法

在OpenCV中,CalOpticalFlowFraneback()函數利用Gunnar Farneback算法進行全局性稠密光流算法,其參數說明如下:

  • (1)prevImg:輸入的8bit單通道前一幀圖像。
  • (2)nextImg:輸入的8bit單通道當前幀圖像。
  • (3)pyr_scale:金字塔參數,0.5為經典參數,每一層是下一層尺度的一般。
  • (4)levels:金字塔的層數。
  • (5)winsize:窗口大小。
  • (6)iterations:迭代次數。
  • (7)poly_n:像素領域的大小,如果值比較大則表示圖像整體比較平滑。
  • (8)poly_sigma:高斯標準差。
  • (9)flags:可以為這些組合——OPTFLOW_USE_INITIAL_FLOW,OPTFLOW_FARNEBACK_GAUSSIAN,返回值為每一個像素點的位移。

基於稠密光流算法的運動軌跡標記的代碼:

#基於稠密光流算法軌跡標記
from numpy import *
import cv2
#定義光流跟蹤標記函數
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
    #創建標記線條端點
    lines=vstack([x,y,x+fx,y+fy]).T.reshape(-1,2,2)
    lines=int32(lines)
    #創建圖像和進行線條標記
    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()
#轉化為灰度圖像
prev_gray=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
while True:
    #讀取視頻幀
    ret,im=cap.read()
    #轉換為灰度圖像
    gray=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
    #光流計算
    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鍵結束
        break
im.release()
cv2.destroyAllWindows()

運動檢測

運動檢測(Motion Detection)是計算機視覺和視頻處理中常用的預處理步驟,是指從視頻中識別發生變化或移動的區域。運動檢測最常見的應用場景是運動目標檢測,也就是對攝像頭記錄的視頻移動目標進行定位到過程,有著非常廣泛的應用。實時目標檢測是許多計算機視覺的重要任務,例如安全監控,增強現實應用,基於對象的視頻壓縮,基於感知的用戶界面及輔助駕駛等。

運動目標檢測算法根據目標與攝像機之間的關系可以分為靜態背景下的運動目標檢測和動態背景下的運動目標檢測。靜態背景下的運動目標檢測,就是從序列圖像中將實際的的變化區域與背景分開。在背景靜止的大前提下進行運動目標檢測的方法有很多,大多側重於背景擾動小噪聲的消除,如背景差分法(Background Difference Nethod ,BDM),幀間差分法(Inter-Frame Difference Method,IFDM),光流法,高斯混合模型(Gaussion Mixed Model,GMM),碼本(Codebook),自組織背景減除(Self-Organizing Background Subtraction,SOBS),視覺背景提取(Visual Background Extractor,VIBE)以及這些方法的變種,如三幀差分法,五幀差分法,或者這些方法的結合。動態背景下的運動目標檢測,相對於靜態背景而言,算法的思路有所不同,一般更側重於匹配,需要進行圖像的全局運動估計與補償,因為在目標和背景同時運動的情況下,無法簡單的根據運動來判斷。動態背景下的運動目標檢測算法也有很多,例如塊匹配(Block Matching,BM)和光流估計(Optical Flow Estimation,OFE)等。

幀間差分法是一種常用的運動目標檢測算法,其基本原理是觀測視頻圖像相鄰幀之間的細微變化來判斷物體是否在運動。攝像機采集的視頻序列具有連續性,如果場景內沒有運動目標,則連續幀的變化很小;如果存在運動目標,由於場景中的目標的運動,目標的影像在不同圖像幀之間的位置會不同,從而導致連續幀之間有顯著變化。該算法通過對時間上連續的兩幀或三幀圖像進行差分運算,對不同幀對應的的像素點灰度值想減,來判斷灰度值的絕對值,當絕對值超過一定閾值時,則可判斷其為運動目標,從而實現目標的檢測功能。

(二幀差分原理圖)

(三幀差分原理圖) 

 在OpebCV中用absdiff()函數實現。

三幀差分法的運算過程如上圖所示。記視頻序列中第n+1幀、第n幀和第n-1幀的圖像分別為fn+1、fn和fn-1,三幀對應像素點的灰度值記為fn+1(x,y)、fn(x,y)和fn-1(x,y),分別得到差分圖像Dn和Dn+1,對差分圖像Dn和Dn+1按照下式進行計算,得到圖像Dn',然後再進行閾值處理、連通性分析,最終提取出運動目標。

在幀間差分法中,閾值T的選擇非常重要。如果閾值T選取的值太小,則無法抑制差分圖像中的噪聲;如果閾值T選取的值太大,又有可能掩蓋差分圖像中目標的部分信息;而且固定的閾值T無法適應場景中光線變化等情況。為此,有人提出瞭在判決條件中加入對整體光照敏感的添加項的方法,將判決條件修改為:

其中,NA為待檢測區域中像素的總數目,λ為光照的抑制系數,A可設為整幀圖像。紅框中的添加項表達瞭整幀圖像中光照的變化情況。如果場景中的光照變化較小,則該項的值趨向於零;如果場景中的光照變化明顯,則該項的值明顯增大,導致上式右側判決條件自適應地增大,最終的判決結果為沒有運動目標,這樣就有效地抑制瞭光線變化對運動目標檢測結果的影響。

下圖中左圖是采用幀間差分法進行運動目標檢測的實驗結果,(b)圖是采用兩幀差分法的檢測結果,(c)圖是采用三幀差分法的檢測結果。視頻序列中的目標運動較快,在這種情況下,運動目標在不同圖像幀內的位置明顯不同,采用兩幀差分法檢測出的目標會出現“重影”的現象,采用三幀差分法,可以檢測出較為完整的運動目標

綜上所述,幀間差分法的原理簡單,計算量小,能夠快速檢測出場景中的運動目標。但由實驗結果可以看出,幀間差分法不能提取出對象的完整區域,隻能提取出邊界。同時依賴於選擇的幀間時間間隔,對快速運動的物體,需要選擇較小的時間間隔,如果選擇不合適,當物體在前後兩幀中沒有重疊時,會被檢測為兩個分開的物體,而對慢速運動的物體,應該選擇較大的時

間差,如果時間選擇不適當,當物體在前後兩幀中幾乎完全重疊時,則檢測不到物體。

因此幀間差分法通常不單獨用在目標檢測中,往往與其它的檢測算法結合使用。常見的是結合背景差分法和幀間差分法的優缺點,使它們優勢互補,從而克服相互的弱點,提高運動檢測的效果。例如在實際的場景中,即便是室內環境,也存在光線等各種變化造成的幹擾,或者人為造成的開燈等光線的強烈變化。所以在背景差分法的實現中,它的固定背景不能一成不變。如果不進行重新初始化,錯誤的檢測結果將隨時間不斷累計,造成惡性循環,從而造成監控失效

利用幀間差分法示例代碼:

#利用幀間差分法進行目標檢測
import time
import cv2
import argparse #用於解析參數
import datetime #用於時間和日期相關處理
import imutils #用於圖像相關處理
import argparse
#創建參數解析器並解析參數
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=[])) #這裡有兩種表達式,JUPYTER隻能使用這種,表示默認參數,pycharme可以用另一種
#如果video參數為None,那麼我們從攝像頭讀取數據
if args.get('video',None) is None:
        camera=cv2.VideoCapture(1)
        if camera is None:
            print("請檢查攝像頭連接")
            exit()
            time.sleep(0.25)
        else:
        #讀取一個視頻
            camera=cv2.VideoCapture('D:\Image\haha.mp4')
'''
初始化視頻流的第一幀。一般情況下,視頻第一幀不會包含運動而僅僅是背景
''' 
firstFrame=None
#遍歷視頻的每一幀
while True:
    '''
    獲取當前視幀並初始化顯示文本,調用camera.read()將返回一個二元組,元組的第一個值是True和False,表明是否成功從緩沖中讀取幀圖像,元組的第二個值就是獲取的當前幀圖像的值。
    '''
    (grabbed,frame)=camera.read()
    text='No Motion Detected'
    #如果不能獲取到幀,說明到瞭視頻的結尾
    if not grabbed:
        break
    frame=imutils.resize(frame,width=500) #調整幀圖像大小
    gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #轉換為灰度圖像
    gray=cv2.GaussianBlur(gray,(21,21),0)#高斯模糊處理
#如果第一幀是None,對其進行初始化
    if firstFrame is None:
        firstFrame=gray
        continue
#將當前幀和第一幀圖像對應的像素點的灰度值相減並求絕對值來計算兩幀的不同
    frameDelta=cv2.absdiff(firstFrame,gray)
#對差分圖像進行閾值處理來顯示圖像中像素點的灰度值有所變化的區域
    thresh=cv2.threshold(frameDelta,25,255,cv2.THRESH_BINARY)[1]
#擴展閾值圖像填充空洞,然後找到閾值圖像中的輪廓
    thresh=cv2.dilate(thresh,None,iterations=2)
    '''
cv2.findContours()函數返回3個值,第一個是所處理的圖像,第二個是輪廓,第三個是每個輪廓對應的屬性
'''
    contours,hierrarchy=cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #老版返回3個接受參數,新版返回兩個;
    #遍歷輪廓
    for c in contours:
    #過濾小的,不相關的輪廓,如果輪廓面積大於min_area,則在前景和移動區域畫邊框
        if cv2.contourArea(c)<args['min_area']:
            continue
    #計算輪廓的邊界框,在當前幀中畫出該框並更新相應文本
        (x,y,w,h)=cv2.boundingRect(c)
        cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
        text='Motion Detected'
#在當前幀上寫文本及時間戳
    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)
    #顯示當前幀並記錄用戶是否按瞭鍵
    cv2.imshow('視頻監控演示',frame)
    cv2.imshow('閾值輪廓圖像',thresh)
    cv2.imshow('幀差分圖像',frameDelta)
    key=cv2.waitKey(1)
    if key==ord('q'):
        break #跳出循環
#釋放攝像機資源並關閉打開的窗口
camera.release()
cv2.destroyAllWindows()

由於視頻不太容易上傳,這裡就使用截屏作為結果展示。

運動方向檢測

在某些應用場合,檢測出運動的物體之後,我們還需知道物體的運動方向,判斷其是否進入或離開檢測區域。對於運動方向的檢測,一般通過檢測圖像的光流場估算圖像的運動場來實現。根據傳統估算方法,需要對圖像中的每一個像素進行計算,算出圖像每一點的運動場,然後得到整幅圖像的運動場。

檢測物體的運動方向,理論上也可以使在幀間差分法的基礎上通過計算幀圖像輪廓中點的變化來實現,但因每次檢測出的輪廓數量不穩定,所以該方式會使得誤差不可控。不過,OpenCv中的goodFeaturesToTrack()函數可用於獲取圖像最大特征隻的角點。我們可以此為契機重新設計物體運動方向檢測算法,步驟如下:

  • (1) 對相鄰兩幀圖像所有像素點通過absdiff()函數進行差分運算得到差分圖像。
  • (2) 將差分圖像轉化成灰度圖像並進行二值化處理。 
  • (3) 利用goodFeaturesToTrack()函數獲得最大特征值的角點。
  • (4) 計算角點的平均特征值,寫入隊列。
  • (5) 維護一個長度為10的隊列,隊列滿時計算隊列中元素的增減情況,並以此來確定目標的運動方向。

利用上述改進算法進行物體運動方向檢測的示例代碼:

#運動方向檢測
import cv2
import numpy as np
import queue  #導入庫主要用於隊列處理
camera=cv2.VideoCapture(1)
if camera is None:
    print('請檢查攝像頭鏈接')
    exit()
width=int(camera.get(3))
height=int(camera.get(4))
 
#參數初始化
firstFrame=None
lastDec=None
firstThresh=None
feature_params=dict(maxCorners=100, #設置最多返回的關鍵點數
                    qualityLevel=0.3, #角點閾值:響應最大值
                    minDistance=7, #角點之間最少像素點(歐氏距離)
                    blockSize=7) #計算一個像素點是否為關鍵點時所取區域
#Lucas-Kanade 光流算法參數設置
lk_params=dict(winSize=(15,15),#搜索窗口的大小
                maxLevel=2,#最大金字塔層數
                criteria=(cv2.TermCriteria_EPS|cv2.TERM_CRITERIA_COUNT,10,0.03))
color=np.random.randint(0,255,(100,3))
num=0
#隊列初始化
q_x=queue.Queue(maxsize=10)
q_y=queue.Queue(maxsize=10)
while True:
    #獲取視頻幀並轉化為灰度圖像
    (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)
    #對圖像進行閾值二值化
    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:#表明隊列在增加
                    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('運動方向檢測',frame)
        firstFrame=gray.copy()
        key=cv2.waitKey(1)&0xFF
        if key==ord('q'):
            break
camera.release()
cv2.destroyAllWindows()

以上就是Python基於OpenCV的視頻圖像處理詳解的詳細內容,更多關於Python OpenCV視頻圖像處理的資料請關註WalkonNet其它相關文章!

推薦閱讀: