基于OpenCV的程序优化嵌入式Linux操作系统

基于OpenCV的程序优化嵌入式Linux操作系统

我正在使用 Buildroot 为 Raspberry PI3 构建自己的嵌入式 Linux 操作系统。该操作系统将用于处理多个应用程序,其中一个应用程序基于 OpenCV (v3.3.0) 执行对象检测。

我开始使用 Raspbian Jessy + Python,但事实证明执行一个简单的示例需要花费大量时间,因此我决定设计自己的 RTOS,具有优化功能 + C++ 开发而不是 Python。

我认为通过这些优化,4 核 RPI + 1GB RAM 可以处理此类应用程序。问题是,即使有了这些东西,最简单的计算机视觉程序也需要花费大量时间。

PC 与 Raspberry PI3 比较

这是我编写的一个简单程序,目的是了解程序每个部分的执行时间的数量级。

#include <stdio.h>
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

#include <time.h>       /* clock_t, clock, CLOCKS_PER_SEC */

using namespace cv;
using namespace std;

int main()
{
    setUseOptimized(true);
    clock_t t_access, t_proc, t_save, t_total;

    // Access time.
    t_access = clock();
    Mat img0 = imread("img0.jpg", IMREAD_COLOR);// takes ~90ms
    t_access = clock() - t_access;

    // Processing time
    t_proc = clock();
    cvtColor(img0, img0, CV_BGR2GRAY); 
    blur(img0, img0, Size(9,9));// takes ~18ms
    t_proc = clock() - t_proc;

    // Saving time
    t_save = clock();
    imwrite("img1.jpg", img0);
    t_save = clock() - t_save;

    t_total = t_access + t_proc + t_save;

    //printf("CLOCKS_PER_SEC = %d\n\n", CLOCKS_PER_SEC);

    printf("(TEST 0) Total execution time\t %d cycles \t= %f ms!\n", t_total,((float)t_total)*1000./CLOCKS_PER_SEC);
    printf("---->> Accessing  in\t %d cycles \t= %f ms.\n", t_access,((float)t_access)*1000./CLOCKS_PER_SEC);
    printf("---->> Processing in\t %d cycles \t= %f ms.\n", t_proc,((float)t_proc)*1000./CLOCKS_PER_SEC);
    printf("---->> Saving     in\t %d cycles \t= %f ms.\n", t_save,((float)t_save)*1000./CLOCKS_PER_SEC);

    return 0;
}

i7 PC 上的执行结果 在此输入图像描述

Raspberry PI 上的执行结果(从 Buildroot 生成的操作系统) 在此输入图像描述

如您所见,存在巨大差异。我需要的是优化每一个细节,以便这个例子加工步骤“接近”实时地发生在最多 15ms 时间内

我的问题是:

  • 如何优化我的操作系统,使其能够处理密集计算应用程序以及如何控制每个部分的优先级?
  • 如何充分利用RPI3的4个核心来满足需求?
  • 除了OpenCV还有其他的可能性吗?
  • 我应该使用 C 而不是 C++ 吗?
  • 您有什么建议的硬件改进吗?

答案1

为了:

如何优化我的操作系统,使其能够处理密集计算应用程序以及如何控制每个部分的优先级?

对于一般优化,除了正常的事情之外,您在操作系统方面无能为力,例如确保仅在后台运行实际需要的内容。在原始 Pi 上,您可以memmove()通过一个名为“cofi”的库来加速类似的功能,LD_PRELOAD该库提供了这些功能的汇编优化版本,但我不确定它是否会对 Pi 3 有帮助。

对于优先级,这确实是需要查看手册页的事情,但是除非您并行化事物,否则您通常无法做到这一点(在您的情况下,似乎明显的解决方案是运行每个步骤,因为它赢得了进程并使用IPC(可能是出于性能原因共享内存)以在它们之间移动数据)。

根据您从测试程序中引用的结果,请特别注意,Pi 上的处理和保存步骤都慢了大约 10 倍,而访问步骤仅慢了大约 5 倍,并且这些数字与将 Pi 3 与使用不到一年的通用 PC 进行比较时的粗略估计。 Pi 中的 CPU 几乎肯定比您运行 PC 测试的 CPU 慢得多(如果您根本没有并行化,那么差距会进一步扩大,因为大多数现代 x86 CPU 都可以在满负载运行的速度比满负载运行所有核心的速度要快得多),这将会产生影响。 ARM ISA 也与 x86 ISA 显着不同(与 x86 相比,ARM 每个周期执行的操作较少,但通常不需要频繁访问 RAM,并且通常不会像 x86 那样导致分支预测未命中成本高昂) ,因此任何针对 GCC 在 PC 上安排事物的方式进行优化的代码在 Pi 上都不会达到最佳效果。

我也不知道您使用的是什么相机,但我希望您可以通过降低正在处理的图像的分辨率来获得更好的时间,并且如果您避免使用,您可能可以减少采集时间压缩格式(不使用有损压缩意味着分辨率不再那么重要)。

如何充分利用RPI3的4个核心来满足需求?

在您自己的代码中并行化。您只需要确保在内核中启用了 SMP(如果您使用的是 RPi 基金会的官方配置,则应该如此),然后尝试并行运行。我不确定 OpenCV 本身对并行化做了多少工作,但您可能还想看看 OpenMP(它提供了一种相当简单的方法来并行化不相互依赖的循环中的迭代)。

除了OpenCV还有其他的可能性吗?

可能有,但每个人都在 OpenCV 上标准化,所以我建议使用它(你会更容易获得实现事物的技术帮助,因为每个人都使用它)。

我应该使用 C 而不是 C++ 吗?

这取决于你如何使用东西。虽然用 C++ 编写慢代码比用 C 容易得多,但用这两种语言编写快速代码并不困难。两种语言中的许多优化技术非常相似(例如,在启动时预先分配所有内容,这样您就不会malloc()在关键部分中调用,或者避免调用stat())。特别是在 C++ 的情况下,要避免std::string像瘟疫一样,它会malloc()到处调用,结果是非常慢(我已经看到从转换std::string到 C 风格字符串的转换在某些情况下性能提高了 40% 以上)。

您有什么建议的硬件改进吗?

假设您试图保持较低的硬件成本并且空间有限(因此选择了 Raspberry Pi),我真的想不到。 Pi(在其所有迭代中)使用的 SoC 非常适合该价格范围内的计算机视觉工作。如果您愿意使用更大一点、更贵一点的东西,我可能会建议使用 NVIDIA Jetson 板(他们使用 Tegra SoC,该 SoC 具有集成了 192 个 CUDA 核心的 Quadro 等效 GPU,因此它可能可以运行您的处理工作量更快),但是让 Buildroot 在那里工作比在 Pi 上工作要复杂得多。

针对评论的编辑:

进程级别的并行化与多线程不同,它是截然不同的(最大的区别在于如何共享资源,默认情况下线程共享所有内容,进程不共享任何内容)。一般来说,当涉及大量处理时,(通常)最好使用基于进程的并行化,因为更容易编写高效的代码,而不必担心线程安全。

就选项而言,您提到的这两个选项可能会对系统性能产生很大影响,但它们最终都是吞吐量和延迟之间的权衡。抢占模型控制如何重新安排在内核模式下运行的事物(例如系统调用)。共有三个选项:

  1. 无抢占:这几乎意味着在内核模式下运行的任何内容都不能被中断。它与 SVR4 和 4.4BSD 的行为方式以及大多数其他较旧的 UNIX 系统的工作方式相匹配。它对吞吐量非常有利,但对延迟非常不利,因此它通常只在具有大量 CPU 的大型服务器上使用(更多 CPU 意味着更有可能运行可被抢占的东西)。
  2. 自愿抢占:这让内核中的每个函数定义可以重新调度的位置。这是大多数面向桌面的 Linux 发行版使用的设置,因为它在吞吐量和延迟之间提供了良好的平衡。
  3. 完全抢占:这意味着(几乎)内核中的任何代码都可以(几乎)随时中断。这对于需要非常低的输入和外部事件延迟的系统非常有用,例如用于实时多媒体工作的系统。这对于吞吐量来说绝对是可怕的,但你无法克服延迟。

相比之下,定时器频率更容易解释。它控制着如果有其他程序等待运行,某些程序可以不间断运行的最长时间。值越高,时间越短(延迟越低,吞吐量越低),值越低,时间越长(延迟越高,吞吐量越高)。一般来说,我建议将抢占模型设置为自愿,将定时器频率设置为 300 Hz,然后开始尝试先更改定时器频率(因为这通常会产生更明显的影响)。

至于 Movidius NCS,是否值得取决于您需要处理多少数据,因为它会受到 USB 连接的带宽限制(Pi 只有一个 USB 2.0 控制器,因此您不仅限于不到 Movidius 设计带宽的十分之一,您还必须至少与以太网适配器共享总线,这会损害您的延迟和吞吐量)。如果您仅以低速率处理 32 位颜色的 1920x1080 单帧,那么它可能是可行的,但如果您需要以全帧速率对同一视频进行流处理,那么您可能会遇到延迟问题。如果您确实选择使用一个,请确保您为它配备了一个供电集线器(否则您可能会遇到问题,因为它试图消耗比 Pi 所能提供的更多的电量)。

相关内容