基於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 所能提供的更多的電量)。

相關內容