10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

cv::face::Facemark を使って 顔の特徴点抽出をさせてみた(OpenCV)

Last updated at Posted at 2019-11-16

きっかけ

OpenCVを使って長い動画を一部だけ保存する方法
動画からキャプチャ画像を撮る方法(OpenCV)
動画から顔認識させてみた(OpenCV:python版)
に続いて立候補動画からOpenCVを使って画像処理していきます。

また例のごとくopencv_contribにある関数ライブラリを使って良さげなものがないか探したのですが、
cv::face::Facemark 顔の特徴点抽出
があったので実装してみます。

初登場したのがOpenCVがversion 3.0 になってからで4年くらい前っぽいです。

言語ですが、Pythonだとopencvのみでの実装はできないようで機械学習ライブラリdlibと併用しないと実現できなさそうです。(詳細はPythonでOpenCVの顔認識を試してみたを参照)

準備

opencv doc : Face landmark detection in an image
をみたのですが、

CascadeClassifier face_cascade;
face_cascade.load(cascade_name);:question:
Mat img = imread(image);
Ptr<Facemark> facemark = createFacemarkKazemi();
facemark->loadModel(filename);

Feature2dと同じ要領で特徴点判別用のクラスをオブジェクト化してメソッドを呼び出すのかと思ったのですが、
filename:thinking::thought_balloon::question:
マジイミフでした。

それで少し調べると、、、
[OpenCV Forum: LBF Facemark Tutorial Issue] (https://answers.opencv.org/question/182326/lbf-facemark-tutorial-issue/) のサンプルコードにヒントが書かれていて、学習済みのlbfmodel.yamlが必要とのことです。それで、実行フォルダ配下にこのyamlファイルをすることとします。

> wget https://raw.githubusercontent.com/kurnianggoro/GSOC2017/master/data/lbfmodel.yaml

あとは、判別メソッドfacemark->fit(grayMat, faces, marks); の引数facesですが、顔認識したエリアをcv::Rectで渡す必要があるので、前処理として今まで通り動画から顔認識させてみた(OpenCV:C++版) で使った、カスケードcv::CascadeClassifierでの顔認識は必要になります。

また、makeするときはインクルードファイルはopencv2/face.hpp、ライブラリファイルはopencv_faceを追加します。

下記の通りにmakefileを作成しました。

GNUmakefile
CXX = clang++
CXXFLAGS =  -I/usr/local/Cellar/opencv/4.1.1_2/include/opencv4/ -I/usr/local/Cellar/boost/1.68.0_1/include/
LDFLAGS = -L/usr/local/Cellar/opencv/4.1.1_2/lib/ -L/usr/local/Cellar/boost/1.68.0_1/lib/
LDLIBS =  -lopencv_core -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_objdetect -lopencv_face -lopencv_videoio -lboost_timer
CXXVERSION = -std=c++11

landmark:landmark.cpp
	$(CXX) $< -o $@ $(CXXFLAGS) $(CXXVERSION) $(LDFLAGS) $(LDLIBS)

clean :
	rm landmark

開発 (画像のとき)

# include <opencv2/opencv.hpp>
# include <opencv2/highgui.hpp>
# include <opencv2/imgproc.hpp>
# include <opencv2/objdetect.hpp>
# include <opencv2/face.hpp>

int main( int argc, char** argv )
{

	cv::Mat imageMat = cv::imread("./capture_2.png", cv::IMREAD_COLOR );
	cv::Mat grayMat;

	cv::cvtColor(imageMat, grayMat, cv::COLOR_BGR2GRAY);

	// 顔全体のカスケード
	std::string cascadePath = "/usr/local/Cellar/opencv/4.1.1_2/share/opencv4/haarcascades/haarcascade_frontalface_alt_tree.xml";
	cv::CascadeClassifier cascade;

	if(!cascade.load(cascadePath))
    	return -1;

    // 顔特徴点抽出用の判別器
	cv::face::FacemarkLBF::Params params;
	params.n_landmarks = 68; 
	params.initShape_n = 10; 
	params.stages_n = 5; 
	params.tree_n = 6; 
	params.tree_depth = 5; 
	params.model_filename = "lbfmodel.yaml"; 

	cv::Ptr<cv::face::Facemark> facemark = cv::face::FacemarkLBF::create(params);
	facemark->loadModel(params.model_filename);

    //顔がどこのあるか判定
	std::vector<cv::Rect> faces;
	cascade.detectMultiScale(grayMat, faces);

    //顔の中にある特徴点を判定
	std::vector<std::vector<cv::Point2f>> marks;
	facemark->fit(grayMat, faces, marks);

	//描画 顔全体
	for (int i = 0; i < faces.size(); ++i) {
		cv::rectangle(imageMat, faces[i], cv::Scalar( 0, 0, 255 ), 2);
	}

	//描画 顔特徴点
	for (int j = 0; j < marks[0].size(); j++){
		std::cout << "(" << marks[0][j].x << "," << marks[0][j].y << ")" << std::endl;
		cv::circle(imageMat, cv::Point(marks[0][j].x, marks[0][j].y), 2, cv::Scalar(0,255,0), -1);
	}

	cv::namedWindow("face landmark", cv::WINDOW_AUTOSIZE);
	cv::imshow("face landmark", imageMat);

	cv::imwrite("output.png", imageMat);
	cv::waitKey(0);

	cv::destroyAllWindows();
}

結果 (画像のとき)

output
(590.9,322.147)
(591.217,336.236)
(591.884,350.683)
(593.762,364.571)
(597.275,378.365)
(604.097,391.034)
(613.718,402.907)
(625.272,411.426)
(637.967,413.993)
(650.729,411.284)
(662.937,404.078)
(674.311,394.063)
(683.501,382.459)
(688.967,368.631)
(691.895,353.862)
(693.695,338.896)
(694.573,323.792)
(599.207,314.833)
(604.014,306.059)
(614.227,303.123)
(624.255,304.961)
(633.462,309.416)
(643.505,308.756)
(653.441,304.1)
(664.577,302.964)
(675.373,306.003)
(681.47,314.908)
(639.315,322.427)
(639.228,331.317)
(639.165,339.942)
(639.073,349.015)
(628.861,356.563)
(633.756,358.463)
(639.046,359.731)
(644.456,358.639)
(649.343,356.906)
(609.223,323.647)
(614.408,320.867)
(621.07,321.2)
(626.829,326.116)
(620.658,326.787)
(613.902,326.445)
(652.744,326.402)
(658.855,321.455)
(665.723,321.071)
(671.209,324.189)
(666.49,326.816)
(659.414,327.152)
(621.229,376.468)
(628.091,373.888)
(634.395,371.597)
(637.988,373.133)
(641.814,371.826)
(647.98,374.261)
(655.28,377.79)
(647.95,383.186)
(642.081,385.906)
(637.896,386.386)
(633.731,385.89)
(627.789,383.062)
(624.089,376.941)
(634.286,376.723)
(637.913,377.249)
(641.863,376.869)
(652.192,377.998)
(641.86,378.551)
(637.961,379.266)
(634.188,378.507)

output.png

いい感じに特徴点が出力されました。

開発 (動画のとき)

# include <opencv2/opencv.hpp>
# include <opencv2/highgui.hpp>
# include <opencv2/imgproc.hpp>
# include <opencv2/objdetect.hpp>
# include <opencv2/face.hpp>
# include <boost/timer/timer.hpp>

int main( int argc, char** argv )
{
	// 入力動画用
	cv::VideoCapture cap("./one_minutes.mp4");

	int cap_width = (int) cap.get(cv::CAP_PROP_FRAME_WIDTH);
	int cap_height = (int) cap.get(cv::CAP_PROP_FRAME_HEIGHT);
	double fps = cap.get(cv::CAP_PROP_FPS);

	// 出力動画用
	cv::VideoWriter writer("face_landmark.mp4", cv::VideoWriter::fourcc('m','p','4','v'), fps, cv::Size(cap_width, cap_height));

	// 顔全体のカスケード
	std::string srcFolder = "/usr/local/Cellar/opencv/4.1.1_2/share/opencv4/haarcascades/";
	std::string faceCascadePath = "haarcascade_frontalface_alt_tree.xml";

	cv::CascadeClassifier face_cascade;

	if(!face_cascade.load(srcFolder + faceCascadePath))
		return -1;

    // 顔特徴点抽出用の判別器
	cv::face::FacemarkLBF::Params params;
	params.n_landmarks = 68; 
	params.initShape_n = 10; 
	params.stages_n = 5; 
	params.tree_n = 6; 
	params.tree_depth = 5; 
	params.model_filename = "lbfmodel.yaml"; 

	cv::Ptr<cv::face::Facemark> facemark = cv::face::FacemarkLBF::create(params);
	facemark->loadModel(params.model_filename);

	boost::timer::cpu_timer timer;

    while(cap.isOpened()){
    	cv::Mat frame;
		cv::Mat grayFrame;
		cap >> frame;  // キャプチャ
		// 様々な処理

		if (frame.empty()){
			break;
		}

		cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY);

	    //顔がどこのあるか判定
		std::vector<cv::Rect> faces;
		face_cascade.detectMultiScale(grayFrame, faces);

	    //顔の中にある特徴点を判定
		std::vector<std::vector<cv::Point2f>> marks;
		facemark->fit(grayFrame, faces, marks);

		//描画 顔全体
		for (int i = 0; i < faces.size(); ++i) {
			cv::rectangle(frame, faces[i], cv::Scalar( 0, 0, 255 ), 2);

			//描画 顔特徴点
			for (int j = 0; j < marks[i].size(); j++){			
				cv::circle(frame, cv::Point(marks[i][j].x, marks[i][j].y), 2, cv::Scalar(0,255,0), -1);
			}
		}

		writer << frame;

		faces.clear();

		for (int i = 0; i < marks.size() ;i++){
			marks[i].clear();
		}

		grayFrame.release();
		frame.release();

    }

	std::string result = timer.format(3,"処理時間:%w秒"); // 結果文字列を取得する
    std::cout << result << std::endl;

    writer.release();
    cap.release();

}

結果 (動画のとき)

landmark.gif

今の方法では、デフォルトのカスケードを使っているので向き・傾きに弱いです。何かしらで学習すればトラッキング精度が上がると思います。

おわりに

顔の特徴点が取れるということは巷で話題のVTuberはこの技術を使っていそうです。
体の骨格も抽出できるので、3Dオブジェクトと同期すれば何かと面白いことができそうです。
てかiPhoneのアニ文字ってdevelopとかで取得できないんでしょうか。:robot:
笑ってはいけないアニ文字がツボに入ってしまった。
笑ってはいけないアニ文字が面白すぎるwwww

参考にしたリンク

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?