【Python】OpenCVを使って動体検知する

2023 年 10 月 13 日 by sumitanik

はじめに

OpenCVを駆使した動体検出は、現代のコンピュータビジョンアプリケーションにおいて欠かせない技術の一つです。この技術は、ビデオ監視、自動運転、セキュリティシステムなど多くの分野で応用でき、画像やビデオから動く対象を検出し、それに関する情報を抽出するために使用されます。

この記事では、動体検知システムの概要、実装内容について説明します。

システム概要

今回試作したシステムは、車内に子供の置き去りにしないように防止することを目的としています。

このシステムでは、以下の2つの動作を想定しています。しかし、完全再現は難しいため、実際には模擬的に実装していきます。

➀車内で撮影した動画を解析することで、子供が置き去りになっていることを検知する

→実際に車内をリアルタイムで撮影することは難しかったため、予めオフィスの座席で撮影した2つの動画(運転手、子供)に対して、人がいるか/いないかを解析して置き去りになっているかを判定する。

検知したら、保護者の携帯等に通知する

→コマンドプロンプトに、在席状況を表示する。

========================
driver:離席、チャイルドシート監視中
child:子供が置き去りになっています!
========================

実装内容

実装では、主に以下の内容を行っています。

  1. 指定した2つの動画を取得する。
  2. 10フレームごとに以下の処理を繰り返す
    1. 10フレームごとに画像ファイルを生成する
    2. 画像サイズを変更し、横並びの状態で新規ウィンドウを表示する 
    3. 前のウィンドウが表示されている場合、前のウィンドウを閉じる
    4. 動体検知によって、運転手と子供の在席を判定する
    5. アラートを表示する
import cv2
import sys
import os
import numpy

# メインプログラム
def main():

    # ディレクトリ
    ##########################################################################
    DIR = 'C:/NEXTVISION/xx/'
    ##########################################################################

    # 動画読み込み(引数から動画1)
    ##########################################################################
    args1 = sys.argv
    VIDEO_NAME1 = 'driver.mp4'
    cap1 = cv2.VideoCapture(DIR + VIDEO_NAME1) 
    fps1 = cap1.get(cv2.CAP_PROP_FPS)
    avg1 = None
    ##########################################################################

    # 動画読み込み(固定値で動画2)
    ##########################################################################
    args2 = sys.argv
    VIDEO_NAME2 = 'child.mp4'
    cap2 = cv2.VideoCapture(DIR + VIDEO_NAME2) 
    fps2 = cap2.get(cv2.CAP_PROP_FPS)
    avg2 = None
    frame_count = 0
    ##########################################################################

    # フレームの画像ファイル
    ##########################################################################
    IMG_NAME1 = 'frame1.jpg'
    IMG_NAME2 = 'frame2.jpg'
    img_path1 = DIR + IMG_NAME1
    img_path2 = DIR + IMG_NAME2
    ##########################################################################

    # 以下、フレーム分繰り返し
    while True:
        # 運転席のフレームを取得
        ret, frame1 = cap1.read()
        if not ret:
            break
        
        # チャイルドシートのフレームを取得
        rets, frame2 = cap2.read()
        if not rets:
            break

        # 10フレームごとに画像ファイルを生成する
        go = frame_count % 3
        window1 = False
        window2 = False
        if go == 0:

            # フレームを画像ファイルとして保存
            cv2.imwrite(IMG_NAME1, frame1)
            cv2.imwrite(IMG_NAME2, frame2)

            # グレースケールに変換
            gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
            gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)

            # 画像出力のための読み込み
            img1 = cv2.imread(img_path1)
            img2 = cv2.imread(img_path2)

            # サイズが大きかったので4分の1に
            img1_2 = cv2.resize(img1, dsize=None, fx=0.5, fy=0.5)
            img2_2 = cv2.resize(img2, dsize=None, fx=0.5, fy=0.5)

            # hconcat:二つの画像を横並びに結合する
            im_h = cv2.hconcat([img1_2, img2_2])

            # 画像サイズを変更し、横並びの状態で新規ウィンドウを表示する
            # 前のウィンドウが表示されている場合、前のウィンドウを閉じる
            if window1 == False and window2 == False:
                cv2.imshow('WindowName1', im_h)
                window1 == True
            elif window1 == True and window2 == False:
                cv2.imshow('WindowName2', im_h)
                window1 == False
                window2 == True
                cv2.destroyWindow ('WindowName1')
            elif window1 == False and window2 == True:
                cv2.imshow('WindowName1', im_h)
                window1 == True
                window2 == False
                cv2.destroyWindow ('WindowName2')
            
            # (初回のみ)現フレームを次の比較用に保存しておく
            if avg1 is None:
                avg1 = gray1.copy().astype("float")
                continue

            # (初回のみ)現フレームを次の比較用に保存しておく
            if avg2 is None:
                avg2 = gray2.copy().astype("float")
                continue

            # 状態を定義
            # 運転手がいる  ⇒ 乗車中
            # 運転手がいない ⇒ 子供がいる ⇒ アラート
            # 運転手がいない ⇒ 子供がいない ⇒ アラート消す
            print("========================")
            isDriver = findDriver(gray1, avg1)
            if isDriver:
                print("driver:乗車中")
            else:
                print("driver:離席、チャイルドシート監視中")

            isChild = findChild(gray2, avg2)             
            if isChild:
                if isDriver:
                    print("child:乗車中")
                else:
                    # アラート表示
                    print("child:子供が置き去りになっています!")
            else:
                print("child:誰も乗っていません")
            print("========================")

            # 後処理
            # 現フレームを次の比較用に保存しておく
            avg1 = gray1.copy().astype("float")
            avg2 = gray2.copy().astype("float")

            # [ESC]Key押下で中断する
            key = cv2.waitKey(100)
            if key == 27: 
                break
        # 次のフレームへ
        frame_count+=1  

    cap1.release()
    cap2.release()


# 運転手が在席しているかの判定
def findDriver(gray1, avg1):
    detected = find_person(gray1, avg1)

    if detected:
        return True

    return False


# 子供が在席しているかの判定
def findChild(gray2, avg2):
    detected = find_person(gray2, avg2)

    if detected:
        return True

    return False


# 人間を検知する(前景セグメンテーション)
def find_person(gray, avg):
    # フレーム差異があれば、配列に座標を格納
    coordinates = []

    # 現在のフレームと移動平均との差を計算
    cv2.accumulateWeighted(gray, avg, 0.6)
    frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))

    # フィルタで均す
    thresh = cv2.threshold(frameDelta, 3, 255, cv2.THRESH_BINARY)[1]

    # 検出した差分の輪郭を抽出
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # 以下、配列格納処理
    # 輪郭で囲まれた領域内の面積が大きい(人間を検知)なら、その領域の重心を配列に格納
    
    for i in range(len(contours)):
        contournow = contours[i]
        # 輪郭[i]の面積
        area = cv2.contourArea(contournow) 
        if area > 50:      
            # 輪郭[i]の重心
            m = cv2.moments(contournow) 

            mx,my= int(m["m10"]/m["m00"]) , int(m["m01"]/m["m00"])
            coordinates.append([mx, my+30])

    # 呼び出し元が確認して、配列の要素が存在すれば、人間を検知、といった仕組み
    return coordinates

# 引数処理
if __name__ == "__main__":
    main()

タグ: ,

TrackBack