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

今回は、フレームの取得と検出をループで繰り返し実行し、カメラ入力に対して常に顔検出を行います。
※ちなみに※
本記事で示すスクリプトでは、カメラから取得するフレームに対して常に顔検出のアルゴリズムを実行しています。
しかし、それでは計算量が大きくなりすぎるので、通常は全フレームに検出を行うのではなく、間引きした特定のタイミングだけ検出を実行し、検出後は特徴点を追跡する手法をとるそうです。
間引きした特定のタイミングの例としては、検出したい物体が映像に現れた最初のフレームや、追跡に使う特徴点数がある一定数を下回ったタイミングなどを指します。
画像処理関係で、前回記事に対して新しい要素はありません。
カメラ映像内の顔検出(リアルタイム)
スクリプト開始後、ループの中で下記を繰り返します。
- カメラ入力から1フレーム取得
- 映像フレーム上に人の顔があれば、顔を検出した位置に黄色い枠を描画
参考ページ
検出処理のループ実行方法を参考にしました。
参考記事中では、先に述べたように、全フレームに対して顔検出するのではなく、特徴点の追跡を行っています。
スクリプト全体
ライブスクリプト全体を下記に示します。また、ブログ掲載用の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オブジェクトに表示用のフレームを入力して、動画プレーヤ上で映像出力を行います。
- 画像左:頭を左右に揺らしても、新しいフレームに対して顔を検出してくれています。
- 画像右:頭を傾けたら、顔として検出できなくなります。
スクリプト解説
実行するスクリプトを順番に説明します。前回記事と重複するものの説明は省いています。
動画プレーヤの生成
%カメラ映像のスクショを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()
フラグが
の間、フレームの取得と顔検出アルゴリズムの実行をwhileループで繰り返します。TRUE
動画プレーヤを閉じたら、ループを終了させます。
顔を検出した(顔検出器が顔位置のBoundingBoxを返した)ら、表示用に顔位置の枠線をフレーム上に描画します。顔を検出していなければ、元のフレームをそのまま表示させます。
表示させるフレーム(
関数を用いてVideoPlayerオブジェクトのアルゴリズムを表示フレームに対して実行させると、プレーヤ上で動画を表示してくれます。videoFrameDetected
`)が決まったら、
`step
※videoPlayerが表示してくれるので、GIF保存用のコードが無くても動きます。GIF保存処理は別記事にて紹介します。

終了処理
%終了処理
release(videoPlayer_Detected);
release(faceDetector);
clear cam;
使ったオブジェクトをデストラクトしておきます。
まとめ
カメラ入力映像に対して、常に顔の検出を実行するスクリプトを紹介しました。
実際に検出ソフトを作る場合には、計算量低減のため、特徴量の追跡処理が追加で必要になります。
また、標準の
では、傾いた顔は検出できません。特徴点の追跡処理を追加することで、顔が傾いても追跡することができるようになるそうで、次回取り入れてみましょう。vision.CascadeObjectDetector
コメント