Python語言OpenCV開發之目標跟蹤
前言
目標跟蹤是對攝像頭視訊中的移動目標進行定位的過程,對視訊的處理分析也越來越成為計算機視覺的主流,而本質上視訊是由一幀幀的影象組成,所以視訊處理最終還是要歸結於影象處理。關於視訊幀如何獲取的,在GUI特性那一章節已經說過,這裡不再講述。
正文
1、 基本的運動檢測
為了檢測視訊中的目標物體,首要任務就是識別視訊幀中耳釘那些可能包含移動目標的區域。有不少實現視訊目標檢測的辦法;例如,當檢測所有移動的目標時,幀之間的差異會變得有用。當檢測視訊中移動的手時,基於面板顏色的均值漂移就是最好的解決方案。當知道跟蹤物件的一方面時,模板匹配會是不做的選擇。下面分析一個簡單的例子:
import cv2 import numpy as np camera = cv2.VideoCapture(0) es = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10,10)) kernel = np.ones((5,5),np.uint8) background = None while (True): ret, frame = camera.read() if background is None:# 第一幀作為背景輸入 background = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 先將幀轉換為灰階 background = cv2.GaussianBlur(background, (21, 21), 0)# 再進行一次模糊處理(平滑) continue gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 先將幀轉換為灰階 gray_frame = cv2.GaussianBlur(gray_frame, (21, 21), 0)# 再進行一次模糊處理(平滑) diff = cv2.absdiff(background, gray_frame) # 計算與背景的差異,並得到一個差分圖 diff = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1]# 應用閾值得到一副黑白圖, diff = cv2.dilate(diff, es, iterations = 2)# 膨脹(dilate)影象,從而對孔(hole)和缺陷(imperfection)進行歸一化處理 image, cnts, hierarchy = cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 計算一幅圖中目標的輪廓 for c in cnts: if cv2.contourArea(c) < 1500: continue (x, y, w, h) = cv2.boundingRect(c)# 計算矩形的邊界框 cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 255, 0), 2) cv2.imshow("contours", frame) cv2.imshow("dif", diff) if cv2.waitKey(int(1000 / 12)) & 0xff == ord("q"): break cv2.destroyAllWindows() camera.release()
補充說明:進行模糊處理的原因:每個輸入的視訊丟回自然震動、光照變化或者攝像頭本身等原因而產生噪聲,對噪聲進行平滑是為了避免在運動和跟蹤時將噪聲檢測出來。當然這裡是使用的OpenCV自帶的UI控制元件,如果想用到工程專案中,顯示在需要的地方,可以使用PyQt結合Matplotlib將畫面完美的顯示出來。感興趣的同學可以參考一下我的PyQT結合OpenCV的Chat中的文章:http://gitbook.cn/gitchat/activity/5a433b3ffee1cd074a5cef06
效果如下:
對於這個簡單的技術,其結果還算可以,但是也是有很大的缺點的,最明顯的問題是需要通過提前設定‘預設’幀作為背景。在一些情況下,由於光照變化頻繁,這種處理方法就顯得相當不靈活。
2、 背景分割器
在OpenCV 3中有兩種種背景分割器:K-Nearest(KNN),Mixture of Gaussians(MOG2);他們對應的演算法用來計算背景分割。OpenCV提供了一個稱為BackgroundSubtractor的類,在分割前景和背景時很方便,它是一個功能很全的類,不僅執行背景分割,而且能夠提高背景檢測的效果,並提供將分類結果儲存到檔案的功能。
2.1 BackgroundSubtractorMOG2
這是個以高斯混合模型為基礎的背景/前景分割演算法,這個演算法的一個特點是它為每一個畫素選擇一個合適數目的高斯分佈。這樣就會對由於亮度等發生變化引起的場景變化產生更好的適應。首先需要建立一個背景物件。但在這裡可以選擇是否檢測陰影。如果 detectShadows = T rue(預設值),它就會檢測並將物體標記出來,但是這樣做會降低處理速度。
下面看一個使用BackgroundSubtractorMOG2的簡單的例子:
import numpy as np
import cv2
cap = cv2.VideoCapture(0)
mog = cv2.createBackgroundSubtractorMOG2()
while(1):
ret, frame = cap.read()
fgmask = mog.apply(frame)
cv2.imshow('frame', fgmask)
if cv2.waitKey(int(1000 / 12)) & 0xff == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
你會發現在視訊中不動的物體會慢慢變成背景(黑色),運動的物體會變成前景(白色)。
效果如下:
2.2 BackgroundSubtractorKNN
下面看一下BackgroundSubtractorKNN來實現的運動檢測:
import cv2
import numpy as np
bs = cv2.createBackgroundSubtractorKNN(detectShadows=True)
camera = cv2.VideoCapture("shipin.flv")
while(1):
ret, frame = camera.read()
fgmask = bs.apply(frame)# 計算獲得前景掩碼
th = cv2.threshold(fgmask.copy(), 244, 255, cv2.THRESH_BINARY)[1]# 設定閾值
dilated = cv2.dilate(th, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3)), iterations=2)# 識別目標
image, contours, hier = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
if cv2.contourArea(c) > 1600:
(x, y, w, h) = cv2.boundingRect(c)# 檢測輪廓
cv2.rectangle(frame, (x,y), (x+w,y+h), (255,255,0), 2)# 繪製檢測結果
cv2.imshow('mog', fgmask)
cv2.imshow('thresh', th)
cv2.imshow('detection', frame)
if cv2.waitKey(int(1000 / 12)) & 0xff == ord("q"):
break
camera.release()
cv2.destroyAllWindows()
說明:為了方便這裡使用了一段視訊檔案代替了攝像頭,可以看出效果非常不錯。
3、 均值漂移和CAMShift
背景分割是一種有效的技術,但並不是進行視訊中目標跟蹤的唯一可用的技術。均值漂移(Meanshift)是一種目標跟蹤演算法,該演算法尋找概率函式離散樣本的最大密度,並重新計算在下一幀中的最大密度,給出了目標的移動方向。
3.1 均值漂移
import numpy as np
import cv2
cap = cv2.VideoCapture(0)
# cap = cv2.VideoCapture('surveillance_demo/768x576.avi')
# capture the first frame
ret,frame = cap.read()
# mark the ROI
r,h,c,w = 10, 200, 10, 200
# wrap in a tuple
track_window = (c,r,w,h)
# extract the ROI for tracking
roi = frame[r:r+h, c:c+w]# 提取感興趣區域ROI
# switch to HSV
hsv_roi = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)# 轉換為HSV色彩空間
# create a mask with upper and lower boundaries of colors you want to track
# # 下面是建立一個包含具有HSV值的ROI所有畫素的掩碼,HSV值在上界與下界之間
mask = cv2.inRange(hsv_roi, np.array((100., 30.,32.)), np.array((180.,120.,255.)))
# calculate histograms of roi
roi_hist = cv2.calcHist([hsv_roi],[0],mask,[180],[0,180])# 計算影象的色彩直方圖
cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)# 計算直方圖後,響應的值被歸一化到0-255範圍內。
# Setup the termination criteria, either 10 iteration or move by atleast 1 pt
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )# 指定均值漂移終止一系列計算行為的方式
# 這裡的停止條件為:均值漂移迭代10次後或者中心移動至少1個畫素時,均值漂移就停止計算中心漂移
# 第一個標誌(EPS或CRITERIA_COUNT)表示將使用兩個條件的任意一個(計數或‘epsilon’,意味著哪個條件最先達到就停止)
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# print dst
# apply meanshift to get the new location
ret, track_window = cv2.meanShift(dst, track_window, term_crit)
# Draw it on image
x,y,w,h = track_window
img2 = cv2.rectangle(frame, (x,y), (x+w,y+h), 255,2)
cv2.imshow('img2',img2)
k = cv2.waitKey(60) & 0xff
if k == 27:
break
else:
break
cv2.destroyAllWindows()
cap.release()
效果如下:移動物體的話,藍色框會跟著移動。
在你移動的過程中你會發現藍色框的大小是固定的,如果由遠及近的運動的話,固定的大小是不合適的,所有我們需要根據目標的大小和角度來對視窗的大小和角度進行修訂。這就需要使用CAMShift技術了。
3.2 CAMShift
import numpy as np
import cv2
cap = cv2.VideoCapture(0)
# take first frame of the video
ret,frame = cap.read()
# setup initial location of window
r,h,c,w = 10,200,10,200 # simply hardcoded the values
track_window = (c,r,w,h)
roi = frame[r:r+h, c:c+w]# 提取感興趣區域ROI
hsv_roi = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)# 轉換為HSV色彩空間
# 下面是建立一個包含具有HSV值的ROI所有畫素的掩碼,HSV值在上界與下界之間
mask = cv2.inRange(hsv_roi, np.array((100., 30.,32.)), np.array((180.,120.,255.)))
roi_hist = cv2.calcHist([hsv_roi],[0],mask,[180],[0,180])# 計算影象的色彩直方圖
cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)# 計算直方圖後,響應的值被歸一化到0-255範圍內。
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )# 指定均值漂移終止一系列計算行為的方式
# 這裡的停止條件為:均值漂移迭代10次後或者中心移動至少1個畫素時,均值漂移就停止計算中心漂移
# 第一個標誌(EPS或CRITERIA_COUNT)表示將使用兩個條件的任意一個(計數或‘epsilon’,意味著哪個條件最先達到就停止)
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)# 直方圖反向投影 得到一個矩陣(每個畫素以概率的形式表示)
ret, track_window = cv2.CamShift(dst, track_window, term_crit)
pts = cv2.boxPoints(ret)# 找到被旋轉矩形的頂點,而折線函式會在幀上繪製矩形的線段
pts = np.int0(pts)
img2 = cv2.polylines(frame,[pts],True, 255,2)
cv2.imshow('img2',img2)
k = cv2.waitKey(60) & 0xff
if k == 27:
break
else:
break
cv2.destroyAllWindows()
cap.release()
4、光流法追蹤運動物體
由於目標物件或者攝像機的移動造成的影象物件在連續兩幀影象中的移動被稱為光流。它是一個 2D 向量場,可以用來顯示一個點從第一幀影象到第二幀影象之間的移動。如下圖所示(https://en.wikipedia.org/wiki/Optical_flow):
圖中顯示了一個點在連續的5幀影象中的移動,箭頭表示光流場的向量,光流在很多領域中都很有用:如由運動重建結構,視訊壓縮等。
4.1 OpenCV 中的 Lucas-Kanade 光流
import numpy as np
import cv2
cap = cv2.VideoCapture('shipin.flv')
# params for ShiTomasi corner detection
feature_params = dict( maxCorners = 100,qualityLevel = 0.3,
minDistance = 7,blockSize = 7 )
# Parameters for lucas kanade optical flow
#maxLevel 為使用的影象金字塔層數
lk_params = dict( winSize = (15,15),
maxLevel = 2,
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
# Create some random colors
color = np.random.randint(0,255,(100,3))
# Take first frame and find corners in it
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
# Create a mask image for drawing purposes
mask = np.zeros_like(old_frame)
while(1):
ret,frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# calculate optical flow 能夠獲取點的新位置
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# Select good points
good_new = p1[st==1]
good_old = p0[st==1]
# draw the tracks
for i,(new,old) in enumerate(zip(good_new,good_old)):
a,b = new.ravel()
c,d = old.ravel()
mask = cv2.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv2.circle(frame,(a,b),5,color[i].tolist(),-1)
img = cv2.add(frame,mask)
cv2.imshow('frame',img)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
# Now update the previous frame and previous points
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
cv2.destroyAllWindows()
cap.release()
4.2 OpenCV 中的稠密光流
import cv2
import numpy as np
cap = cv2.VideoCapture("surveillance_demo/768x576.avi")
ret, frame1 = cap.read()
prvs = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
while(1):
ret, frame2 = cap.read()
next = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)
flow = cv2.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
#cv2.cartToPolar Calculates the magnitude and angle of 2D vectors.
mag, ang = cv2.cartToPolar(flow[...,0], flow[...,1])
hsv[...,0] = ang*180/np.pi/2
hsv[...,2] = cv2.normalize(mag,None,0,255,cv2.NORM_MINMAX)
rgb = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR)
cv2.imshow('frame2',rgb)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
elif k == ord('s'):
cv2.imwrite('opticalfb.png',frame2)
cv2.imwrite('opticalhsv.png',rgb)
prvs = next
cap.release()
cv2.destroyAllWindows()
結語:本節就到此,謝謝。