前言
OpenCV 3.3正式发布后,对深度学习(dnn模块)提供了更好的支持,dnn模块目前支持Caffe、TensorFlow、Torch、PyTorch等深度学习框架。
另外,新版本中使用预训练深度学习模型的API同时兼容C++和Python,让系列操作变得非常简便
从硬盘加载模型;对输入图像进行预处理;将图像输入网络,获取输出的分类。
当然,我们不能、也不该用OpenCV训练深度学习模型,但这个新版本让我们能把用深度学习框架训练好了的模型拿来,高效地用在OpenCV之中。
在很久很久以前,用Opencv2.4x跟Opencv3.0进行人脸检测的时候,都是基于HAAR级联,但是我测试过HAAR级联,明显不太准,而且速度慢。但是现在啦,深度学习的快速发展,使人脸检测 越来越精准,在Opencv4发布以来,官方支持的人脸检测方法已经转换为支持深度学习的快准狠的方法了,就连HAAR级联训练的工具都在Opencv4中除名了,其实已经有了支持深度学习的人脸检测算法,并且准确率和速度都比HAAR好,那再学HAAR级联无疑是浪费时间和精力了,要学会跟着时代的步伐嘛,所以与时俱进很重要的。
注意:本文不涉及任何原理,只讲具体的应用。
一、下载模型文件
在OpenCV的\sources\samples\dnn\face_detector目录下,有一个download_weights.py脚本文件,首先运行一下,下载模型文件。下载的模型文件分别为:
Caffe模型:
res10_300x300_ssd_iter_140000_fp16.caffemodel
deploy.prototxt
tensorflow模型:
opencv_face_detector_uint8.pb
opencv_face_detector.pbtxt
pb文件的能够保存tensorflow计算图中的操作节点以及对应的各张量,方便我们日后直接调用之前已经训练好的计算图。
其中tensorflow的模型OpenCV官方对它进行了量化处理,大小只有2MB左右,非常适合在各种场景下使用。
如果没有安装python,我将这几个模型文件上传到了网盘,直接下载下来就可以了。
链接:/s/19XUTKXBriInd9goHWBwLSg
提取码:fye8
二、演示代码
1.读入模型文件
String modelDesc = "D:/VC projects/dnn_face_detection-model/opencv_face_detector.pbtxt";String modelBinary = "D:/VC projects/dnn_face_detection-model/opencv_face_detector_uint8.pb";
12
2.初始化网络,读入权重和网络结构,调用cv::dnn对应的后端设置函数
dnn::Net net = readNetFromTensorflow(modelBinary, modelDesc);net.setPreferableBackend(DNN_BACKEND_OPENCV);net.setPreferableTarget(DNN_TARGET_CPU);
123
这样子程序再运行dnn计算时,用CPU运行。
当然也可以用GPU加速:
// when using opencv 4 and data > 1022, the cuda is supportnet.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
123
3.对输入数据进行调整。
Mat inputBlob = blobFromImage(frame, inScaleFactor, Size(inWidth, inHeight), meanVal, false, false);
1
4.输入、输出
net.setInput(inputBlob, "data");//detection返回值标签、置信度、目标位置的4个坐标信息[xmin ymin xmax ymax]Mat detection = net.forward("detection_out");
123
完整代码:
#include <opencv2/dnn.hpp>#include <opencv2/opencv.hpp>using namespace cv;using namespace cv::dnn;#include <iostream>#include <cstdio>using namespace std;const size_t inWidth = 300;const size_t inHeight = 300;const double inScaleFactor = 1.0;//为了减去平均值(mean):为了消除同一场景下不同光照的图片,对我们最终的分类或者神经网络的影响,我们常常对图片的R、G、B通道的像素求一个平均值,然后将每个像素值减去我们的平均值,这样就可以得到像素之间的相对值,就可以排除光照的影响。const Scalar meanVal(104.0, 177.0, 123.0);const float confidenceThreshold = 0.7;void face_detect_dnn();void mtcnn_demo();int main(int argc, char** argv) {face_detect_dnn();waitKey(0);return 0;}void face_detect_dnn() {//读入模型路径//String modelDesc = "D:/VC projects/dnn_face_detection-model/deploy.prototxt";//String modelBinary = "D:/VC projects/dnn_face_detection-model/res10_300x300_ssd_iter_140000_fp16.caffemodel";String modelDesc = "D:/VC projects/dnn_face_detection-model/opencv_face_detector.pbtxt";String modelBinary = "D:/VC projects/dnn_face_detection-model/opencv_face_detector_uint8.pb";//初始化网络//dnn::Net net = readNetFromCaffe(modelDesc, modelBinary);dnn::Net net = readNetFromTensorflow(modelBinary, modelDesc);net.setPreferableBackend(DNN_BACKEND_OPENCV);net.setPreferableTarget(DNN_TARGET_CPU);if (net.empty()) {printf("could not load net...\n");return;}//打开摄像头//VideoCapture capture("D:/images/video/Boogie_Up.mp4");VideoCapture capture(0);if (!capture.isOpened()) {printf("Could not load camera...\n");return;}Mat frame;int count = 0;while (capture.read(frame)) {//用于返回从操作系统启动到当前所经的计时周期数int64 start = getTickCount();if (frame.empty()) break;//水平镜像调整//flip(frame, frame, 1);imshow("input", frame);if (frame.channels() == 4) cvtColor(frame, frame, COLOR_BGRA2BGR);//输入数据调整/*深度学习或图片分类的预处理blobFromImage1.整体像素值减去平均值(mean)2.通过缩放系数(scalefactor)对图片像素值进行缩放*/Mat inputBlob = blobFromImage(frame, inScaleFactor, Size(inWidth, inHeight), meanVal, false, false);net.setInput(inputBlob, "data");//人脸检测Mat detection = net.forward("detection_out");//detection返回值标签、置信度、目标位置的4个坐标信息[xmin ymin xmax ymax]vector<double> layersTimings;//用于返回CPU的频率,也就是一秒内重复的次数//1000 *总次数/一秒内重复的次数= 时间(ms)double freq = getTickFrequency() / 1000;double time = net.getPerfProfile(layersTimings) / freq;Mat detectionMat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>());ostringstream ss;for (int i = 0; i < detectionMat.rows; ++i) {//置信度0-1之间float confidence = detectionMat.at<float>(i, 2);if (confidence > confidenceThreshold) {count++;//坐标int xLeftBottom = static_cast<int>(detectionMat.at<float>(i, 3)*frame.cols);int yLeftBottom = static_cast<int>(detectionMat.at<float>(i, 4) * frame.rows);int xRightTop = static_cast<int>(detectionMat.at<float>(i, 5) * frame.cols);int yRightTop = static_cast<int>(detectionMat.at<float>(i, 6) * frame.rows);//画框Rect object((int)xLeftBottom, (int)yLeftBottom,(int)(xRightTop - xLeftBottom),(int)(yRightTop - yLeftBottom));rectangle(frame, object, Scalar(0, 255, 0));ss << confidence;//添加标签String conf(ss.str());String label = "Face: " + conf;int baseLine = 0;Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);rectangle(frame, Rect(Point(xLeftBottom, yLeftBottom - labelSize.height),Size(labelSize.width, labelSize.height + baseLine)),Scalar(255, 255, 255), FILLED);putText(frame, label, Point(xLeftBottom, yLeftBottom),FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0));}}float fps = getTickFrequency() / (getTickCount() - start);ss.str("");ss << "FPS:" << fps << ";inference time: " << time << " ms";putText(frame, ss.str(), Point(20, 20), 0, 0.75, Scalar(0, 0, 255), 2, 8);imshow("dnn_face_detect", frame);if (waitKey(1) >= 0) break;}printf("total face: %d\n", count);}