源码地址:https://github.com/mc-jesus/face_detect_n_track
1、整张图检测人脸;detectFaceAllSizes
2、人脸区域检测人脸;detectFaceAroundRoi
3、作为模板做模板匹配(跟踪);detectFacesTemplateMatching
#pragma once
#include <opencv2\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\objdetect\objdetect.hpp>
class VideoFaceDetector
{
public:
VideoFaceDetector(const std::string cascadeFilePath, cv::VideoCapture &videoCapture);
~VideoFaceDetector();
cv::Point getFrameAndDetect(cv::Mat &frame);
cv::Point operator>>(cv::Mat &frame);
void setVideoCapture(cv::VideoCapture &videoCapture);
cv::VideoCapture* videoCapture() const;
void setFaceCascade(const std::string cascadeFilePath);
cv::CascadeClassifier* faceCascade() const;
void setResizedWidth(const int width);
int resizedWidth() const;
bool isFaceFound() const;
cv::Rect face() const;
cv::Point facePosition() const;
void setTemplateMatchingMaxDuration(const double s);
double templateMatchingMaxDuration() const;
private:
static const double TICK_FREQUENCY;
cv::VideoCapture* m_videoCapture = NULL;
cv::CascadeClassifier* m_faceCascade = NULL;
std::vector<cv::Rect> m_allFaces;
cv::Rect m_trackedFace;
cv::Rect m_faceRoi;
cv::Mat m_faceTemplate;
cv::Mat m_matchingResult;
bool m_templateMatchingRunning = false;
int64 m_templateMatchingStartTime = 0;
int64 m_templateMatchingCurrentTime = 0;
bool m_foundFace = false;
double m_scale;
int m_resizedWidth = 320;
cv::Point m_facePosition;
double m_templateMatchingMaxDuration = 3;
cv::Rect doubleRectSize(const cv::Rect &inputRect, const cv::Rect &frameSize) const;
cv::Rect biggestFace(std::vector<cv::Rect> &faces) const;
cv::Point centerOfRect(const cv::Rect &rect) const;
cv::Mat getFaceTemplate(const cv::Mat &frame, cv::Rect face);
void detectFaceAllSizes(const cv::Mat &frame);
void detectFaceAroundRoi(const cv::Mat &frame);
void detectFacesTemplateMatching(const cv::Mat &frame);
};
#include "VideoFaceDetector.h"
#include <iostream>
#include <opencv2\imgproc.hpp>
const double VideoFaceDetector::TICK_FREQUENCY = cv::getTickFrequency();
VideoFaceDetector::VideoFaceDetector(const std::string cascadeFilePath, cv::VideoCapture &videoCapture)
{
setFaceCascade(cascadeFilePath);
setVideoCapture(videoCapture);
}
void VideoFaceDetector::setVideoCapture(cv::VideoCapture &videoCapture)
{
m_videoCapture = &videoCapture;
}
cv::VideoCapture *VideoFaceDetector::videoCapture() const
{
return m_videoCapture;
}
void VideoFaceDetector::setFaceCascade(const std::string cascadeFilePath)
{
if (m_faceCascade == NULL) {
m_faceCascade = new cv::CascadeClassifier(cascadeFilePath);
}
else {
m_faceCascade->load(cascadeFilePath);
}
if (m_faceCascade->empty()) {
std::cerr << "Error creating cascade classifier. Make sure the file" << std::endl
<< cascadeFilePath << " exists." << std::endl;
}
}
cv::CascadeClassifier *VideoFaceDetector::faceCascade() const
{
return m_faceCascade;
}
void VideoFaceDetector::setResizedWidth(const int width)
{
m_resizedWidth = std::max(width, 1);
}
int VideoFaceDetector::resizedWidth() const
{
return m_resizedWidth;
}
bool VideoFaceDetector::isFaceFound() const
{
return m_foundFace;
}
cv::Rect VideoFaceDetector::face() const
{
cv::Rect faceRect = m_trackedFace;
faceRect.x = (int)(faceRect.x / m_scale);
faceRect.y = (int)(faceRect.y / m_scale);
faceRect.width = (int)(faceRect.width / m_scale);
faceRect.height = (int)(faceRect.height / m_scale);
return faceRect;
}
cv::Point VideoFaceDetector::facePosition() const
{
cv::Point facePos;
facePos.x = (int)(m_facePosition.x / m_scale);
facePos.y = (int)(m_facePosition.y / m_scale);
return facePos;
}
void VideoFaceDetector::setTemplateMatchingMaxDuration(const double s)
{
m_templateMatchingMaxDuration = s;
}
double VideoFaceDetector::templateMatchingMaxDuration() const
{
return m_templateMatchingMaxDuration;
}
VideoFaceDetector::~VideoFaceDetector()
{
if (m_faceCascade != NULL) {
delete m_faceCascade;
}
}
cv::Rect VideoFaceDetector::doubleRectSize(const cv::Rect &inputRect, const cv::Rect &frameSize) const
{
cv::Rect outputRect;
// Double rect size
outputRect.width = inputRect.width * 2;
outputRect.height = inputRect.height * 2;
// Center rect around original center
outputRect.x = inputRect.x - inputRect.width / 2;
outputRect.y = inputRect.y - inputRect.height / 2;
// Handle edge cases
if (outputRect.x < frameSize.x) {
outputRect.width += outputRect.x;
outputRect.x = frameSize.x;
}
if (outputRect.y < frameSize.y) {
outputRect.height += outputRect.y;
outputRect.y = frameSize.y;
}
if (outputRect.x + outputRect.width > frameSize.width) {
outputRect.width = frameSize.width - outputRect.x;
}
if (outputRect.y + outputRect.height > frameSize.height) {
outputRect.height = frameSize.height - outputRect.y;
}
return outputRect;
}
cv::Point VideoFaceDetector::centerOfRect(const cv::Rect &rect) const
{
return cv::Point(rect.x + rect.width / 2, rect.y + rect.height / 2);
}
cv::Rect VideoFaceDetector::biggestFace(std::vector<cv::Rect> &faces) const
{
assert(!faces.empty());
cv::Rect *biggest = &faces[0];
for (auto &face : faces) {
if (face.area() < biggest->area())
biggest = &face;
}
return *biggest;
}
/*
* Face template is small patch in the middle of detected face.
*/
cv::Mat VideoFaceDetector::getFaceTemplate(const cv::Mat &frame, cv::Rect face)
{
face.x += face.width / 4;
face.y += face.height / 4;
face.width /= 2;
face.height /= 2;
cv::Mat faceTemplate = frame(face).clone();
return faceTemplate;
}
void VideoFaceDetector::detectFaceAllSizes(const cv::Mat &frame)
{
// Minimum face size is 1/5th of screen height
// Maximum face size is 2/3rds of screen height
m_faceCascade->detectMultiScale(frame, m_allFaces, 1.1, 3, 0,
cv::Size(frame.rows / 5, frame.rows / 5),
cv::Size(frame.rows * 2 / 3, frame.rows * 2 / 3));
if (m_allFaces.empty()) return;
m_foundFace = true;
// Locate biggest face
m_trackedFace = biggestFace(m_allFaces);
// Copy face template
m_faceTemplate = getFaceTemplate(frame, m_trackedFace);
// Calculate roi
m_faceRoi = doubleRectSize(m_trackedFace, cv::Rect(0, 0, frame.cols, frame.rows));
// Update face position
m_facePosition = centerOfRect(m_trackedFace);
}
void VideoFaceDetector::detectFaceAroundRoi(const cv::Mat &frame)
{
// Detect faces sized +/-20% off biggest face in previous search
m_faceCascade->detectMultiScale(frame(m_faceRoi), m_allFaces, 1.1, 3, 0,
cv::Size(m_trackedFace.width * 8 / 10, m_trackedFace.height * 8 / 10),
cv::Size(m_trackedFace.width * 12 / 10, m_trackedFace.width * 12 / 10));
if (m_allFaces.empty())
{
// Activate template matching if not already started and start timer
m_templateMatchingRunning = true;
if (m_templateMatchingStartTime == 0)
m_templateMatchingStartTime = cv::getTickCount();
return;
}
// Turn off template matching if running and reset timer
m_templateMatchingRunning = false;
m_templateMatchingCurrentTime = m_templateMatchingStartTime = 0;
// Get detected face
m_trackedFace = biggestFace(m_allFaces);
// Add roi offset to face
m_trackedFace.x += m_faceRoi.x;
m_trackedFace.y += m_faceRoi.y;
// Get face template
m_faceTemplate = getFaceTemplate(frame, m_trackedFace);
// Calculate roi
m_faceRoi = doubleRectSize(m_trackedFace, cv::Rect(0, 0, frame.cols, frame.rows));
// Update face position
m_facePosition = centerOfRect(m_trackedFace);
}
void VideoFaceDetector::detectFacesTemplateMatching(const cv::Mat &frame)
{
// Calculate duration of template matching
m_templateMatchingCurrentTime = cv::getTickCount();
double duration = (double)(m_templateMatchingCurrentTime - m_templateMatchingStartTime) / TICK_FREQUENCY;
// If template matching lasts for more than 2 seconds face is possibly lost
// so disable it and redetect using cascades
if (duration > m_templateMatchingMaxDuration) {
m_foundFace = false;
m_templateMatchingRunning = false;
m_templateMatchingStartTime = m_templateMatchingCurrentTime = 0;
m_facePosition.x = m_facePosition.y = 0;
m_trackedFace.x = m_trackedFace.y = m_trackedFace.width = m_trackedFace.height = 0;
return;
}
// Edge case when face exits frame while
if (m_faceTemplate.rows * m_faceTemplate.cols == 0 || m_faceTemplate.rows <= 1 || m_faceTemplate.cols <= 1) {
m_foundFace = false;
m_templateMatchingRunning = false;
m_templateMatchingStartTime = m_templateMatchingCurrentTime = 0;
m_facePosition.x = m_facePosition.y = 0;
m_trackedFace.x = m_trackedFace.y = m_trackedFace.width = m_trackedFace.height = 0;
return;
}
// Template matching with last known face
//cv::matchTemplate(frame(m_faceRoi), m_faceTemplate, m_matchingResult, CV_TM_CCOEFF);
cv::matchTemplate(frame(m_faceRoi), m_faceTemplate, m_matchingResult, CV_TM_SQDIFF_NORMED);
cv::normalize(m_matchingResult, m_matchingResult, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
double min, max;
cv::Point minLoc, maxLoc;
cv::minMaxLoc(m_matchingResult, &min, &max, &minLoc, &maxLoc);
// Add roi offset to face position
minLoc.x += m_faceRoi.x;
minLoc.y += m_faceRoi.y;
// Get detected face
//m_trackedFace = cv::Rect(maxLoc.x, maxLoc.y, m_trackedFace.width, m_trackedFace.height);
m_trackedFace = cv::Rect(minLoc.x, minLoc.y, m_faceTemplate.cols, m_faceTemplate.rows);
m_trackedFace = doubleRectSize(m_trackedFace, cv::Rect(0, 0, frame.cols, frame.rows));
// Get new face template
m_faceTemplate = getFaceTemplate(frame, m_trackedFace);
// Calculate face roi
m_faceRoi = doubleRectSize(m_trackedFace, cv::Rect(0, 0, frame.cols, frame.rows));
// Update face position
m_facePosition = centerOfRect(m_trackedFace);
}
cv::Point VideoFaceDetector::getFrameAndDetect(cv::Mat &frame)
{
*m_videoCapture >> frame;
// Downscale frame to m_resizedWidth width - keep aspect ratio
m_scale = (double) std::min(m_resizedWidth, frame.cols) / frame.cols;
cv::Size resizedFrameSize = cv::Size((int)(m_scale*frame.cols), (int)(m_scale*frame.rows));
cv::Mat resizedFrame;
cv::resize(frame, resizedFrame, resizedFrameSize);
if (!m_foundFace)
detectFaceAllSizes(resizedFrame); // Detect using cascades over whole image
else {
detectFaceAroundRoi(resizedFrame); // Detect using cascades only in ROI
if (m_templateMatchingRunning) {
detectFacesTemplateMatching(resizedFrame); // Detect using template matching
}
}
return m_facePosition;
}
cv::Point VideoFaceDetector::operator>>(cv::Mat &frame)
{
return this->getFrameAndDetect(frame);
}
Taily老段的微信公众号,欢迎交流学习
https://blog.csdn.net/taily_duan/article/details/81214815