【OpenCV C++】顔認識をリアルタイムで行いたい ~4日目~
どうも、プログラミングの鬼シヨツ鬼です。
「OpenCV(C++)で顔認識をリアルタイムに行いたいぜ」って人に向けて、僕が試行錯誤した開発記を連載6回で書いています。
過去の記事は↓からどうぞ!
【OpenCV C++】顔認識をリアルタイムで行いたい ~1日目~ - シヨツ鬼のブログ
ちなみに、この記事は下記の動画と関連しています。
こちらの動画を先に見ていただくと、実際に動く際のイメージが掴めます。
YouTube:【自作】ゆゆうたさんをバーチャルユーチューバー化してみた
また、OpenCVのインストール方法はこちらの動画をご覧ください。
YouTube:OpenCVのインストール方法【Visual Studio】【C++】
4日目:顔の追従検索で検出速度向上!
3日目のアイデアを実現します。
やりたいこと
・(今度こそ)顔を一度検知した場合、次のフレームでは、直前の顔検知位置より一回り大きい範囲を検出範囲とする
ソースコード
#include "opencv2/opencv.hpp" #include "opencv2/highgui.hpp" #include <vector> using namespace cv; using namespace std; int main() { VideoCapture cap(0); // USBカメラのオープン if (!cap.isOpened()) //カメラが起動できなかった時のエラー処理 { return -1; } Mat frame; //USBカメラから得た1フレームを格納する場所 CascadeClassifier cascade; //カスケード分類器格納場所 cascade.load("C:/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml"); //正面顔情報が入っているカスケード vector<Rect> faces; //輪郭情報を格納場所 Mat detection_frame;//顔の検出範囲 Rect roi; int detection_flag = 0;//直前に顔を検出したか(0:してない 1:した) int x = 0;//顔座標の左上のx座標 int y = 0;//顔座標の左上のy座標 int x_end = 0;//顔座標の右下のx座標 int y_end = 0;//顔座標の右下のy座標 int basic_flag = 0;//連続で顔を検知しているかフラグ(0:いいえ(初めての検知) 1:はい(2連続以上の検知)) int x_basic = 0;//基準点のX座標 int y_basic = 0;//基準点のY座標 while (1)//無限ループ { cap >> frame; //USBカメラが得た動画の1フレームを格納 //直前のフレームで顔が検出されていない場合 if (detection_flag == 0) { //検出範囲はカメラ映像全体とする detection_frame = frame; //基準点をリセット basic_flag = 0; x_basic = 0; y_basic = 0; } else {//直前のフレームで顔が検出された場合 //検出範囲として、直前のフレームの顔検出の範囲より一回り(上下左右50pixel)大きい範囲とする Rect roi(Point(x - 50, y - 50), Point(x_end + 50, y_end + 50)); detection_frame = frame(roi); //検出範囲をピンク枠で囲う rectangle(frame, Point(x - 50, y - 50), Point(x_end + 50, y_end + 50), Scalar(200, 0, 255), 3); //連続検索フラグを1(2連続以上の) basic_flag = 1; } detection_flag = 0; //格納されたフレームに対してカスケードファイルに基づいて顔を検知 cascade.detectMultiScale(detection_frame, faces, 1.2, 5, 0, Size(20, 20)); //顔を検出した場合 if (faces.size() > 0) { //顔の検出フラグを1(発見)にする detection_flag = 1; //顔座標の左上の座標を求める if (basic_flag == 0) {//初検知の場合 //初検知の場合は検出された値をそのまま使う x = faces[0].x; y = faces[0].y; } else if (basic_flag == 1) {//連続検知の場合 //連続検知の場合は、検出座標と直前の基準点を使って顔座標を検出する //(x_basic - 50):カメラキャプチャ全体の座標から見た検出範囲の左上の座標(ピンク枠の左上) //faces[0].x:切り出したフレーム(ピンク枠内)から見た顔の左上の座標(赤枠の左上) x = (x_basic - 50) + faces[0].x ; y = (y_basic - 50) + faces[0].y ; } //顔座標の右下の座標を求める x_end = x + faces[0].width; y_end = y + faces[0].height; //基準点を今算出した顔座標に更新する x_basic = x; y_basic = y; rectangle(frame, Point(x, y), Point(x_end, y_end), Scalar(0, 0, 255), 3); //printf("(%d,%d) (%d,%d)\n", x, y, x_end, y_end); } imshow("window", frame);//画像を表示. int key = waitKey(1); if (key == 113)//qボタンが押されたとき { break;//whileループから抜ける(終了) } } destroyAllWindows(); return 0; }
解説
ポイントは「切り出したフレーム内で検知した顔の座標」と「カメラキャプチャ全体の座標」が異なるということです。
切り出したフレーム内で顔の検出を行うと、そのフレーム内での座標が取れるため、そのままその座標を使ってカメラキャプチャのフレームに四角を描くと全部左上に表示されるようなことになります。
まとめ
検出速度はかなり向上しました!
3日目では座標の扱いで混乱しましたが、落ち着いてやればそんなに難しいものではなかったですね(笑)
また、実は今のソースコードだと次のような問題が発生します。
・顔が画面の隅にある場合、検出範囲が枠外となり、エラーで異常停止する
・顔が少しでも斜めになると検出不可となる。
5日目ではここを解消していきます。
続きはこちらの記事へ
https://shiyotsuki.hatenablog.com/entry/virtual_youtuber_5
最後まで読んでくれてありがとう。
参考になったら「☆」を押してね。そして僕のYouTube・Twitterもよろしくね。