Python

PythonとOpenCVを使った動体検出プログラムの作成方法

SEOを意識したタイトル: PythonとOpenCVを使った動体検出プログラムの作成方法

動画から動体を検出する技術は、セキュリティシステム、交通監視、研究プロジェクトなど、多岐にわたる分野で重要な役割を果たしています。この記事では、PythonとOpenCVを使用して動体検出を行う基本的な方法を紹介します。プログラムの解説を通じて、動体検出の基礎から応用まで学べる内容となっており、プログラミング初心者でも理解しやすいように構成しています。

アイコン名を入力

・動体検出の基本的な仕組みを理解したいが、どこから始めればよいかわからない。
・OpenCVでの動体検出のコーディング方法がわからない。
・動画データから動体を効率的に検出するプログラムを作成したいが、具体的な手順やコードが不明。

こんな疑問に答えます。

解決できること

・PythonとOpenCVを使用した動体検出の基礎知識を習得できる。
・具体的な動体検出プログラムのコードとその解説を通じて、プログラミングスキルが向上する。
・自分のプロジェクトに応用可能な動体検出の技術を習得し、独自のアプリケーション開発が可能になる。

YouTube動画と少し違うかもしれませんがこのようなことができます。

参考動画はこちらです。

🚀 0円で現役エンジニアから学べる【Techスクールオンライン】のお申込みをお勧めします。 このオンラインスクールでは、現役のエンジニアから直接学ぶことができ、プログラミングの基礎から高度なスキルまでを習得できます。しかも、今なら 0円 で受講できるチャンスです。 エンジニア転職を考えている方やプログラミングに興味がある方、新しいスキルを習得したい方に特におすすめです。

第1章: 動体検出プログラムの全体概要

早速プログラムの内容を紹介します。
このプログラムは、動画ファイルからフレームを1つずつ読み込み、動体がある場合にその部分を検出し、結果を表示するものです。OpenCVライブラリを活用して、動画処理、画像のグレースケール変換、背景との差分計算、閾値処理による動体検出、そして検出された動体の描画を行います。

import cv2  # OpenCVライブラリのインポート
import pandas as pd  # pandasライブラリのインポート(このプログラムでは使用していない)
import glob  # globライブラリのインポート(このプログラムでは使用していない)

# 分析対象の動画ファイルパス
filepath='Cars1900.mp4'
# 動画ファイルを読み込み filepathを0にするとUSBカメラの動画になる
cap = cv2.VideoCapture(filepath)

# 動画のプロパティからフレームレート、フレームの幅、高さを取得し、サイズを設定
fps = int(cap.get(cv2.CAP_PROP_FPS))
W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
size = (W, H)

# 背景画像用の変数、処理中のフレーム番号、検出した動体の数、動体検出に関するフレーム番号を格納するリストの初期化
avg = None
frameNo = 0
P_count = 0
list = [0,0,0,0,0]

# 動体検知範囲の設定(赤い枠の範囲)
pointX = 550
pointY = 600
widthX = 300
widthY = 100

# フレームを読み込むための無限ループ
while True:
    ret, frame = cap.read()  # 1フレームずつ取得
    if not ret:  # フレームがない場合はループを抜ける
        break

    frameNo = frameNo + 1  # 処理フレーム番号の更新
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # フレームをグレースケールに変換
    if avg is None:  # 最初のフレームを背景画像として設定
        avg = gray.copy().astype("float")
        continue

    # 背景画像を更新(環境に合わせて調整)
    avg = cv2.accumulateWeighted(gray, avg, 0.60)
    # 現在のフレームと背景画像の差分を計算(環境に合わせて調整)
    frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))
    # 動体検知範囲を適用
    frameDelta = frameDelta[pointY:pointY+widthY, pointX:pointX+widthX]
    # 差分画像を閾値処理して動体を検出(環境にあわせて調整)
    thresh = cv2.threshold(frameDelta, 10, 255, cv2.THRESH_BINARY)

    # 輪郭を検出
    contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    thresh_con = frame.copy()  # 処理結果を表示するためのフレームのコピー
    for contour in contours:
        area = cv2.contourArea(contour)  # 輪郭の面積を計算
        if area > 3000:  # 面積が一定値以上の場合に処理(環境に合わせて調整)
            x, y, w, h = cv2.boundingRect(contour)  # 動体の枠を取得
            # 動体に枠線を描画
            cv2.rectangle(thresh_con, (x+pointX, y+pointY), (x+pointX + w, y+pointY + h), (0, 255, 0), cv2.LINE_4)

            list.pop(0)  # リストから最古のフレーム番号を削除
            list.append(frameNo)  # 現在のフレーム番号を追加
            # 特定の条件を満たす場合に画像を保存
            if list-list[0] > 10 and list-list < 2:
                P_count += 1  # 検出カウントを更新
                cv2.imwrite('./img/img'+str(P_count)+'.jpg', thresh_con)  # 画像を保存

            # 動体の面積を表示
            cv2.putText(thresh_con, text='area='+str(area), org=(x+pointX-10, y+pointY-10), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_4)

    # 動体検出カウントを表示
    cv2.putText(thresh_con, text='Count='+str(P_count), org=(20, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1.5, color=(0, 255, 0), thickness=4, lineType=cv2.LINE_4)
    # 動体検知範囲を赤い枠で表示
    cv2.rectangle(thresh_con, (pointX, pointY), (pointX+widthX, pointY+widthY), (0, 0, 255), cv2.LINE_4)

    # 結果を画面に表示
    cv2.imshow("Frame1", thresh_con)
    cv2.imshow("Frame2", frameDelta)
    cv2.imshow("Frame3", bin_frame)
    
    key = cv2.waitKey(1)  # キー入力を待つ(1ms)
    if key == 27:  # Escキーが押されたら終了
        break

# キャプチャを解放
cap.release()
# すべてのOpenCVウィンドウを閉じる
cv2.destroyAllWindows()

第2章: 動体検出のための前処理

動体検出プログラムにおいて、前処理は非常に重要な役割を果たします。このプロセスは、動画から取得したフレームを解析しやすくするために、画像の変換や背景画像の準備などを行います。前処理の目的は、動体と背景を効果的に区別し、後続の処理で動体を正確に検出できるようにすることです。

2.1 グレースケール変換

動体検出の最初のステップは、読み込んだフレームをグレースケールに変換することです。この変換により、画像から色情報を削除し、明るさ(輝度)の情報のみを保持します。グレースケール変換を行う理由は、処理の複雑さを減らし、アルゴリズムの計算速度を向上させるためです。色情報は動体検出において必ずしも重要ではなく、輝度情報のみでも十分な場合が多いためです。

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # フレームをグレースケールに変換

2.2 背景画像の初期化と更新

動体検出では、背景画像と現在のフレームとの差分を利用して動体を識別します。プログラムの初期段階で最初のフレームを背景画像として設定し、その後、cv2.accumulateWeighted関数を使用して背景画像を逐次更新します。この関数は、指定された重みを用いて現在のフレームを背景画像に融合させ、環境の変化に対応します。

if avg is None:
    avg = gray.copy().astype("float")
cv2.accumulateWeighted(gray, avg, 0.60)  # 背景画像を更新

2.3 差分画像の生成

背景画像と現在のフレームとの差分を計算することで、動体が存在する場所を特定します。この差分計算にはcv2.absdiff関数を使用します。差分画像では、静止している背景部分は輝度差が小さく、動いている部分(動体)は輝度差が大きくなります。

frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))

2.4 動体検知範囲の適用

特定の範囲内でのみ動体を検出したい場合、差分画像から特定の領域を切り出すことができます。これにより、画像全体を処理する代わりに、興味のある領域のみに焦点を当てることが可能になり、無関係な動きによる誤検出を減らすことができます。

frameDelta = frameDelta[pointY:pointY+widthY, pointX:pointX+widthX]

2.5 結論

この章では、動体検出プログラムにおける前処理のステップについて詳細に説明しました。グレースケール変換、背景画像の初期化と更新、差分画像の生成、そして動体検知範囲の適用は、効率的かつ正確な動体検出を行うための基礎を形成します。これらの前処理ステップを通じて、プログラムは動体と背景を効果的に区別し、後続の処理での動体検出の精度を高めることができます。

第3章: 動体検出と結果の保存

動体検出プロセスの核心部分は、前処理された画像から動体を特定し、その情報を利用することです。この章では、差分画像から動体を検出し、その結果をどのように処理し保存するかについて詳しく解説します。

3.1 閾値処理による動体の検出

差分画像では、動体と背景との輝度差を利用していますが、この差分情報をもとに動体を明確に区別するには閾値処理が必要です。cv2.threshold関数を使用して、差分画像を二値化します。この処理により、差分が閾値以上(動体とみなされる輝度差)のピクセルは白(255)、それ未満のピクセルは黒(0)に変換されます。これによって、動体が白い領域として明確に識別されます。

thresh = cv2.threshold(frameDelta, 10, 255, cv2.THRESH_BINARY)

3.2 輪郭の検出と動体の特定

二値化された画像上で、cv2.findContours関数を用いて輪郭を検出します。この関数は、白い領域(動体)の輪郭を見つけ出し、それらの形状を表す点のリストを返します。検出された輪郭に基づき、動体の位置、サイズ、形状などの情報を取得することができます。

contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

cv2.findContours関数は、二値画像から輪郭を検出するためのOpenCVの関数です。この関数は画像内の白い部分(前景)を輪郭として検出し、それらの輪郭を表す点のリストを返します。cv2.findContours関数の引数について詳しく説明します。

引数の詳細

cv2.findContours(image, mode, method)の形式で、以下の引数を取ります。

  1. image: 入力画像で、二値画像(白黒の画像)である必要があります。この関数は入力画像を変更する可能性があるため、.copy()を使用して元の画像を保持しておくのが一般的です。
  2. mode: 輪郭を検出する際の輪郭の階層情報を指定します。主に以下の値が使用されます。
    • cv2.RETR_EXTERNAL: 最も外側の輪郭のみを検出します。このモードを使用すると、オブジェクトの外周部分のみが抽出され、オブジェクト内部の輪郭は無視されます。
    • cv2.RETR_LIST: すべての輪郭を検出し、階層関係を無視します。検出された輪郭はリストに追加されますが、特定の親子関係は考慮されません。
    • cv2.RETR_CCOMP: すべての輪郭を検出し、二つのレベルの階層を構成します。外側の輪郭は階層の最上位に、オブジェクトの穴(内部の輪郭)は次のレベルに配置されます。
    • cv2.RETR_TREE: すべての輪郭を検出し、完全な階層構造を復元します。このモードは輪郭間の全ての親子関係を保持します。
  3. method: 輪郭の近似方法を指定します。輪郭を形成する点の数を減らすことで、輪郭データのサイズを削減し、処理の効率化を図ることができます。
    • cv2.CHAIN_APPROX_NONE: 輪郭を形成するすべての点を保持します。隣接する2点の間にある中間点もすべて保持されます。
    • cv2.CHAIN_APPROX_SIMPLE: 輪郭を形成する点を圧縮し、輪郭を完全に表現するのに必要な点のみを保持します。例えば、直線の輪郭はその両端の点のみを保持し、曲線の部分はその形状を表現するのに十分な最小限の点を保持します。

戻り値

この関数は3つの値を返しますが、OpenCVのバージョンによっては2つの値のみを返す場合があります。

  • 輪郭: 検出された輪郭を表す点のリストのリスト。各輪郭は点のリストとして表されます。
  • 階層: 輪郭の階層情報。各輪郭に対して、[Next, Previous, First_Child, Parent]という形式の情報が含まれています。

例示したcv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)では、最も外側の輪郭のみを検出し、輪郭を形成する点を圧縮しています。これは、多くの実用的なアプリケーションで一般的な設定であり、処理速度とメモリ使用量のバランスを取りながら、必要な輪郭情報を効率的に取得できます。

openCVの公式サイトが詳しいので、ぜひ見てください。

3.3 動体に対する処理

検出された各輪郭に対して、面積が一定値以上のものを動体として認識します。小さすぎる輪郭はノイズや誤検出とみなして無視します。動体と判断された場合、その輪郭を元に外接矩形を計算し、動体を囲む枠を描画します。さらに、動体検出の回数をカウントし、条件に応じて動体が捉えられたフレームを画像ファイルとして保存します。

for contour in contours:
    area = cv2.contourArea(contour)  # 輪郭の面積を計算
    if area > 3000:  # 面積が一定値以上なら動体とみなす
        x, y, w, h = cv2.boundingRect(contour)  # 外接矩形の座標とサイズを取得
        cv2.rectangle(thresh_con, (x+pointX, y+pointY), (x+pointX + w, y+pointY + h), (0, 255, 0), 2)  # 動体に枠線を描画

3.4 結果の表示と保存

動体検出の結果はリアルタイムで表示され、特定の条件を満たす場合には画像として保存されます。これにより、後で分析を行うためのデータを収集することが可能です。また、動体の面積や検出回数などの情報も画面に表示され、動体検出プロセスの進行状況を把握することができます。

3.5 結論

この章では、閾値処理による動体の検出、輪郭の検出と動体の特定、そして結果の表示と保存について詳しく解説しました。動体検出は、動画監視、交通流分析、行動認識など、多様なアプリケーションで利用される重要な技術です。このプロセスを通じて、動体検出の基本原理と具体的な実装方法について理解を深めることができました。

第4章: 動体検出プログラムの応用例

このプログラムは、シンプルながらも強力な動体検出機能を提供します。セキュリティカメラの映像解析、交通監視システム、動物の行動分析など、様々な分野で応用可能です。さらに、動体検出の精度を高めるためには、動体検出範囲の調整や閾値の最適化、さらには検出アルゴリズムの改善など、プログラムのカスタマイズが可能です。

この記事を通じて、PythonとOpenCVを使用した基本的な動体検出プログラムの作成方法を理解し、自分のプロジェクトに応用するための基礎を築くことができました。動体検出技術は進化し続けており、新しい知見や技術を取り入れながらスキルを磨いていくことが重要です。

🚀 0円で現役エンジニアから学べる【Techスクールオンライン】のお申込みをお勧めします。 このオンラインスクールでは、現役のエンジニアから直接学ぶことができ、プログラミングの基礎から高度なスキルまでを習得できます。しかも、今なら 0円 で受講できるチャンス。
私がツナグバに登録してから、求人情報が豊富に届き、自分に合った仕事を見つけることができました。特に、第二新卒向けの求人情報が多いので、自分のスキルや経験を活かしながら新たなキャリアに挑戦することができました。転職活動は不安も多いですが、ツナグバのサポートがあれば、成功への道が明るく感じました。