0

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

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

这个想法是允许在不同的特征检测算法以及不同的匹配过程之间轻松切换。另外,根据我的研究,我的理解是只有几个基本的匹配过程,而新的匹配方法主要侧重于对不良匹配的额外剪枝过程(例如同一篇文章中的空间一致性)。所有剪枝过程都需要完成初始匹配,然后对从匹配耦合的基础图像和查询图像中提取的特征做一些额外的工作,拒绝错误的匹配。


我对设计的想法如下:

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

在这个问题中向我指出的问题来自于特征检测和匹配过程的具体结果,它们涉及设计的装饰器部分。每个都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另一方面,向量不提供这样的接口,它们的访问功能要求我不仅要进行复制,还要重新计算特征的内部表示。这反过来导致装饰器需要访问内部超类指针的私有(或受保护)变量

我设法在不违反任何隐私限制的情况下授予我对所需向量的自我访问权限(我(认为)我没有做任何邪恶的实施),但有人建议访问超类的私有成员的想法违反了装饰器模式的想法。


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

4

1 回答 1

1

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

1:定义每个过滤器的接口/签名,例如过滤matchResult签名可以是:

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

(注意:您可能需要一个适用于feats和的过滤器matches

2:使用以下方法实现过滤器:

  • 继承/虚函数(类似于你的装饰器)
  • 作为 c++ 函子
  • 作为自由函数

3:为您的类添加一个成员变量以imageMatcher查找已注册的过滤器

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

5:实现您的getMatches()成员函数,以便将每个注册的过滤器应用于matches. 如果您传递对matches成员的引用,它将在应用每个过滤器时被修改。

示例,假设选择了基于函子的方法

便利类型定义

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;

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

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

于 2012-04-04T14:23:53.523 回答