Atom's tech blog

OpenCVで人検出と行動を追跡をしてみた

概要(HAAR)

  • 人は全身用のHAARカスケードファイル「haarcascade_fullbody.xml」を使用
  • 人を判定する最低サイズは minSize=(40, 40)
  • オプティカルフローで隣接フレーム間の物体の動きを検出
  • 追跡した箇所を線で描画
  • 人検出した箇所を矩型で描画
  • 人検出した数を左下画面に表示

コメント

  • 人追跡はサンプルコードをほぼ流用して動作できた
  • 人追跡はオプティカルフローを使用している
  • オプティカルフローとは、二枚以上の時間的に連続する画像を用いて、その画像内で共通して写っている部分などから動作やあるパターンが移動する方向を推定しベクトルにしたもの。ロボットの物体追跡やドローンの位置制御推定、物体の速度を測定などに使えるみたい
  • ラズパイでも動作可能
  • 今回はサンプルコードでも使用しているOpenCVで提供している動画ファイルを活用
  • 恐らく人検出と人追跡の特徴量検出が異なるので、片方だけ検出できないケースある
  • 人検知はHAARとHOG+SVMの2つをトライ
  • 使ったツールバージョンは以下
  • MacOS Catalina / python3.5.6 / Opencv3.4.2 / VS code1.38.1
イメージ1(HAAR)

f:id:iAtom:20201008155102p:plain

イメージ2(HAAR)

f:id:iAtom:20201008155130p:plain

イメージ3(HAAR)

f:id:iAtom:20201008155146p:plain

ファイル構成

  • human_detect_harr.py : 人検出/人追跡ソースファイル
  • 768x576.avi:動画ファイル

ソースコード(HAAR)

import numpy as np
import cv2
import time

# 体全体のカスケードファイル
fullbody_detector = cv2.CascadeClassifier("/Users/local/source/opencv/face_recognition/data_xml/haarcascade_fullbody.xml")
# サンプル画像
cap = cv2.VideoCapture('768x576.avi')

# Shi-Tomasiのコーナー検出パラメータ
feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.3,
                       minDistance = 7,
                       blockSize = 7 )

# Lucas-Kanade法のパラメータ
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

# ランダムに色を100個生成(値0~255の範囲で100行3列のランダムなndarrayを生成)
color = np.random.randint(0, 255, (100, 3))

# 最初のフレームの処理
end_flag, frame = cap.read()
# グレースケール変換
gray_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 追跡に向いた特徴
feature_prev = cv2.goodFeaturesToTrack(gray_prev, mask = None, **feature_params)
# 元の配列と同じ形にして0を代入
mask = np.zeros_like(frame)

while(end_flag):
    # グレースケールに変換
    gray_next = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    start = time.time()  
    # 全身の人を検出 
    # minSize:物体が取り得る最小サイズ。これよりも小さい物体は無視される
    # minNeighbors:物体候補となる矩形は,最低でもこの数だけの近傍矩形を含む
    body = fullbody_detector.detectMultiScale(gray_next,scaleFactor=1.1, minNeighbors=3, minSize=(40, 40))
    end = time.time()  
    # 検出時間を表示  
#    print("{} : {:4.1f}ms".format("detectTime", (end - start) * 1000))

    # オプティカルフロー検出
    # オプティカルフローとは物体やカメラの移動によって生じる隣接フレーム間の物体の動きの見え方のパターン
    feature_next, status, err = cv2.calcOpticalFlowPyrLK(gray_prev, gray_next, feature_prev, None, **lk_params)
    # オプティカルフローを検出した特徴点を選別(0:検出せず、1:検出した)
    good_prev = feature_prev[status == 1]
    good_next = feature_next[status == 1]

    # オプティカルフローを描画
    for i, (next_point, prev_point) in enumerate(zip(good_next, good_prev)):
        prev_x, prev_y = prev_point.ravel()
        next_x, next_y = next_point.ravel()
        mask = cv2.line(mask, (next_x, next_y), (prev_x, prev_y), color[i].tolist(), 2)
        frame = cv2.circle(frame, (next_x, next_y), 5, color[i].tolist(), -1)
    img = cv2.add(frame, mask)
    
    # 人検出した数表示のため変数初期化
    human_cnt = 0
    # 人検出した部分を長方形で囲う
    for (x, y, w, h) in body:
        cv2.rectangle(img, (x, y),(x+w, y+h),(0,255,0),2)
        # 人検出した数を加算
        human_cnt += 1

    # 人検出した数を表示
    cv2.putText(img, "Human Cnt:{}".format(int(human_cnt)),(10,550), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)
    # ウィンドウに表示
    cv2.imshow('human_view', img)

   # ESCキー
    k = cv2.waitKey(1)
    if k == 27:
        break

    # 次のフレーム、ポイントの準備
    gray_prev = gray_next.copy()
    feature_prev = good_next.reshape(-1, 1, 2)
    end_flag, frame = cap.read()

# 終了処理
cv2.destroyAllWindows()
cap.release()

概要(HOG+SVM

  • HOG(Histograms of Oriented Gradients)特徴量は領域内の勾配方向ごとの勾配強度を計算してそれをヒストグラムにしたもの。画像の輝度の変化の境界線を取り出す事ができる。簡単いうとエッジ検出ができるというイメージかと。
  • SVMSupport Vector Machine)は入力が正解か不正解を分類する分類器。教師あり学習を用いるパターン認識モデルの一つ(by Wikipedia
  • OpenCVにはHOG+SVMの学習済み人の識別器(cv2.HOGDescriptor_getDefaultPeopleDetector)があるため、こちらを使ってみる事にする
イメージ1(HOG+SVM

f:id:iAtom:20201008155234p:plain

イメージ2(HOG+SVM

f:id:iAtom:20201008155259p:plain

イメージ3(HOG+SVM

f:id:iAtom:20201008155320p:plain

ソースコードHOG+SVM

import numpy as np
import cv2
import time

# HoG特徴量の計算
hog = cv2.HOGDescriptor()

# サンプル画像
cap = cv2.VideoCapture('768x576.avi')

# Shi-Tomasiのコーナー検出パラメータ
feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.3,
                       minDistance = 7,
                       blockSize = 7 )

# Lucas-Kanade法のパラメータ
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

# ランダムに色を100個生成(値0~255の範囲で100行3列のランダムなndarrayを生成)
color = np.random.randint(0, 255, (100, 3))

# 最初のフレームの処理
end_flag, frame = cap.read()

# グレースケール変換
gray_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 追跡に向いた特徴
feature_prev = cv2.goodFeaturesToTrack(gray_prev, mask = None, **feature_params)
# 元の配列と同じ形にして0を代入
mask = np.zeros_like(frame)

# 全身の人を検出(SVM)
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
hogParams = {'winStride': (8, 8), 'padding': (32, 32), 'scale': 1.1}

while(end_flag):
    # グレースケールに変換
    gray_next = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    #時間取得
    start = time.time()
    # 人を検出した座標
    human, r = hog.detectMultiScale(frame, **hogParams)
    #時間取得
    end = time.time()
    # 検出時間を表示
#    print("{} : {:4.1f}ms".format("detectTime", (end - start) * 1000)) 

    # オプティカルフロー検出
    # オプティカルフローとは物体やカメラの移動によって生じる隣接フレーム間の物体の動きの見え方のパターン
    feature_next, status, err = cv2.calcOpticalFlowPyrLK(gray_prev, gray_next, feature_prev, None, **lk_params)
    # オプティカルフローを検出した特徴点を選別(0:検出せず、1:検出した)
    good_prev = feature_prev[status == 1]
    good_next = feature_next[status == 1]

    # オプティカルフローを描画
    for i, (next_point, prev_point) in enumerate(zip(good_next, good_prev)):
        prev_x, prev_y = prev_point.ravel()
        next_x, next_y = next_point.ravel()
        mask = cv2.line(mask, (next_x, next_y), (prev_x, prev_y), color[i].tolist(), 2)
        frame = cv2.circle(frame, (next_x, next_y), 5, color[i].tolist(), -1)

    svm_img = cv2.add(frame, mask)

    # 人検出した数表示のため変数初期化
    svm_human_cnt = 0  
    # 人検出した部分を長方形で囲う(SVM)
    for (x, y, w, h) in human:
        cv2.rectangle(svm_img, (x, y),(x+w, y+h),(0,0,255), 2)
        svm_human_cnt += 1

    # 人検出した数を表示
    cv2.putText(svm_img, "Human Cnt:{}".format(int(svm_human_cnt)),(10,550), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)
    cv2.moveWindow("SVM",650,100) # Window表示位置指定 
    # ウィンドウに表示
    cv2.imshow('SVM', svm_img)

    # ESCキー押下で終了
    if cv2.waitKey(30) & 0xff == 27:
        break

    # 次のフレーム、ポイントの準備
    gray_prev = gray_next.copy()
    feature_prev = good_next.reshape(-1, 1, 2)
    end_flag, frame = cap.read()

# 終了処理
cv2.destroyAllWindows()
cap.release()

HAAR+CascadeHOG+SVMを同時に動かしてみた

イメージ1(Cascade/SVM

f:id:iAtom:20201008155420p:plain

イメージ2(Cascade/SVM

f:id:iAtom:20201008155453p:plain

イメージ3(Cascade/SVM

f:id:iAtom:20201008155523p:plain

イメージ4(Cascade/SVM

f:id:iAtom:20201008155538p:plain

ソースコード(HAAR+SVM

ソースコードにはharrとsvmの画像を横に結合(cv2.hconcat)し、保存(cv2.imwrite)する処理も含まれているのでご注意を。

import numpy as np
import cv2

# 体全体のカスケードファイル
fullbody_detector = cv2.CascadeClassifier("/Users/local/source/opencv/face_recognition/data_xml/haarcascade_fullbody.xml")
# HoG特徴量の計算
hog = cv2.HOGDescriptor()

# サンプル画像
cap = cv2.VideoCapture('768x576.avi')

# Shi-Tomasiのコーナー検出パラメータ
feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.3,
                       minDistance = 7,
                       blockSize = 7 )

# Lucas-Kanade法のパラメータ
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

# ランダムに色を100個生成(値0~255の範囲で100行3列のランダムなndarrayを生成)
color = np.random.randint(0, 255, (100, 3))

# 最初のフレームの処理
end_flag, frame = cap.read()

# グレースケール変換
gray_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 追跡に向いた特徴
feature_prev = cv2.goodFeaturesToTrack(gray_prev, mask = None, **feature_params)
# 元の配列と同じ形にして0を代入
mask = np.zeros_like(frame)

frame_cnt=0
while(end_flag):
    # グレースケールに変換
    gray_next = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # 全身の人を検出(haarcascade_fullbody.xml) 
    # minSize:物体が取り得る最小サイズ。これよりも小さい物体は無視される
    # minNeighbors:物体候補となる矩形は,最低でもこの数だけの近傍矩形を含む
    body = fullbody_detector.detectMultiScale(gray_next,scaleFactor=1.1, minNeighbors=3, minSize=(40, 40))

    # 全身の人を検出(SVM)
    hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
    hogParams = {'winStride': (8, 8), 'padding': (32, 32), 'scale': 1.1}
    # 人を検出した座標
    human, r = hog.detectMultiScale(frame, **hogParams)

    # オプティカルフロー検出
    # オプティカルフローとは物体やカメラの移動によって生じる隣接フレーム間の物体の動きの見え方のパターン
    feature_next, status, err = cv2.calcOpticalFlowPyrLK(gray_prev, gray_next, feature_prev, None, **lk_params)
    # オプティカルフローを検出した特徴点を選別(0:検出せず、1:検出した)
    good_prev = feature_prev[status == 1]
    good_next = feature_next[status == 1]

    # オプティカルフローを描画
    for i, (next_point, prev_point) in enumerate(zip(good_next, good_prev)):
        prev_x, prev_y = prev_point.ravel()
        next_x, next_y = next_point.ravel()
        mask = cv2.line(mask, (next_x, next_y), (prev_x, prev_y), color[i].tolist(), 2)
        frame = cv2.circle(frame, (next_x, next_y), 5, color[i].tolist(), -1)
    img = cv2.add(frame, mask)
    svm_img = cv2.add(frame, mask)

    # 人検出した数表示のため変数初期化
    human_cnt = 0
    # 人検出した部分を長方形で囲う(Haar)
    for (x, y, w, h) in body:
        cv2.rectangle(img, (x, y),(x+w, y+h),(0,255,0),2)
        # 人検出した数を加算
        human_cnt += 1
 
    svm_human_cnt = 0  
    # 人検出した部分を長方形で囲う(SVM)
    for (x, y, w, h) in human:
        cv2.rectangle(svm_img, (x, y),(x+w, y+h),(0,0,255), 2)
        svm_human_cnt += 1

    # 人検出した数を表示
    cv2.putText(img, "haar Human Cnt:{}".format(int(human_cnt)),(10,550), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) 
    cv2.moveWindow("Haar",10,100) # Window表示位置指定    
    # ウィンドウに表示
    cv2.imshow('Haar', img)

    # 人検出した数を表示
    cv2.putText(svm_img, "Svm Human Cnt:{}".format(int(svm_human_cnt)),(10,550), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)
    cv2.moveWindow("SVM",650,100) # Window表示位置指定 
    # ウィンドウに表示
    cv2.imshow('SVM', svm_img)


    frame_cnt += 1
    # 画像を結合する   
    image_hcomcat = cv2.hconcat([img, svm_img])
    # 画像を保存する
    cv2.imwrite("./harr_svm_frame"+str(frame_cnt)+".png",image_hcomcat)

    # ESCキー押下で終了
    if cv2.waitKey(30) & 0xff == 27:
        break

    # 次のフレーム、ポイントの準備
    gray_prev = gray_next.copy()
    feature_prev = good_next.reshape(-1, 1, 2)
    end_flag, frame = cap.read()

# 終了処理
cv2.destroyAllWindows()
cap.release()

人検出時間

アルゴリズム Ave. Min. Max.
SVM 47.4ms 40.4ms 63.9ms
HAAR 20.7ms 15.5ms 28.0ms

まだ中身が理解できていない箇所あり。もっと勉強しないといけない......