1. 程式人生 > >影象基礎22 運動偵測

影象基礎22 運動偵測

本文學習資源來自《機器學習實踐指南》

定義

運動偵測,英文翻譯為“Motion detection technology”,一般也叫移動檢測,常用於無人值守監控錄影和自動報警。通過攝像頭按照不同幀率採集得到的影象會被CPU按照一定演算法進行計算和比較,當畫面有變化時,如有人走過,鏡頭被移動,計算比較結果得出的數字會超過閾值並指示系統能自動作出相應的處理。
—–百度百科

視訊採集

示例中首先設定捕獲裝置,然後每15毫秒更新一次畫面顯示,並檢測是否退出。

# -*- coding: utf-8 -*-
import cv2

mycap = cv2.VideoCapture(0
) id=0 while True: ret , im=mycap.read() cv2.imshow('myvideo',im) # 每15毫秒採集1次 key = cv2.waitKey(15) # 空格鍵退出 if key ==32: break elif key == ord('c'): cv2.imwrite('vd_'+str(id)+'.png',im) id += 1

每次按c鍵後,採集連線影象。

影象序列陣列

可按幀生成視訊影象序列陣列。

# -*- coding: utf-8 -*-
import cv2 import numpy as np mycap = cv2.VideoCapture(0) myframes = [] while True: ret, im=mycap.read() cv2.imshow('myvideo',im) # 採集 myframes.append(im) # 每15ms採集1次 key = cv2.waitKey(15) # 空格鍵退出 if key ==32: break # 生成視訊幀陣列 myframes = np.array(myframes) print(myframes)

讀取視訊檔案

mycapture = cv2.VideoCapture("視訊檔案")

差分演算法

差分檢測根據當前影象與參考影象的差別分析來判斷序列影象中是否有運動的物體。在環境亮度變化不大的情況下,如果對應畫素灰度值的差異小於某個閾值,則認為畫面靜止無運動變化,如果影象區域某處的灰度變化大於某個閾值,則認為這是由於影象中運動 的物體所引起的,然後求出運動目標在影象中的位置。

  1. 基於相鄰幀差的演算法:
    將前後兩幀影象對應畫素點的灰度值相減;
  2. 基於背景影象與當前幀差的演算法:
    將當前幀和背景幀對應畫素的灰度值相減;

基於相鄰幀差的演算法

幀差法的特點是實現簡單,運算速度快,對於動態環境自適應性是很強的,對光線的變化不是十分的敏感。但是在運動體內易產生空洞.特別是目標運動速度較快時,影響目標區域準確提取。我們以年輛檢測為例,車輛檢測除了要檢測出運動車輛.同時還要檢測出暫時停止的車輛,在這個方面,此類方法無能為力。而且如果車輛的體積較大,那麼車輛在前後幀中根容易產生重疊部分,尤其是大貨車,這使得幀問差分的結果主要為車頭和車尾。車輛中間部分的差分值相對報小.形成空洞,不利於檢測。

演算法過程:
1. 將當前幀的灰度影象與前一幀的灰度影象相減,得到差分值;當差分值大於某個閾值時,將差分矩陣的相應位置設定為1,否則設定為0.
差分矩陣的計算公式:
D(i,j)=

其中,fk(i,j) 表示第 k 個影象矩陣(i,j) 處的灰度值,D(i,j) 表示差分矩陣(i,j) 處的值。
2. 如果差分矩陣不全為0,則表示檢測到運動 。如果有必要,可按一定的運動檢測次數對運動畫面進行分組,並融合疊加形成多組運動軌跡圖。
3. 取下一幀影象,轉到(1),直到所有幀影象全部處理退出。

輸入圖片示例:
這裡寫圖片描述

程式示例

# -*- coding: utf-8 -*-
import cv2
import numpy as np

fn=[]
for i in range(7):
    fn.append("vd_"+str(i) + ".png")

img = []    # 灰度影象陣列
colorimg =[]  # 彩色影象陣列
myimg =[]   # 臨時變數
for i in range(len(fn) -1):
    tmpimg = (cv2.imread(fn[i]))
    colorimg.append(tmpimg)
    myimg = cv2.cvtColor(tmpimg,cv2.COLOR_BGR2GRAY)
    img.append(myimg)

# 差分計算
myimg1 = colorimg[0].copy()
myimg2 = myimg1.copy()
w = myimg1.shape[1]
h = myimg1.shape[0]

moveimg1 = np.zeros((h, w),np.uint8)
moveimg2 = np.zeros((h,w,3),np.uint8)

for ii in range(len(fn)-2):
    print (u'開始分析第' + str(ii+2) + u'個運動影象')
    myd = img[ii+1] -img[ii]
    # 生成差分矩陣
    THRESHOLD = int(np.median(abs(myd))) # 取中位數做為閾值
    mymove = np.ones([h,w],np.uint8)
    for i in range(h):
        for j in range(w):
            if abs(myd[i,j]) < THRESHOLD or myd[i,j]==0:
                mymove[i,j]=0
    # 如果有物體運動
    if np.sum(mymove)>0:
        print(u"第"+str(ii+2)+u"個運動影象發生了變化 ")
        moveimg1 = img[ii+1]*(1-mymove)*0.16+img[ii]*(1-mymove)*0.16+moveimg1
        moveimg2 = colorimg[ii+1]*0.16 + colorimg[ii]*0.16+moveimg2
        moveimg1 = np.array(moveimg1,np.uint8)
        moveimg2 = np.array(moveimg2,np.uint8)

# 顯示移動部分
moveimg1 = moveimg1-img[0]

# 灰度圖二值化以更好地顯示運動的效果
retval, showimg1 = cv2.threshold(moveimg1, 140,255,cv2.THRESH_BINARY)
showimg1=moveimg1
showimg2=moveimg2

cv2.imshow("move1", showimg1)
cv2.imshow("move2", showimg2)
cv2.waitKey()
cv2.destroyAllWindows()

這裡寫圖片描述

基於背景影象與當前幀差的演算法

# -*- coding: utf-8 -*-
import cv2
import numpy as np

fn=[]
for i in range(7):
    fn.append("vd_"+str(i) + ".png")

img = []    # 灰度影象陣列
colorimg =[]  # 彩色影象陣列
myimg =[]   # 臨時變數
for i in range(len(fn) -1):
    tmpimg = (cv2.imread(fn[i]))
    colorimg.append(tmpimg)
    myimg = cv2.cvtColor(tmpimg,cv2.COLOR_BGR2GRAY)
    img.append(myimg)

# 差分計算
myimg = colorimg[0].copy()
w = myimg.shape[1]
h = myimg.shape[0]

moveimg = np.zeros((h, w, 3),np.uint8)
for ii in range(len(fn)-2):
    print (u'開始分析第' + str(ii+2) + u'個運動影象')
    myd = img[ii+1] -img[0]
    # 生成差分矩陣
    THRESHOLD = int(np.median(abs(myd))) # 取中位數做為閾值
    mymove = np.ones([h,w],np.uint8)
    for i in range(h):
        for j in range(w):
            if abs(myd[i,j]) < THRESHOLD or myd[i,j]==0:
                mymove[i,j]=0
    # 如果有物體運動
    if np.sum(mymove)>0:
        print(u"第"+str(ii+2)+u"個運動影象發生了變化 ")
        moveimg = colorimg[ii+1]*0.16 + colorimg[ii] * 0.16 + moveimg
        moveimg = np.array(moveimg, np.uint8)

# 顯示移動部分
showimg = moveimg
cv2.imshow("move", showimg)
cv2.waitKey()
cv2.destroyAllWindows()

執行結果:
這裡寫圖片描述

 光流法

光流的概念是Gibson在1950年首先提出來的。它是空間運動物體在觀察成像平面上的畫素運動的瞬時速度,是利用影象序列中畫素在時間域上的變化以及相鄰幀之間的相關性來找到上一幀跟當前幀之間存在的對應關係,從而計算出相鄰幀之間物體的運動資訊的一種方法。一般而言,光流是由於場景中前景目標本身的移動、相機的運動,或者兩者的共同運動所產生的。其計算方法可以分為三類:
(1)基於區域或者基於特徵的匹配方法;
(2)基於頻域的方法;
(3)基於梯度的方法;
簡單來說,光流是空間運動物體在觀測成像平面上的畫素運動的“瞬時速度”。光流的研究是利用影象序列中的畫素強度資料的時域變化和相關性來確定各自畫素位置的“運動”。研究光流場的目的就是為了從圖片序列中近似得到不能直接得到的運動場。
光流法的前提假設:
(1)相鄰幀之間的亮度恆定;
(2)相鄰視訊幀的取幀時間連續,或者,相鄰幀之間物體的運動比較“微小”;
(3)保持空間一致性;即,同一子影象的畫素點具有相同的運動
這裡有兩個概念需要解釋:
運動場,其實就是物體在三維真實世界中的運動;
光流場,是運動場在二維影象平面上的投影。

示例程式碼:

# -*- coding: utf-8 -*-
import cv2
from numpy import *

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,x].T

    # create line endpoints
    lines = vstack([x,y,x+fx,y+fy]).T.reshape(-1,2,2)
    lines = int32(lines)

    # create image and draw
    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


# 設定需要採集視訊的裝置ID為0,從第0個攝像頭採集
cap = cv2.VideoCapture(0)
# 前一個影象
ret,im = cap.read()
prev_gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

while True:
    # get grayscale image
    ret, im = cap.read()
    gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)

    # compute flow
    # flow = cv2.calcOpticalFlowFarneback(prev_gray,gray,None,0.5,3,15,3,5,1.2,0)
    flow = cv2.calcOpticalFlowFarneback(prev_gray, gray, float(0), float(0), 3, 15, 3, 5, float(1), 0)
    prev_gray = gray

    # plot the flow vectors
    cv2.imshow('Optical flow', draw_flow(gray, flow))
    if cv2.waitKey(10) == 32:
        break