きっかけ
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
マジイミフでした。
それで少し調べると、、、
[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を作成しました。
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();
}
結果 (画像のとき)
(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)
いい感じに特徴点が出力されました。
開発 (動画のとき)
# 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();
}
結果 (動画のとき)
今の方法では、デフォルトのカスケードを使っているので向き・傾きに弱いです。何かしらで学習すればトラッキング精度が上がると思います。
おわりに
顔の特徴点が取れるということは巷で話題のVTuberはこの技術を使っていそうです。
体の骨格も抽出できるので、3Dオブジェクトと同期すれば何かと面白いことができそうです。
てかiPhoneのアニ文字ってdevelopとかで取得できないんでしょうか。
参考にしたリンク
- cv::face::Facemark Class Reference
- opencv doc : Face landmark detection in an image
- PythonでOpenCVの顔認識を試してみた
- [OpenCV Forum: LBF Facemark Tutorial Issue] (https://answers.opencv.org/question/182326/lbf-facemark-tutorial-issue/)