ホリケン's diary

趣味はでぃーぷらーにんぐ

OpenCVを使った移動物体検出


目次


* フレーム差分法

フレーム差分法(フレーム間の同じ位置の画素値の差の絶対値を画素とする画像から移動物体を切り出す方法)を用いて動画を処理する.

"""動画を読み込み加工して保存する"""

vid_name = os.path.join("kento.mp4")
cap = cv2.VideoCapture(vid_name)

fourcc = cv2.VideoWriter_fourcc(*'MJPG')
out = cv2.VideoWriter('for_hatena_0.avi',fourcc, 60.0, (1280,720))
ret, frame = cap.read()
last_last_frame = cv2.cvtColor(frame,cv2.COLOR_RGB2GRAY)
ret, frame = cap.read()
last_frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)

threshold = 15
while(cap.isOpened):
    ret, frame = cap.read()
    if frame is None:
        break
    frame = cv2.cvtColor(frame,cv2.COLOR_RGB2GRAY)
        
    # 差分計算
    new_frame1 = cv2.absdiff(frame,last_frame)
    new_frame2 = cv2.absdiff(last_frame, last_last_frame)
    new_frame = cv2.bitwise_and(new_frame1, new_frame2)
    
    # Medianフィルターによるノイズ除去
    new_frame = cv2.medianBlur(new_frame,5)
    
    # 2値化
    new_frame[new_frame>threshold]=255
    new_frame[new_frame<=threshold]=0
    
    new_frame = cv2.cvtColor(new_frame,cv2.COLOR_GRAY2RGB) 
    out.write(new_frame)
    last_last_frame = last_frame
    last_frame = frame
    
    
cap.release()
out.release()

結果: (この動画は僕が椅子に座るところを撮ったものだが)人っぽい輪郭が映し出されているのがわかる 背景は動いていないので、もちろん黒抜きの画像となった。


for hatena 0


* オプティカルフローの可視化

オプティカルフローとはフレーム間の対応画素の移動量をベクトルデータとして表現したもの。周囲数ピクセルも同じベクトルを持つ(制約条件)と近似することで計算を可能とした。OpenCVには"疎なオプティカルフロー"と"密なオプティカルフロー"との二つをモジュールがある。

1. 疎なオプティカルフロー

Lucas-Kanadeの方法(LK法)によるもの。固定した特徴点と周囲の3x3ピクセルを考慮したオプティカルフローを計算する。

"""オプティカルフロー"""
import cv2
import numpy as np
import os

# cap = cv2.VideoCapture(os.path.join("data","train","000029.mpg"))
cap  = cv2.VideoCapture("walk.mp4")
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
out = cv2.VideoWriter('for_hatena_1.avi',fourcc, 60.0, (1920,1080))
# cap = cv2.VideoCapture("29.avi")

# params for ShiTomasi corner detection
feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.1,
                       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)

count = 0
while(1):
    count += 1
    ret,frame = cap.read()
    if frame is None:
        break
    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)

    out.write(img)

    # Now update the previous frame and previous points
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1,1,2)
    if count % 5 == 0:
        p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)

cv2.destroyAllWindows()
cap.release()

結果: 線で軌跡が描かれている。これは5フレームごとに特徴量をとってその動きを追っている。(フレーム差分法と同じ動画を使うと僕の顔がでてしまうためフリー動画を使った。)


for hatena 1

2. 密なオプティカルフロー

この場合は画像中の全ての画素についてオプティカルフローを計算する。

"""差分オプティカルフロー"""
cap  = cv2.VideoCapture("kento.mp4")
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
out = cv2.VideoWriter('kento_2.avi',fourcc, 60.0, (1280,720))

ret, frame1 = cap.read()
prvs = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
while(1):
    count += 1
    ret, frame2 = cap.read()
    if frame2 is None:
        break
    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)

    out.write(rgb)
    prvs = next

cap.release()
cv2.destroyAllWindows()

ここでは方向ベクトルごとに色分けされている。


kento 2

また、以下に定義する関数によって動画中にオプティカルフローのベクトル情報を図示したものを加えることができる。

def draw_flow(img, gray, flow, step=16):
    h, w = img.shape[:2]
    y, x = np.mgrid[step / 2:h:step, step / 2:w:step].reshape(2, -1).astype(int)
    fx, fy = flow[y, x].T
    lines = np.vstack([x, y, x + fx, y + fy]).T.reshape(-1, 2, 2)
    lines = np.int32(lines)

    vis = cv2.cvtColor(gray, cv2.COLOR_GRAY2RBG)
    vis = 255 - vis  
    rad = int(step / 2)

    i = 0  
    for (x1, y1), (x2, y2) in lines:
        pv = img[y1, x1]
        col = (int(pv[0]), int(pv[1]), int(pv[2]))
        r = rad - int(rad * abs(fx[i]) * .05)
        cv2.circle(vis, (x1, y1), abs(r), col, -1)
        i += 1
    cv2.polylines(vis, lines, False, (255, 255, 0))
    return vis


for hatena 2 このエフェクトめちゃくちゃかっこいい。。。

参考: http://www.digifie.jp/blog/archives/1448
参考: http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_video/py_lucas_kanade/py_lucas_kanade.html