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

How should I organise a pile of singly used functions?

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

  1. 从相机读取图像。

  2. 对图像施展魔法。

  3. 显示转换后的图像。

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

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

main() 函数仍然只是为每个 class 创建一个对象并随后调用它们的方法:

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

在我的印象中,这与一个一个地调用 50 个 OpenCV 函数本质上是一样的。

我开始怀疑这种类型的代码是否需要 OOP 设计。毕竟,我可以简单地提供一个函数do_magic(),或者三个函数preprocess()process()postprocess()。但是,这种方法也不是一个好的做法:它仍然只是一堆函数调用,被分成不同的函数。

我想知道,是否有一些通用的做法来组织这种类似脚本的代码?如果此代码是大型 OOP 系统的一部分,会怎样?

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

The Pipes and Filters architectural pattern provides a structure for systems that process a stream of data. Each processing step is encapsulated in a filter component. Data is passed through pipes between adjacent filters. Recombining filters allows you to build families of related systems.

另请参阅《企业集成模式》一书中的这篇简短description(带图片)。

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

设计此类管道之前要考虑的限制条件:

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

提议的设计。

视频管道

视频管道是模块的集合。现在,假设模块是一个 class ,它的 process 方法被一些数据调用。每个模块如何执行将取决于这些模块如何存储在 VideoPipeline 中!为了进一步说明,请参阅以下两类:-

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

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

对于多线程应用程序,速度是最重要的。所以,当然,您需要所有模块 运行ning 128 核机器。这就是管道 class 发挥作用的地方。如果每个 Pipeline 对象 运行s 在一个单独的线程中,可能有 10 或 20 个模块的整个应用程序开始 运行ning 多线程。请注意,single-thread/multithread 方法可以配置为可配置

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

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

管道

  1. 创建 PipelineElement 的链表。
  2. 使流水线的处理方法调用第一个元素的处理方法。

管道元素

  1. 首先,PipelineElement 通过调用其 ImageProcessor(见下文)来处理信息。 PipelineElement 会将一个数据包(包含所有数据,见下文)传递给 ImageProcessor 并接收更新的数据包。
  2. 如果下一个元素不为空,则调用下一个 PipelineElement 进程并传递更新的数据包。
  3. 如果 PipelineElement 的下一个元素为空,则停止。这个元素很特别,因为它有一个 Observer 对象。 Observer 字段的其他 PipelineElement 将设置为 null。

FrameReader(VIdeoReader/ImageReader)

为 video/image reader 创建一个摘要 class。无论您处理视频或图像还是多个,处理都是一次完成一帧,因此创建一个抽象 class(interface) ImageProcessor.

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

ImageProcessor

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

观察者
一种特殊的 class,它扩展了 PipelineElement 并使用 GUI 或磁盘提供了有意义的输出。

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

数据包
一个数据包将存储所有信息,例如输入或配置 class 对象。通过这种方式,您可以存储中间计算并使用配置管理器观察更改算法配置的实时效果。

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