動画から顔認識させてみた(OpenCV:C++版)

# きっかけ
[動画から顔認識させてみた(OpenCV:python版)](https://qiita.com/satsukiya/items/80072bd8f607ba65f6ea) で編集した動画ファイルを出力するのに4~5分とかなり時間がかかったのが気になりました。
Pythonでは画像データの含んだ変数は手動で破棄することができず、ガーベージコレクションにて自動でメモリを開放しているのが原因で遅くなっていると思います。(たぶん:expressionless:）
言語の構造とは別として、C++では処理後に画像データを`.release()`メソッドを呼び出せばメモリ開放ができるので幾分速く実装できると思います。

ここでは、[動画から顔認識させてみた(OpenCV:python版)](https://qiita.com/satsukiya/items/80072bd8f607ba65f6ea)にあるコードをC++に書き換えて速度を計測してみます。

# 準備

C++で時間を計測したい場合は、 `#include<time.h>`でできるっぽいのですがここでは`boost`を使ってみます。

また、例のごとくHomebrewからインストールします。

```
brew install boost
```

インクルードファイルパスは `/usr/local/Cellar/boost/1.68.0_1/include/`、ライブラリパスは`/usr/local/Cellar/boost/1.68.0_1/lib/` を使います。

それで使うインクルードファイルは `boost/timer/timer.hpp`、ライブラリファイルは`boost_timer`です。

opencvの方では、動画入出力と顔認識するので `opencv_objdetect` `opencv_videoio` のライブラリファイルを使います。

makeファイルは下記の通りにまとまります。

```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_videoio -lboost_timer
CXXVERSION = -std=c++11

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

clean :
	rm detect_face
```

# 開発

```C++:detect_face.cpp
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.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);

	std::string srcFolder = "/usr/local/Cellar/opencv/4.1.1_2/share/opencv4/haarcascades/";
	std::string faceCascadePath = "haarcascade_frontalface_alt_tree.xml";
	std::string rightEyeCascadePath = "haarcascade_righteye_2splits.xml";
	std::string leftEyeCascadePath = "haarcascade_lefteye_2splits.xml";

	cv::CascadeClassifier face_cascade;
	cv::CascadeClassifier right_eye_cascade;
	cv::CascadeClassifier left_eye_cascade;

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

	if( !right_eye_cascade.load(srcFolder + rightEyeCascadePath))
		return -1;

	if(!left_eye_cascade.load(srcFolder + leftEyeCascadePath))
		return -1;

	cv::VideoWriter writer("detect_face.mp4", cv::VideoWriter::fourcc('m','p','4','v'), fps, cv::Size(cap_width, cap_height));

	std::cout << "frame width:" << cap_width << std::endl;
	std::cout << "frame height:" << cap_height << std::endl;
	std::cout << "frame fps:" << fps << std::endl;

	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;
		std::vector<cv::Rect> right_eyes;
		std::vector<cv::Rect> left_eyes;

		face_cascade.detectMultiScale(grayFrame, faces);

		for (int i = 0; i < faces.size(); ++i) {

			int width_center = (int)(faces[i].width * 0.5);
			cv::Mat faceRightGrayMat(grayFrame, cv::Rect(faces[i].x, faces[i].y , width_center, faces[i].height));
			cv::Mat faceLeftGrayMat(grayFrame, cv::Rect(faces[i].x + width_center, faces[i].y , width_center, faces[i].height));

			right_eye_cascade.detectMultiScale(faceRightGrayMat, right_eyes);
			left_eye_cascade.detectMultiScale(faceLeftGrayMat, left_eyes);


			if (0 < right_eyes.size()  && 0 < left_eyes.size() ){
				cv::Rect r = right_eyes[0];
				cv::Rect l = left_eyes[0];
				
				//右目はオレンジ
				cv::rectangle(frame, cv::Rect(
										faces[i].x + r.x, 
										faces[i].y + r.y, 
										r.width, 
										r.height
										),
					 cv::Scalar( 0, 255, 255 ), 2);
				
				//左目は赤
				cv::rectangle(frame, cv::Rect(
										faces[i].x + width_center + l.x, 
										faces[i].y + l.y, 
										l.width,
										l.height
									), 
					cv::Scalar( 0, 0, 255 ), 2);

				//顔全体は緑
				cv::rectangle(frame, faces[i], cv::Scalar( 0, 255, 0 ), 2);
			}
			faceRightGrayMat.release();
			faceLeftGrayMat.release();
		}

		writer << frame;

		faces.clear();
		right_eyes.clear();
		left_eyes.clear();
		
		grayFrame.release();
		frame.release();

    }

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

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

```

あとは、

```shell
> make
> ./detect_face
```
とコマンドを打てば編集された動画ファイルが出力されます。

# 結果

| 言語       |       速度 (秒) |
|:-----------|------------:|
| Python   　|       276.574|
| C++        |     181.530 |

1分半くらいC++の方が速い結果になりました。
もう少し早く終わるかなと思ったのですが、フレームのサイズが大きかったためか3分くらいかかりました。
GPU搭載のPC環境下ではもっと速いと思います。

# おわりに
次に実装したい関数ライブラリがC++のみにしかないので、一度C++版で動画のI/Oをしてみたかったっす。


# 参考にしたリンク
- [opencv cookbook ファイルに動画を書き出す](http://opencv.jp/cookbook/opencv_io.html#id7)
- [ Boost逆引きリファレンス 処理時間の計測] (https://boostjp.github.io/tips/timer.html)