OpenCV 4.12.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_<MODULE_NAME>=OFF" 標誌來停用其他 contrib 模組的編譯。之後,您可以在 "<build_folder>/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("extra parameters:%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 檔案中所示,包括建立其例項、新增訓練資料、執行訓練過程、載入訓練好的模型以及執行擬合以獲得面部特徵點。
  • 資料組織 建議將新演算法的資料分為 3 個部分
    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(){};
    }