深度神经网络——加载 Caffe 框架模型 OpenCV v4.8.0

下一个教程如何启用 Halide 后端以提高效率

原作者Vitaliy Lyudvichenko
兼容性OpenCV >= 3.3

简介

在本教程中,你将学习如何使用 opencv_dnn 模块,利用 Caffe 模型动物园中的 GoogLeNet 训练网络进行图像分类。

我们将在以下图片上演示此示例的结果。

在这里插入图片描述

布兰航天飞机

源代码

我们将使用示例应用程序中的片段,可从此处下载。

#include <fstream>
#include <sstream>
#include <iostream>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include "common.hpp"
std::string keys =
    "{ help  h          | | Print help message. }"
    "{ @alias           | | An alias name of model to extract preprocessing parameters from models.yml file. }"
    "{ zoo              | models.yml | An optional path to file with preprocessing parameters }"
    "{ input i          | | Path to input image or video file. Skip this argument to capture frames from a camera.}"
    "{ initial_width    | 0 | Preprocess input image by initial resizing to a specific width.}"
    "{ initial_height   | 0 | Preprocess input image by initial resizing to a specific height.}"
    "{ std              | 0.0 0.0 0.0 | Preprocess input image by dividing on a standard deviation.}"
    "{ crop             | false | Preprocess input image by center cropping.}"
    "{ framework f      | | Optional name of an origin framework of the model. Detect it automatically if it does not set. }"
    "{ needSoftmax      | false | Use Softmax to post-process the output of the net.}"
    "{ classes          | | Optional path to a text file with names of classes. }"
    "{ backend          | 0 | Choose one of computation backends: "
                            "0: automatically (by default), "
                            "1: Halide language (http://halide-lang.org/), "
                            "2: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
                            "3: OpenCV implementation, "
                            "4: VKCOM, "
                            "5: CUDA, "
                            "6: WebNN }"
    "{ target           | 0 | Choose one of target computation devices: "
                            "0: CPU target (by default), "
                            "1: OpenCL, "
                            "2: OpenCL fp16 (half-float precision), "
                            "3: VPU, "
                            "4: Vulkan, "
                            "6: CUDA, "
                            "7: CUDA fp16 (half-float preprocess) }";
/*
std::string keys =
    "{ help h | | Print help message. }"
    "{ @alias | | 从 models.yml 文件中提取预处理参数的模型别名。}"
    "{ zoo | models.yml | 预处理参数文件的可选路径 }"
    "{ input i | | 输入图像或视频文件的路径。从摄像头捕捉帧时可跳过该参数。}
    "{ initial_width | 0 | 预处理输入图像,将其初始大小调整为特定宽度。}
    "{ initial_height | 0 | 将输入图像的大小预处理为特定高度。}
    "{ std | 0.0 0.0 0.0 | 按标准偏差对输入图像进行预处理。}
    "{ crop | false | 通过居中裁剪对输入图像进行预处理。}
    "{ framework f |模型原点框架的可选名称。如果未设置,则自动检测。}"
    "{ needSoftmax | false | 使用 Softmax 对网络输出进行后处理。}
    "{ classes | | 包含类名的文本文件的可选路径。}"
    "{ backend | 0 | 选择其中一个计算后端: "
                            "0: 自动(默认), "
                            " "1: Halide 语言(http://halide-lang.org/), "
                            " "2: 英特尔深度学习推理引擎 (https://software.intel.com/openvino-toolkit), " "3: OpenCV 实现, " "4.
                            " "3:OpenCV 实现, "
                            " "4:VKCOM, "
                            "5:CUDA
                            "6: WebNN }"
    "{ target | 0 | 选择目标计算设备之一: "
                            " "0: CPU 目标(默认), "
                            " "1: OpenCL, " "2: OpenCL fp16
                            " "2: OpenCL fp16(半浮点精度), "
                            " "3: VPU
                            " "4:Vulkan
                            " "6:CUDA, "
                            "7:CUDA fp16(半浮点预处理)}";
*/
using namespace cv;
using namespace dnn;
std::vector<std::string> classes;
int main(int argc, char** argv)
{
    CommandLineParser parser(argc, argv, keys);
    const std::string modelName = parser.get<String>("@alias");
    const std::string zooFile = parser.get<String>("zoo");
    keys += genPreprocArguments(modelName, zooFile);
    parser = CommandLineParser(argc, argv, keys);
    parser.about("Use this script to run classification deep learning networks using OpenCV.");
    if (argc == 1 || parser.has("help"))
    {
        parser.printMessage();
        return 0;
    }
    int rszWidth = parser.get<int>("initial_width");
    int rszHeight = parser.get<int>("initial_height");
    float scale = parser.get<float>("scale");
    Scalar mean = parser.get<Scalar>("mean");
    Scalar std = parser.get<Scalar>("std");
    bool swapRB = parser.get<bool>("rgb");
    bool crop = parser.get<bool>("crop");
    int inpWidth = parser.get<int>("width");
    int inpHeight = parser.get<int>("height");
    String model = findFile(parser.get<String>("model"));
    String config = findFile(parser.get<String>("config"));
    String framework = parser.get<String>("framework");
    int backendId = parser.get<int>("backend");
    int targetId = parser.get<int>("target");
    bool needSoftmax = parser.get<bool>("needSoftmax");
    std::cout<<"mean: "<<mean<<std::endl;
    std::cout<<"std: "<<std<<std::endl;
    // 打开包含类名的文件。
    if (parser.has("classes"))
    {
        std::string file = parser.get<String>("classes");
        std::ifstream ifs(file.c_str());
        if (!ifs.is_open())
            CV_Error(Error::StsError, "File " + file + " not found");
        std::string line;
        while (std::getline(ifs, line))
        {
            classes.push_back(line);
        }
    }
    if (!parser.check())
    {
        parser.printErrors();
        return 1;
    }
    CV_Assert(!model.empty());
    Net net = readNet(model, config, framework);
    net.setPreferableBackend(backendId);
    net.setPreferableTarget(targetId);
    // 创建窗口
    static const std::string kWinName = "Deep learning image classification in OpenCV";
    namedWindow(kWinName, WINDOW_NORMAL);
    VideoCapture cap;
    if (parser.has("input"))
        cap.open(parser.get<String>("input"));
    else
        cap.open(0);
    // 处理帧。
    Mat frame, blob;
    while (waitKey(1) < 0)
    {
        cap >> frame;
        if (frame.empty())
        {
            waitKey();
            break;
        }
        if (rszWidth != 0 && rszHeight != 0)
        {
            resize(frame, frame, Size(rszWidth, rszHeight));
        }
        blobFromImage(frame, blob, scale, Size(inpWidth, inpHeight), mean, swapRB, crop);
        // 检查 std 值。
        if (std.val[0] != 0.0 && std.val[1] != 0.0 && std.val[2] != 0.0)
        {
            // 将 blob 除以 std。
            divide(blob, std, blob);
        }
        net.setInput(blob);
        // double t_sum = 0.0;
        // double t;
        int classId;
        double confidence;
        cv::TickMeter timeRecorder;
        timeRecorder.reset();
        Mat prob = net.forward();
        double t1;
        timeRecorder.start();
        prob = net.forward();
        timeRecorder.stop();
        t1 = timeRecorder.getTimeMilli();
        timeRecorder.reset();
        for(int i = 0; i < 200; i++) {
            timeRecorder.start();
            prob = net.forward();
            timeRecorder.stop();
            Point classIdPoint;
            minMaxLoc(prob.reshape(1, 1), 0, &confidence, 0, &classIdPoint);
            classId = classIdPoint.x;
            // 添加效率信息。
            // std::vector<double> layersTimes;
            // double freq = getTickFrequency() / 1000;
            // t = net.getPerfProfile(layersTimes) / freq;
            // t_sum += t;
        }
        if (needSoftmax == true)
        {
            float maxProb = 0.0;
            float sum = 0.0;
            Mat softmaxProb;
            maxProb = *std::max_element(prob.begin<float>(), prob.end<float>());
            cv::exp(prob-maxProb, softmaxProb);
            sum = (float)cv::sum(softmaxProb)[0];
            softmaxProb /= sum;
            Point classIdPoint;
            minMaxLoc(softmaxProb.reshape(1, 1), 0, &confidence, 0, &classIdPoint);
            classId = classIdPoint.x;
        }
        std::string label = format("Inference time of 1 round: %.2f ms", t1);
        std::string label2 = format("Average time of 200 rounds: %.2f ms", timeRecorder.getTimeMilli()/200);
        putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));
        putText(frame, label2, Point(0, 35), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));
        // 打印预测的类。
        label = format("%s: %.4f", (classes.empty() ? format("Class #%d", classId).c_str() :
                                                      classes[classId].c_str()),
                                   confidence);
        putText(frame, label, Point(0, 55), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));
        imshow(kWinName, frame);
    }
    return 0;
}

说明

  1. 首先,下载 GoogLeNet 模型文件:bvlc_googlenet.prototxtbvlc_googlenet.caffemodel

还需要包含 ILSVRC2012 类名的文件:classes_ILSVRC2012.txt

将这些文件放到本程序示例的工作目录下。

  1. 使用 .prototxt 和 .caffemodel 文件的路径读取并初始化网络
net net = readNet(model, config, framework);
net.setPreferableBackend(backendId);
net.setPreferableTarget(targetId)

如果其中一个文件 modelconfig 的扩展名为 .caffemodel.prototxt,则可以跳过参数 framework。这样,函数 cv::dnn::readNet 就能自动检测模型的格式。

  1. 读取输入图像并转换为可被 GoogleNet 接受的 Blob 格式
VideoCapture cap;
if (parser.has("input"))
    cap.open(parser.get<String>("input"));
else
    cap.open(0);

cv::VideoCapture 可同时加载图像和视频。

blobFromImage(frame, blob, scale, Size(inpWidth, inpHeight), mean, swapRB, crop)// 检查 std 值。
if (std.val[0] != 0.0 && std.val[1] != 0.0 && std.val[2] != 0.0)
{
    // 将 blob 除以 std。
    divide(blob, std, blob)}

使用 cv::dnn::blobFromImage 函数对图像进行必要的预处理,如调整大小和对蓝色、绿色和红色通道分别进行均值减法(-104, -117, -123),然后将图像转换为 1x3x224x224 形状的四维 blob(即所谓的批处理)。

  1. 将 blob 传递给网络
net.setInput(blob)
  1. 向前传递
// double t_sum = 0.0;
// double t;
int classId;
double confidence;
cv::TickMeter timeRecorder;
timeRecorder.reset();
Mat prob = net.forward()double t1;
timeRecorder.start();
prob = net.forward();
timeRecorder.stop();
t1 = timeRecorder.getTimeMilli();
timeRecorder.reset()for(int i = 0; i < 200; i++) {

在前向传递过程中,会计算每个网络层的输出,但在本例中,我们只需要最后一层的输出。

  1. 确定最佳类别
Point classIdPoint;
minMaxLoc(prob.reshape(1, 1), 0, &confidence, 0, &classIdPoint);
classId = classIdPoint.x;

我们将网络输出(包含 1000 个 ILSVRC2012 图像类别中每个类别的概率)放到 prob blob 中。并找到其中最大值元素的索引。该索引与图像类别相对应。

  1. 通过命令行运行示例
./example_dnn_classification --model=bvlc_googlenet.caffemodel --config=bvlc_googlenet.prototxt --width=224 --height=224 --classes=classification_classes_ILSVRC2012.txt --input=space_shuttle.jpg --mean="104 117 123"

对于我们的图像,我们得到的航天飞机类别预测准确率超过 99%。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值