上一段时间参加了某个比赛,其中有一环节需要用多人脸检测;由于其他队伍好多采用的商用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;
}
这个方法非常适用于像素足够情况下的情况(也就是没有特别小尺寸的人脸)
下次更新一个用模板匹配的方法检测,能够进一步检测到小的人脸
来源:CSDN
作者:BlainWu
链接:https://blog.csdn.net/qq_22945165/article/details/102889150