文章目錄
本篇開始學習視頻分析,視頻分析主要是在視頻中對目標進行跟蹤,上來就是Meanshift and Camshift算法。
Meanshift
Meanshift算法簡單介紹:任意選定的初始迭代點,畫一個半徑記爲H的藍色圓,將其內所有的點與圓心相連,組成一個向量集,然後對此向量集進行向量相加,相加的結果如黑色向量所示,其終點指向下圖所示的藍色點,則下一次迭代以該藍色點爲圓心,H爲半徑畫圓,然後求這個圓內以圓心爲起點所有向量的和。如此迭代下去,圓的中心點爲收斂於一個固定的點,也就是概率密度最大的地方。所以 均值漂移算法本質上是一種基於梯度的優化算法。
Meanshift的opencv實現:首先需要在視頻畫面中選取一個目標,畫出初始邊框,計算直方圖,然後通過直方圖反向投影,用於Meanshift計算。計算直方圖時,僅考慮Hue視角。爲防止光線較暗造成的錯誤,可以用cv.inRange()函數濾除。
- 函數原型: retval, window = cv.meanShift( probImage, window, criteria )
- probImage:概率分佈圖像,可以是目標直方圖的反向投影
- Window:初始搜索窗口,可以是使用Rect定義ROI
- Criteria:確定窗口搜索停止的準則,OpenCV實現該算法時定義了兩個停止條件:迭代次數達到設置的最大值;窗口中心的漂移值小於某個設定的限值。
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('slow.flv')
# take first frame of the video
ret,frame = cap.read()
# setup initial location of window
r,h,c,w = 250,90,400,125 # simply hardcoded the values
track_window = (c,r,w,h)
# set up the ROI for tracking
roi = frame[r:r+h, c:c+w]
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
# Setup the termination criteria, either 10 iteration or move by atleast 1 pt
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# apply meanshift to get the new location
ret, track_window = cv.meanShift(dst, track_window, term_crit)
# Draw it on image
x,y,w,h = track_window
img2 = cv.rectangle(frame, (x,y), (x+w,y+h), 255,2)
cv.imshow('img2',img2)
k = cv.waitKey(60) & 0xff
if k == 27:
break
else:
cv.imwrite(chr(k)+".jpg",img2)
else:
break
cv.destroyAllWindows()
cap.release()
Camshift
在上例中,對目標跟蹤的框帶下不會隨着目標的遠近大小的不同,而改變目標框的大小,這是不合理的,因此需要動態調整目標框的大小。
Camshift它是MeanShift算法的改進,稱爲連續自適應的MeanShift算法,CamShift算法的全稱是"Continuously Adaptive Mean-SHIFT",它的基本思想是視頻圖像的所有幀作MeanShift運算,並將上一幀的結果(即Search Window的中心和大小)作爲下一幀MeanShift算法的Search Window的初始值,如此迭代下去。
- 函數原型: retval, window = cv.CamShift( probImage, window, criteria )
- probImage:概率分佈圖像,可以是目標直方圖的反向投影
- Window:初始搜索窗口,可以是使用Rect定義ROI
- Criteria:確定窗口搜索停止的準則,OpenCV實現該算法時定義了兩個停止條件:迭代次數達到設置的最大值;窗口中心的漂移值小於某個設定的限值。
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('slow.flv')
# take first frame of the video
ret,frame = cap.read()
# setup initial location of window
r,h,c,w = 250,90,400,125 # simply hardcoded the values
track_window = (c,r,w,h)
# set up the ROI for tracking
roi = frame[r:r+h, c:c+w]
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
# Setup the termination criteria, either 10 iteration or move by atleast 1 pt
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# apply meanshift to get the new location
ret, track_window = cv.CamShift(dst, track_window, term_crit)
# Draw it on image
pts = cv.boxPoints(ret)
pts = np.int0(pts)
img2 = cv.polylines(frame,[pts],True, 255,2)
cv.imshow('img2',img2)
k = cv.waitKey(60) & 0xff
if k == 27:
break
else:
cv.imwrite(chr(k)+".jpg",img2)
else:
break
cv.destroyAllWindows()
cap.release()
光流Optical Flow
光流的概念是Gibson在1950年首先提出來的。它是空間運動物體在觀察成像平面上的像素運動的瞬時速度,是利用圖像序列中像素在時間域上的變化以及相鄰幀之間的相關性來找到上一幀跟當前幀之間存在的對應關係,從而計算出相鄰幀之間物體的運動信息的一種方法。一般而言,光流是由於場景中前景目標本身的移動、相機的運動,或者兩者的共同運動所產生的。
光流(Optical Flow)是一種研究圖像對齊的算法,一般包括兩大類:稀疏光流和稠密光流。顧名思義,稀疏光流就是研究圖像中稀疏點的光流,這些點一般是角點;稠密光流則是研究圖像中所有點的偏移量。
稀疏光流Lucas-Kanade Optical Flow
- 函數原型:
nextPts, status, err = cv.calcOpticalFlowPyrLK( prevImg, nextImg, prevPts, nextPts[, status[, err[, winSize[, maxLevel[, criteria[, flags[, minEigThreshold]]]]]]] ) - prevImg:第一幀8bit灰度圖像,或者是buildOpticalFlowPyramid創建的金字塔圖像
- nextImg:第二幀8bit灰度圖像,或者是與prevImg相同尺寸與類型的金字塔圖像
- prevPts:光流法需要找到的二維點的vector。點座標必須是單精度浮點數
- nextPts:包含輸入特徵在第二幅圖像中計算出的新位置的二維點(單精度浮點座標)的輸出vector。當使用OPTFLOW_USE_INITIAL_FLOW 標誌時,nextPts的vector必須與input的大小相同
- status:輸出狀態vector,類型:unsigned chars;如果找到了對應特徵的流,則將向量的每個元素設置爲1,否則置0
- err:誤差輸出vector。vector的每個元素被設置爲對應特徵的誤差,可以在flags參數中設置誤差度量的類型;如果沒有找到流,則未定義誤差(使用status參數來查找此類情況)
- winSize:每級金字塔的搜索窗口大小
- maxLevel:最大金字塔層次數,如果設置爲0,則不使用金字塔(單級);如果設置爲1,則使用兩個級別,等等。如果金字塔被傳遞到input,那麼算法使用的級別與金字塔同級別但不大於MaxLevel。
- criteria:指定迭代搜索算法的終止準則;在指定的最大迭代次數標準值criteria.maxCount之後,或者當搜索窗口移動小於criteria.epsilon
- flags:操作標誌,可選參數:
OPTFLOW_USE_INITIAL_FLOW:使用初始估計,存儲在nextPts中;如果未設置標誌,則將prevPts複製到nextPts並被視爲初始估計。
OPTFLOW_LK_GET_MIN_EIGENVALS:使用最小本徵值作爲誤差度量(見minEigThreshold描述);如果未設置標誌,則將原始周圍的一小部分和移動的點之間的 L1 距離除以窗口中的像素數,作爲誤差度量 - minEigThreshold:算法所計算的光流方程的2x2標準矩陣的最小本徵值(該矩陣稱爲[Bouguet00]中的空間梯度矩陣)÷ 窗口中的像素數。如果該值小於MinEigThreshold,則過濾掉相應的特徵,相應的流也不進行處理。因此可以移除不好的點並提升性能。
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('slow.flv')
# params for ShiTomasi corner detection
feature_params = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
# Parameters for lucas kanade optical flow
lk_params = dict( winSize = (15,15),
maxLevel = 2,
criteria = (cv.TERM_CRITERIA_EPS | cv.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 = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.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 = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# calculate optical flow
p1, st, err = cv.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 = cv.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv.circle(frame,(a,b),5,color[i].tolist(),-1)
img = cv.add(frame,mask)
cv.imshow('frame',img)
k = cv.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)
cv.destroyAllWindows()
cap.release()
稠密光流Dense Optical Flow
CalcOpticalFlowFarneback()函數是利用用Gunnar Farneback的算法計算全局性的稠密光流算法(即圖像上所有像素點的光流都計算出來),由於要計算圖像上所有點的光流,故計算耗時,速度慢。
- 函數原型:flow = cv.calcOpticalFlowFarneback( prev, next, flow, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags),參數含義與上一函數類似。
- prev:第一幀單通道8bit灰度圖像,
- next:第二幀8bit灰度圖像,或者是與prev相同尺寸與類型的金字塔圖像
- flow:輸出的光流圖像,與prev圖像尺寸相同,類型是CV_32FC2.
- pyr_scale:上下兩層金字塔圖像的尺寸比例, pyr_scale=0.5,表示每層減小一半
- levels:金字塔層數,包括初始圖像; levels=1 表示不產生金字塔圖像,只有原始圖像
- winsize:均值窗口大小,越大越能denoise並且能夠檢測快速移動目標,但會引起模糊運動區域
- iterations:每層金字塔的迭代次數
- poly_n:像素鄰域大小,一般爲5,7等,較大的值意味着圖像將以更平滑的表面進行近似處理產生更粗獷的算法和更模糊的運動場
- poly_sigma:高斯標註差,一般爲1-1.5,or poly_n=5, you can set poly_sigma=1.1, for poly_n=7, a good value would be poly_sigma=1.5.
- flags:計算方法。主要包括OPTFLOW_USE_INITIAL_FLOW和OPTFLOW_FARNEBACK_GAUSSIAN
import cv2 as cv
import numpy as np
cap = cv.VideoCapture("vtest.avi")
ret, frame1 = cap.read()
prvs = cv.cvtColor(frame1,cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
while(1):
ret, frame2 = cap.read()
next = cv.cvtColor(frame2,cv.COLOR_BGR2GRAY)
flow = cv.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
mag, ang = cv.cartToPolar(flow[...,0], flow[...,1])
hsv[...,0] = ang*180/np.pi/2
hsv[...,2] = cv.normalize(mag,None,0,255,cv.NORM_MINMAX)
bgr = cv.cvtColor(hsv,cv.COLOR_HSV2BGR)
cv.imshow('frame2',bgr)
k = cv.waitKey(30) & 0xff
if k == 27:
break
elif k == ord('s'):
cv.imwrite('opticalfb.png',frame2)
cv.imwrite('opticalhsv.png',bgr)
prvs = next
cap.release()
cv.destroyAllWindows()
背景減除法Background Subtraction
主要是從背景圖像中對目標進行跟蹤。
BackgroundSubtractorMOG
- 函數原型:createBackgroundSubtractorMOG ( int history = 200,int nmixtures = 5,double backgroundRatio = 0.7,double noiseSigma = 0 )
- history:用於訓練背景的幀數,
- nmixtures:高斯混合次數
- backgroundRatio:背景比率
- noiseSigma:背景噪聲強度, 0 表示自動計算
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('vtest.avi')
fgbg = cv.createBackgroundSubtractorMOG()
while(1):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
cv.imshow('frame',fgmask)
k = cv.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv.destroyAllWindows()
BackgroundSubtractorMOG2
-函數原型:createBackgroundSubtractorMOG2 ( int history = 500,double varThreshold = 16,bool detectShadows = true )
- history:用於訓練背景的幀數,默認爲500幀,如果不手動設置learningRate,history就被用於計算當前的learningRate,此時history越大,learningRate越小,背景更新越慢
- varThreshold:方差閾值,用於判斷當前像素是前景還是背景。一般默認16,如果光照變化明顯,如陽光下的水面,建議設爲25,36,具體去試一下也不是很麻煩,值越大,靈敏度越低
- detectShadows:true,,算法會檢測陰影並設置mask,這樣或造成速度減慢如果不需要,則設置爲 false
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('vtest.avi')
fgbg = cv.createBackgroundSubtractorMOG2()
while(1):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
cv.imshow('frame',fgmask)
k = cv.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv.destroyAllWindows()
BackgroundSubtractorGMG
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('vtest.avi')
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE,(3,3))
fgbg = cv.createBackgroundSubtractorGMG()
while(1):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
fgmask = cv.morphologyEx(fgmask, cv.MORPH_OPEN, kernel)
cv.imshow('frame',fgmask)
k = cv.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv.destroyAllWindows()
這篇更難學了,原理性的東西寫不了多少,只能總結一下用法了。