opencv多人脸检测,大幅降低误检、漏检解决一脸多框问题 (方法一)

我怕爱的太早我们不能终老 提交于 2019-12-01 23:34:51

上一段时间参加了某个比赛,其中有一环节需要用多人脸检测;由于其他队伍好多采用的商用SDK,竞争激烈,又不能误检更不能漏检测,而且保证框的数量只能和人脸数量对应,不能一个人脸上有多个框。
而我还是喜欢自己写,所以想出了几个方案实现了一下,实现都比较简单而且准确率都得到了保证。
比赛结束,最终因为性别检测出现了错误,与冠军失之交臂,我的方案好在是自己写的,顺便把源码也给大家

因为是在嵌入式上应用的,所以代码是用 C++实现

1.预读

为了方便后面大家理解,我在这里把我的思路县说一下,顺便把一些参数必要的部分说一下。

1.1思路

基础是使用的是Haar级联分类器,但是直接使用Haar级联分类器,单纯的调参都不能达到很好的效果。
所以是先用低阈值的人脸检测初步筛选出人脸框(因为是低阈值所以框总是比),然后在初筛的人脸框部分再进行人眼睛检测,只有同时存在人眼的部分才会做成人脸输出。

1.2Haar级联分类器使用心得

Haar级联分类器速度快,容易调用,是很好的工具。基础的使用教程,我放在这里
opencv 官网object模块使用例子
https://docs.opencv.org/master/db/d28/tutorial_cascade_classifier.html

其中创建检测器的参数在这里说明一下,
cv::CascadeClassifier::detectMultiScale ( InputArray image,
std::vector< Rect >
double scaleFactor = 1.1,
int minNeighbors = 3,
int flags = 0,
Size minSize = Size(),
Size maxSize = Size()
)
第一个参数是图像矩阵,即输入一个Mat即可;
第二个参数是输出口,输出一个 Rect型的容器,一个Rect存放的是四个整型值(x,y,w,h)xy为矩形框左上角的像素坐标,wh分别代表着矩形框的宽和高;
第三个参数是每次搜索框缩小的比例,看一下官网例子前的算法解析就知道,级联分类器是从大框开始搜索,大框搜索完一遍,再用小一级的框集进行重新搜索。而这个两级之间的大小比例就是这个参数。
第四个参数是重复确定框,比如默认是3,一块人脸只有被重复扫描到3次是人脸才会放在输出里面,可以想到,当数值增大的时候,判断的要求就会变高,误检率会变低,但漏检率会随之升高;当数值更小比如2时,会向相反变化。经过我的测试,单纯靠改变这个参数,无法达到自己的期望
第五个参数如果是使用opencv3可以忽略,没有区别,默认0就可以。
第六个参数和第七个参数是一个道理,也就是之前说的第三个参数里面大小框的关系。第三个参数是两个级别框的比例,而最后这两个参数是最大框和最小框的设定。如果在使用的过程中,确定不会出现大脸或者特别小的脸,可以通过设置这两个参数加快检测速度。

1.3去掉重叠度高的人脸框

考虑到嵌入式计算能力,我们为了达到实时的效果所以要把数值计算尽量变成逻辑运算。
从最简单的情况推理,假设有两个框(x1,y1,w1,h1) ,(x2,y2,w2,h2)如果要保证完全不重叠,则有以下关系:(在纸上画一下就看得出来)
x1+w1<x2
y1+h1<y2

但是当我们要求允许部分重叠时候,就可以添加一个参数:
重叠度 thr ,thr取值为0~1;
x1+(w1 x thr)<x2
y1+(h1 x thr)<y2

2.代码实现(封装成类,可以直接调用)

头文件.h

//
// Created by peilin on 2019/10/2.
//

#ifndef OPERATOR_CASCADE_CLASSIFER_H
#define OPERATOR_CASCADE_CLASSIFER_H

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;

class cascade_classifer {
public:

    cascade_classifer();
    ~cascade_classifer();
    vector<Rect> faces(Mat frame);
    vector<Rect> eyes(Mat frame);

private:
    CascadeClassifier face_cascade; \\初始化一个人脸检测器
    CascadeClassifier eyes_cascade; \\初始化一个人眼检测器
    String FACE_MODEL_PATH; \\人脸模型文件地址
    String EYES_MODEL_PATH; \\人眼模型地址
    int FACE_MINS;  \\调整参数,即人脸检测器的第四个参数,这里单独拿出来是为了方便修改
    float FACE_OVERLAP_RATE;  \\人脸的重叠度,即1.3中的那个阈值thr
    bool isOverlap(Rect rct1, Rect rct2);  \\判断两个框是否重叠,bool型函数
};

#endif //OPERATOR_CASCADE_CLASSIFER_H

~~~~~~~~~~~~~~~分割线~~~~~~~~~~~~~~~~~~~~

类的主函数.cpp

//
// Created by peilin on 2019/10/2.
//

#include "cascade_classifer.h"

cascade_classifer::cascade_classifer()
{
    FACE_MODEL_PATH = "face_model_path"; \\存放脸模型的参数
    EYES_MODEL_PATH = "eyes_model_path"; \\存放人眼模型的地址
    FACE_OVERLAP_RATE = 0.8;
    FACE_MINS = 2;
    face_cascade.load(FACE_MODEL_PATH);
    eyes_cascade.load(EYES_MODEL_PATH);
}
cascade_classifer::~cascade_classifer()
{
}

vector<Rect> cascade_classifer::faces(Mat frame) {
    //init and preproce
    cascade_classifer cascade;
    size_t num_faces, repeat, repeat_final;
    Mat frame_gray;
    vector<Rect> faces_init, eyes, faces, faces_final;

    if (frame.empty())
        cout << "No image in frame!" << endl;
    cvtColor(frame, frame_gray, COLOR_BGR2GRAY);

    //detect
    face_cascade.detectMultiScale(frame_gray, faces_init, 1.1, FACE_MINS,0);
	\\faces_init用来存放初筛的人脸框
    if(faces_init.size()==0)
        return faces_init; \\如果是初筛也没有框,则直接返回空
    else
    {
        faces.push_back(faces_init[0]);
        for(repeat=1;repeat<faces_init.size();repeat++)
        {
            for(repeat_final=0;repeat_final<faces.size();repeat_final++)
            {
            \\判断是否有重叠框,去掉重叠度高框
                if(cascade.isOverlap(faces_init[repeat], faces[repeat_final])) 
                    break;
            }
            if(repeat_final==faces.size())
                faces.push_back(faces_init[repeat]);
            else continue;
        }
    }
        
    for(num_faces=0;num_faces<faces.size();num_faces++)
    {
        eyes=cascade.eyes(frame_gray(faces[num_faces]));
        if(eyes.size())
        {
            faces_final.push_back(faces[num_faces]);
            rectangle(frame, cvPoint(faces[num_faces].x, faces[num_faces].y),
                      cvPoint(faces[num_faces].x + faces[num_faces].width,
                              faces[num_faces].y + faces[num_faces].height),
                      Scalar(255, 0, 0),2);
        }
    }
    return faces_final;
}

vector<Rect> cascade_classifer::eyes(Mat frame)
{
    vector<Rect> eyes_data;
    eyes_cascade.detectMultiScale(frame, eyes_data);
    return eyes_data;
}

bool cascade_classifer::isOverlap(Rect rc1,Rect rc2)
{
    if ((rc1.x + rc1.width*FACE_OVERLAP_RATE)  > rc2.x &&
        (rc2.x + rc2.width*FACE_OVERLAP_RATE)  > rc1.x &&
        (rc1.y + rc1.height*FACE_OVERLAP_RATE) > rc2.y &&
        (rc2.y + rc2.height*FACE_OVERLAP_RATE) > rc1.y
        )
        return true;
    else
        return false;
}

这个方法非常适用于像素足够情况下的情况(也就是没有特别小尺寸的人脸)
下次更新一个用模板匹配的方法检测,能够进一步检测到小的人脸

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!