OpenCV 4.11.0
开源计算机视觉库
加载中…
搜索中…
未找到匹配项
向Facemark API添加新算法

目标

本教程将指导您学习如何:

  • 将新的面部特征点检测算法集成到Facemark API中
  • 编译特定的contrib模块
  • 在函数中使用额外的参数

解释

  • 添加类头文件

    新算法的类头文件应添加到include/opencv2/face中的新文件中。以下是一个可用于集成新算法的模板,将FacemarkNEW更改为新算法的代表性名称,并使用相应的代表性文件名保存。

    @code{.cpp}
    class CV_EXPORTS_W FacemarkNEW : public Facemark {
    public:
        struct CV_EXPORTS Config {
            Config();
    
            /*read only parameters - just for example*/
            double detect_thresh;         //!<  detection confidence threshold
            double sigma;                 //!<  another parameter
    
            void read(const FileNode& /*fn*/);
            void write(FileStorage& /*fs*/) const;
        };
    
        /*Builder and destructor*/
        static Ptr<FacemarkNEW> create(const FacemarkNEW::Config &conf = FacemarkNEW::Config() );
        virtual ~FacemarkNEW(){};
    };
    @endcode
    
  • 添加实现代码

    在源代码文件夹中创建一个新文件,其名称代表新算法。以下是一个可用的模板。

    @code{.cpp}
    #include "opencv2/face.hpp"
    #include "precomp.hpp"
    
    namespace cv
    {
        FacemarkNEW::Config::Config(){
            detect_thresh = 0.5;
            sigma=0.2;
        }
    
        void FacemarkNEW::Config::read( const cv::FileNode& fn ){
            *this = FacemarkNEW::Config();
    
            if (!fn["detect_thresh"].empty())
                fn["detect_thresh"] >> detect_thresh;
    
            if (!fn["sigma"].empty())
                fn["sigma"] >> sigma;
    
        }
    
        void FacemarkNEW::Config::write( cv::FileStorage& fs ) const{
            fs << "detect_thresh" << detect_thresh;
            fs << "sigma" << sigma;
        }
    
        /*implementation of the algorithm is in this class*/
        class FacemarkNEWImpl : public FacemarkNEW {
        public:
            FacemarkNEWImpl( const FacemarkNEW::Config &conf = FacemarkNEW::Config() );
    
            void read( const FileNode& /*fn*/ );
            void write( FileStorage& /*fs*/ ) const;
    
            void loadModel(String filename);
    
            bool setFaceDetector(bool(*f)(InputArray , OutputArray, void * extra_params));
            bool getFaces( InputArray image , OutputArray faces, void * extra_params);
    
            Config config;
    
        protected:
    
            bool addTrainingSample(InputArray image, InputArray landmarks);
            void training();
            bool fit(InputArray image, InputArray faces, InputOutputArray landmarks, void * runtime_params);
    
            Config config; // configurations
    
            /*proxy to the user defined face detector function*/
            bool(*faceDetector)(InputArray , OutputArray, void * );
        }; // class
    
        Ptr<FacemarkNEW> FacemarkNEW::create(const FacemarkNEW::Config &conf){
            return Ptr<FacemarkNEWImpl>(new FacemarkNEWImpl(conf));
        }
    
        FacemarkNEWImpl::FacemarkNEWImpl( const FacemarkNEW::Config &conf ) :
            config( conf )
        {
            // other initialization
        }
    
        bool FacemarkNEWImpl::addTrainingSample(InputArray image, InputArray landmarks){
            // pre-process and save the new training sample
            return true;
        }
    
        void FacemarkNEWImpl::training(){
            printf("training\n");
        }
    
        bool FacemarkNEWImpl::fit(
            InputArray image,
            InputArray faces,
            InputOutputArray landmarks,
            void * runtime_params)
        {
            if(runtime_params!=0){
                // do something based on the extra parameters
            }
    
            printf("fitting\n");
            return 0;
        }
    
        void FacemarkNEWImpl::read( const cv::FileNode& fn ){
            config.read( fn );
        }
    
        void FacemarkNEWImpl::write( cv::FileStorage& fs ) const {
            config.write( fs );
        }
    
        void FacemarkNEWImpl::loadModel(String filename){
            // load the model
        }
    
        bool FacemarkNEWImpl::setFaceDetector(bool(*f)(InputArray , OutputArray, void * extra_params )){
            faceDetector = f;
            isSetDetector = true;
            return true;
        }
    
        bool FacemarkNEWImpl::getFaces( InputArray image , OutputArray roi, void * extra_params){
            if(!isSetDetector){
                return false;
            }
    
            if(extra_params!=0){
                //extract the extra parameters
            }
    
            std::vector<Rect> & faces = *(std::vector<Rect>*)roi.getObj();
            faces.clear();
    
            faceDetector(image.getMat(), faces, extra_params);
    
            return true;
        }
    }
    
    @endcode
    
  • 编译代码

    清除构建文件夹,然后重新构建整个库。请注意,您可以通过向cmake添加“-D BUILD_opencv_=OFF”标志来停用其他contrib模块的编译。之后,您可以在“/modules/face”中执行make命令以加快编译过程。

最佳实践

  • 处理额外参数 为了处理额外的参数,应该创建一个新的结构体来保存所有需要的参数。这是一个参数容器的示例

    struct CV_EXPORTS Params
    {
    Params( Mat rot = Mat::eye(2,2,CV_32F),
    Point2f trans = Point2f(0.0,0.0),
    float scaling = 1.0
    );
    Mat R;
    Point2f t;
    float scale;
    };

    这是一个提取额外参数的代码片段

    if(runtime_params!=0){
    Telo* conf = (Telo*)params;
    Params* params
    std::vector<Params> params = *(std::vector<Params>*)runtime_params;
    for(size_t i=0; i<params.size();i++){
    fit(img, landmarks[i], params[i].R,params[i].t, params[i].scale);
    }
    }else{
    // 执行其他操作
    }

    这是一个将额外参数传递到fit函数的示例

    FacemarkAAM::Params * params = new FacemarkAAM::Params(R,T,scale);
    facemark->fit(image, faces, landmarks, params)

    为了理解这个方案,这里有一个简单的示例,您可以尝试编译并查看其工作方式。

    struct Params{
    int x,y;
    Params(int _x, int _y);
    };
    Params::Params(int _x,int _y){
    x = _x;
    y = _y;
    }
    void test(int a, void * params=0){
    printf("a:%i\n", a);
    if(params!=0){
    Params* params = (Params*)params;
    printf("额外参数:%i %i\n", params->x, params->y);
    }
    }
    int main(){
    Params* params = new Params(7,22);
    test(99, params);
    return 0;
    }
  • 最小化依赖项 强烈建议在编译时使代码尽可能小。为此,鼓励开发人员避免使用imgcodecshighgui等重量级依赖项。
  • 文档和示例 请根据需要更新文档,并为新算法提供示例代码。
  • 测试代码 算法应附带其相应的测试代码,以确保算法与各种类型的环境(Linux、Windows64、Windows32、Android等)兼容。应执行几个基本测试,如test/test_facemark_lbf.cpp文件中所示,包括创建其实例、添加训练数据、执行训练过程、加载训练好的模型以及执行拟合以获得面部特征点。
  • 数据组织 建议将新算法的数据分为三个部分
    class CV_EXPORTS_W FacemarkNEW : public Facemark {
    public
    struct CV_EXPORTS Params
    {
    // 用作额外参数的变量
    }
    struct CV_EXPORTS Config
    {
    // 用于配置算法的变量
    }
    struct CV_EXPORTS Model
    {
    // 用于存储模型信息的变量
    }
    static Ptr<FacemarkNEW> create(const FacemarkNEW::Config &conf = FacemarkNEW::Config() );
    virtual ~FacemarkNEW(){};
    }