在学习过caffemodel加载之后,回头看看这个dnn里面都编译了哪些函数?
先看blob头文件:
#ifndef __OPENCV_DNN_DNN_BLOB_HPP__ #define __OPENCV_DNN_DNN_BLOB_HPP__ #include <opencv2/core.hpp> #include <vector> #include <ostream> namespace cv { namespace dnn { //该类用来存储和处理blob struct BlobShape { explicit BlobShape(int ndims = 4, int fill = 1); //!< Creates n-dim shape and fill its by @p fill BlobShape(int num, int cn, int rows, int cols); //创建4-dim shape [num,cn,rows,cols] BlobShape(int ndims, const int *sizes); //创建n-dim的数组 BlobShape(const std::vector<int> &sizes); //创建n-dim的vector template<int n> BlobShape(const Vec<int, n> &shape); //!< Creates n-dim shape from @ref cv::Vec //返回维度的数目 int dims() const; //返回axis的尺度地址,最后的一个axis,为-1,如果不存在,则报错 int &size(int axis); //返回axis的大小 int size(int axis) const; //和size(axis)操作一样 int operator[](int axis) const; //和size(int) const操作一样 int &operator[](int axis); //和size(int) const操作一样,但是如果不存在axis就返回1. int xsize(int axis) const; //返回axes的所有大小 ptrdiff_t total(); //返回连续数组的第一个元素的指针 const int *ptr() const; //判断两个blob是否相等 bool equal(const BlobShape &other) const; bool operator== (const BlobShape &r) const; private: cv::AutoBuffer<int,4> sz; }; //提供连续n维cpu和gpu数组的计算方法,该方法支持CPU和GPU的切换和同步 class CV_EXPORTS Blob { public: explicit Blob(); //重构blob的尺寸和类型 explicit Blob(const BlobShape &shape, int type = CV_32F); /** @brief Constucts 4-dimensional blob (so-called batch) from image or array of images. * @param image 2-dimensional multi-channel or 3-dimensional single-channel image (or array of images) * @param dstCn specify size of second axis of ouptut blob */ explicit Blob(InputArray image, int dstCn = -1); //创建一个特定维度和类型的blob void create(const BlobShape &shape, int type = CV_32F); //创建从cv::Mat或cv::UMat获取的数据blob void fill(InputArray in); //如果dedpCopy为false这CPU数据不被分配,然后创建blob void fill(const BlobShape &shape, int type, void *data, bool deepCopy = true); Mat& matRef(); //返回cv::mat的地址,包含blob数据 const Mat& matRefConst() const; //返回cv::mat的地址,包含blob只读数据 UMat &umatRef(); //返回cv::umat的地址,包含blob数据(没有补充数据) const UMat &umatRefConst() const; //返回cv::umat的地址,包含只读blob数据(没有补充数据) //返回blob的维度 int dims() const; int size(int axis) const; int xsize(int axis) const; //计算维度区间,左边是第一维的值包括该值,右边则不包括最后一维的值 size_t total(int startAxis = 0, int endAxis = INT_MAX) const; //如果axis范围在0到dims范围之内,则将axis索引转化为标准格式 int canonicalAxis(int axis) const; //返回blob的shape BlobShape shape() const; //判断两个blob是否相同 bool equalShape(const Blob &other) const; //获取blob的前两维层 Mat getPlane(int n, int cn); //4维blob的shape获取 int cols() const; //返回第四维的列数 int rows() const; //返回第三维的行数 int channels() const; //返回第二维通道数大小 int num() const; //返回第一维blob的大小 Size size2() const; //返回行和列的尺寸 Vec4i shape4() const; //返回前四个blob的axes //返回blob中的元素线性索引的坐标 //如果n<dims()然后未规定的坐标用0,填充 //如果n>dims()然后多余的坐标被忽视 template<int n> size_t offset(const Vec<int, n> &pos) const; //重载offset size_t offset(int n = 0, int cn = 0, int row = 0, int col = 0) const; //CPU指针的获取 //返回存储在CPU位置的blob元素指针 //n与第一个axis相连,cn是第二个axis //如果dims()>4然后未规定的坐标用0,填充 //如果dims()<4然后多余的坐标被忽视 uchar *ptr(int n = 0, int cn = 0, int row = 0, int col = 0); //重载 template<typename TFloat> TFloat *ptr(int n = 0, int cn = 0, int row = 0, int col = 0); //重载浮点指针 float *ptrf(int n = 0, int cn = 0, int row = 0, int col = 0); //TODO: add const ptr methods //和其他blob共享数据,并返回this指针 Blob &shareFrom(const Blob &blob); //重新构建blob的shape Blob &reshape(const BlobShape &shape); //返回blob的类型 int type() const; private: const int *sizes() const; Mat m; }; //! @} } } #include "blob.inl.hpp" #endif其实存储少不了的就是字典,对于查询和使用相当方便,所以看看dict.hpp:
#ifndef __OPENCV_DNN_DNN_DICT_HPP__ #define __OPENCV_DNN_DNN_DICT_HPP__ #include <opencv2/core.hpp> #include <map> #include <ostream> namespace cv { namespace dnn { //这个结构体将存储标量值或者数组,有一下类型:double类型,cv::String类型,int64类型(使用很少,因为double可以至少存储2^52整数) struct DictValue { DictValue(const DictValue &r); DictValue(int p = 0) : type(Param::INT), pi(new AutoBuffer<int64,1>) { (*pi)[0] = p; } //!< Constructs integer scalar DictValue(unsigned p) : type(Param::INT), pi(new AutoBuffer<int64,1>) { (*pi)[0] = p; } //!< Constructs integer scalar DictValue(double p) : type(Param::REAL), pd(new AutoBuffer<double,1>) { (*pd)[0] = p; } //!< Constructs floating point scalar DictValue(const String &p) : type(Param::STRING), ps(new AutoBuffer<String,1>) { (*ps)[0] = p; } //!< Constructs string scalar template<typename TypeIter> static DictValue arrayInt(TypeIter begin, int size); //!< Constructs integer array template<typename TypeIter> static DictValue arrayReal(TypeIter begin, int size); //!< Constructs floating point array template<typename TypeIter> static DictValue arrayString(TypeIter begin, int size); //!< Constructs array of strings template<typename T> T get(int idx = -1) const; //将带索引的数组元素转换为要求的类型. int size() const; //判断类型函数 bool isInt() const; bool isString() const; bool isReal() const; DictValue &operator=(const DictValue &r); friend std::ostream &operator<<(std::ostream &stream, const DictValue &dictv); ~DictValue(); private: int type; union { AutoBuffer<int64, 1> *pi; AutoBuffer<double, 1> *pd; AutoBuffer<String, 1> *ps; void *p; }; DictValue(int _type, void *_p) : type(_type), p(_p) {} void release(); }; /** @brief This class implements name-value dictionary, values are instances of DictValue. */ class CV_EXPORTS Dict { typedef std::map<String, DictValue> _Dict; _Dict dict; public: //检查字典中key值是否存在 bool has(const String &key); //如果key在字典中,然后返回指针的值,否则返回空 DictValue *ptr(const String &key); //如果key在字典中,然后返回指针的值,否则返回错误 const DictValue &get(const String &key) const; //重载 template <typename T> T get(const String &key) const; //如果key在字典中,然后返回指针的值,否则返回错误值 template <typename T> T get(const String &key, const T &defaultValue) const; //设置新的键值为key或者添加新的key-value对到字典 template<typename T> const T &set(const String &key, const T &value); friend std::ostream &operator<<(std::ostream &stream, const Dict &dict); }; //! @} } } #endif然后看看dnn.hpp头文件有什么特别之处:
#ifndef __OPENCV_DNN_DNN_HPP__ #define __OPENCV_DNN_DNN_HPP__ #include <vector> #include <opencv2/core.hpp> #include <opencv2/dnn/dict.hpp> #include <opencv2/dnn/blob.hpp> namespace cv { namespace dnn //! This namespace is used for dnn module functionlaity. { //dnn的初始化和layers的建立 CV_EXPORTS void initModule(); //这个类为初始化网络提供所有的数据,它包括标量参数字典(参数通过字典地址来读取),blob参数和可选择的元信息(name和layer对象) struct CV_EXPORTS LayerParams : public Dict { std::vector<Blob> blobs; //学习的参数list存储为blob. String name; //layer层的名字 String type; //通过layer工厂创建的layer层名字类型 }; //这个接口类允许创建新的layer。从layer派生的每个类必须实现allocate()内存方法来声明它的输出和前向传播计算的输出。在使用新的类之前必须要在layer工厂中注册新的 //类 struct CV_EXPORTS Layer { //学习的参数必须存储在Net::getParam()能够读取到的地方 std::vector<Blob> blobs; //内存的缓冲和blobs的输出必须考虑到输入的shape,参数input是输入blob的vector,out是输出blobs的vector(必须分配内存) //这个方法根据输入blob和internal层的参数shape要求产生blob,如果这个函数第一次使用,然后输出包含空的blob vector的尺寸有输出链接决定的。如果输入的blob尺 //寸被改变了,然后这个方法称为多尺度(翻译不是很恰当) virtual void allocate(const std::vector<Blob*> &input, std::vector<Blob> &output) = 0; //前向传播,in是输入,out是输出结果 virtual void forward(std::vector<Blob*> &input, std::vector<Blob> &output) = 0; //返回输入数组的索引 //输入参数是blob的label名字 //每一层的输入输出通过%<layer_name%>[.output_name]被标记 //这种方法将输入的label与输入的vector映射起来 virtual int inputNameToIndex(String inputName); //返回输出数组的index virtual int outputNameToIndex(String outputName); String name; //!< Name of the layer instance, can be used for logging or other internal purposes. String type; //!< Type name which was used for creating layer by layer factory. Layer(); explicit Layer(const LayerParams ¶ms); //!< Initialize only #name, #type and #blobs fields. virtual ~Layer(); }; //这个类允许创建和操作复杂的神经网络。神经网络是一种有向图,顶点是layer的实例化,边是输入输出层之间的关系 //每个网络层有唯一的ID和唯一的name。 //LayerId可以存储每个layer的name和layer的id class CV_EXPORTS Net { public: Net(); //!< Default constructor. ~Net(); //!< Destructor frees the net only if there aren't references to the net anymore. //添加一个新的layer到net类中。 //参数name是添加layer的name。 //参数类型是经过类型注册后的layer的类型名字。 //params是初始化layer的。 //返回创建的layer的ID或者失败返回-1. int addLayer(const String &name, const String &type, LayerParams ¶ms); //添加层和链接前一层的输出与后一层的输入的添加层 int addLayerToPrev(const String &name, const String &type, LayerParams ¶ms); //转换layer的名字为整数ID,返回值为layer的id,如果layer错误返回-1. int getLayerId(const String &layer); //封装字符串和整数 typedef DictValue LayerId; //删除layer层 void deleteLayer(LayerId layer); /** * Descriptors have the following template <DFN><layer_name>[.input_number]</DFN>: * - the first part of the template <DFN>layer_name</DFN> is sting name of the added layer. * If this part is empty then the network input pseudo layer will be used; * - the second optional part of the template <DFN>input_number</DFN> * is either number of the layer input, either label one. * If this part is omitted then the first layer input will be used. * * @see setNetInputs(), Layer::inputNameToIndex(), Layer::outputNameToIndex() */ //链接上一层和下一层,上一层的输出作为下一层的输入。 void connect(String outPin, String inpPin); void connect(int outLayerId, int outNum, int inpLayerId, int inpNum); // As any other layer, this layer can label its outputs and this function provides an easy way to do this. //设置网络输入虚拟层的输出名字,每个网络允许有自己的网络输入虚拟层,id为0. //这一层存储使用者的数据,不进行任何计算。 //事实上,这一层layer提供唯一的方法将自己的数据输入到网络。 // void setNetInputs(const std::vector<String> &inputBlobNames); void forward(); void forward(LayerId toLayer); //从网络开始层进行计算,前向传播到输出层 void forward(LayerId startLayer, LayerId toLayer); void forward(const std::vector<LayerId> &startLayers, const std::vector<LayerId> &toLayers); //优化前向传播,不实现网络,在前面的前向传播之后,进行这些层的前向传播是不变的。 void forwardOpt(LayerId toLayer); /** @overload */ void forwardOpt(const std::vector<LayerId> &toLayers); //设置新的值给输出的blob。 //outputName是更新层输出blob的描述 //blob是新的blob //如果更新blob不是空,这blob必须有相同shape。 void setBlob(String outputName, const Blob &blob); //返回输出层 //outputName输出层blob的名字 Blob getBlob(String outputName); //设置新的值给学习参数层。 //numParam在blob的数组中layer参数的索引。 //新的blob的值 //如果新的blob的shape与前面的blob的shape不同,前向传播可能失败 void setParam(LayerId layer, int numParam, const Blob &blob); //返回blob的layer参数 Blob getParam(LayerId layer, int numParam = 0); private: struct Impl; Ptr<Impl> impl; }; /** @brief Small interface class for loading trained serialized models of different dnn-frameworks. */ //导入不同的训练好的模型 class Importer { public: //添加加载的layer到net和layers之间的连接 virtual void populateNet(Net net) = 0; virtual ~Importer(); }; //创建网络框架,参数prototxt为配置文件路劲;caffemodel为模型路径。返回导入接口指针,失败则返回空。 CV_EXPORTS Ptr<Importer> createCaffeImporter(const String &prototxt, const String &caffeModel = String()); /** @brief Creates the importer of <a href="http://torch.ch">Torch7</a> framework network. * @param filename path to the file, dumped from Torch by using torch.save() function. * @param isBinary specifies whether the network was serialized in ascii mode or binary. * @returns Pointer to the created importer, NULL in failure cases. * * @warning Torch7 importer is experimental now, you need explicitly set CMake opencv_dnn_BUILD_TORCH_IMPORTER flag to compile its. * * @note Ascii mode of Torch serializer is more preferable, because binary mode extensively use long type of C language, * which has different bit-length on different systems. * * The loading file must contain serialized <a href="https://github.com/torch/nn/blob/master/doc/module.md">nn.Module</a> object * with importing network. Try to eliminate a custom objects from serialazing data to avoid importing errors. * * List of supported layers (i.e. object instances derived from Torch nn.Module class): * - nn.Sequential * - nn.Parallel * - nn.Concat * - nn.Linear * - nn.SpatialConvolution * - nn.SpatialMaxPooling, nn.SpatialAveragePooling * - nn.ReLU, nn.TanH, nn.Sigmoid * - nn.Reshape * * Also some equivalents of these classes from cunn, cudnn, and fbcunn may be successfully imported. */ //这个是torch导入接口,因为torch不熟,所以就不多介绍。 CV_EXPORTS Ptr<Importer> createTorchImporter(const String &filename, bool isBinary = true); /** @brief Loads blob which was serialized as torch.Tensor object of Torch7 framework. * @warning This function has the same limitations as createTorchImporter(). */ CV_EXPORTS Blob readTorchBlob(const String &filename, bool isBinary = true); //! @} } } #include <opencv2/dnn/layer.hpp> #include <opencv2/dnn/dnn.inl.hpp> #endif /* __OPENCV_DNN_DNN_HPP__ */
最后一部分是layer.hpp:
#ifndef __OPENCV_DNN_LAYER_HPP__ #define __OPENCV_DNN_LAYER_HPP__ #include <opencv2/dnn.hpp> namespace cv { namespace dnn { //注册新的layer的工厂模式 class CV_EXPORTS LayerFactory { public: //每一个layer类必须提供这个函数给factory typedef Ptr<Layer>(*Constuctor)(LayerParams ¶ms); //注册layer类的类型名字和构造器 static void registerLayer(const String &type, Constuctor constructor); //没有注册的layer类的type name static void unregisterLayer(const String &type); //创建注册类的实例化,参数有layer名字,初始化layer的参数 static Ptr<Layer> createLayerInstance(const String &type, LayerParams& params); private: LayerFactory(); struct Impl; static Ptr<Impl> impl(); }; //注册类构造器的运行时间,输入参数有layer的名字,创建注册layer的constructor函数的指针,此宏必须放置在函数代码中。 #define REG_RUNTIME_LAYER_FUNC(type, constuctorFunc) \ LayerFactory::registerLayer(#type, constuctorFunc); //注册类的运行时间,输入参数有layer的名字,class是c++ 类,来自layer,此宏必须放置在函数代码中。 #define REG_RUNTIME_LAYER_CLASS(type, class) \ LayerFactory::registerLayer(#type, _layerDynamicRegisterer<class>); //在模块加载时注册层构造函数,输入参数有layer的名字,创建注册layer的constructor函数的指针,此宏必须放置在函数代码中。 #define REG_STATIC_LAYER_FUNC(type, constuctorFunc) \ static _LayerStaticRegisterer __LayerStaticRegisterer_##type(#type, constuctorFunc); //在模块加载时注册层构造函数,输入参数有layer的名字,class是c++ 类,来自layer,此宏必须放置在函数代码中。 #define REG_STATIC_LAYER_CLASS(type, class) \ Ptr<Layer> __LayerStaticRegisterer_func_##type(LayerParams ¶ms) \ { return Ptr<Layer>(new class(params)); } \ static _LayerStaticRegisterer __LayerStaticRegisterer_##type(#type, __LayerStaticRegisterer_func_##type); //下面的类模板是动态注册 template<typename LayerClass> Ptr<Layer> _layerDynamicRegisterer(LayerParams ¶ms) { return Ptr<Layer>(new LayerClass(params)); } //允许在模块加载是自动注册创建layer struct _LayerStaticRegisterer { String type; _LayerStaticRegisterer(const String &type, LayerFactory::Constuctor constuctor) { this->type = type; LayerFactory::registerLayer(type, constuctor); } ~_LayerStaticRegisterer() { LayerFactory::unregisterLayer(type); } }; } } #endif