Atom's tech blog

OpenCVで動体検知をトライ(フレーム間差分法)

概要

  • 3画像で差分検出し変化した箇所を動体検出としている
  • 画像は単に配列データとして扱う
  • カラーの配列データをグレースケール画像の配列データに変換
  • 差分箇所を論理積演算で特定する
  • 差分箇所の外接を矩形で囲む
  • 動体検知すると「Motion Detected」と表示
  • 変化量を「Change Vol 数値」で表示

コメント

  • サンプルコードを参考に一部カスタマイズしてトライしてみたけど、なんとなく動いてそう。
  • 掲載したソースは「Mac」で動作確認済み(ラズパイでも動作するはず)
  • FPS表示値は少し嘘っぽいなー
  • 動画イメージを掲載したかったが方法がわからず断念
  • 途中の画像のみ掲載しました
  • 使ったツールバージョンは以下
  • MacOS Mojave / python3.5.6 / Opencv3.4.2 / VS code1.38.1

イメージ画像(その1)

f:id:iAtom:20201008143016j:plain

イメージ画像(その2)

f:id:iAtom:20201008143009j:plain

ファイル構成

ソースコード

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

cap_camera = None
getimg1 = None
getimg2 = None
getimg3 = None
view_frame = None
before = None

# Device0のcamera
cap_camera = cv2.VideoCapture(0)
## 画像サイズをVGAにする
cap_camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640) 
cap_camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) 

########################################
# 動体検出差分取得メソッド[フレーム間差分法]  #
########################################
def motion_check(img1,img2,img3):
    ## グレースケール画像に変換 
    gray1 = cv2.cvtColor(img1, cv2.COLOR_RGB2GRAY)
    gray2 = cv2.cvtColor(img2, cv2.COLOR_RGB2GRAY)
    gray3 = cv2.cvtColor(img3, cv2.COLOR_RGB2GRAY)
    # 第一画像と第二の画像差の絶対差分値
    diff_12 = cv2.absdiff(gray1, gray2)
    # 第二画像と第三の画像差の絶対差分値
    diff_23 = cv2.absdiff(gray2, gray3)
    ############################################################
    # 同じ画像データの同じ箇所を1 違う箇所を0にする。                 #
    # 論理積ANDををとることで変化があるところの差分を検出する           #
    ############################################################
    bitwise_diff_and = cv2.bitwise_and(diff_12, diff_23)
    ## 色の二値化 ###
    ret, diff_thresholding = cv2.threshold(bitwise_diff_and, 30, 255, cv2.THRESH_BINARY)
    ## ノイズ除去##
    diff = cv2.medianBlur(diff_thresholding, 5)
    return diff

# 最初の画像を取得
getimg1 = getimg2 = getimg3= cap_camera.read()[1]

while True:
    #FPS算出のため、事前に時間を取得
    tick = cv2.getTickCount()
    # 「フレーム間差分法」を使って3画像から差分を検出する
    diff = motion_check(getimg1, getimg2, getimg3)
    # 差分箇所が0でdiffに入っているので、0の数を数える
    cnt = cv2.countNonZero(diff)
    ## 最新画像をグレースケールに変換 ##
    gray = cv2.cvtColor(getimg3,cv2.COLOR_BGR2GRAY)
    if before is None :
        before = gray.copy().astype('float')
        continue  
 
    # 差分箇所が500以上である程度変化するが多い時だけ動体検知とする
    if cnt > 500:
        # 加重平均
        cv2.accumulateWeighted(gray,before,0.7)
        # 差分検出
        diff_frame = cv2.absdiff(gray, cv2.convertScaleAbs(before))
        # 画素値が閾値より大きければある値(白色)を割り当て,閾値より小さい別の値(黒色)を割り当てる
        thl = cv2.threshold(diff_frame,5,255,cv2.THRESH_BINARY)[1]
        image, contours, hierarchy = cv2.findContours(thl.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        max_area = 0
        target = None
        for i in contours:
            area = cv2.contourArea(i)
            if max_area < area and area < 30000 and area > 3000:
                max_area = area
                target = i

        # 動いているエリアが500以上を外接矩形で囲む
        if max_area >= 500:
            # 輪郭に外接する長方形を取得する
            x,y,w,h = cv2.boundingRect(target)
            # 矩形で囲んだ画像を作成
            areaframe = cv2.rectangle(getimg3,(x,y),(x+w,y+h),(0,255,0),2)
            # 検出文字列を表示用データ作成
            cv2.putText(areaframe, "Motion Detected", (10,405), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)
            # 表示用に載せ替え
            view_frame = areaframe
    else:
        # 表示用に載せ替え
        view_frame = getimg3

    # 画像データを移動し最新データをgetimg3に保存する
    getimg1 = getimg2
    getimg2 = getimg3
    getimg3 = cap_camera.read()[1]

    # FPS計算
    fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick)
    # FPSを左下に表示
    cv2.putText(view_frame, "FPS:{} ".format(int(fps)),(10, 430), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)
    # 変化量を左下に表示
    cv2.putText(view_frame, "Change Vol:{}".format(int(cnt)),(10,450), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)
    cv2.imshow('Motion Detect Image', view_frame)
  
    # ESCキー
    k = cv2.waitKey(1)
    if k == 27:
        break

cap_camera.release()
cv2.destroyAllWindows()