[OpenCV-Python] OpenCV 中視頻分析 部分 VI


部分 VI
視頻分析

OpenCV-Python 中文教程(搬運)目錄


39 Meanshift 和 和 Camshift


目標
  • 本節我們要學習使用 Meanshift 和 Camshift 算法在視頻中找到並跟蹤
目標對象
39.1 Meanshift
  Meanshift 算法的基本原理是和很簡單的。假設我們有一堆點(比如直方圖反向投影得到的點),和一個小的圓形窗口,我們要完成的任務就是將這個窗口移動到最大灰度密度處(或者是點最多的地方)。如下圖所示:

    Intuition behind meanshift
初始窗口是藍色的“C1”,它的圓心為藍色方框“C1_o”,而窗口中所有點質心卻是“C1_r”(小的藍色圓圈),很明顯圓心和點的質心沒有重合。所以移動圓心 C1_o 到質心 C1_r,這樣我們就得到了一個新的窗口。這時又可以找到新窗口內所有點的質心,大多數情況下還是不重合的,所以重復上面的操作:將新窗口的中心移動到新的質心。就這樣不停的迭代操作直到窗口的中心和其所包含點的質心重合為止(或者有一點小誤差)。按照這樣的操作我們的窗口最終會落在像素值(和)最大的地方。如上圖所示“C2”是窗口的最后位址,我們可以看出來這個窗口中的像素點最多。整個過程如下圖所示:
    Meanshift on static image
通常情況下我們要使用直方圖方向投影得到的圖像和目標對象的起始位置。
當目標對象的移動會反映到直方圖反向投影圖中。就這樣,meanshift 算法就把我們的窗口移動到圖像中灰度密度最大的區域了。


39.2 OpenCV 中的 Meanshift
  要在 OpenCV 中使用 Meanshift 算法首先我們要對目標對象進行設置,計算目標對象的直方圖,這樣在執行 meanshift 算法時我們就可以將目標對象反向投影到每一幀中去了。另外我們還需要提供窗口的起始位置。在這里我們值計算 H(Hue)通道的直方圖,同樣為了避免低亮度造成的影響,我們使用函數 cv2.inRange() 將低亮度的值忽略掉。

import numpy as np
import cv2

cap = cv2.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 =  cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv2.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)

# 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 )

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)

        # 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:
            cv2.imwrite(chr(k)+".jpg",img2)

    else:
        break

cv2.destroyAllWindows()
cap.release()

下面是我使用 meanshift 算法對一個視頻前三幀分析的結果:

    Meanshift result


39.3 Camshift
  你認真看上面的結果了嗎?這里面還有一個問題。我們的窗口的大小是固定的,而汽車由遠及近(在視覺上)是一個逐漸變大的過程,固定的窗口是不合適的。所以我們需要根據目標的大小和角度來對窗口的大小和角度進行修訂。

OpenCVLabs 為我們帶來的解決方案(1988 年):一個被叫做 CAMshift 的算法。
這個算法首先要使用 meanshift,meanshift 找到(並覆蓋)目標之后,再去調整窗口的大小,s = 2 \times \sqrt{\frac{M_{00}}{256}} 。它還會計算目標對象的最佳外接橢圓的角度,並以此調節窗口角度。然后使用更新后的窗口大小和角度來在原來的位置繼續進行 meanshift。重復這個過程知道達到需要的精度。

    Meanshift on static image

39.4 OpenCV 中的 Camshift
  與 Meanshift 基本一樣,但是返回的結果是一個帶旋轉角度的矩形(這是我們的結果),以及這個矩形的參數(被用到下一次迭代過程中)。下面是代碼:

import numpy as np
import cv2

cap = cv2.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 =  cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv2.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)

# 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 )

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)

        # apply meanshift to get the new location
        ret, track_window = cv2.CamShift(dst, track_window, term_crit)

        # Draw it on image
        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:
            cv2.imwrite(chr(k)+".jpg",img2)

    else:
        break

cv2.destroyAllWindows()
cap.release()

對三幀圖像分析的結果如下:

    Camshift result

練習
1. OpenCV 的官方示例中有一個 camshift 的交互式演示,搞定它吧!

# 待排版
#!/usr/bin/env python
'''
Camshift tracker
================
This is a demo that shows mean-shift based tracking
You select a color objects such as your face and it tracks it.
This reads from video camera (0 by default, or the camera number the user enters)
http://www.robinhewitt.com/research/track/camshift.html
Usage:
------
camshift.py [<video source>]
To initialize tracking, select the object with mouse
Keys:
-----
ESC - exit
b - toggle back-projected probability visualization
'''
import numpy as np
import cv2
# local module
import video
class App(object):
def __init__(self, video_src):
self.cam = video.create_capture(video_src)
ret, self.frame = self.cam.read()
cv2.namedWindow('camshift')
cv2.setMouseCallback('camshift', self.onmouse)
self.selection = None
self.drag_start = None
self.tracking_state = 0
self.show_backproj = False
def onmouse(self, event, x, y, flags, param):
x, y = np.int16([x, y])
if event == cv2.EVENT_LBUTTONDOWN:
self.drag_start = (x, y)
self.tracking_state = 0
# 官方示例中下面一行判斷有問題,作如下修改就可以了
if self.drag_start and event == cv2.EVENT_MOUSEMOVE:
# print x,y
if flags==cv2.EVENT_FLAG_LBUTTON:
# print 'ok'
h, w = self.frame.shape[:2]
xo, yo = self.drag_start
x0, y0 = np.maximum(0, np.minimum([xo, yo], [x, y]))
x1, y1 = np.minimum([w, h], np.maximum([xo, yo], [x, y]))
self.selection = None
if x1-x0 > 0 and y1-y0 > 0:
self.selection = (x0, y0, x1, y1)
print self.selection
else:
self.drag_start = None
if self.selection is not None:
self.tracking_state = 1
def show_hist(self):
bin_count = self.hist.shape[0]
bin_w = 24

 


40 光流


目標
 本節我們將要學習:
  • 光流的概念以及 Lucas-Kanade 光流法
  • 使用函數 cv2.calcOpticalFlowPyrLK() 對圖像中的特征點進行跟蹤


40.1 光流
  由於目標對象或者攝像機的移動造成的圖像對象在連續兩幀圖像中的移動被稱為光流。它是一個 2D 向量場,可以用來顯示一個點從第一幀圖像到第二幀圖像之間的移動。如下圖所示(Image Courtesy: Wikipedia article onOptical Flow):

    Optical Flow
上圖顯示了一個點在連續的五幀圖像間的移動。箭頭表示光流場向量。光流在很多領域中都很有用:
  • 由運動重建結構
  • 視頻壓縮
  • Video Stabilization 等
 光流是基於一下假設的:
  1. 在連續的兩幀圖像之間(目標對象的)像素的灰度值不改變。
  2. 相鄰的像素具有相同的運動
第一幀圖像中的像素 I (x,y,t) 在時間 dt 后移動到第二幀圖像的(x+dx,y+dy)處。根據第一條假設:灰度值不變。所以我們可以得到:
      I(x,y,t) = I(x+dx, y+dy, t+dt)
對等號右側進行泰勒級數展開,消去相同項,兩邊都除以 dt,得到如下方程:
      f_x u + f_y v + f_t = 0 \;
其中:
      f_x = \frac{\partial f}{\partial x} \; ; \; f_y = \frac{\partial f}{\partial x}

u = \frac{dx}{dt} \; ; \; v = \frac{dy}{dt}
上邊的等式叫做光流方程。其中 f x 和 f y 是圖像梯度,同樣 f t 是時間方向的梯度。但(u,v)是不知道的。我們不能在一個等式中求解兩個未知數。有幾個方法可以幫我們解決這個問題,其中的一個是 Lucas-Kanade 法


40.2 Lucas-Kanade 法
  現在我們要使用第二條假設,鄰域內的所有點都有相似的運動。LucasKanade 法就是利用一個 3x3 鄰域中的 9 個點具有相同運動的這一點。這樣我們就可以找到這 9 個點的光流方程,用它們組成一個具有兩個未知數 9 個等式的方程組,這是一個約束條件過多的方程組。一個好的解決方法就是使用最小二乘擬合。下面就是求解結果:
      \begin{bmatrix} u \\ v \end{bmatrix} =
\begin{bmatrix}
    \sum_{i}{f_{x_i}}^2  &  \sum_{i}{f_{x_i} f_{y_i} } \\
    \sum_{i}{f_{x_i} f_{y_i}} & \sum_{i}{f_{y_i}}^2
\end{bmatrix}^{-1}
\begin{bmatrix}
    - \sum_{i}{f_{x_i} f_{t_i}} \\
    - \sum_{i}{f_{y_i} f_{t_i}}
\end{bmatrix}
(有沒有發現上邊的逆矩陣與 Harris 角點檢測器非常相似,這說明角點很適合被用來做跟蹤)
從使用者的角度來看,想法很簡單,我們取跟蹤一些點,然后我們就會獲得這些點的光流向量。但是還有一些問題。直到現在我們處理的都是很小的運動。
如果有大的運動怎么辦呢?圖像金字塔。我們可以使用圖像金字塔的頂層,此時小的運動被移除,大的運動裝換成了小的運動,現在再使用 Lucas-Kanade算法,我們就會得到尺度空間上的光流。


40.3 OpenCV 中的 Lucas-Kanade 光流
  上述所有過程都被 OpenCV 打包成了一個函數:cv2.calcOpticalFlowPyrLK()。
現在我們使用這個函數創建一個小程序來跟蹤視頻中的一些點。要跟蹤那些點呢?我們使用函數 cv2.goodFeatureToTrack() 來確定要跟蹤的點。我們首先在視頻的第一幀圖像中檢測一些 Shi-Tomasi 角點,然后我們使用 LucasKanade 算法迭代跟蹤這些角點。我們要給函數 cv2.calcOpticlaFlowPyrLK()傳入前一幀圖像和其中的點,以及下一幀圖像。函數將返回帶有狀態數的點,如果狀態數是 1,那說明在下一幀圖像中找到了這個點(上一幀中角點),如果狀態數是 0,就說明沒有在下一幀圖像中找到這個點。我們再把這些點作為參數傳給函數,如此迭代下去實現跟蹤。代碼如下:
(上面的代碼沒有對返回角點的正確性進行檢查。圖像中的一些特征點甚至在丟失以后,光流還會找到一個預期相似的點。所以為了實現穩定的跟蹤,我們應該每個一定間隔就要進行一次角點檢測。OpenCV 的官方示例中帶有這樣一個例子,它是每 5 幀進行一個特征點檢測。它還對光流點使用反向檢測來選取好的點進行跟蹤。示例為/samples/python2/lk_track.py)

import numpy as np
import cv2

cap = cv2.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 = (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()

下面是我的到的結果:

    Lucas-Kanade method for optical flow


40.4 OpenCV 中的稠密光流
  Lucas-Kanade 法是計算一些特征點的光流(我們上面的例子使用的是Shi-Tomasi 算法檢測到的角點)。OpenCV 還提供了一種計算稠密光流的方法。它會圖像中的所有點的光流。這是基於 Gunner_Farneback 的算法(2003 年)。
下面的例子就是使用上面的算法計算稠密光流。結果是一個帶有光流向量(u,v)的雙通道數組。通過計算我們能得到光流的大小和方向。我們使用顏色對結果進行編碼以便於更好的觀察。方向對應於 H(Hue)通道,大小對應於 V(Value)通道。代碼如下:

import cv2
import numpy as np
cap = cv2.VideoCapture("vtest.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)

    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()

結果如下:
      Dense Optical Flow
OpenCV 的官方示例中有一個更高級的稠密光流/samples/python2/
opt_flow.py,去搞定它吧!


41 背景減除


目標
  • 本節我們將要學習 OpenCV 中的背景減除方法
41.1 基礎
  在很多基礎應用中背景檢出都是一個非常重要的步驟。例如顧客統計,使用一個靜態攝像頭來記錄進入和離開房間的人數,或者是交通攝像頭,需要提取交通工具的信息等。在所有的這些例子中,首先要將人或車單獨提取出來。技術上來說,我們需要從靜止的背景中提取移動的前景。
如果你有一張背景(僅有背景不含前景)圖像,比如沒有顧客的房間,沒有交通工具的道路等,那就好辦了。我們只需要在新的圖像中減去背景就可以得到前景對象了。但是在大多數情況下,我們沒有這樣的(背景)圖像,所以我們需要從我們有的圖像中提取背景。如果圖像中的交通工具還有影子的話,那這個工作就更難了,因為影子也在移動,僅僅使用減法會把影子也當成前景。真是一件很復雜的事情。
為了實現這個目的科學家們已經提出了幾種算法。OpenCV 中已經包含了其中三種比較容易使用的方法。我們一個一個學習一下吧。


41.2 BackgroundSubtractorMOG
  這是一個以混合高斯模型為基礎的前景/背景分割算法。它是 P.KadewTraKuPong和 R.Bowden 在 2001 年提出的。它使用 K(K=3 或 5)個高斯分布混合對背景像素進行建模。使用這些顏色(在整個視頻中)存在時間的長短作為混合的權重。背景的顏色一般持續的時間最長,而且更加靜止。一個像素怎么會有分布呢?在 x,y 平面上一個像素就是一個像素沒有分布,但是我們現在講的背景建模是基於時間序列的,因此每一個像素點所在的位置在整個時間序列中就會有很多值,從而構成一個分布。
在編寫代碼時,我們需要使用函數:cv2.createBackgroundSubtractorMOG()創建一個背景對象。這個函數有些可選參數,比如要進行建模場景的時間長度,高斯混合成分的數量,閾值等。將他們全部設置為默認值。然后在整個視頻中我們是需要使用 backgroundsubtractor.apply() 就可以得到前景的掩模了。
下面是一個簡單的例子:

import numpy as np
import cv2

cap = cv2.VideoCapture('vtest.avi')

fgbg = cv2.createBackgroundSubtractorMOG()

while(1):
    ret, frame = cap.read()

    fgmask = fgbg.apply(frame)

    cv2.imshow('frame',fgmask)
    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

 


41.3 BackgroundSubtractorMOG2
  這個也是以高斯混合模型為基礎的背景/前景分割算法。它是以 2004 年和 2006 年 Z.Zivkovic 的兩篇文章為基礎的。這個算法的一個特點是它為每一個像素選擇一個合適數目的高斯分布。(上一個方法中我們使用是 K 高斯分布)。這樣就會對由於亮度等發生變化引起的場景變化產生更好的適應。
和前面一樣我們需要創建一個背景對象。但在這里我們我們可以選擇是否檢測陰影。如果 detectShadows = True(默認值),它就會檢測並將影子標記出來,但是這樣做會降低處理速度。影子會被標記為灰色。
239
www.linuxidc.com
# -*- coding: utf-8 -*-
"""
Created on Mon Jan 27 18:16:29 2014
@author: duan
"""
import numpy as np
import cv2
cap = cv2.VideoCapture('vtest.avi')
fgbg = cv2.createBackgroundSubtractorMOG2()
while(1):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
cv2.imshow('frame',fgmask)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv2.destroyAllWindows()


41.4 BackgroundSubtractorGMG
  此算法結合了靜態背景圖像估計和每個像素的貝葉斯分割。這是 2012 年Andrew_B.Godbehere,Akihiro_Matsukawa 和 Ken_Goldberg 在文章中提出的。
它使用前面很少的圖像(默認為前 120 幀)進行背景建模。使用了概率前景估計算法(使用貝葉斯估計鑒定前景)。這是一種自適應的估計,新觀察到的對象比舊的對象具有更高的權重,從而對光照變化產生適應。一些形態學操作如開運算閉運算等被用來除去不需要的噪音。在前幾幀圖像中你會得到一個黑色窗口。
對結果進行形態學開運算對與去除噪聲很有幫助。

import numpy as np
import cv2

cap = cv2.VideoCapture('vtest.avi')

fgbg = cv2.createBackgroundSubtractorMOG2()

while(1):
    ret, frame = cap.read()

    fgmask = fgbg.apply(frame)

    cv2.imshow('frame',fgmask)
    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

 


41.5 結果
原始圖像
下圖顯示了一段視頻中的第 200 幀圖像

    Original frame

BackgroundSubtractorMOG 的結果

    Result of BackgroundSubtractorMOG
2 BackgroundSubtractorMOG2 的結果灰色區域代表陰影

    Result of BackgroundSubtractorMOG2
BackgroundSubtractorGMG 的結果使用形態學開運算將噪音去除。

    Result of BackgroundSubtractorGMG
更多資源
練習


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM