面部特徵檢測應用很多,我將在下一節介紹當前項目用到一個典型例子,因爲疲勞檢測有一張方案是通過檢測人眼的閉合時間來實現的,在實際裝車應用中效果還不錯。本節先介紹一下opencv中自帶的特徵點檢測功能,後續將講解如何使用opencv+dlib實現疲勞檢測功能。
現在OpenCV支持幾種本地特徵檢測算法。然而,由於兩個原因,實際使用中還需要做更多的工作
1、Python支持:截至OpenCV3.4似乎仍然沒有python支持
2、缺乏訓練過的模型:在三種用於特徵檢測的算法中,我只能找到一個訓練過的模型。這不是很難解決的問題。在最壞的情況下,我們將嘗試我們自己的模型,並使它可供人們使用。
預訓練模型:
在進行本實驗之前請先下載模型文件,https://github.com/kurnianggoro/GSOC2017/blob/master/data/lbfmodel.yaml(用於獲取特徵點),還要用到一個haarcascade_frontalface_alt2.xml(用於檢測人臉),該模型在OpenCV安裝目錄中的\data\ haarcascades下。
先上代碼:
1個頭文件:drawLandmarks.hpp
#ifndef _renderFace_H_
#define _renderFace_H_
using namespace cv;
using namespace std;
#define COLOR Scalar(255, 200,0)
// drawPolyLine draws a poly line by joining
// successive points between the start and end indices.
void drawPolyline
(
Mat &im,
const vector<Point2f> &landmarks,
const int start,
const int end,
bool isClosed = false
)
{
// Gather all points between the start and end indices
vector <Point> points;
for (int i = start; i <= end; i++)
{
points.push_back(cv::Point(landmarks[i].x, landmarks[i].y));
}
// Draw polylines.
polylines(im, points, isClosed, COLOR, 2, 16);
}
void renderFace(cv::Mat &im, vector<Point2f> &landmarks)
{
// Draw face for the 68-point model.
if (landmarks.size() == 68)
{
drawPolyline(im, landmarks, 0, 16); // Jaw line
drawPolyline(im, landmarks, 17, 21); // Left eyebrow
drawPolyline(im, landmarks, 22, 26); // Right eyebrow
drawPolyline(im, landmarks, 27, 30); // Nose bridge
drawPolyline(im, landmarks, 30, 35, true); // Lower nose
drawPolyline(im, landmarks, 36, 41, true); // Left eye
drawPolyline(im, landmarks, 42, 47, true); // Right Eye
drawPolyline(im, landmarks, 48, 59, true); // Outer lip
drawPolyline(im, landmarks, 60, 67, true); // Inner lip
}
else
{ // If the number of points is not 68, we do not know which
// points correspond to which facial features. So, we draw
// one dot per landamrk.
for(int i = 0; i < landmarks.size(); i++)
{
circle(im,landmarks[i],3, COLOR, FILLED);
}
}
}
1個cpp文件:facialLandmarkDetection.cpp
#include <opencv2/opencv.hpp>
#include <opencv2/face.hpp>
#include "drawLandmarks.hpp"
using namespace std;
using namespace cv;
using namespace cv::face;
int main(int argc,char** argv)
{
// Load Face Detector
CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml");
// Create an instance of Facemark
Ptr<Facemark> facemark = FacemarkLBF::create();
// Load landmark detector
facemark->loadModel("lbfmodel.yaml");
// Set up webcam for video capture
VideoCapture cam(1);
// Variable to store a video frame and its grayscale
Mat frame, gray;
// Read a frame
while(cam.read(frame))
{
// Find face
vector<Rect> faces;
// Convert frame to grayscale because
// faceDetector requires grayscale image.
cvtColor(frame, gray, COLOR_BGR2GRAY);
// Detect faces
faceDetector.detectMultiScale(gray, faces);
// Variable for landmarks.
// Landmarks for one face is a vector of points
// There can be more than one face in the image. Hence, we
// use a vector of vector of points.
vector< vector<Point2f> > landmarks;
// Run landmark detector
bool success = facemark->fit(frame,faces,landmarks);
if(success)
{
// If successful, render the landmarks on the face
for(int i = 0; i < landmarks.size(); i++)
{
drawLandmarks(frame, landmarks[i]);
}
}
// Display results
imshow("Facial Landmark Detection", frame);
// Exit loop if ESC is pressed
if (waitKey(1) == 27) break;
}
return 0;
}
最後是cmake文件:
cmake_minimum_required(VERSION 2.8)
project(fd)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INClUDE_DIRS})
add_executable(fd facialLandmarkDetection.cpp)
target_link_libraries(fd ${OpenCV_LIBS})
代碼解析:
1、加載人臉檢測器:
所有的面部特徵檢測算法都以裁剪出的面部圖像作爲輸入。因此,我們的第一步是檢測圖像中的所有人臉,並將這些人臉矩形框傳遞給特徵點檢測器。CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml");加載OpenCV的HAAR人臉檢測器(haarcascade_frontalface_alt2.xml)。
2、創建Facemark實例:
Ptr<Facemark> facemark = FacemarkLBF::create();,我們創建了Facemark類的一個實例。它被包裹在OpenC V智能指針(Ptr)中,所以您不必擔心內存管理。智能指針是c++的基礎知識,請自行百度。
3、加載特徵點檢測器:
接下來,我們加載特徵點檢測器(lbfmodel.yaml)。這個地標探測器是在幾千幅面部圖像和相應的特徵點上訓練的。帶有標註特徵點的面部圖像的公共數據集可以在這裏找到https://ibug.doc.ic.ac.uk/resources/facial-point-annotations/。
4、從攝像頭捕捉幀:
下一步是抓取一個視頻幀並處理它。VideoCapture cam(0);設置從本機第一個攝像頭捕獲視頻,對應的設備文件爲/dev/video0。可以使用 VideoCapture cap(“myvideo.mp4”);來讀取視頻文件, 然後,我們不斷從視頻抓取幀,直到ESC被按下。
5、檢測面部:
我們在視頻的每個幀上運行人臉檢測器。人臉檢測器的輸出是矩形的向量,對應圖像中的一個或多個人臉。faceDetector.detectMultiScale(gray, faces);
6、運行面部地標檢測器:
我們將原始圖像和檢測到的人臉矩形傳送到面部特徵點檢測器。對於每一張臉,我們得到68個特徵點,這些特徵點被存儲在一個向量中。因爲一個幀中可能有多個人臉,所以我們必須通過一個點向量的向量來存儲地標,每個子向量對應一張臉。
bool success = facemark->fit(frame,faces,landmarks);
7、繪製地標:
一旦我們獲得了特徵點,我們就可以把它們畫在圖像上進行展示。繪圖的代碼在drawLandmarks.hpp中。
drawLandmarks(frame, landmarks[i]);
最後,實際代碼運行效果如下:(沒錯,this guy is me ~ _~)