MATLABとWebカメラで簡易顔検出(その1.5)

前回記事では、カメラ入力から1フレームだけ取得して、フレーム上の顔を検出するスクリプトを紹介しました。

MATLABとWebカメラで簡易顔検出(その1) - 惨事のおやつ
記事移転しました。 移転先はこちら。 tonakai1070.com

今回は、フレームの取得と検出をループで繰り返し実行し、カメラ入力に対して常に顔検出を行います。

※ちなみに※
本記事で示すスクリプトでは、カメラから取得するフレームに対して常に顔検出のアルゴリズムを実行しています。
しかし、それでは計算量が大きくなりすぎるので、通常は全フレームに検出を行うのではなく、間引きした特定のタイミングだけ検出を実行し、検出後は特徴点を追跡する手法をとるそうです。
間引きした特定のタイミングの例としては、検出したい物体が映像に現れた最初のフレームや、追跡に使う特徴点数がある一定数を下回ったタイミングなどを指します。

画像処理関係で、前回記事に対して新しい要素はありません。

カメラ映像内の顔検出(リアルタイム)

スクリプト開始後、ループの中で下記を繰り返します。

  1. カメラ入力から1フレーム取得
  2. 映像フレーム上に人の顔があれば、顔を検出した位置に黄色い枠を描画

参考ページ

ライブ ビデオ取得を使用した、顔の検出と追跡 - MATLAB & Simulink - MathWorks 日本
この例では、KLT アルゴリズムを使用してライブ ビデオ ストリーム内の顔を自動的に検出し追跡する方法を説明します。

検出処理のループ実行方法を参考にしました。
参考記事中では、先に述べたように、全フレームに対して顔検出するのではなく、特徴点の追跡を行っています。

スクリプト全体

ライブスクリプト全体を下記に示します。また、ブログ掲載用のGIFアニメを保存する処理を含んでいます。

%camera接続
webcamlist
cam = webcam(1);
%preview(cam) %カメラ動作確認、確認できたらコメントアウト

%顔検出器のコンストラクト
faceDetector = vision.CascadeObjectDetector();
%カメラ映像のスクショを1枚取得(動画プレーヤの画面サイズ設定用)
videoFrame = snapshot(cam);
frameSize = size(videoFrame)

%映像表示ウィンドウ作成
margin=30; %フチの幅
videoWindowPosSize_Detected = [100 100 [frameSize(2), frameSize(1)]+margin ]
videoPlayer_Detected = vision.VideoPlayer('Position', videoWindowPosSize_Detected);
%GIF保存用の事前準備
frame_index=1;
GifFrames={} %1フレームのイメージ配列格納用。cell配列で格納する
FrameTimeStamps=datetime.empty %フレームレート間隔計算用のタイムスタンプログ。プッシュする型(今回はdatetime型)の空のベクトルとして初期化

%ループ処理で入力映像に検出処理をかけ続ける

%ループフラグ
runLoop = true;

while runLoop

    %最新のスクショ取得
    videoFrame = snapshot(cam);

    %最新のスクショに対して顔検出実行
    bbox = step(faceDetector, videoFrame);

    if ~isempty(bbox)
        %顔を検出した場合は表示用フレームに検出位置のボックスを描画
        videoFrameDetected = insertShape(videoFrame,'Rectangle', bbox);
    else
        %顔が検出されなかった場合はオリジナル(枠なし)映像を表示
        videoFrameDetected = videoFrame;
    end

    %プレーヤ上でに最新フレームを表示
    step(videoPlayer_Detected, videoFrameDetected);

    %GIF保存用にフレームをCell配列に保存しておく
    GifFrames{frame_index}=videoFrameDetected;
    %タイムスタンプをプッシュ
    FrameTimeStamps(frame_index)=datetime('now');
    frame_index=frame_index+1;

    runLoop = isOpen(videoPlayer_Detected);
end

%GIFアニメとして保存
SaveFramesAsGif('result.gif', GifFrames, calcFrameInterval(FrameTimeStamps));
%終了処理
release(videoPlayer_Detected);
release(faceDetector);
clear cam;
function interval = calcFrameInterval(FrameTimeStamps)
%GIFアニメの各フレームの記録時刻(タイムスタンプ)から、フレーム間隔を計算する

    %タイムスタンプから各フレームの間隔を計算
    Durations=FrameTimeStamps(2:end)-FrameTimeStamps(1:end-1);

    %GIFアニメのフレーム間隔は、元映像各フレーム間隔の中央値を採用
    interval=seconds(median(Durations));
end

function SaveFramesAsGif(filename, Frames, FrameInterval)
%入力されたフレームデータをGIFファイルとして保存する

    n=length(Frames);

    %1フレーム目の出力でGIFに無限ループ再生属性を設定
    [A,map] = rgb2ind(Frames{1},256);
    imwrite(A,map,filename,'gif','LoopCount',Inf,'DelayTime',FrameInterval);

    %2フレーム以降をappendモードで出力
    for i=2:n
        [A,map] = rgb2ind(Frames{i},256);
        imwrite(A,map,filename,'gif','WriteMode','append','DelayTime',FrameInterval);
    end
end

実行結果

カメラから取得した1フレームに対して顔検出&枠の描画をするのは前回と同様。
videoPlayerオブジェクトに表示用のフレームを入力して、動画プレーヤ上で映像出力を行います。

[f:id:tamakinaoto7010:20201010162048g:plain]
実行結果
  • 画像左:頭を左右に揺らしても、新しいフレームに対して顔を検出してくれています。
  • 画像右:頭を傾けたら、顔として検出できなくなります。

スクリプト解説

実行するスクリプトを順番に説明します。前回記事と重複するものの説明は省いています。

動画プレーヤの生成
%カメラ映像のスクショを1枚取得(動画プレーヤの画面サイズ設定用)
videoFrame = snapshot(cam);
frameSize = size(videoFrame)

%映像表示ウィンドウ作成
margin=30; %映像プレーヤウィンドウのフチの幅
videoWindowPosSize_Detected = [100 100 [frameSize(2), frameSize(1)]+margin ]
videoPlayer_Detected = vision.VideoPlayer('Position', videoWindowPosSize_Detected);

動画プレーヤの画面サイズを設定する際に必要になるので、カメラからフレームを一つ取得し、カメラ入力映像のサイズを取得しておきます。

vision.VideoPlayer()を用いて、動作プレーヤオブジェクトをコンストラクトします。コンストラクト時に、'Position'というプロパティに対してプレーヤウィンドウの位置とサイズを設定します。

実行結果

顔検出のループ実行

%ループフラグ
runLoop = true;

while runLoop

    %最新のスクショ取得
    videoFrame = snapshot(cam);

    %最新のスクショに対して顔検出実行
    bbox = step(faceDetector, videoFrame);

    if ~isempty(bbox)
        %顔を検出した場合は表示用フレームに検出位置のボックスを描画
        videoFrameDetected = insertShape(videoFrame,'Rectangle', bbox);
    else
        %顔が検出されなかった場合はオリジナル(枠なし)映像を表示
        videoFrameDetected = videoFrame;
    end

    %プレーヤ上でに最新フレームを表示
    step(videoPlayer_Detected, videoFrameDetected);

% ~(GIF保存部は略)~

    runLoop = isOpen(videoPlayer_Detected);
end

カメラからフレームを1枚取得して、取得したフレームに対してvision.CascadeObjectDetector()で作成しておいた顔検出器のアルゴリズムを実行します(前回と同じ)。
フラグがTRUEの間、フレームの取得と顔検出アルゴリズムの実行をwhileループで繰り返します。
動画プレーヤを閉じたら、ループを終了させます。

顔を検出した(顔検出器が顔位置のBoundingBoxを返した)ら、表示用に顔位置の枠線をフレーム上に描画します。顔を検出していなければ、元のフレームをそのまま表示させます。

表示させるフレーム(videoFrameDetected `)が決まったら、`step関数を用いてVideoPlayerオブジェクトのアルゴリズムを表示フレームに対して実行させると、プレーヤ上で動画を表示してくれます。

※videoPlayerが表示してくれるので、GIF保存用のコードが無くても動きます。GIF保存処理は別記事にて紹介します。

MATLABでカメラ入力映像をGIFファイルに保存
MATLABでただいま画像処理をお勉強中なのですが、ブログ掲載用に実行結果をGIFアニメ保存したかったので、保存用の関数を作成しました。参考ページ上記事で紹介されているスクリプトを参考にしました。私の場合は映像を保存することが目的で...

終了処理

%終了処理
release(videoPlayer_Detected);
release(faceDetector);
clear cam;

使ったオブジェクトをデストラクトしておきます。

まとめ

カメラ入力映像に対して、常に顔の検出を実行するスクリプトを紹介しました。
実際に検出ソフトを作る場合には、計算量低減のため、特徴量の追跡処理が追加で必要になります。

また、標準のvision.CascadeObjectDetectorでは、傾いた顔は検出できません。特徴点の追跡処理を追加することで、顔が傾いても追跡することができるようになるそうで、次回取り入れてみましょう。

コメント