使用环境:win10 64bits,VS2015,Opencv4.1.0
1. 概述
最近的几年Opencv开始在DNN领域耕耘,推出了网络inference的接口,其速度还是比较快的,特别是在本来就使用Opencv的图像处理代码中可以直接使用DNN模块直接加载model做inference,免除了添加附加依赖支持的问题。在4.1.0的版本中已经实现了将model从caffe、tensorflow、onnx等的导入。下面是这个版本中Opencv支持的网络层(cv::dnn::Layer)。
但是在实际使用过程中还是会出现层不支持的情况,这里使用caffe下的网络定义演示自定义层的定义,这里需要完成的功能是:通过添加一个DataNormLayer来完成不涉及参数加载的数据处理操作(图像归一化操作)。
2. custom layer的一些必要元素
dnn模块对应的文档:dnn文档
opencv给出的教程文档:custom layer tutorial
step1: 要添加自定义的层首先需要的就是将自定义的层继承自cv::dnn::Layer,DataNormLayer是自定义层类名
class DataNormLayer : public cv::dnn::Layer
{};
step2: 实现指定格式的类构造函数:
DataNormLayer(const cv::dnn::LayerParams ¶ms);
其中参数的定义为:
class CV_EXPORTS LayerParams : public Dict
{
public:
//TODO: Add ability to name blob params
std::vector<Mat> blobs; //存储模型layer的权值
String name; //layer的名称
String type; //layer的类型
};
step3: 定义实例创建静态函数:
static cv::Ptr<cv::dnn::Layer> create(cv::dnn::LayerParams& params)
{
return cv::Ptr<cv::dnn::Layer>(new DataNormLayer(params));
}
step4: 根据输入blob的大小计算输出blob的大小
virtual bool getMemoryShapes(const std::vector<std::vector<int> > &inputs,
const int requiredOutputs,
std::vector<std::vector<int> > &outputs,
std::vector<std::vector<int> > &internals) const CV_OVERRIDE;
step5: 在知道输入blob大小之后可以通过finalize来做一些操(optional):
virtual void finalize(cv::InputArrayOfArrays inputs_arr, cv::OutputArrayOfArrays outputs_arr) CV_OVERRIDE;
step6: 网络的前向传播forward,在内部定义数据的处理流程,并返回结果
virtual void forward(cv::InputArrayOfArrays inputs_arr,
cv::OutputArrayOfArrays outputs_arr,
cv::OutputArrayOfArrays internals_arr) CV_OVERRIDE;
step7: 将定义好的custom Layer进行注册
#include <opencv2/dnn/layer.details.hpp> // CV_DNN_REGISTER_LAYER_CLASS
CV_DNN_REGISTER_LAYER_CLASS(DataNorm, DataNormLayer);
这里需要注意的custom layer实例创建的时候是forward函数调用的时候(才会调用create函数),之后依次执行DataNormLayer构造函数, getMemoryShapes(...), finalize(...),forward(...)。
3. custom layer定义
首先prototxt里面定义自定层:
#layer definition
layer {
name: "data_norm"
type: "DataNorm"
bottom: "data"
top: "data"
DataNorm_param {
scale_ratio: 0.00392157
mean_value: 127.5
}
}
自定义层的类声明文件:
class DataNormLayer : public cv::dnn::Layer
{
public:
DataNormLayer(const cv::dnn::LayerParams ¶ms);
~DataNormLayer();
//step 1 called (called when inference)在整个net调用forward的时候调用,网络构建权值提取阶段不调用
static cv::Ptr<cv::dnn::Layer> create(cv::dnn::LayerParams& params)
{
return cv::Ptr<cv::dnn::Layer>(new DataNormLayer(params));
}
//step 2 called
virtual bool getMemoryShapes(const std::vector<std::vector<int> > &inputs,
const int requiredOutputs,
std::vector<std::vector<int> > &outputs,
std::vector<std::vector<int> > &internals) const CV_OVERRIDE;
//step 3 called
virtual void finalize(cv::InputArrayOfArrays inputs_arr, cv::OutputArrayOfArrays outputs_arr) CV_OVERRIDE;
//layer forward
virtual void forward(cv::InputArrayOfArrays inputs_arr,
cv::OutputArrayOfArrays outputs_arr,
cv::OutputArrayOfArrays internals_arr) CV_OVERRIDE;
private:
//根据输入计算数据存储位置偏移
static inline int offset(const cv::MatSize& size, int c, int x, int y, int b)
{
return x + size[3] * (y + size[2] * (c + size[1] * b));
}
float scale_ratio; //归一化系数
float mean_value; //均值
};
类的定义文件:
DataNormLayer::DataNormLayer(const cv::dnn::LayerParams ¶ms):Layer(params)
{
this->scale_ratio = params.get<float>("scale_ratio", .0);
this->mean_value = params.get<float>("mean_value", .0);
std::cout << "get param: scale_ratio(" << this->scale_ratio << "), mean_value(" <<
this->mean_value << ")" << std::endl;
}
DataNormLayer::~DataNormLayer()
{
}
//step 2 call
//根据输入的尺寸计算输出的维度
bool DataNormLayer::getMemoryShapes(const std::vector<std::vector<int> > &inputs,
const int requiredOutputs,
std::vector<std::vector<int> > &outputs,
std::vector<std::vector<int> > &internals) const
{
std::vector<int> outShape(4);
outShape[0] = inputs[0][0]; // batch size
outShape[1] = inputs[0][1]; // number of channels
outShape[2] = inputs[0][2]; // number of H
outShape[3] = inputs[0][3]; // unmber of W
outputs.assign(1, outShape);
return false;
}
//step 3 call
//根据已知的输入数据的维度可以做一些额外的操作
void DataNormLayer::finalize(cv::InputArrayOfArrays inputs_arr, cv::OutputArrayOfArrays outputs_arr)
{
std::vector<cv::Mat> outputs;
outputs_arr.getMatVector(outputs);
std::vector<cv::Mat> inputs;
inputs_arr.getMatVector(inputs);
std::cout << "inputs[0] size:[" << inputs[0].size[0] << "," << inputs[0].size[1] << "," <<
inputs[0].size[2] << "," << inputs[0].size[3] << "]" << std::endl;
std::cout << "outputs[0] size:[" << outputs[0].size[0] << "," << outputs[0].size[1] << "," <<
outputs[0].size[2] << "," << outputs[0].size[3] << "]" << std::endl;
}
//layer forward
void DataNormLayer::forward(cv::InputArrayOfArrays inputs_arr,
cv::OutputArrayOfArrays outputs_arr,
cv::OutputArrayOfArrays internals_arr)
{
std::vector<cv::Mat> inputs, outputs;
inputs_arr.getMatVector(inputs);
outputs_arr.getMatVector(outputs);
cv::Mat& inp = inputs[0];
cv::Mat& out = outputs[0];
const float* inpData = (float*)inp.data;
float* outData = (float*)out.data;
const int batchSize = inp.size[0];
const int numChannels = inp.size[1];
const int inpHeight = inp.size[2];
const int inpWidth = inp.size[3];
const int outHeight = inpHeight;
const int outWidth = inpWidth;
for (int b = 0; b < batchSize; ++b)
{
for (int y = 0; y < outHeight; ++y)
{
for (int x = 0; x < outWidth; ++x)
{
for (int c = 0; c < numChannels; ++c)
{
//做减均值与系数缩放
float interpolation = (inpData[offset(inp.size, c, x, y, b)] - this->mean_value) * this->scale_ratio;
outData[offset(out.size, c, x, y, b)] = interpolation;
}
}
}
}
}
本文档介绍了如何在OpenCV 4.1.0中为DNN模块添加自定义层,以处理图像归一化操作。通过继承`cv::dnn::Layer`,定义DataNormLayer类,实现必要的构造函数、计算输出Blob大小、前向传播等功能。此外,还展示了prototxt文件中自定义层的声明和类定义文件的内容。
178

被折叠的 条评论
为什么被折叠?



