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



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

  1. 从相机读取图像。

  2. 对图像做一些魔术。

  3. 显示转换后的图像。

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

我对组织这种类型的代码感到困惑(感觉更像是一个脚本)。我曾经为图像处理的每个逻辑步骤创建多个类。比如说,在这里我可以创建 3 个类,如ImagePreprocessorImageProcessorImagePostprocessor,并将上述 50 个 OpenCV 调用和 temorary 图像相应地拆分给它们。但它感觉不像是一个共鸣的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 系统的一部分,那会是什么样子?

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

在设计此类管道之前要考虑的约束:

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

建议的设计。

视频管道

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

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

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

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

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

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

管道

  1. 创建 PipelineElement 的链接列表。
  2. 使第一个元素的管道调用进程方法的过程方法。

管道元素

  1. 首先,PipelineElement 通过调用其 ImageProcessor 来处理信息(请阅读下文)。 PipelineElement 会将一个数据包(所有数据,请阅读下文)传递给 ImageProcessor 并接收更新的数据包。
  2. 如果下一个元素不为 null,则调用下一个 PipelineElement 进程并传递更新的数据包。
  3. 如果 PipelineElement 的下一个元素为 null,则停止。此元素很特殊,因为它具有观察者对象。对于"观察者"字段,其他管道元素将设置为 null。

FrameReader(VIdeoReader/ImageReader)

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

  1. 对象存储对管道的引用。
  2. 对于每一帧,它通过调用 Pipeline 的 process 方法推送信息。

图像处理器

没有前映像处理器和映像后处理器。例如,retinex 处理用作后处理,但某些应用程序可以将其用作预处理。Retinex 处理类将实现 ImageProcessor。每个元素将保存其 ImageProcessor 和下一个 PipeLineElement 对象。

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

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

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

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

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

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

另请参阅企业集成模式一书中的此简短描述(带图像)。

最新更新