Opencv中DNN模块添加custom layer

本文档介绍了如何在OpenCV 4.1.0中为DNN模块添加自定义层,以处理图像归一化操作。通过继承`cv::dnn::Layer`,定义DataNormLayer类,实现必要的构造函数、计算输出Blob大小、前向传播等功能。此外,还展示了prototxt文件中自定义层的声明和类定义文件的内容。
部署运行你感兴趣的模型镜像

使用环境: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 &params);

其中参数的定义为:

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 &params);
	~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 &params):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;
				}
			}
		}
	}

}

您可能感兴趣的与本文相关的镜像

TensorFlow-v2.15

TensorFlow-v2.15

TensorFlow

TensorFlow 是由Google Brain 团队开发的开源机器学习框架,广泛应用于深度学习研究和生产环境。 它提供了一个灵活的平台,用于构建和训练各种机器学习模型

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值