opencv + tensorflow + C++ 对RSNet模型进行预测

本文介绍了一种使用opencv + c++ 可以在生产环境下调用tensorflow pb文件进行预测的方法。
该方法不需要在生产环境下搭建python运行环境。 流程上相对简单清晰。如果要将神经网络/机器学习添加到几年前项目中,也比较简单可行。

一.pb文件的生成

本人使用的RsNet模型完全参考github上官方给出的demo训练生成。网址https://github.com/tensorflow/models
训练入口文件official/resnet/imagenet_main.py (主线版本 v1.8.1)
只定义了2个分类,具体的训练方法就不再累

训练完成后会在根目录生成一个imagenet_model文件夹,里面存储了模型文件(ckpt文件)。接下来需要将模型文件固化成pb文件。为了让opencv可以使用这个pb文件,需要定义好神经网络input入口与output出口

import tensorflow as tf
from tensorflow.python.framework import graph_util
import cv2
import numpy as np
import imagenet_main #训练的入口文件

MODEL_SAVE_PATH="imagenet_model/"

#三通道图片默认的数据,我只用了一个通道的值测试。真实应用环境下,值应该是图片的平均辉度
_R_MEAN = 123.68/255 
_G_MEAN = 116.78/255 
_B_MEAN = 103.94/255 

#入口位置 图片维度格式 NHWC chanel在最后一个维度
x_cv_imput = tf.placeholder(tf.float32,[1,448,448,1],name='cv-input')  
model = imagenet_main.ImagenetModel(34, 'channels_last', version=2)
logits = model(x_cv_imput, False)

#定义了网络出口 各分类的概率 概率最大的分类就是神经网络判定的结果
result = tf.nn.softmax(logits,name = 'cv-out-probabilities')

#定义了一个train.Saver 用来保存pb文件
saver = tf.train.Saver()

#开始session 并重新载入训练过的ckpt
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH)
saver.restore(sess, ckpt.model_checkpoint_path)

#定义pb的输出参数 其中 output_node_names = ['cv-out-probabilities'] 为出口
output_graph_def = graph_util.convert_variables_to_constants(sess, sess.graph_def,output_node_names = ['cv-out-probabilities'])

#输入一个图片进行预测,并输出结果。为了与在opencv中的结果进行对比
#图片resize到448×448,单通道,float类型,并进行归一化处理
img = cv2.imread("resize.png")
img = cv2.resize(img, (448,448), interpolation=cv2.INTER_AREA)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = np.asarray(img, dtype='float')
img = img/255.0
img = img - [_R_MEAN]

_result,_y_cv = sess.run([result,logits],feed_dict={x_cv_imput:np.reshape(img, [1,448,448,1])})
print _result

#保存pb文件为renet.pn
tf.train.write_graph(output_graph_def, '', 'rsnet.pb', as_text=False)

输出结果:
[[ 9.99989152e-01 1.08901504e-05]

二.使用opencv导入pb文件进行预测

opencv使用的版本为3.4.2 曾经使用3.3版本进行过测试,但错误太多,主要是很多神经网络运算不支持,印象中有FusedBatchNorm等

代码如下:

#include <opencv2/core.hpp>
#include "opencv2/dnn.hpp"

using namespace cv;
using namespace cv::dnn;
using namespace std;

int main(int argc, char *argv[])
{
    net = readNetFromTensorflow("rsnet.pb");
    if (net.empty()) {//导入失败,返回
        return;
    }
    net.setPreferableBackend(DNN_BACKEND_OPENCV);//如果编译opencv时使用了mkl等加速,可以修改为其他的dnn_backend

    //使用下面的代码,可以看到除入口外的Layer名称
    std::vector<String> names = net.getLayerNames();
    for(auto iter1=names.begin(); iter1!=names.end(); ++iter1 ){
        cout<<*iter1<<endl;
    }

    //载入图片并reszie和归一化处理
    Mat img = cv::imread("resize.png",cv::IMREAD_GRAYSCALE);
    resize(img ,img ,Size(448,448),0,0,INTER_AREA);
    img.convertTo(img, CV_32F);
    img= img/ 255.0 - 123.68 / 255.0;

    //opencv进行预测前,专门有个函数将图片Mat转换为4维Mat
    Mat inputBlob = blobFromImage(img);

    net.setInput(blob, "cv-input");//网络入口
    Mat out = net.forward("cv-out-probabilities");//预测,并定义了网络出口
    float* data = out.ptr<float>(0);
    cout<<out.size<<endl;//输出结果Mat的大小
    cout<<data[0]<<endl;//第一个分类的概率
    cout<<data[1]<<endl;//第二个分析的概率
}

输出结果:
Pad
conv2d/Conv2D
max_pooling2d/MaxPool
batch_normalization/FusedBatchNorm
Relu
conv2d_2/Conv2D
conv2d_3/Conv2D
batch_normalization_2/FusedBatchNorm
Relu_1
……

1 x 2
0.999989
1.08901e-0

结果与python下的计算一样

三.注意点 和 困惑的地方

1.测试时出现了以下错误
OpenCV(3.4.2) Error: Unspecified error (Can’t create layer “ExpandDims” of type “ExpandDims”) in cv::dnn::experimental_dnn_v5::LayerData::getLayerInstance, file …….

应该是tf.expanddims这个函数暂不支持,修改了python文件,屏蔽了 tf.expanddims 这个函数就好了

2.神经网络输入的维度
上述代码中,python定义的input网络维度为(1,448,448,1)。而如果打印出 opencv blobFromImage函数返回的inputBlob维度,会发现是 1×1×448×448 。两者的维度是不一样的,理论上应该会报错。但输出的结果是正确的。。。
我也尝试修改 inputBlob 维度 为 1×448×448×1,运行后反而报错了,维度不正确。。

查看opencv源码,在modules\dnn\src\tensorflow\tf_importer.cpp中找到一段对 “Pad”的处理,有维度交换的操作,可能与此有关。在Pad运行时,进行了维度调整。

 else if (type == "Pad")
        {
            Mat paddings = getTensorContent(getConstBlob(layer, value_id, 1));
            CV_Assert(paddings.type() == CV_32SC1);
            if (paddings.total() == 8)
            {
                // Perhabs, we have NHWC padding dimensions order.
                //  N    H    W    C
                // 0 1  2 3  4 5  6 7
                std::swap(*paddings.ptr<int32_t>(0, 2), *paddings.ptr<int32_t>(0, 6));
                std::swap(*paddings.ptr<int32_t>(0, 3), *paddings.ptr<int32_t>(0, 7));
                //  N    C    W    H
                // 0 1  2 3  4 5  6 7
                std::swap(*paddings.ptr<int32_t>(0, 4), *paddings.ptr<int32_t>(0, 6));
                std::swap(*paddings.ptr<int32_t>(0, 5), *paddings.ptr<int32_t>(0, 7));
                //  N    C    H    W
                // 0 1  2 3  4 5  6 7
            }
            layerParams.set("paddings", DictValue::arrayInt<int*>((int*)paddings.data, paddings.total()));

            int id = dstNet.addLayer(name, "Padding", layerParams);
            layer_id[name] = id;

            connect(layer_id, dstNet, parsePin(layer.input(0)), id, 0);
        }

opencv + tensorflow + C++ 对RSNet模型进行预测》有1个想法

  1. Pingback引用通告: opencv 中使用 cudnn 预测CNN网络 | YangYouji's WebSite

发表回复

您的电子邮箱地址不会被公开。