임베디드 Linux 운영 체제를 위한 OpenCV 기반 프로그램 최적화 [닫기]

임베디드 Linux 운영 체제를 위한 OpenCV 기반 프로그램 최적화 [닫기]

저는 Buildroot를 사용하여 Raspberry PI3용 임베디드 Linux 운영 체제를 구축하고 있습니다. 운영 체제는 여러 응용 프로그램을 처리하는 데 사용되며 그 중 하나는 OpenCV(v3.3.0)를 기반으로 객체 감지를 수행합니다.

Raspbian Jessy + Python을 사용하기 시작했는데 간단한 예제를 실행하는 데 시간이 많이 걸린다는 사실이 밝혀져 Python 대신 최적화된 기능 + C++ 개발로 나만의 RTOS를 설계하기로 결정했습니다.

이러한 최적화를 통해 4코어 RPI + 1GB RAM이 이러한 애플리케이션을 처리할 수 있다고 생각합니다. 문제는 이런 것에도 불구하고 가장 간단한 컴퓨터 비전 프로그램이 시간이 많이 걸린다는 것입니다.

PC와 라즈베리 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

을 위한:

집약적인 컴퓨팅 애플리케이션을 처리할 수 있도록 운영 체제를 최적화하고 각 부분의 우선 순위를 어떻게 제어할 수 있습니까?

일반적인 최적화의 경우 실제로 필요한 것만 백그라운드에서 실행되는지 확인하는 등 일반적인 작업 외에 OS 측에서 수행할 수 있는 작업이 많지 않습니다. 원본 Pi에서는 이러한 기능의 어셈블리 최적화 버전을 제공하는 memmove()"cofi"라는 ​​라이브러리를 통해 유사한 기능의 속도를 높일 수 있지만 LD_PRELOADPi 3에서는 도움이 될지 확실하지 않습니다.

우선 순위에 대해서는 실제로 매뉴얼 페이지를 살펴봐야 할 사항이지만 병렬화하지 않는 한 일반적으로 그렇게 할 수 없습니다. (귀하의 경우 확실한 해결책은 프로세스를 획득하고 IPC를 사용할 때 각 단계를 실행하는 것 같습니다(아마도 성능상의 이유로 공유 메모리) 간에 데이터를 이동합니다.

테스트 프로그램에서 인용한 결과에 따르면 특히 처리 및 저장 단계는 Pi에서 약 10배 느린 반면 액세스 단계는 약 5배만 느리며 이 수치는 Pi 3을 사용하는 것과 비슷합니다. 범용 PC와 비교했을 때 대략적인 추정치보다 적습니다. Pi의 CPU는 PC 테스트를 실행하는 CPU보다 거의 확실히 훨씬 느릴 것입니다. (병렬화를 전혀 하지 않으면 대부분의 최신 x86 CPU가 전체 로드에서 다른 CPU보다 더 빠르게 실행될 수 있으므로 격차가 더 넓어집니다. 전체 로드 시 코어가 훨씬 더 빨라짐), 이는 영향을 미칠 것입니다. ARM ISA는 x86 ISA와도 크게 다릅니다(ARM은 x86에 비해 주기당 더 적은 작업을 수행하지만 일반적으로 RAM에 자주 액세스할 필요가 없으며 x86만큼 비용이 많이 드는 분기 예측 누락이 발생하지 않습니다). GCC가 PC에서 작업을 정렬하는 방식에 최적화되어 있으며 Pi에서는 최적으로 작동하지 않습니다.

또한 어떤 카메라를 사용하고 있는지는 모르지만 처리 중인 이미지의 해상도를 낮추면 더 나은 시간을 얻을 수 있기를 바라며 압축 사용을 피하면 획득 시간을 줄일 수 있을 수도 있습니다. 형식(손실 압축을 사용하지 않으면 해상도가 더 이상 중요하지 않음을 의미함)

수요를 충족하기 위해 RPI3의 4개 코어를 최대한 활용하는 방법은 무엇입니까?

자신만의 코드로 병렬화하세요. 커널에서 SMP가 활성화되어 있는지 확인한 다음(RPi 재단의 공식 구성을 사용하는 경우 활성화되어야 함) 병렬로 실행해 보면 됩니다. OpenCV 자체가 병렬화를 얼마나 수행하는지 잘 모르겠지만 OpenMP(서로 의존하지 않는 루프에서 반복을 병렬화하는 매우 간단한 방법을 제공함)를 살펴보는 것이 좋습니다.

OpenCV 외에 다른 가능성이 있습니까?

있을 수도 있지만 모두가 OpenCV에서 표준화하고 있으므로 이를 사용하는 것이 좋습니다(모든 사람이 OpenCV를 사용하므로 구현에 대한 기술적인 도움을 받는 것이 더 쉬울 것입니다).

C++ 대신 C를 사용해야 합니까?

물건을 어떻게 사용하느냐에 따라 다릅니다. C보다 C++에서 느린 코드를 작성하는 것이 훨씬 쉽지만 두 언어 모두 빠른 코드를 작성하는 것은 어렵지 않습니다. 두 언어의 많은 최적화 기술은 매우 유사합니다(예: malloc()중요한 섹션에서 호출 하지 않도록 시작 시 모든 것을 사전 할당 하거나 호출을 피함 stat()). 특히 C++의 경우, 모든 곳에서 호출되어 결과적으로 속도가 매우 느려지는 std::string문제를 피하십시오( 어떤 경우에는 C 스타일 문자열로 변환하여 성능이 40% 이상 향상되는 것을 malloc()보았습니다 ).std::string

제안된 하드웨어 개선 사항이 있습니까?

하드웨어 비용을 낮게 유지하고 공간이 제한되어 있다고 가정하면(따라서 Raspberry Pi), 저는 정말로 그것을 생각할 수 없습니다. Pi(모든 반복)는 이 가격대의 컴퓨터 비전 작업에 매우 적합한 SoC를 사용합니다. 조금 더 크고 더 비싼 제품을 사용하고 싶다면 NVIDIA Jetson 보드를 추천할 것입니다(192 CUDA 코어가 통합된 Quadro 동급 GPU가 있는 Tegra SoC를 사용하므로 실행이 가능할 수도 있습니다). 처리 작업량이 더 빨라지지만 Buildroot를 작동시키는 것은 Pi보다 훨씬 더 복잡합니다.

의견에 대한 응답으로 편집:

프로세스 수준 병렬화는 멀티스레딩과 다릅니다(가장 큰 차이점은 리소스가 공유되는 방식입니다. 기본적으로 스레드는 모든 것을 공유하고 프로세스는 아무것도 공유하지 않습니다). 일반적으로 처리량이 많은 경우 스레드 안전에 대해 걱정할 필요 없이 효율적인 코드를 작성하는 것이 더 쉽기 때문에 프로세스 기반 병렬화를 사용하는 것이 (보통) 더 좋습니다.

옵션에 관한 한 언급한 두 가지는 시스템 성능에 큰 영향을 미칠 수 있지만 궁극적으로 처리량과 대기 시간 간의 균형을 유지합니다. 선점 모델은 커널 모드에서 실행되는 것(예: 시스템 호출)이 다시 예약되는 방식을 제어합니다. 세 가지 옵션이 있습니다:

  1. 선점 없음: 이는 커널 모드에서 실행 중인 모든 작업이 중단될 수 없음을 의미합니다. 이는 SVR4 및 4.4BSD의 동작뿐만 아니라 대부분의 다른 이전 UNIX 시스템 작동 방식과도 일치합니다. 처리량에는 매우 좋지만 대기 시간에는 매우 나쁩니다. 따라서 일반적으로 CPU가 많은 대규모 서버에서만 사용됩니다. CPU가 많을수록 선점할 수 있는 작업을 실행할 가능성이 더 높아집니다.
  2. 자발적 선점: 이를 통해 커널의 각 기능이 다시 예약될 수 있는 위치를 정의할 수 있습니다. 이는 처리량과 대기 시간 간의 적절한 균형을 제공하기 때문에 대부분의 데스크톱 지향 Linux 배포판에서 사용되는 설정입니다.
  3. 완전 선점: 이는 커널의 (거의) 모든 코드가 언제든지 (거의) 중단될 수 있음을 의미합니다. 이는 실시간 멀티미디어 작업에 사용되는 시스템과 같이 매우 낮은 입력 및 외부 이벤트 대기 시간이 필요한 시스템에 유용합니다. 이는 처리량 측면에서는 절대적으로 좋지 않지만 대기 시간을 이길 수는 없습니다.

이에 비해 타이머 주파수는 해석하기가 더 쉽습니다. 실행 대기 중인 다른 프로그램이 있는 경우 일부 프로그램이 중단 없이 실행될 수 있는 최대 시간을 제어합니다. 값이 높을수록 시간이 짧아지고(대기 시간이 짧아지고 처리량이 낮아짐), 값이 낮을수록 시간이 길어집니다(대기 시간이 길어지고 처리량이 높아짐). 일반적으로 선점 모델을 자발적으로 설정하고 타이머 주파수를 300Hz로 설정한 다음 타이머 주파수를 먼저 변경해 보는 것이 좋습니다(보통 이것이 더 눈에 띄는 영향을 미치기 때문입니다).

Movidius NCS의 경우, 그 가치가 있는지 여부는 처리해야 하는 데이터의 양에 따라 달라집니다. 이는 USB 연결의 대역폭에 의해 제한되기 때문입니다(Pi에는 USB 2.0 컨트롤러만 있으므로 100% 미만으로 제한될 뿐만 아니라 Movidius가 설계된 대역폭의 10분의 1에 해당하므로 최소한 이더넷 어댑터와 버스를 공유해야 하므로 대기 시간과 처리량이 저하됩니다. 32비트 색상으로 1920x1080의 단일 프레임을 낮은 속도로 처리하는 경우 가능할 수도 있지만 동일한 비디오를 전체 프레임 속도로 스트리밍해야 하는 경우 대기 시간 문제가 발생할 수 있습니다. 하나를 사용하기로 선택한 경우, 전원이 공급되는 허브가 있는지 확인하세요. 그렇지 않으면 Pi가 제공할 수 있는 것보다 더 많은 전력을 끌어오려고 하므로 문제가 발생할 수 있습니다.

관련 정보