适合模块化特征匹配应用的设计模式?

时间:2012-04-04 08:37:38

标签: design-patterns decorator matching conceptual

由于我在this问题上对我的系统设计有一些负面评论(关于此类系统的实施),我希望如果我提出问题,我可以得到一些更好的建议。

我正在尝试设计一个用于视频帧中特征匹配的模块化应用程序(例如,在非常接近的电影或视频帧上进行匹配,例如Sivic,Zisserman在this article中的“产品”)

这个想法是允许在不同的特征检测算法以及不同的匹配过程之间轻松切换。另外,根据我的研究,我的理解是只有一些基本的匹配程序,而新的匹配方法主要关注不良匹配的额外修剪程序(例如same article中的空间一致性)。所有修剪过程都需要完成初始匹配,然后对基础上的提取的特征进行一些额外的工作,并通过匹配查询图像,拒绝糟糕的比赛。


我对设计的想法如下:

  • 实现基础接口featureDetector
  • 所有具体的特征检测算法都继承自featureDetector接口(例如siftDetector
  • 实现基础接口featureMatcher
  • 所有具体匹配方法都继承自featureMatcher接口(例如class bruteForceMatcher或OpenCV匹配器的包装器,如cvMatcher
  • 实施基础接口imageMatcher,实现Strategy模式,以便选择featureDetectorfeatureMatcher
  • 对于所有匹配的修剪过程,实现一个继承基本匹配接口的Decorator接口:class matcherDecorator : public imageMatcher
  • 每个额外的修剪/过滤过程实现matcherDecorator接口(例如spatialConsistencyFilter)并且仅包含imageMatcher*作为(唯一)参数的构造函数(表示要装饰的组件)

this question中指出的问题来自于特征检测和匹配过程的具体结果,它们涉及设计的 Decorator 部分。每个imageMatcher都应该从两个图像(基本和查询)中保存提取的要素,以及在提取的要素之间进行匹配。功能的内部表示与通过imageMatcher的公共访问功能提供给用户的功能描述符略有不同:

class imageMatcher{
    private: // or protected:
        ...
        ...
        std::vector <internalFeatureDescriptor> feats[2];
            // no more than 500 - 1000 features can be expected

        std::vector <std::pair <int, int> > matches;
            // size is the same order of magnitude as the number of features
        ...
    public:
        std::vector <userFriendlyFeatures> getFeatures(int baseOrQuery);
        const std::vector <std::pair<int, int> > &getMatches();
        ...
};

现在,由于特征向量(以及匹配向量)非常“重”,我不希望将它们复制到每个嵌套装饰器(过滤器)中使用它们。我对matches向量没有任何问题,因为它为用户提供了一个公共接口,允许装饰者访问引用并省略复制数据的需要。另一方面,feats向量不提供这样的接口,并且它们的访问功能不仅要求我复制,还需要重新计算要素的内部表示。这反过来导致装饰者需要访问内部超类指针的私有(或受保护)变量。

我设法允许我自己访问所需的向量而不违反任何隐私限制(我(我认为)我没有做任何邪恶的实现),但有人建议访问私有成员的想法超类违反了装饰器模式的想法。


所有这一切,我对如何重构我的代码,对我当前实现的评论以及与我的应用程序设计有关的任何其他建议感兴趣。

1 个答案:

答案 0 :(得分:1)

装饰器模式的替代方法是将过滤器实现为函数/函子。

1:定义每个过滤器的界面/签名,例如对于过滤matchResult,签名可以是:

std::function<void (std::vector <std::pair <int, int> >& )>

(注意:您可能需要适用于featsmatches的过滤器)

2:使用以下方式实施过滤器:

  • 继承/虚函数(类似于装饰器)
  • 作为c ++仿函数
  • 作为免费功能

3:将成员变量添加到imageMatcher类,以便使用已注册的过滤器

4:向您imageMatcher类添加成员函数以注册过滤器

5:实施您的getMatches()会员功能,以便将每个注册过滤器应用于matches。如果您传递对matches成员的引用,则会在应用每个过滤器时对其进行修改。

示例,假设选择了仿函数基础方法

Covenience typedef

typdef std::vector <std::pair <int, int> > match_result;

过滤器的签名是:

typedef std::function< void (match_result& )> match_filter_type;

`imageMatcher'类看起来像:

class imageMatcher{
    private: // or protected:
        ...
        ...
        match_result matches;
        // size is the same order of magnitude as the number of features

        std::vector< match_filter_type > match_filters;
    ...
    public:
        imageMatcher& registerMatchFilter( match_filter_type filter )
        {
            match_filters.push_back( filter );
            return *this;
        }

        const std::vector <std::pair<int, int> > &getFilteredMatches()
        {
          // c++11 could be replaced with older style for loop
          for( auto& filt: match_filters)  
          {
            // note matches will be modified (see note below)
            filt( matches );
          }
          return matches;
        }
   };

过滤器可能如下所示:

void DoSomeFiltering( match_result& matches )
{
    // apply the filter modifying matches
}

第二个过滤器可能是:

struct ComplexFilter
{
   ComplexFilter( params... );  

   void operator()( match_result& matches );
};

过滤器注册如下:

myImageMatcher.registerMatchFilter( ComplexFilter( args... ) );

// call chaining
myImageMatcher.registerMatchFilter( AnotherFilter( args... ) )
              .registerMatchFilter( OneMoreFilter( args... ) )
              .registerMatchFilter( FilterXXX( args... ) );

注意:getFilteredMatches按照注册时的顺序应用过滤器,每个过滤器直接修改matches,这是您想要的吗?如果不是getFilteredMatches可以制作matches的副本,然后将过滤器应用于副本。然后通过值返回副本(注意,只有1个副本,即使在较旧的c ++ 03编译器上,返回的向量也会被优化掉)。

您可以认为您更喜欢继承使用自由函数/仿函数。在这种情况下,match_filters成员变量成为基类对象的向量,即

class MatchFilterBase;
std::vector< std::shared_ptr<MatchFilterBase> >  match_filters;

继承apporach可能更接近您当前的decorator pattern实现,减少了需要执行的重构量。

我的感觉是使用装饰器模式直接修改被装饰对象的内部内容并不自然,因此可能需要通过变通方法来访问受保护/私有数据。