学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为3322字,预计阅读9分钟
前言
《OpenCV源码Android端编译,用时三天,我编了个寂寞。。。》文中介绍了编译OpenCV+Contrib模块的编译,虽然Andorid下编译始终没有获得libopencv_java4.so的库,不过在Windows下编译还是正常的,今天主要介绍人脸特征点的检测功能,就用到Contrib模块中的FaceMarkLBF。
实现效果
上面的GIF图中可以看出来,除了第一张多人里面有侧脸的,检测特征点时有点差,基本检测的都还挺不错。文章最后有Demo的GitHub源码地址。
实现方式
# | 思路 |
---|---|
1 | 加载OpenCV DNN和FacemarkLBF的模型(FacemarkLBF在OpenCVr的Contrib模块中) |
2 | 使用DNN人脸检测获取图像中所有人脸的矩形框 |
3 | 调用FaceMarkLBF中的fit针对Mat和上面获得的人脸矩形框进行特征点检测,检测的结果存放为vector<vector<Point2f>>格式。 |
重点说明
微卡智享
01
关于OpenCV配置
使用VS2019编译的OpenCV及Contrib模块的源码后,生成的dll的动态库是VC16了,不是原来的VC15,而且在Debug和Relese都编译了一遍(用处就在正式环境中速度会提高接近10倍),所以会有opencv_world451.dll和opencv_world451d.dll两个动态库。
属性管理器里面也加入Debug和Relese的配置
Debug和Relese的VC++目录还是编译后的OpenCV目录。
链接器中Debug这里填上opencv_world451d.lib,而Relese里就填opencv_world451.lib。
02
DNN人脸检测
同《实践|OpenCV4.2使用DNN进行人脸检测一(图片篇)》文章中一样,已经把DNN的人脸检测单位写在一个类中
原来的detect的函数中我们是在原图上画出红色矩形框,返回的是vector<Mat>的值,因为特征点需要的是vector<Rect>的值,所以又新写了一个函数
-
bool dnnfacedetect::detectRect(Mat frame, vector<Rect> &rects)
-
{
-
Mat tmpsrc = frame;
-
-
-
// 修改通道数
-
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>());
-
-
-
if (detectionMat.rows <=
0)
return
false;
-
-
-
//检测出的结果进行绘制和存放到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));
-
-
-
rects.push_back(rect);
-
}
-
}
-
-
-
return
true;
-
}
03
LBF人脸特征点
LBF人脸特征点检测也单独写了一个类,里面也比较简单,构造函数直接加载模型,然后一个检测的函数。
-
#include "chkfacemark.h"
-
-
-
-
-
chkfacemark::chkfacemark(
string lfmodel)
-
{
-
_lfbmodel = lfmodel;
-
//创建对象
-
_facemark = FacemarkLBF::create();
-
//加载模型
-
_facemark->loadModel(_lfbmodel);
-
}
-
-
-
chkfacemark::~chkfacemark()
-
{
-
_facemark.release();
-
}
-
-
-
bool chkfacemark::facemarkdetector(Mat src, vector<Rect> faces, vector<vector<Point2f>> &facemarks)
-
{
-
return _facemark->fit(src, faces, facemarks);
-
}
-
-
04
多张图片加载
以前的Demo中只是加载了一张图片,这次是直接加了一个目录下的文件。定义了文件目录后,使用cv::glob的函数可以把所有的文件名存放到vector<string>的变量里。
-
//加载多张图片
-
string picdesc =
"E:/DCIM/person/";
-
vector<
string> filenames;
-
cv::glob(picdesc, filenames);
然后再循环处理filenames的方法即可。
05
关于图像缩放的问题
上面两个图中可以看到,像素差别好大,如果不用缩放的话,第二张图只能看到左上角的图片,为了解决这个问题,所以写了一个函数用于处理图像绽放的问题。
# | 图像缩放思路 |
---|---|
1 | 设定横向图像的最大宽度、纵向图像的最大高度 |
2 | 根据输入的图像判断是横向还是纵向 |
3 | 横向如果宽度超过最大宽度,按最大宽度除当前宽度算出比例,然后进行Resize的缩放(纵向就是高度超过最大高度处理) |
-
//计算图像缩放
-
void MatResize(Mat& frame, int maxwidth, int maxheight)
-
{
-
double scale;
-
//判断图像是水平还是垂直
-
bool isHorizontal = frame.cols > frame.rows ?
true :
false;
-
-
-
//根据水平还是垂直计算缩放
-
if (isHorizontal) {
-
if (frame.cols > maxwidth) {
-
scale = (
double)maxwidth / frame.cols;
-
resize(frame, frame, Size(
0,
0), scale, scale);
-
}
-
}
-
else {
-
if (frame.rows > maxheight) {
-
scale = (
double)maxheight / frame.rows;
-
resize(frame, frame, Size(
0,
0), scale, scale);
-
}
-
}
-
}
然后在每次读取完图像后先进行缩放处理,即可正常显示了。
比较核心的东西上面都已经讲完了,全部的代码可以访问下面的链接,或是点击最后的阅读原文下载。里面的LBF模型文件还有DNN的模型文件都在代码里面。
源码地址
https://github.com/Vaccae/OpenCVDnnfacedecet.git
完
扫描二维码
获取更多精彩
微卡智享
「 往期文章 」
转载:https://blog.csdn.net/Vaccae/article/details/113932038