我该如何组织一堆单独使用的功能?

时间:2017-02-26 16:01:46

标签: oop design-patterns architecture code-organization

我正在编写一个基于C ++ OpenCV的计算机视觉程序。该计划的基本思想可以描述如下:

  1. 从相机中读取图像。

  2. 为图像做一些魔术。

  3. 显示变换后的图像。

  4. 程序核心逻辑的实现(步骤2)属于OpenCV函数的顺序调用,用于图像处理。这是大约50个函数调用。创建一些临时图像对象以存储中间结果,但除此之外,不会创建任何其他实体。步骤2中的功能仅使用一次。

    我对组织这种类型的代码感到困惑(感觉更像是一个脚本)。我曾经为图像处理的每个逻辑步骤创建了几个类。说,在这里我可以创建3个类,如ImagePreprocessorImageProcessorImagePostprocessor,并在它们之间相应地拆分上述50个OpenCV调用和临时图像。但它并不像是一个合理的OOP设计。这些类只不过是一种存储函数调用的方法。

    main()函数仍会只创建每个类的单个对象,并因此调用其方法:

    image_preprocessor.do_magic(img);
    image_processor.do_magic(img);
    image_postprocessor.do_magic(img);
    

    根据我的印象,与逐个调用50个OpenCV函数基本相同。

    我开始质疑这种类型的代码是否完全依赖于OOP设计。毕竟,我只需提供一个函数do_magic(),或三个函数preprocess()process()postprocess()。但是,这种方法也不是一种好的做法:它仍然只是一堆函数调用,分成不同的函数。

    我想知道,是否有一些常见的做法来组织这种类似脚本的代码?如果这个代码是大型OOP系统的一部分,会有什么方法呢?

2 个答案:

答案 0 :(得分:1)

这种结构适用于管道和过滤器架构(参见Frank Buschmann的模式导向软件架构第1卷:模式系统):

  

管道和过滤器架构模式提供了一种结构   处理数据流的系统。每个处理步骤都是   封装在过滤器组件中。数据通过管道传递   相邻滤波器之间。重组过滤器允许您构建   相关系统的家庭。

另请参阅Enterprise Integration Patterns一书中的这篇简短的description(包含图片)。

答案 1 :(得分:1)

通常,在图像处理中,您拥有各种图像处理模块的管道。同样适用于视频处理,其中每个图像根据视频中的时间戳顺序进行处理。

在设计此类管道之前需要考虑的限制因素:

  1. 这些模块的执行顺序并不总是相同。因此,管道应易于配置。
  2. 管道的所有模块应该可以相互并行执行。
  3. 管道的每个模块也可以具有多线程操作。 (超出了这个答案的范围,但当单个模块成为管道的瓶颈时,这是个好主意。)
  4. 每个模块都应该轻松遵循设计,并具有内部实施变更的灵活性,而不会影响其他模块。
  5. 一个模块预处理帧的好处应该可以用于以后的模块。
  6. 提议的设计。

    视频渠道

    视频管道是模块的集合。现在,假设module是一个用一些数据调用其进程方法的类。每个模块的执行方式取决于这些模块在VideoPipeline中的存储方式!要进一步解释,请参阅以下两个类别: -

    这里,假设我们有模块A,B和C,它们总是以相同的顺序执行。我们将使用第1帧,第2帧和第3帧的视频讨论该解决方案。

    一个。链接列表:在单线程应用程序中,第1帧首先由A执行,然后是B,然后执行C.对下一帧重复该过程,依此类推。所以链表似乎是单线程应用程序的绝佳选择。

    对于多线程应用程序,速度才是最重要的。所以,当然,您希望所有模块都运行128核机器。这是Pipeline类发挥作用的地方。如果每个Pipeline对象在一个单独的线程中运行,则整个应用程序可能有10或20个模块开始运行多线程。请注意,可以使单线程/多线程方法可配置

    湾有向非循环图:当您具有高处理能力并希望减少管道的输入和响应时间之间的滞后时,可以进一步改进上面链接列表的实现。这种情况是模块C不依赖于B而是依赖于A.在这种情况下,模块B和模块C可以使用基于DAG的实现来并行处理任何帧。但是,我不建议这样做,因为与增加的复杂性相比,益处不是那么大,因为模块B和C的输出的进一步管理需要由模块D完成,其中D取决于B或C或两者。场景数量增加。

    因此,为简单起见,让我们使用基于LinkedList的设计。

    <强>管道

    1. 创建PipelineElement的链接列表。
    2. 制作第一个元素的管道调用处理方法的处理方法。
    3. <强> PipelineElement

      1. 首先,PipelineElement通过调用其ImageProcessor来处理信息(如下所示)。 PipelineElement将一个Packet(所有数据,如下所示)传递给ImageProcessor并接收更新的数据包。
      2. 如果next元素不为null,则调用next PipelineElement进程并传递更新的数据包。
      3. 如果PipelineElement的下一个元素为null,则停止。这个元素很特殊,因为它有一个Observer对象。对于Observer字段,其他PipelineElement将设置为null。
      4. <强> FrameReader(VIdeoReader /的ImageReader)

        对于视频/图像阅读器,请创建一个抽象类。无论您是处理视频还是图像还是多个,处理都是一次一帧地完成,因此创建一个抽象类(接口)ImageProcessor。

        1. FrameReader对象存储对管道的引用。
        2. 对于每个帧,它通过调用Pipeline的处理方法来推送信息。
        3. <强> ImageProcessor

          没有Pre和Post ImageProcessor。例如,retinex处理用作后处理,但某些应用程序可以将其用作预处理。 Retinex处理类将实现ImageProcessor。每个元素都将保存其ImageProcessor和Next PipeLineElement对象。

          <强>观察
           一个特殊的类,它扩展了PipelineElement并使用GUI或磁盘提供有意义的输出。

          <强>多线程
              1.使每个方法在其线程中运行     2.每个线程将轮询来自BlockingQueue(2-3个帧的小尺寸)的消息,以充当两个PipelineElements之间的缓冲区。注意:队列有助于平均每个模块的速度。因此,小抖动(一个模块占用太长时间的帧)不会影响视频输出速率并提供流畅的播放。

          <强>分组
              数据包将存储所有信息,例如输入或配置类对象。这样,您可以存储中间计算,并观察使用配置管理器更改算法配置的实时效果。

          总而言之,每个元素现在可以并行处理。第一个元素将处理第n个帧,第二个元素将处理第n个帧,很快,但是由此可能会出现更多问题,例如管道瓶颈和由于每个元素可用的核心功率较少而导致的额外延迟。 / p>