简介

  随着研究的逐渐深入,我了解到pytorch训练模型再用libtorch加载实际上是一种很绕弯的方法,它的确能够满足我们的需求,但也许不是最好的一种方法。如今比较通用的模型格式是ONNX模型,所以我就在想能否使用这种通用的模型来解决问题呢?

  远在天边,近在眼前,没想到OpenCV自己就有DNN模块能够直接加载,我们要做的只需要将pytorch训练出的pth模型转成ONNX模型即可。本文章将介绍如何实现将pytorch训练处的模型转成ONNX模型并用DNN加载预测。

pth模型转ONNX

  上一篇文章我们介绍了如何用pytorch构建网络并且训练模型,训练出的模型格式为pth,不清楚的朋友可以自行去看看上一篇文章,这里接着上一篇,介绍一下如何转模型的格式。

1.加载pth模型:

model = LeNet5(6)

state_dict = torch.load(input_pth_model, map_location='cpu').state_dict()

# load the model
model.load_state_dict(state_dict)

  这里说明一下,通过load().state_dict()加载的仅为模型的参数,能够更加轻量化节省内存提升运行速度。如果是直接load则是直接加载整个模型的所有数据,所占用内存会较大一些。

2.转化模型

dummy_input = torch.randn(1, 1, input_img_size, input_img_size)

input_names = ["input_image"]

output_names = ["output_classification"]

model.eval()

# 通过这里转化成onnx模型
torch.onnx.export(model, dummy_input, output_ONNX, verbose=True, input_names=input_names,

output_names=output_names)

  其中input_img_size,input_pth_model,output_ONNX都需要自己指定。

加载ONNX模型

  这里我们首先创建一个类,能够达到管理网络,加载模型并预测等功能。

类的创建

#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <iostream>

using namespace std;
using namespace cv;

class Dnn_NumDetect
{
public:
    Dnn_NumDetect(const string& path);
    // 运行前向传递以计算图层的输出
    Point2f forward(Mat& src);
private:
    dnn::Net Lenet5;

    // 加载onnx模型
    void loadModel(const string& path);
    // 矩阵归一化
    void Mat_Normalization(Mat &matrix);
};

类的实现

#include "Dnn.h"

/**
 * @brief   使用Opencv Dnn Module 读取ONNX模型
 * @note    如果OpenCV是使用Intel的推理引擎库编译的,则DNN_BACKEND_DEFAULT表示DNN_BACKEND_INFERENCE_ENGINE。 
 *          否则表示DNN_BACKEND_OPENCV。 
 */
Dnn_NumDetect::Dnn_NumDetect(const string &path)
{
    this->loadModel(path);
    //网络在支持的地方使用特定的计算后端
    this->Lenet5.setPreferableBackend(dnn::DNN_BACKEND_OPENCV);
    //网络在特定的目标设备上进行计算
    this->Lenet5.setPreferableTarget(dnn::DNN_TARGET_CPU);
}

/**
 * @brief   加载ONNX模型
 */
void Dnn_NumDetect::loadModel(const string &path)
{
    this->Lenet5 = dnn::readNetFromONNX(path);
    CV_Assert(!this->Lenet5.empty());
}

/**
 * @brief   运行前向传递以计算图层的输出
 * @return  指定层的第一个输出的Blob。
 */
Point2f Dnn_NumDetect::forward(Mat &src)
{
    CV_Assert(!this->Lenet5.empty());
    // 设置输入
    Mat input;
    input = dnn::blobFromImage(src);
    this->Lenet5.setInput(input);

    Mat prob = this->Lenet5.forward();
    // cout << prob <<endl;
    // 矩阵归一化
    this->Mat_Normalization(prob);
    // cout << prob <<endl;
    Point classIdPoint;
    double confidence;
    //查找最大值和最小值
    minMaxLoc(prob.reshape(1, 1), 0, &confidence, 0, &classIdPoint);
    int classId = classIdPoint.x;
    return Point2f(classId, confidence);
}

  如果你需要用到更多关于DNN模块的功能,可以直接去opencv官网的tutorial上查找,其中的dnn module中有更多详细的介绍。

python opencv 不能用cuda加速 opencv加载pth_DNN

总结

  关于数字识别,尝试了三种方式:

  1、用torch直接构建LeNet5网络,产生pt模型

  2、用pytorch训练出pth模型后转换成pt模型,在torch加载

  3、用Opencv的dnn module 加载onnx模型(更方便用Openvino进行加速)

  在计算运行时间的时候发现一个奇怪的现象,第一次预测时通常要花较长的时间,而之后运行时间将大幅度下降。

三种方式对比情况:

  torch直接构建网络训练的时间最长,且准确率最低,不建议用这种方式。

  torch加载pt模型和dnn module加载onnx模型准确率大致相同,但在运行时间上onnx模型更胜一筹。

参考内容

Deep Neural Networks (dnn module)