Facemark:使用OpenCV進行面部特徵點檢測

 面部特徵檢測應用很多,我將在下一節介紹當前項目用到一個典型例子,因爲疲勞檢測有一張方案是通過檢測人眼的閉合時間來實現的,在實際裝車應用中效果還不錯。本節先介紹一下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 ~ _~)

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章