콘텐츠로 이동

C/C++ API를 사용한 객체 인식

이 튜토리얼에서는 RBLN SDK C/C++ API를 사용하여 PyTorch YOLOv8 모델을 배포하는 방법을 배우게 됩니다. 모델은 RBLN SDK Python API를 사용하여 컴파일되며, 그 결과로 생성된 *.rbln 파일은 RBLN SDK C/C++ API를 사용하여 배포됩니다.

이러한 접근 방식은 Python API를 사용한 간편한 모델 준비와 C/C++의 빠른 추론 성능을 결합합니다. 튜토리얼에서 사용된 전체 코드는 RBLN Model Zoo에서 확인할 수 있습니다.

이 튜토리얼은 다음 단계들을 포함합니다:

  1. Python API를 사용하여 YOLOv8m모델을 컴파일하고 로컬 저장소에 저장하는 방법
  2. 컴파일된 모델을 C/C++ 런타임 기반 추론 환경에서 배포하는 방법

전제 조건

시작하기 전에, 다음 패키지들이 설치되어 있는지 확인해 주세요:

1단계. 컴파일 방법

RBLN Python API는 RBLN SDK 내에서 컴파일과 추론을 모두 처리할 수 있는 포괄적인 기능을 제공하지만, RBLN SDK C/C++ API는 추론 작업만 지원합니다. 따라서 본 튜토리얼에서는 모델 컴파일에는 RBLN Python API를 사용하고, 추론 작업에는 RBLN C/C++ API를 사용합니다.

모델 준비

먼저 ultralytics에서 Yolov8m 모델을 가져올 수 있습니다.

1
2
3
4
5
6
7
8
from ultralytics import YOLO
import rebel
import torch

model_name = "yolov8m"
yolo = YOLO(f"{model_name}.pt")
model = yolo.model.eval()
model(torch.zeros(1, 3, 640, 640))

모델 컴파일

torch 모델 torch.nn.Module이 준비되면, rebel.compile_from_torch() 메서드를 사용하여 간단히 컴파일할 수 있습니다.

# 컴파일 모델
compiled_model = rebel.compile_from_torch(model, [("x", [1, 3, 640, 640], "float32")], npu="RBLN-CA02")

NPU가 호스트 머신에 설치되어 있다면, rebel.compile_from_torch() 함수에서 npu 인자를 생략할 수 있습니다. 이 경우 함수가 자동으로 설치된 NPU를 감지하고 사용합니다. 그러나 NPU가 호스트 머신에 설치되어 있지 않다면, 오류를 방지하기 위해 npu 인자를 사용하여 대상 NPU를 지정해야 합니다.

현재 지원되는 NPU 이름은 RBLN-CA02, RBLN-CA12 두 가지입니다. 대상 NPU의 이름을 모르는 경우, NPU가 설치된 호스트 머신의 명령어 입력창에서 rbln-stat 명령을 실행하여 확인할 수 있습니다.

컴파일된 모델 저장

컴파일된 모델을 로컬 저장소에 저장하려면, compiled_model.save() 메서드를 사용할 수 있습니다. 이 함수를 사용하면 배포를 위해 컴파일된 모델을 저장할 수 있습니다:

# 로컬 저장소에 컴파일된 모델을 저장
compiled_model.save(f"{model_name}.rbln")   # model_name = yolov8m

컴파일 진행

앞서 설명된 컴파일 코드는 compile.py에 포함되어 있습니다. 모델을 컴파일하고 rbln 파일을 생성하려면 다음 명령으로 compile.py를 실행하세요:

python compile.py --model-name=yolov8m

이 과정이 성공적으로 완료하면, 로컬 저장소에서 yolov8m.rbln을 찾을 수 있습니다. 이 파일은 컴파일된 YOLOv8m 모델을 포함하고 있으며, RBLN SDK C/C++ API를 사용하여 배포할 준비가 된 상태입니다.

2단계. RBLN SDK C/C++ API를 사용한 배포 방법

이제 RBLN SDK C/C++ API를 사용하여 컴파일된 모델을 로드하고, 추론을 실행하고, 출력 결과를 확인할 수 있습니다.

CMake 빌드 스크립트 준비

이 튜토리얼은 이미지 전/후처리를 위해 OpenCV를 사용하고, 명령줄 인터페이스(CLI)에서 사용자 매개변수를 파싱하기 위해 argparse를 사용합니다.

다음 CMake 스크립트는 외부 패키지에 대한 의존성과 이들을 예제 애플리케이션 코드와 연결하는 방법을 설명합니다.

# 외부 패키지에 대한 의존성 정의
include(FetchContent)
include(cmake/opencv.cmake)
include(cmake/argparse.cmake)

# 실행 파일의 이름 정의
add_executable(object_detection main.cc)

# 패키지 의존성에 대한 링크 정보 업데이트: OpenCV
find_package(OpenCV CONFIG REQUIRED)
target_link_libraries(object_detection ${OpenCV_LIBS})

# 의존성에 대한 링크 정보 업데이트: RBLN
find_package(rbln CONFIG REQUIRED)
target_link_libraries(object_detection rbln::rbln_runtime)

# 의존성 포함 업데이트: argparse
target_include_directories(object_detection PRIVATE ${argparse_INCLUDE_DIRS})

입력 준비

사전 훈련된 Yolov8m 모델에 필요한 전처리된 이미지를 입력 데이터로 준비해야 합니다. OpenCV가 제공하는 다양한 비전 API를 사용하여 입력 이미지에 대한 전처리를 수행합니다.

  // 이미지 전처리
  std::string input_path = "${SAMPLE_PATH}/people4.jpg";
  cv::Mat image;
  try {
    image = cv::imread(input_path);
  } catch (const cv::Exception &err) {
    std::cerr << err.what() << std::endl;
    std::exit(1);
  }

  // CV 행렬로 이미지 변환
  cv::Mat blob =
      cv::dnn::blobFromImage(GetSquareImage(image, 640), 1. / 255., cv::Size(),
                             cv::Scalar(), true, false, CV_32F);

추론 실행

RBLN SDK C/C++ API는 동기와 비동기 추론 방식을 지원합니다. 간략화된 API들에 대한 설명은 아래를 참조하세요.

RBLN API rbln_create_model은 저장된 모델의 경로를 입력 인자로 전달하여 컴파일된 모델을 로드하는 데 사용됩니다.

또한, rbln_create_runtime을 사용하여 RBLNModel, 모듈 이름, 장치 ID로부터 동기 런타임을 생성할 수 있습니다. 비동기 동작을 위해서는 동기식 런타임에 사용한 것과 동일한 인자들을 rbln_create_async_runtime에 전달하여 비동기 런타임을 생성할 수 있습니다.

런타임에 입력 이미지를 할당하기 위해 rbln_set_input을 사용합니다. 이 API는 RBLNRuntime, 입력 버퍼의 인덱스, 전처리된 버퍼의 주소를 인자로 받습니다. 해당 API는 동기식 동작에서만 해당됩니다.

모든 입력이 업데이트되면, RBLNRuntime을 인자로 rbln_run을 호출하여 동기 추론을 수행할 수 있습니다. 비동기 동작의 경우 rbln_async_run에 입력 버퍼와 출력 버퍼를 전달함으로써 비동기 추론이 수행 가능합니다.

마지막으로, rbln_get_output을 사용하여 추론 결과가 포함된 출력 버퍼를 검색할 수 있습니다. 이 API는 RBLNRuntime과 출력 인덱스를 인자로 받습니다. 비동기식 동작의 경우, rbln_run을 호출할 때 입력 버퍼와 출력 버퍼를 전달했으므로, 해당 출력 버퍼들을 직접 참조하면 됩니다.

각 추론 모드에서 필요한 API 사용법에 대해서는 다음 두 가지 예제를 참조해 주세요:

  • 동기 실행

      std::string model_path = "${SAMPLE_PATH}/yolov8m.rbln";
    
      RBLNModel *mod = rbln_create_model(model_path.c_str());
      RBLNRuntime *rt = rbln_create_runtime(mod, "default", 0);
    
      // 입력 데이터 설정
      rbln_set_input(rt, 0, blob.data);
    
      // 동기 추론 실행
      rbln_run(rt);
    
      // 출력 결과 가져오기
      void *data = rbln_get_output(rt, 0);
    

  • 비동기 실행

      std::string model_path = "${SAMPLE_PATH}/yolov8m.rbln";
    
      RBLNModel *mod = rbln_create_model(model_path.c_str());
      RBLNRuntime *rt = rbln_create_async_runtime(mod, "default", 0);
    
      // 출력 버퍼 할당
      auto buf_size = rbln_get_layout_nbytes(rbln_get_output_layout(rt, 0));
      std::vector<float> logits(buf_size/sizeof(float));
    
      // 비동기 추론 실행
      int rid = rbln_async_run(rt, blob.data, logits.data());
    
      // 추론 완료 대기
      rbln_async_wait(rt, rid, 1000);
    

후처리

출력 data는 (1, 84, 8400) 형태의 float32 데이터 배열로, 각 요소는 검출된 객체의 좌표, 클래스 ID, 그리고 신뢰도 점수를 나타냅니다. 이 출력은 처리 과정에서 박스, 신뢰도, 클래스 ID으로 분리됩니다. 그 다음, 비최대값 억제(NMS) 과정을 진행합니다. NMS가 완료된 후, 검출 결과는 박스 출력을 위한 후처리 과정을 거칩니다.

아래의 샘플 코드는 다음 순서로 진행됩니다:

  1. 모델 출력 데이터를 OpenCV Mat 형식으로 변환합니다.
  2. 검출된 객체들에 대해 반복:
    • 경계 박스 좌표를 계산합니다.
    • 클래스 신뢰도 점수를 찾고 가장 높은 점수의 클래스를 선택합니다.
    • 이 정보를 배열에 저장합니다.
  3. 중복 검출 정보를 제거하기 위해 NMS를 적용합니다.
  4. NMS 후 남은 각 검출에 대해:
    • 원본 이미지에 경계 박스를 그립니다.
    • 해당 카테고리에서 일치하는 클래스 이름과 신뢰도 점수를 박스 위에 텍스트로 추가합니다.
  5. 결과 이미지를 로컬 저장소에 저장합니다.
  // NMS를 위한 후처리
  const RBLNTensorLayout *layout = rbln_get_output_layout(rt, 0);
  cv::Mat logits{layout->ndim, layout->shape, CV_32F};
  memcpy(logits.data, data, rbln_get_layout_nbytes(layout));

  std::vector<cv::Rect> nms_boxes;
  std::vector<float> nms_confidences;
  std::vector<size_t> nms_class_ids;
  for (size_t i = 0; i < layout->shape[2]; i++) {
    auto cx = logits.at<float>(0, 0, i);
    auto cy = logits.at<float>(0, 1, i);
    auto w = logits.at<float>(0, 2, i);
    auto h = logits.at<float>(0, 3, i);
    auto x = cx - w / 2;
    auto y = cy - h / 2;
    cv::Rect rect{static_cast<int>(x), static_cast<int>(y), static_cast<int>(w),
                  static_cast<int>(h)};
    float confidence = std::numeric_limits<float>::min();
    int cls_id;
    for (size_t j = 4; j < layout->shape[1]; j++) {
      if (confidence < logits.at<float>(0, j, i)) {
        confidence = logits.at<float>(0, j, i);
        cls_id = j - 4;
      }
    }
    nms_boxes.push_back(rect);
    nms_confidences.push_back(confidence);
    nms_class_ids.push_back(cls_id);
  }
  std::vector<int> nms_indices;
  cv::dnn::NMSBoxes(nms_boxes, nms_confidences, 0.25f, 0.45f, nms_indices);

  // Draw output image
  cv::Mat output_img = image.clone();
  for (size_t i = 0; i < nms_indices.size(); i++) {
    auto idx = nms_indices[i];
    auto class_id = nms_class_ids[idx];
    auto scaled_box = ScaleBox(nms_boxes[idx], output_img.size(), 640);
    cv::rectangle(output_img, scaled_box, cv::Scalar(255, 0, 0));
    std::stringstream ss;
    ss << COCO_CATEGORIES[class_id] << ": " << nms_confidences[idx];
    cv::putText(output_img, ss.str(), scaled_box.tl() - cv::Point(0, 1),
                cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(255, 0, 0));
  }
  cv::imwrite("result.jpg", output_img);

리소스 해제

1
2
3
4
5
  // 런타임 해제
  rbln_destroy_runtime(rt);

  // 모델 해제
  rbln_destroy_model(mod);

CMake를 사용하여 빌드하는 방법

위의 API 예제들에 대한 전체 코드는 RBLN Model Zoo C++ 예제에 포함되어 있습니다. 다음 명령어를 사용하여 코드를 쉽게 컴파일하고 실행 가능한 바이너리를 생성할 수 있습니다:

${SAMPLE_PATH}는 예제 애플리케이션의 경로를 나타냅니다. (예: rbln-model-zoo/cpp/object_detection)

1
2
3
4
mkdir ${SAMPLE_PATH}/build
cd ${SAMPLE_PATH}/build
cmake ..
make

Note

앞서 언급했듯이, 예제 애플리케이션은 이미지 처리 작업을 위해 OpenCV API를 사용합니다. 이를 위해, CMake 빌드 시스템이 OpenCV를 소스에서 직접 가져와 설치합니다. 이 과정은 시스템 사양과 인터넷 연결 속도에 따라 5분 이상 소요될 수 있습니다.

실행 파일 실행 방법

위의 모든 단계를 완료했다면, cmake 디렉토리 아래에서 동기식과 비동기식 추론을 위한 object_detectionobject_detectionn_async라는 이름의 실행 가능한 바이너리들을 찾을 수 있습니다.

  • 동기 실행
    ${SAMPLE_PATH}/build/object_detection -i ${SAMPLE_PATH}/people4.jpg  -m ${SAMPLE_PATH}/yolov8m.rbln
    
  • 비동기 실행
    ${SAMPLE_PATH}/build/object_detection_async -i ${SAMPLE_PATH}/people4.jpg  -m ${SAMPLE_PATH}/yolov8m.rbln
    

출력 결과는 다음과 같습니다:   Image