본문 바로가기
Work/OpenCV

Face Landmark Traker

by 희붕 2020. 5. 18.

사실 영상처리 분야에 대해서는 하나도 관심이 없지만 교수님의 지시사항으로 연구 일환으로 진행한 내용이며

연구실에서 진행한 내용이지만 해당 내용은 두개의 예제를 합친것과 같다고 생각되기 때문에 블로그에 작성해도 큰 문제가 없을것으로 예상된다. 예제는 오픈소스인데다 참고한 알고리즘 블로그 또한 오픈소스이다.

 

Open CV의 영상처리와 dlib라이브러리의 영상처리 두가지를 섞어서 사용한 기술이다.

 

dlib라이브러리의 example폴더에 face_landmark_detection_ex.cpp 와 webcam_face_pose_ex.cpp와 아래 소개할 블로그의 HeadPose에 대한 내용을 섞은 느낌이라고 생각하면된다.

 

www.learnopencv.com/head-pose-estimation-using-opencv-and-dlib/

 

Head Pose Estimation using OpenCV and Dlib

In this tutorial we will learn how to estimate the pose of a human head in a photo using OpenCV and Dlib. In many applications, we need to know how the head is tilted with respect to a camera. In a virtual reality application, for example, one can use the

www.learnopencv.com

HeadPose에 대한 코드 및 알고리즘에 대한 내용은 이분께서 블로그에 잘 작성해 주셨기에 따로 적지 않는다.

이분의 코드를 약간 변형해서 Wecam에서도 사용할 수 있도록 변형했다.

 

웹캠이 없어서 스마트폰을 웹캠으로 만들어주는 프로그램들을 이용해서 테스트 했다.

dlib19.19 버전과 OpenCV 4.2.0 버전을 이용하였다.

 

이 코드를 만약에 사용하실 분이 계시다면

웹캠에 얼굴이 2명이상 잡히면 죽는 버그를 수정해야 사용하실 수 있습니다.

 

#if (_MSC_VER >= 1915)
#define no_init_all deprecated
#endif

#include <dlib/opencv.h>
#include <opencv2/highgui/highgui.hpp>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>
#include <dlib/image_processing.h>
#include <dlib/gui_widgets.h>

#include <opencv2/opencv.hpp>

using namespace dlib;
using namespace std;

int main()
{
	try
	{
		cv::VideoCapture cap(0);
		if (!cap.isOpened())
		{
			//Sleep(1);
			cerr << "Unable to connect to camera" << endl;
			return 1;
		}

		image_window win;

		// Load face detection and pose estimation models.
		frontal_face_detector detector = get_frontal_face_detector();
		shape_predictor pose_model;
		deserialize("shape_predictor_68_face_landmarks.dat") >> pose_model;

		// Grab and process frames until the main window is closed by the user.
		while (!win.is_closed())
		{
			// Grab a frame
			cv::Mat temp;
			if (!cap.read(temp))	
			{
				break;
			}
			// Turn OpenCV's Mat into something dlib can deal with.  Note that this just
			// wraps the Mat object, it doesn't copy anything.  So cimg is only valid as
			// long as temp is valid.  Also don't do anything to temp that would cause it
			// to reallocate the memory which stores the image as that will make cimg
			// contain dangling pointers.  This basically means you shouldn't modify temp
			// while using cimg.
			IplImage ipl_img = cvIplImage(temp);
			cv_image<bgr_pixel> cimg(ipl_img);
			 
			// Detect faces 
			// 얼굴갯수
			std::vector<rectangle> faces = detector(cimg);

			// Find the pose of each face.
			std::vector<full_object_detection> shapes;
			
			std::vector<cv::Point2d> image_points;
			// 얼굴 갯수에 따른 루프
			for (unsigned long i = 0; i < faces.size(); ++i)
			{
				full_object_detection shape = pose_model(cimg, faces[i]);
				
				// 68 land mark 사용중이라 68개 전부 출력됨
				/*
				cout << "number of parts: " << shape.num_parts() << endl;
				for (int j = 0; j <= shape.num_parts(); ++j) {
					cout << "pixel position of "<< j << " part:  " << shape.part(j) << endl;
				}
				*/

				// 2D image Points
				image_points.push_back(cv::Point2d(shape.part(30).x(), shape.part(30).y()));    // Nose tip
				image_points.push_back(cv::Point2d(shape.part(8).x(),  shape.part(8).y()));     // Chin
				image_points.push_back(cv::Point2d(shape.part(45).x(), shape.part(45).y()));    // Left eye left corner
				image_points.push_back(cv::Point2d(shape.part(36).x(), shape.part(36).y()));    // Right eye right corner
				image_points.push_back(cv::Point2d(shape.part(54).x(), shape.part(54).y()));    // Left Mouth corner
				image_points.push_back(cv::Point2d(shape.part(48).x(), shape.part(48).y()));    // Right mouth corner

				shapes.push_back(shape);
			}
			
			if (image_points.size() == 0)
			{
				win.clear_overlay();
				win.set_image(cimg);
				continue;
			}

			// 임의의 3D 모델 포인트
			// 3D model points.
			std::vector<cv::Point3d> model_points;
			model_points.push_back(cv::Point3d(0.0f, 0.0f, 0.0f));               // Nose tip
			model_points.push_back(cv::Point3d(0.0f, -330.0f, -65.0f));          // Chin
			model_points.push_back(cv::Point3d(-225.0f, 170.0f, -135.0f));       // Left eye left corner
			model_points.push_back(cv::Point3d(225.0f, 170.0f, -135.0f));        // Right eye right corner
			model_points.push_back(cv::Point3d(-150.0f, -150.0f, -125.0f));      // Left Mouth corner
			model_points.push_back(cv::Point3d(150.0f, -150.0f, -125.0f));       // Right mouth corner

			// Camera internals
			double focal_length = temp.cols; // Approximate focal length.
			cv::Point2d center = cv::Point2d(temp.cols / 2, temp.rows / 2);
			cv::Mat camera_matrix = (cv::Mat_<double>(3, 3) << focal_length, 0, center.x, 0, focal_length, center.y, 0, 0, 1);
			cv::Mat dist_coeffs = cv::Mat::zeros(4, 1, cv::DataType<double>::type); // Assuming no lens distortion
			//cout << "Camera Matrix " << endl << camera_matrix << endl;

			// Output rotation and translation
			cv::Mat rotation_vector; // Rotation in axis-angle form
			cv::Mat translation_vector;

			// Solve for pose
			cv::solvePnP(model_points, image_points, camera_matrix, dist_coeffs, rotation_vector, translation_vector);

			// Project a 3D point (0, 0, 1000.0) onto the image plane.
			// We use this to draw a line sticking out of the nose
			std::vector<cv::Point3d> nose_end_point3D;
			std::vector<cv::Point2d> nose_end_point2D;

			nose_end_point3D.push_back(cv::Point3d(0, 0, 1000.0));
			projectPoints(nose_end_point3D, rotation_vector, translation_vector, camera_matrix, dist_coeffs, nose_end_point2D);

			for (int i = 0; i < image_points.size(); i++)
			{
				circle(temp, image_points[i], 3, cv::Scalar(0, 0, 255), -1);
			}

			cv::line(temp, image_points[0], nose_end_point2D[0], cv::Scalar(255, 0, 0), 2);

			cout << "Rotation Vector " << endl << rotation_vector << endl;
			//cout << "Translation Vector" << endl << translation_vector << endl;
			//cout << nose_end_point2D << endl;

			// Display it all on the screen
			win.clear_overlay();
			win.set_image(cimg);
			win.add_overlay(render_face_detections(shapes));
		}
	}
	catch (serialization_error& e)
	{
		cout << "You need dlib's default face landmarking model file to run this example." << endl;
		cout << "You can get it from the following URL: " << endl;
		cout << "   http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2" << endl;
		cout << endl << e.what() << endl;
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
}