OpenCV3.3 版本第一次把深度神经网络 (DNN) 模块引入到正式发布版本中, 最新的 OpenCV3.4 中 DNN 模块发布了两个必杀技, 一个支持 Faster R-CNN 的对象检测, 比 SSD 与 YOLO 这些模型有更好的检测精度与小对象检测能力, 另外一个是支持基于 SSD+Resnet 模型的人脸检测, 虽然速度还达不到 HAAR 级联检测器的实时性, 但是准确性与模型泛化能力可以说完爆 HAAR 级联检测器方式的人脸检测算法. 作为 OpenCV 开发者需要人脸检测功能时候又多了一种更加可靠的选择, 这里我们首先简单介绍一下什么是残差网络, 然后给出其人脸检测模型在 OpenCV 基于摄像头实时人脸检测演示.
一: 残差网络(Resnet)
最初的 CNN 网络 LeNet 与 AlexNet 卷积层都比较少, VGG 通过小的卷积核实现了网络深度的增加取得了显著效果, 但是当层数过度增加的时候就发现训练错误与测试错误都在增加, 图示如下:
最开始人们以为是因为梯度消失或者梯度爆炸导致的, 不过随着大家的努力, 认为这个不是一个过拟合问题, 而是网络褪化现象, 所以针对这种情况, MSRA 何凯明团队提出了一种新的网络模型 - Residual Networks, 其主要思想是使用残差结构来训练网络, 一个残差结构如下:
作者认为 F(x) = H(x)-x 所以得到 H(x) = F(x) + x 这样的恒等映射, 然后作者就建立 34 层 plain 网络与 34 层的残差网络作为对比, 而最左边的 VGG-19 网络作为参考, 整个的网络结构显示如下:
--- 图太大啦!!!
模型建立好的之后, 作者在不同的数据集上进行了训练与测试, 均观察到残差网络的效果要明显优于 34 层 plain 网络, 而且发现基于残差结构的网络层数越深效果越好, 而 34 层 plain 网络跟 18 层的 plain 网络相比有明显的褪化现象出现. 对比训练的结果如下:
在残差网络没有出来之前, 很少有网络的层数会超过 100 层, 但是残差网络可以达到上千层, 毫无疑问何凯明团队也凭借残差网络模型在 2015 年的 ImageNet 图像分类比赛中获得了冠军, 当时使用 152 层的残差网络. OpenCV 中人脸检测的残差网络模型是基于 SSD 实现的, 所以速度还是挺快的, 而且效果是特别的好. 废话不多说了, 下面我就看看 OpenCV 中如何使用它实现人脸检测.
二: 人脸检测代码实现
模型是基于 Caffe 网络训练生成的, 所以在开始写程序之前的第一件事情就是要下载模型文件与描述文件, 这个我已经下载好了, 大家就不用 ××× 了, 直接去我的 github 地址上下载模型文件即可
https://github.com/gloomyfish1998/opencv_tutorial
下载好模型之后放在本地的一个文件夹下即可, 然后就可以开始编程工作啦.
首先需要加载模型成网络:
- String modelDesc = "D:/vcprojects/images/dnn/face/deploy.prototxt";
- String modelBinary = "D:/vcprojects/images/dnn/face/res10_300x300_ssd_iter_140000.caffemodel";
- // 初始化网络
- dnn::Net net = readNetFromCaffe(modelDesc, modelBinary);
- if (net.empty())
- {
- printf("could not load net...\n");
- return -1;
- }
然后要打开本地相机或者一段视频文件, 使用 VideoCapture 对象即可, 代码如下:
- // 打开摄像头
- VideoCapture capture(0);
- if (!capture.isOpened()) {
- printf("could not load camera...\n");
- return -1;
- }
打开相机成功之后就可以读写每帧图像, 然后转换成网络可以接受的数据类型, 代码如下:
- // 输入数据调整
- Mat inputBlob = blobFromImage(frame, inScaleFactor,
- Size(inWidth, inHeight), meanVal, false, false);
- net.setInput(inputBlob, "data");
然后在 OpenCV 中通过调用 net.forward 实现检测, 对结果提取置信分数 (0~1) 之间, 对大于阈值 (假设 0.5) 的提取 BOX 位置, 就可以绘制矩形框显示了, 这部分的代码如下:
- // 人脸检测
- Mat detection = net.forward("detection_out");
- vector<double> layersTimings;
- 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;
- ss <<"FPS:" << 1000 / time << "; time:" << time << "ms";
- putText(frame, ss.str(), Point(20, 20), 0, 0.5, Scalar(0, 0, 255));
- 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) * 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.str("");
- 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), CV_FILLED);
- putText(frame, label, Point(xLeftBottom, yLeftBottom),
- FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0));
- }
- }
最终运行显示结果如下, 脸部无遮挡, 正常情况下:
脸部无遮挡, 头部倾斜的情况下:
脸部有遮挡的情况下:
更多倾斜, 侧脸, 模糊等各种情况下:
可见残差网络模型是何等的强大, 到这里是不是该点一首凉凉送给 HAAR 级联检测器了. 上述 demo 完整源代码, 可以在 GITHUB 上下载.
https://github.com/gloomyfish1998/opencv_tutorial
OpenCV DNN 教程学习
来源: http://blog.51cto.com/gloomyfish/2094611