实践|OpenCV4.2使用DNN进行人脸检测一(图片篇)

2020-04-08 12:26:42 浏览数 (1)

使用OpenCV进行人脸检测我写过两篇文章《C OpenCV之级联分类器--人脸检测》和《Android NDK OpenCV级联方式实时进行人脸检测》,不过这两篇里面用到的检测方式都是HAAR级联检测器,现在OpenCV4里面官方支持的人脸检测方法也已经是基于深度学习的方法进行检测了,所以我们这篇主要就是看OpenCV下用DNN进行人脸检测。

实现DNN的人脸检测,需要先下载模型文件,在OpenCV的sourcessamplesdnnface_detector目录下,有一个download_weights.py脚本文件,可以通过Pycharm执行下就可以自动下载两种模型:

01

Caffe模型

代码语言:javascript复制
res10_300x300_ssd_iter_140000_fp16.caffemodel
deploy.prototxt

直接的下载地址为:

代码语言:javascript复制
https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20180205_fp16/res10_300x300_ssd_iter_140000_fp16.caffemodel

02

tensorflow模型

代码语言:javascript复制
opencv_face_detector_uint8.pb
opencv_face_detector.pbtxt

直接的下载地址为:

代码语言:javascript复制
https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20180220_uint8/opencv_face_detector_uint8.pb

Tips

由于我个人不用Python,所以模型我是自己下载的,这里我也只用了tensorflow的方式,所以就只下了后面的opencv_face_detector_uint8.pb

DNN的核心函数

#

核心函数

1

readNetFromTensorflow

2

blobFromImage

3

setInput

4

forward

01

readNetFrom

根据我们用的模型不同改为不同的函数,后面的参数就是加载模型文件

代码语言:javascript复制
//Tensorflow
cv::dnn::readNetFromTensorflow(_modelbinary, _modeldesc)

//Caffe
cv::dnn::readNetFromCaffe(_modelbinary, _modeldesc)

02

blobFromImage

代码语言:javascript复制
Mat cv::dnn::blobFromImage(
    InputArray     image,  //输入图像
    double     scalefactor = 1.0,   //图像缩放的比率
    const Size &     size = Size(),  //返回的Mat中数据的尺寸
    const Scalar &     mean = Scalar(),    //关于mean参数,如果之前没有深入研究过深度学习,这个还是不太好理解的。首先给出mean的数值:(104 117 123);数字从什么地方来的呢?这个是在Net训练的时候设定的,可以看到在训练的时候transform_param中设置了mean
    bool     swapRB = false,  //是否交换R和B分量
    bool     crop = false,  //裁剪标志,指示是否在调整大小后裁剪图像
    int     ddepth = CV_32F //图像的数据类型,目前仅支持32F和8U
    )

03

setInput

代码语言:javascript复制
void cv::dnn::Net::setInput    (    
    InputArray     blob,  //上 个函数blobFromImage的返回值
    const String &     name = "",  //输入图层的名称
    double     scalefactor = 1.0,  //可选的标准化比例
    const Scalar &     mean = Scalar() //可选的平均减法值
    )

04

forward

代码语言:javascript复制
 Mat forward(const String& outputName = String())
 
 //outputName:需要输出的图层的名称
 //返回:指定图层outputName的第一个输出的blob。默认情况下,为整个网络运行正向传递。注意:返回Mat类型,这是一个4D数,rows and cols can only hold 2 dimensions, so they are not used here, and set to -1

DNN检测封装

我直接把DNN的检测的封装了一个名称为dnnfacedetect的C 的类出来,可以直接拷贝复用了。

dnnfacedetect.h

代码语言:javascript复制
#pragma once

#include<opencv2/opencv.hpp>
#include<opencv2/dnn/dnn.hpp>

using namespace std;
using namespace cv;

class dnnfacedetect
{
private:
  string _modelbinary, _modeldesc;
  dnn::Net _net;
public:
  //构造函数 传入模型文件
  dnnfacedetect();
  dnnfacedetect(string modelBinary, string modelDesc);

  ~dnnfacedetect();
  //置信阈值
  float confidenceThreshold;
  double inScaleFactor;
  size_t inWidth;
  size_t inHeight;
  Scalar meanVal;

  //初始化DNN网络
  bool initdnnNet();

  //人脸检测
  vector<Mat> detect(Mat frame);
};

dnnfacedetect.cpp

代码语言:javascript复制
#include "dnnfacedetect.h"


dnnfacedetect::dnnfacedetect()
{
  dnnfacedetect("", "");
}

//构造函数
dnnfacedetect::dnnfacedetect(string modelBinary, string modelDesc)
{
  _modelbinary = modelBinary;
  _modeldesc = modelDesc;

  //初始化置信阈值
  confidenceThreshold = 0.6;
  inScaleFactor = 0.5;
  inWidth = 300;
  inHeight = 300;
  meanVal = Scalar(104.0, 177.0, 123.0);
}

dnnfacedetect::~dnnfacedetect()
{
  _net.~Net();
}

//初始化dnnnet
bool dnnfacedetect::initdnnNet()
{
  _net = dnn::readNetFromTensorflow(_modelbinary, _modeldesc);
  _net.setPreferableBackend(dnn::DNN_BACKEND_OPENCV);
  _net.setPreferableTarget(dnn::DNN_TARGET_CPU);

  return !_net.empty();
}

//人脸检测
vector<Mat> dnnfacedetect::detect(Mat frame)
{
  Mat tmpsrc = frame;
  vector<Mat> dsts = vector<Mat>();
  // 修改通道数
  if (tmpsrc.channels() == 4)
    cvtColor(tmpsrc, tmpsrc, COLOR_BGRA2BGR);
  // 输入数据调整
  Mat inputBlob = dnn::blobFromImage(tmpsrc, inScaleFactor,
    Size(inWidth, inHeight), meanVal, false, false);
  _net.setInput(inputBlob, "data");

  //人脸检测
  Mat detection = _net.forward("detection_out");

  Mat detectionMat(detection.size[2], detection.size[3],
    CV_32F, detection.ptr<float>());

  //检测出的结果进行绘制和存放到dsts中
  for (int i = 0; i < detectionMat.rows; i  ) {
    //置值度获取
    float confidence = detectionMat.at<float>(i, 2);
    //如果大于阈值说明检测到人脸
    if (confidence > confidenceThreshold) {
      //计算矩形
      int xLeftBottom = static_cast<int>(detectionMat.at<float>(i, 3) * tmpsrc.cols);
      int yLeftBottom = static_cast<int>(detectionMat.at<float>(i, 4) * tmpsrc.rows);
      int xRightTop = static_cast<int>(detectionMat.at<float>(i, 5) * tmpsrc.cols);
      int yRightTop = static_cast<int>(detectionMat.at<float>(i, 6) * tmpsrc.rows);
      //生成矩形
      Rect rect((int)xLeftBottom, (int)yLeftBottom,
        (int)(xRightTop - xLeftBottom),
        (int)(yRightTop - yLeftBottom));

      //截出图矩形存放到dsts数组中
      Mat tmp = tmpsrc(rect);
      dsts.push_back(tmp);

      //在原图上用红框画出矩形
      rectangle(frame, rect, Scalar(0, 0, 255));
    }
  }

  return dsts;
}

Tips

上面的初始化和检测这块基本都是这个模式,最主要就是几个参数的配置,

创建项目

新建一个C 的项目,配置OpenCV可以看《VS2017配置OpenCV通用属性》,然后把我们需要用的模型文件和要检测的图片拷贝进去

在main.cpp的调用代码

代码语言:javascript复制
#include<opencv2/opencv.hpp>
#include<iostream>
#include <direct.h>
#include "dnnfacedetect.h"

using namespace std;
using namespace cv;

int main(int argc, char* argv) {
  //获取程序目录
  char filepath[256];
  _getcwd(filepath, sizeof(filepath));

  cout << filepath << endl;
  //定义模型文件
  string ModelBinary = (string)filepath   "/opencv_face_detector_uint8.pb";
  string ModelDesc = (string)filepath   "/opencv_face_detector.pbtxt";

  //图片文件
  string picdesc = (string)filepath   "/lena.jpg";

  cout << ModelBinary << endl;
  cout << ModelDesc << endl;

  //加载图片
  Mat frame = imread(picdesc);
  imshow("src", frame);

  try
  {
    //初始化
    dnnfacedetect fdetect = dnnfacedetect(ModelBinary, ModelDesc);
    if (!fdetect.initdnnNet())
    {
      cout << "初始化DNN人脸检测失败!" << endl;
      return -1;
    }

    if (!frame.empty()) {
      vector<Mat> dst = fdetect.detect(frame);
      if (!dst.empty()) {
        for (int i = 0; i < dst.size(); i  ) {
          string title = "dst"   i;
          imshow(title, dst[i]);
        }
        imshow("src2", frame);
      }
    }
  }
  catch (const std::exception & ex)
  {
    cout << ex.what() << endl;
  }

  waitKey(0);
  return 0;
}

这样整个代码就完成了,我们来看看运行的效果。

实现效果

上图中可以看到,左边的是源图,中间小的就是我们人脸检测出来后截取的图,右图就是在源图的基础上用红框把人脸标识出来的图,这样我们的DNN实现人脸检测就完成了。

0 人点赞