在学习过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