본문 바로가기

OpenCV

OpenCV DNN 모듈을 활용한 딥 러닝: 완벽 가이드

반응형

 

컴퓨터 비전 분야는 1960년대 후반부터 존재해 왔습니다. 이미지 분류와 객체 감지는 컴퓨터 비전 분야에서 연구자들이 수십 년 동안 해결하려고 노력해 온 가장 오래된 문제 중 하나입니다. 신경망과 딥러닝을 활용함으로써 컴퓨터가 높은 정확도로 객체를 이해하고 인식할 수 있는 단계에 도달했으며, 많은 경우 인간을 능가하기도 합니다. 컴퓨터 비전을 활용한 신경망과 딥러닝에 대해 알아보려면 OpenCV DNN 모듈이 좋은 시작점이 될 것입니다. 고도로 최적화된 CPU 성능 덕분에 강력한 GPU 기반 시스템이 없더라도 초보자도 쉽게 시작할 수 있습니다.

 

 

 

이와 관련하여 이 블로그 게시물은 가장 좋은 시작점이 될 것입니다.

 

OpenCV DNN을 이용한 객체 감지 및 이미지 분류 결과.

 

 

그림 1. OpenCV DNN 모듈을 사용한 딥러닝 1000×800

 

이론적인 부분뿐만 아니라 OpenCV DNN을 활용한 실제 경험도 다룹니다. 이미지와 실시간 비디오에서 분류 및 객체 감지를 자세히 논의합니다 .

 

목차

OpenCV DNN 모듈이란 무엇인가요?

왜 OpenCV DNN 모듈을 선택해야 하나요?

OpenCV DNN이 지원하는 다양한 딥러닝 기능

어떤 모델을 지원하나요?

어떤 프레임워크를 지원하나요?

OpenCV DNN을 사용한 이미지 분류에 대한 단계별 가이드입니다.

OpenCV DNN을 이용한 객체 감지

이미지에서의 객체 감지.

실시간 비디오에서의 객체 감지.

요약

 

그럼, 지금 바로 블로그 게시물로 넘어가서 OpenCV DNN 모듈을 사용하여 컴퓨터 비전에서 딥 러닝을 시작해 보겠습니다.

 

OpenCV DNN 모듈이란 무엇인가요?

 

OpenCV는 최고의 컴퓨터 비전 라이브러리 중 하나로 널리 알려져 있습니다. 딥러닝 추론 기능도 갖추고 있습니다. 가장 큰 장점은 다양한 프레임워크에서 다양한 모델을 로딩하여 여러 딥러닝 기능을 구현할 수 있다는 것입니다. 다양한 프레임워크의 모델을 지원하는 기능은 OpenCV 3.3 버전부터 포함되어 있습니다. 하지만 이 분야를 처음 접하는 많은 사람들은 OpenCV의 이 훌륭한 기능을 잘 알지 못합니다. 따라서 재미있고 유익한 학습 기회를 놓치는 경우가 많습니다.

 

왜 OpenCV DNN 모듈을 선택해야 하나요?

 

OpenCV DNN 모듈은 이미지와 비디오에 대한 딥러닝 추론만 지원합니다. 미세 조정 및 학습은 지원하지 않습니다. 하지만 OpenCV DNN 모듈은 딥러닝 기반 컴퓨터 비전을 처음 접하는 초보자에게 완벽한 시작점이 될 수 있습니다.

 

OpenCV DNN 모듈의 가장 큰 장점 중 하나는 Intel 프로세서에 고도로 최적화되어 있다는 것입니다. 객체 감지 및 이미지 분할 애플리케이션을 위해 실시간 비디오에서 추론을 실행할 때 우수한 FPS를 얻을 수 있습니다. 특정 프레임워크를 사용하여 사전 학습된 모델을 사용할 때 DNN 모듈에서 더 높은 FPS를 얻는 경우가 많습니다. 예를 들어, 다양한 프레임워크에서 이미지 분류 추론 속도를 살펴보겠습니다.

 

다양한 프레임워크에 대한 CPU에서의 이미지 분류 추론 속도 비교.

 

 

그림 2. 다양한 프레임워크에 대한 CPU에서의 이미지 분류 추론 속도 비교.

 

위 결과는 DenseNet121 모델의 추론 시간입니다. 놀랍게도 OpenCV는 TensorFlow의 기존 구현보다 훨씬 빠르지만 PyTorch에는 근소한 차이로 뒤처집니다. 실제로 TensorFlow의 추론 시간은 1초에 가까운 반면, OpenCV는 200밀리초 미만이 걸립니다 .

 

위 벤치마크는 이 글 작성 시점의 최신 버전인 PyTorch 1.8.0, OpenCV 4.5.1, TensorFlow 2.4를 사용하여 수행되었습니다. 모든 테스트는 Intel Xeon 프로세서 2.3GHz가 탑재된 Google Colab에서 수행되었습니다.

 

객체 감지의 경우에도 마찬가지입니다.

 

다양한 프레임워크에 대한 CPU에서의 객체 감지 속도 비교.

 

 

그림 3. 다양한 프레임워크에 대한 CPU에서의 객체 감지 속도 비교.

 

위 그래프는 원래 Darknet 프레임워크와 OpenCV에서 Tiny YOLOv4를 사용한 비디오의 FPS 결과를 보여줍니다. 벤치마크는 2.6GHz 클럭 속도의 Intel i7 8세대 노트북 CPU에서 수행되었습니다. 같은 비디오에서 OpenCV의 DNN 모듈은 35 FPS로 실행되는 반면 , OpenMP와 AVX로 컴파일된 Darknet은 15 FPS로 실행되는 것을 확인할 수 있습니다 . OpenMP 또는 AVX를 사용하지 않은 Darknet의 Tiny YOLOv4는 3 FPS로 가장 느립니다 . 두 경우 모두 원래 Darknet Tiny YOLOv4 모델을 사용했다는 점을 고려하면 이는 상당한 차이입니다.

 

위 그래프는 CPU에서 작동할 때 OpenCV DNN 모듈의 실제 유용성과 성능을 보여줍니다. CPU에서도 빠른 추론 속도를 제공하기 때문에 연산 능력이 제한된 엣지 디바이스에서 훌륭한 배포 도구로 활용할 수 있습니다. ARM 프로세서 기반 엣지 디바이스가 대표적인 예입니다. 아래 그래프는 이를 잘 보여줍니다.

 

Raspberry Pi 3B에서 실행되는 다양한 프레임워크와 신경망 모델의 FPS(초당 프레임)를 비교한 막대 그래프입니다.

 

 

 

그림 4. Raspberry Pi 3B에서 실행되는 다양한 프레임워크와 다양한 신경망 모델의 FPS(초당 프레임)를 비교한 막대 그래프.

 

위 그래프는 Raspberry Pi 3B에서 실행되는 다양한 프레임워크와 모델의 FPS를 보여줍니다. 결과는 매우 인상적입니다. SqueezeNet 및 MobileNet 모델의 경우, OpenCV는 FPS 측면에서 다른 모든 프레임워크를 능가합니다. GoogLeNet의 경우, OpenCV가 2위이며, TensorFlow가 가장 빠릅니다. Network in Network의 경우, OpenCV Raspberry FPS가 가장 느립니다.

 

위의 몇 가지 그래프는 최적화된 OpenCV와 신경망 추론 속도의 차이를 보여줍니다. 이 데이터는 OpenCV DNN 모듈에 대해 자세히 알아볼 만한 충분한 이유가 됩니다.

 

OpenCV DNN 모듈이 지원하는 다양한 딥러닝 기능

 

OpenCV DNN 모듈을 사용하면 이미지와 비디오에 대한 딥러닝 기반 컴퓨터 비전 추론을 수행할 수 있다는 것을 확인했습니다. 이 모듈이 지원하는 모든 기능을 살펴보겠습니다. 흥미롭게도, 우리가 생각할 수 있는 대부분의 딥러닝 및 컴퓨터 비전 작업이 지원됩니다. 다음 목록을 통해 해당 기능들을 대략적으로 파악할 수 있습니다.

 

  1. 이미지 분류.
  2. 객체 감지.
  3. 이미지 분할.
  4. 텍스트 감지 및 인식.
  5. 포즈 추정.
  6. 깊이 추정.
  7. 사람과 얼굴의 검증 및 감지.
  8. 리드라는 사람.

 

이 목록은 광범위하며 다양한 실용적인 딥러닝 사용 사례를 제공합니다. OpenCV 저장소의 Deep Learning in OpenCV Wiki 페이지를 방문하여 이러한 사례에 대해 자세히 알아보세요 .

 

인상적인 점은 시스템의 하드웨어와 컴퓨팅 성능에 따라 선택할 수 있는 다양한 모델이 있다는 것입니다(나중에 자세히 살펴보겠습니다). 최첨단 결과를 위한 매우 컴퓨팅 집약적인 모델부터 저전력 엣지 디바이스에서 실행 가능한 모델까지 모든 사용 사례에 맞는 모델을 찾을 수 있습니다.

 

위의 모든 사용 사례를 하나의 블로그 게시물에서 다루는 것은 불가능합니다. 따라서 OpenCV DNN을 사용하는 다양한 모델의 작동 방식을 알아보기 위해 객체 감지와 사람 자세 추정에 대해 자세히 살펴보겠습니다.

 

OpenCV DNN 모듈이 지원하는 다양한 모델

 

위에서 논의한 모든 애플리케이션을 지원하려면 사전 학습된 모델이 많이 필요합니다. 또한, 선택할 수 있는 최첨단 모델도 많습니다. 다음 표는 다양한 딥러닝 애플리케이션에 따른 몇 가지 모델을 나열한 것입니다.

 

mage Classification Object Detection Image Segmentation Text detection and recognition Human Pose estimation Person and face detection
Alexnet MobileNet SSD DeepLab Easy OCR Open Pose Open Face
GoogLeNet VGG SSD UNet CRNN Alpha Pose Torchreid
VGG Faster R-CNN FCN     Mobile FaceNet
ResNet EfficientDet       OpenCV
FaceDetector
SqueezeNet          
DenseNet          
ShuffleNet          
EfficientNet          

 

 

위에 언급된 모델들이 전부는 아닙니다. 훨씬 더 많은 모델이 존재합니다. 앞서 언급했듯이, 각 모델을 하나의 블로그에 자세히 나열하거나 논의하는 것은 거의 불가능합니다. 위 목록은 DNN 모듈이 컴퓨터 비전 분야에서 딥러닝을 탐구하는 데 얼마나 실용적인지 잘 보여줍니다.

 

OpenCV DNN 모듈이 지원하는 다양한 프레임워크

 

위의 모든 모델을 살펴보면 "이 모든 모델이 단일 프레임워크에서 지원될까?" 라는 의문이 듭니다. 실제로는 그렇지 않습니다.

 

OpenCV DNN 모듈은 여러 주요 딥러닝 프레임워크를 지원합니다. OpenCV DNN 모듈이 지원하는 딥러닝 프레임워크는 다음과 같습니다.

 

Caffe

 

OpenCV DNN에서 사전 훈련된 Caffe 모델을 사용하려면 두 가지가 필요합니다. 하나는 사전 훈련된 가중치를 포함하는 model.caffemodel 파일입니다. 다른 하나는 .prototxt 확장자를 가진 모델 아키텍처 파일입니다. 이 파일은 모든 신경망 계층의 정의를 포함하는 JSON과 유사한 구조를 가진 일반 텍스트 파일입니다. 이 파일의 구조를 자세히 알아보려면 이 링크를 방문하세요 .

 

텐서플로우

 

사전 학습된 TensorFlow 모델을 로드하려면 두 개의 파일이 필요합니다.모델 가중치 파일과 protobuf 텍스트 파일에는 모델 구성이 포함되어 있습니다.가중치 파일의 확장자는 .pb이며, 이는 사전 학습된 모든 가중치를 포함하는 protobuf 파일입니다.이전에 TensorFlow를 사용해 본 적이 있다면 .pb 파일이 모델을 저장하고 가중치를 고정한 후 얻는 모델 체크포인트라는 것을 알고 있을 것입니다.모델 구성은 .pbtxt 파일 확장자를 가진 protobuf 텍스트 파일에 보관됩니다.

 

참고: 최신 버전의 TensorFlow에서는 모델 가중치 파일이 .pb 형식이 아닐 수 있습니다.사용자가 저장한 .ckpt 또는 .h5 형식인 모델을 사용하려는 경우에도 마찬가지입니다.이 경우 OpenCV DNN 모듈에서 모델을 사용하기 전에 몇 가지 중간 단계를 수행해야 합니다. 이런 경우, 모델을 ONNX 형식으로 변환한 다음 .pb 형식으로 변환하는 것이 모든 것이 예상대로 작동하도록 보장하는 가장 좋은 방법입니다.

 

토치와 파이토치

 

Torch 모델 파일을 로드하려면 사전 훈련된 가중치가 포함된 파일이 필요합니다. 일반적으로 이 파일의 확장자는 .t7 또는 .net입니다. 하지만 최신 PyTorch 모델은 .pth 확장자를 가지므로 먼저 ONNX로 변환하는 것이 가장 좋습니다. OpenCV DNN은 ONNX 모델을 지원하므로 ONNX로 변환한 후에는 바로 로드할 수 있습니다.

 

다크넷

 

OpenCV DNN 모듈은 유명한 Darknet 프레임워크도 지원합니다. Darknet 프레임워크에서 공식 YOLO 모델을 사용해 본 사람이라면 이를 인지할 수 있을 것입니다.

 

일반적으로 다크넷 모델을 로드하려면 .weights 확장자를 가진 모델 가중치 파일 하나가 필요합니다. 다크넷 모델의 네트워크 설정 파일은 항상 .cfg 파일입니다.

 

Keras 및 PyTorch와 같은 다양한 프레임워크에서 ONNX 형식으로 변환된 모델 사용

 

PyTorch나 TensorFlow와 같은 프레임워크에서 학습된 모델은 OpenCV DNN 모듈에서 직접 사용하기에 적합하지 않은 경우가 많습니다. 이러한 경우, 일반적으로 모델을 ONNX(Open Neural Network Exchange) 형식으로 변환하여 그대로 사용하거나 TensorFlow나 PyTorch와 같은 다른 프레임워크에서 지원하는 형식으로 변환할 수 있습니다.

 

ONNX 모델을 로드하려면 OpenCV DNN 모듈의 .onnx 가중치 파일이 필요합니다.

 

다양한 프레임워크, 해당 가중치 파일, 구성 파일에 대한 자세한 내용은 공식 OpenCV 문서를 참조하세요 .

 

위 목록은 아마도 모든 유명 딥러닝 프레임워크를 포괄할 것입니다. OpenCV DNN 모듈이 지원하는 모든 프레임워크와 모델에 대한 자세한 내용은 공식 위키 페이지를 참조하세요 .

 

여기에 나와 있는 모든 모델은 OpenCV DNN 모듈에서 완벽하게 작동하는지 테스트되었습니다. 이론적으로 위 프레임워크의 모든 모델은 DNN 모듈에서 작동해야 합니다. 올바른 가중치 파일과 해당 신경망 아키텍처 파일만 찾으면 됩니다. 이 튜토리얼의 코딩 부분을 시작하면 더 명확해질 것입니다.

 

이론은 충분히 다루었습니다. 이제 이 튜토리얼의 코딩 부분을 살펴보겠습니다. 먼저 OpenCV DNN 모듈을 사용하여 이미지 분류를 전체적으로 살펴보겠습니다. 그런 다음 DNN 모듈을 사용하여 객체 감지를 수행해 보겠습니다.

 

OpenCV DNN 모듈을 사용한 이미지 분류에 대한 완벽한 가이드

 

이 섹션에서는 OpenCV DNN 모듈을 사용하여 이미지를 분류합니다. 이 섹션을 마치면 모든 내용을 명확하게 이해할 수 있도록 각 단계를 자세히 살펴보겠습니다.

 

Caffe 프레임워크를 사용하여 매우 유명한 ImageNet 데이터셋으로 학습된 신경망 모델을 사용할 것입니다. 특히 분류 작업에는 DensNet121 심층 신경망 모델을 사용할 것입니다 . 이 모델의 장점은 ImageNet 데이터셋의 1,000개 클래스로 사전 학습되어 있다는 것입니다. 따라서 모델이 분류하려는 이미지를 이미 인식하고 있을 것으로 예상할 수 있습니다. 따라서 광범위한 이미지 중에서 선택할 수 있습니다.

 

우리는 이미지 분류 작업에 호랑이의 다음 이미지를 사용할 것입니다.

 

OpenCV의 DNN 모듈을 사용하여 분류에 사용될 호랑이 이미지입니다.

 

 

 

그림 5. OpenCV의 DNN 모듈을 사용하여 분류에 사용할 호랑이 이미지.

 

아주 간단하게 말해서, 이미지를 분류하는 동안 따라야 할 단계는 다음과 같습니다.

 

  1. 디스크에서 클래스 이름 텍스트 파일을 로드하고 필요한 라벨을 추출합니다.
  2. 디스크에서 사전 학습된 신경망 모델을 로드합니다.
  3. 디스크에서 이미지를 로드하고 딥 러닝 모델에 맞는 올바른 입력 형식으로 이미지를 준비합니다.
  4. 입력 이미지를 모델에 전방 전파하여 출력을 얻습니다.

 

이제 코드와 함께 각 단계를 자세히 살펴보겠습니다.

 

모듈 임포트 및 클래스 텍스트 파일 로드


파이썬 코드에는 OpenCV 및 Numpy 모듈을 임포트해야 합니다. C++의 경우 OpenCV와 OpenCV의 DNN 라이브러리를 포함해야 합니다.

 

파이썬:

 

import cv2

import numpy as np

 

C++

 

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/dnn/all_layers.hpp>
  
using namespace std;
using namespace cv;
using namespace dnn;

 

사용할 DenseNet121 모델이 1,000개의 ImageNet 클래스로 학습되었다는 점을 언급했던 것을 기억하세요. 이 1,000개의 클래스를 메모리에 로드하여 쉽게 접근할 수 있는 방법이 필요합니다. 이러한 클래스는 일반적으로 텍스트 파일로 제공됩니다. 이러한 파일 중 하나는 classification_classes_ILSVRC2012.txt 파일이며, 모든 클래스 이름이 다음 형식으로 포함되어 있습니다.

 

tench, Tinca tinca

goldfish, Carassius auratus

great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias

tiger shark, Galeocerdo cuvieri

hammerhead, hammerhead shark

 

각 줄에는 단일 이미지에 해당하는 모든 레이블 또는 클래스 이름이 포함됩니다. 예를 들어, 첫 번째 줄에는 tench, Tinca Tinca. 이 두 이름은 같은 종류의 물고기에 속합니다. 마찬가지로 두 번째 줄에는 금붕어에 속하는 두 이름이 있습니다. 일반적으로 첫 번째 이름은 거의 모든 사람이 알고 있는 가장 흔한 이름입니다.

 

이러한 텍스트 파일을 로드하고 각 줄에서 이름을 추출하여 이미지를 분류할 때 레이블로 사용하는 방법을 살펴보겠습니다.

 

파이썬:

 

# read the ImageNet class names
with open('../../input/classification_classes_ILSVRC2012.txt', 'r') as f:
   image_net_names = f.read().split('\n')
# final class names (just the first word of the many ImageNet names for one image)
class_names = [name.split(',')[0] for name in image_net_names]

 

C++

 

std::vector<std::string> class_names;
   ifstream ifs(string("../../input/classification_classes_ILSVRC2012.txt").c_str());
   string line;
   while (getline(ifs, line))
   {
       class_names.push_back(line);
   }

 

먼저, 모든 클래스 이름이 포함된 텍스트 파일을 읽기 모드로 열고 각 줄을 나누어 나눕니다. 이제 모든 클래스가 image_net_names다음 형식으로 목록에 저장됩니다.

 

[‘tench, Tinca tinca’, ‘goldfish, Carassius auratus’, ‘great white shark, white shark, man-eater, man-eating shark’, ...]

 

하지만 각 줄의 첫 번째 이름만 필요합니다. 두 번째 코드 줄에서 바로 그 역할을 합니다. image_net_names 목록의 각 요소에 대해 쉼표(,)를 구분 기호로 사용하여 요소를 나누고 첫 번째 요소만 유지합니다. 이 이름들은 목록에 저장됩니다 class_names. 이제 목록은 다음과 같습니다.

 

['tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead', …]

 

디스크에서 사전 학습된 DenseNet121 모델 로드

 

앞서 논의한 바와 같이, Caffe 딥러닝 프레임워크를 사용하여 훈련된 사전 훈련된 DenseNet121 모델을 사용할 것입니다.

모델 가중치 파일(.caffemodel)과 모델 구성 파일(.prototxt)이 필요합니다.

코드를 살펴본 후 모델 로딩에 대한 설명 부분으로 넘어가 보겠습니다.

 

파이썬:

 

# load the neural network model
model = cv2.dnn.readNet(model='../../input/DenseNet_121.caffemodel', config='../../input/DenseNet_121.prototxt', framework='Caffe')

 

 

C++

 

// load the neural network model
   auto model = readNet("../../input/DenseNet_121.prototxt",
                       "../../input/DenseNet_121.caffemodel",
                       "Caffe");

 

보시다시피, readNet()OpenCV DNN 모듈에서 호출되는 함수를 사용하고 있는데, 이 함수는 세 개의 입력 인수를 받습니다.

 

  • model: 사전 훈련된 가중치 파일의 경로입니다. 이 경우에는 사전 훈련된 Caffe 모델입니다.
  • config: 이는 모델 구성 파일의 경로이며, 이 경우에는 Caffe 모델의 .prototxt 파일입니다.
  • framework: 마지막으로, 모델을 로드할 프레임워크 이름을 입력해야 합니다. 여기서는 Caffe 프레임워크를 사용합니다.

 

DNN 모듈은 이 함수 외에도 readNet()특정 프레임워크에서 모델을 로드하는 함수도 제공하는데, 이 경우 인수를 제공할 필요가 없습니다 framework. 해당 함수는 다음과 같습니다.

 

  1. readNetFromCaffe(): 사전 학습된 Caffe 모델을 불러오는 데 사용되며, prototxt 파일 경로 와 Caffe 모델 파일 경로 , 두 개의 인수를 받습니다 .
  2. readNetFromTensorflow(): 이 함수를 사용하면 TensorFlow 사전 학습된 모델을 직접 불러올 수 있습니다. 이 함수는 두 개의 인수를 받습니다 . 하나는 고정된 모델 그래프 경로 이고, 다른 하나는 모델 아키텍처 protobuf 텍스트 파일 경로 입니다 .
  3. readNetFromTorch(): 이 함수를 사용하면 () 함수를 사용하여 저장된 Torch 및 PyTorch 모델을 불러올 수 있습니다 . 모델 경로를 인수로torch.save 제공해야 합니다 .
  4. readNetFromDarknet(): DarkNet 프레임워크를 사용하여 학습된 모델을 로드하는 데 사용됩니다. 여기에도 두 개의 인수를 제공해야 합니다 . 하나는 모델 가중치 경로 이고, 다른 하나는 모델 구성 파일 경로 입니다 .
  5. readNetFromONNX(): 이것을 사용하면 ONNX 모델을 로드할 수 있으며 ONNX 모델 파일의 경로만 제공하면 됩니다.

 

이 블로그 게시물에서는 readNet() 함수를 사용하여 사전 학습된 모델을 불러오는 방법을 설명합니다. 객체 감지 섹션에서도 동일한 함수를 사용할 것입니다.

 

이미지를 읽고 모델 입력을 위해 준비하세요

 

평소처럼 OpenCV의 () 함수를 사용하여 디스크에서 이미지를 읽어옵니다 imread. 몇 가지 추가적으로 처리해야 할 사항이 있습니다. DNN 모듈을 사용하여 불러오는 사전 학습된 모델은 읽어온 이미지를 직접 입력으로 받지 않습니다. 따라서 그 전에 몇 가지 전처리 과정을 거쳐야 합니다.

 

먼저 코드를 작성해 보면 기술적인 세부 사항을 파악하기가 훨씬 수월해질 것입니다.

 

파이썬:

 

# load the image from disk
image = cv2.imread('../../input/image_1.jpg')
# create blob from image
blob = cv2.dnn.blobFromImage(image=image, scalefactor=0.01, size=(224, 224), mean=(104, 117, 123))

 

C++

 

// load the image from disk
Mat image = imread("../../input/image_1.jpg");
// create blob from image
Mat blob = blobFromImage(image, 0.01, Size(224, 224), Scalar(104, 117, 123));

 

 

이미지를 읽을 때, 해당 이미지는 현재 디렉토리에서 두 단계 위의 디렉토리에 위치하며 입력 폴더 내에 있다고 가정합니다. 다음 몇 단계는 매우 중요합니다. blobFromImage() 함수는 모델에 입력할 수 있도록 이미지를 올바른 형식으로 준비합니다. 모든 인수를 살펴보고 그에 대해 자세히 알아보겠습니다.

 

 

 

  • image: 이것은 우리가 imread() 함수를 사용하여 방금 읽은 입력 이미지입니다.
  • scalefactor: 이 값은 제공된 값만큼 이미지 크기를 조정합니다. 기본값은 1이며, 이 경우 크기 조정이 수행되지 않습니다.
  • size: 이미지 크기가 조정될 크기입니다. ImageNet 데이터셋으로 학습된 대부분의 분류 모델은 이 크기만 예상하므로, 224×224로 크기를 지정했습니다.
  • mean: 평균 인수는 매우 중요합니다. 이는 실제로 이미지의 RGB 색상 채널에서 뺀 평균값입니다. 이를 통해 입력값을 정규화하고 최종 입력값을 다양한 조도 스케일에 대해 불변하게 만듭니다.

 

여기서 한 가지 더 주의해야 할 점이 있습니다. 모든 딥러닝 모델은 배치 단위의 입력을 기대합니다. 그러나 여기에는 이미지가 하나뿐입니다. 그럼에도 불구하고, 여기서 얻는 블롭 출력은 실제로 의 형태를 갖습니다 [1, 3, 224, 224]. () 함수에 의해 배치 차원 하나가 추가된 것을 확인할 수 있습니다 blobFromImage. 이것이 신경망 모델의 최종적이고 올바른 입력 형식입니다.

 

모델을 통해 입력을 전방 전파합니다.

 

이제 입력이 준비되었으므로 예측을 할 수 있습니다.

 

파이썬

 

# set the input blob for the neural network
model.setInput(blob)
# forward pass image blog through the model
outputs = model.forward()

 

예측을 하는 데는 두 단계가 있습니다.

 

  • 먼저, 디스크에서 로드한 신경망 모델에 입력 블롭을 설정해야 합니다.
  • 두 번째 단계는 forward() 함수를 사용하여 블롭을 모델 전체에 전방 전파하는 것입니다. 이를 통해 모든 출력을 얻을 수 있습니다.

 

위의 코드 블록에서 두 단계를 모두 수행합니다.

 

출력값은 배열 형태로 모든 예측 결과를 담고 있습니다. 하지만 출력값과 클래스 레이블을 제대로 확인하기 전에 몇 가지 전처리 단계를 완료해야 합니다.

현재 출력값의 형상은 (1, 1000, 1, 1)이며, 이 상태로는 클래스 레이블을 추출하기 어렵습니다. 따라서 다음 코드 블록은 출력값의 형상을 재구성합니다. 이후 올바른 클래스 레이블을 쉽게 얻을 수 있으며, 레이블 ID를 클래스 이름에 매핑할 수 있습니다.

 

파이썬:

 

final_outputs = outputs[0]
# make all the outputs 1D
final_outputs = final_outputs.reshape(1000, 1)
# get the class label
label_id = np.argmax(final_outputs)
# convert the output scores to softmax probabilities
probs = np.exp(final_outputs) / np.sum(np.exp(final_outputs))
# get the final highest probability
final_prob = np.max(probs) * 100.
# map the max confidence to the class label names
out_name = class_names[label_id]
out_text = f"{out_name}, {final_prob:.3f}"
# put the class name text on top of the image
cv2.putText(image, out_text, (25, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.imwrite('result_image.jpg', image)

 

C++

 

// set the input blob for the neural network
model.setInput(blob);
// forward pass the image blob through the model
Mat outputs = model.forward(); 
Point classIdPoint;
double final_prob;
minMaxLoc(outputs.reshape(1, 1), 0, &final_prob, 0, &classIdPoint);
int label_id = classIdPoint.x; 
// Print predicted class.
string out_text = format("%s, %.3f", (class_names[label_id].c_str()), final_prob);
// put the class name text on top of the image
putText(image, out_text, Point(25, 50), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 255, 0), 2);
imshow("Image", image);
imwrite("result_image.jpg", image);

 

 

출력 데이터를 형상화한 후에는 (1000, 1,) 형태를 가지며, 이는 1000개의 레이블에 대해 1000개의 행을 보유함을 의미합니다. 각 행은 클래스 레이블에 대응하는 점수를 저장하며, 다음과 같은 형태를 띱니다.

 

 

[[-1.44623446e+00]

[-6.37421310e-01]

[-1.04836571e+00]

[-8.40160131e-01]

]

 

이들 중 가장 높은 레이블 인덱스를 추출하여 label_id에 저장합니다. 그러나 이 점수들은 실제 확률 점수가 아닙니다. 모델이 가장 높은 점수를 받은 레이블을 예측할 확률을 알기 위해서는 소프트맥스 확률을 구해야 합니다.

위의 Python 코드에서는 np.exp(final_outputs) / np.sum(np.exp(final_outputs))를 사용하여 점수를 소프트맥스 확률로 변환합니다. 그런 다음 가장 높은 확률 점수에 100을 곱하여 예측 점수 백분율을 구합니다.

마지막 단계는 이미지 상단에 클래스 이름과 백분율을 주석 처리하는 것입니다. 그런 다음 이미지를 시각화하고 결과를 디스크에 저장합니다.

코드를 실행하면 다음과 같은 출력을 얻을 수 있습니다.

 

DenseNet121 모델은 해당 이미지를 거의 91%의 신뢰도로 호랑이 이미지로 예측하는데, 이는 매우 좋은 결과입니다.

 

 

 

그림 6. DenseNet121 모델은 이미지를 호랑이로 예측하는데, 신뢰도가 거의 91%로 매우 우수합니다.

 

DenseNet121 모델은 이미지를 호랑이 이미지로 정확하게 예측했으며, 신뢰도도 91%로 매우 좋습니다. 결과는 매우 좋습니다.

 

위 섹션에서는 DenseNet121 신경망 모델을 사용하여 OpenCV DNN 모듈을 사용하여 이미지를 분류하는 방법을 살펴보았습니다. 또한 DNN 모듈의 작동 방식을 더 잘 이해하기 위해 각 단계를 자세히 살펴보았습니다.

 

다음 섹션에서는 OpenCV DNN과 이미지 및 비디오의 객체 감지를 활용하겠습니다.

 

OpenCV DNN을 이용한 객체 감지

 

OpenCV DNN 모듈을 사용하면 딥러닝과 컴퓨터 비전에서 객체 감지를 쉽게 시작할 수 있습니다. 분류와 마찬가지로, 이미지와 적절한 모델을 로드하고 입력을 모델에 전달합니다. 객체 감지에서 적절한 시각화를 위한 전처리 단계는 약간 다릅니다. 이 블로그 게시물의 나머지 부분에서 이 모든 내용을 다루겠습니다.

 

이미지에서의 객체 감지부터 시작해 보겠습니다.

 

OpenCV DNN을 사용한 이미지에서의 객체 감지

 

분류와 마찬가지로, 여기에서도 사전 학습된 모델을 활용할 것입니다. 이 모델들은 딥러닝 기반 객체 감지 모델의 현재 벤치마크 데이터셋인 MS COCO 데이터셋을 기반으로 학습되었습니다.

 

MS COCO는 사람부터 자동차, 칫솔까지 거의 80개의 객체 클래스를 가지고 있습니다. 이 데이터셋에는 일상생활에서 사용하는 80개의 객체 클래스가 포함되어 있습니다. 또한 객체 감지를 위해 MS COCO 데이터셋의 모든 레이블을 텍스트 파일로 불러올 것입니다.

 

객체 감지를 위해 다음 이미지를 사용합니다.

 

교통 체증에 갇혀 기다리는 사람들.

 

 

 

그림 7. 객체 감지를 위한 입력 이미지. 사람, 자전거, 오토바이 등 많은 객체가 어지럽게 배치되어 있으므로 모델에 대한 좋은 테스트가 될 것입니다.

 

TensorFlow 딥러닝 프레임워크를 사용하여 MS COCO 데이터셋을 학습한 MobileNet SSD(Single Shot Detector)를 사용할 것입니다 . SSD 모델은 일반적으로 다른 객체 감지 모델보다 빠릅니다. 또한, MobileNet 백본은 계산 집약도가 낮습니다. 따라서 OpenCV DNN을 이용한 객체 감지 학습을 시작하기에 적합한 모델입니다.

 

코딩 부분부터 시작해 보겠습니다.

 

파이썬

 

import cv2

import numpy as np

 

C++

 

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/dnn/all_layers.hpp>
  
using namespace std;
using namespace cv;
using namespace dnn;

 

  • Python 코드에서는 먼저 cv2와 numpy 모듈을 가져옵니다.
  • C++의 경우 OpenCV와 OpenCV DNN 라이브러리를 포함해야 합니다.

 

파이썬

 

# load the COCO class names
with open('object_detection_classes_coco.txt', 'r') as f:
   class_names = f.read().split('\n')
  
# get a different color array for each of the classes
COLORS = np.random.uniform(0, 255, size=(len(class_names), 3))

 

C++

 

std::vector<std::string> class_names;
   ifstream ifs(string("../../../input/object_detection_classes_coco.txt").c_str());
string line;
while (getline(ifs, line))
{
    class_names.push_back(line);
}

 

 

다음으로 object_detection_classes_coco.txt 파일을 읽습니다. 이 파일에는 각 클래스 이름이 새 줄로 구분되어 있습니다. 각 클래스 이름을 class_names 리스트에 저장합니다.

class_names 리스트는 다음과 유사할 것입니다.

 

['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', … 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush', '']

 

이와 함께 세 개의 정수 값을 튜플 형태로 저장하는 COLORS 배열도 있습니다. 각 클래스의 경계 상자를 그릴 때 이러한 무작위 색상을 적용할 수 있습니다. 가장 좋은 점은 각 클래스마다 다른 색상의 경계 상자가 적용되어 최종 결과에서 클래스를 쉽게 구분할 수 있다는 것입니다.

 

MobileNet SSD 모델 로드 및 입력 준비

 

이전에 사용했던 readNet() 함수를 사용하여 MobileNet SSD 모델을 로드합니다.

 

파이썬

 

# load the DNN model
model = cv2.dnn.readNet(model='frozen_inference_graph.pb', config='ssd_mobilenet_v2_coco_2018_03_29.pbtxt.txt',framework='TensorFlow')

 

 

C++

 

// load the neural network model
auto model = readNet("../../../input/frozen_inference_graph.pb",
"../../../input/ssd_mobilenet_v2_coco_2018_03_29.pbtxt.txt", "TensorFlow");

 

 

위의 코드 블록에서:

 

  • 모델 인수는 가중치가 포함된 사전 학습된 모델인 동결된 추론 그래프 경로를 입력으로 받습니다.
  • 인수 config는 protobuf 텍스트 파일인 모델 구성 파일의 경로를 허용합니다.
  • framework마지막으로, 이 경우에는 TensorFlow인 를 지정합니다 .

 

다음으로, 디스크에서 이미지를 읽고 입력 BLOB 파일을 준비합니다.

 

파이썬

 

# read the image from disk
image = cv2.imread('../../input/image_2.jpg')
image_height, image_width, _ = image.shape
# create blob from image
blob = cv2.dnn.blobFromImage(image=image, size=(300, 300), mean=(104, 117, 123), swapRB=True)
# set the blob to the model
model.setInput(blob)
# forward pass through the model to carry out the detection
output = model.forward()

 

 

C++

 

// read the image from disk
Mat image = imread("../../../input/image_2.jpg");
int image_height = image.cols;
int image_width = image.rows;
//create blob from image
Mat blob = blobFromImage(image, 1.0, Size(300, 300), Scalar(127.5, 127.5, 127.5),true, false);
//create blob from image
model.setInput(blob);
//forward pass through the model to carry out the detection
Mat output = model.forward();
Mat detectionMat(output.size[2], output.size[3], CV_32F, output.ptr<float>());

 

객체 탐지를 위해 blobFromImage() 함수에서 약간 다른 인자 값을 사용하고 있습니다.

  • 크기를 300×300으로 지정하는 것은 거의 모든 프레임워크에서 SSD 모델이 일반적으로 기대하는 입력 크기입니다. TensorFlow에서도 마찬가지입니다.
  • 이번에는 swapRB 인수도 사용합니다. 일반적으로 OpenCV는 이미지를 BGR 형식으로 읽지만, 객체 탐지 모델은 RGB 형식의 입력을 기대합니다. 따라서 swapRB 인수는 이미지의 R과 B 채널을 교환하여 RGB 형식으로 만듭니다.


그런 다음 blob을 MobileNet SSD 모델에 설정하고 forward() 함수를 사용하여 전파합니다.

출력 구조는 다음과 같습니다:

 

[[[[0.00000000e+00 1.00000000e+00 9.72869813e-01 2.06566155e-02 1.11088693e-01 2.40461200e-01 7.53399074e-01]]]]

 

  • 여기서 인덱스 위치 1에는 클래스 레이블이 포함되어 있으며, 1에서 80까지 가능합니다.
  • 인덱스 위치 2에는 신뢰도 점수가 포함됩니다. 이는 확률 점수가 아니라, 감지된 클래스에 속하는 객체에 대한 모델의 신뢰도입니다.
  • 마지막 네 가지 값 중 처음 두 개는 경계 상자의 x, y 좌표이고, 마지막 값은 경계 상자의 너비와 높이입니다.

 

탐지된 항목을 반복하고 경계 상자 그리기

 

이제 에서 감지된 객체들을 반복하고 output, 감지된 각 객체 주위에 경계 상자를 그릴 준비가 되었습니다. 다음은 감지된 객체들을 반복하는 코드입니다.

 

파이썬

 

# loop over each of the detection
for detection in output[0, 0, :, :]:
   # extract the confidence of the detection
   confidence = detection[2]
   # draw bounding boxes only if the detection confidence is above...
   # ... a certain threshold, else skip
   if confidence > .4:
       # get the class id
       class_id = detection[1]
       # map the class id to the class
       class_name = class_names[int(class_id)-1]
       color = COLORS[int(class_id)]
       # get the bounding box coordinates
       box_x = detection[3] * image_width
       box_y = detection[4] * image_height
       # get the bounding box width and height
       box_width = detection[5] * image_width
       box_height = detection[6] * image_height
       # draw a rectangle around each detected object
       cv2.rectangle(image, (int(box_x), int(box_y)), (int(box_width), int(box_height)), color, thickness=2)
       # put the FPS text on top of the frame
       cv2.putText(image, class_name, (int(box_x), int(box_y - 5)), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
  
cv2.imshow('image', image)
cv2.imwrite('image_result.jpg', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

C++

 

for (int i = 0; i < detectionMat.rows; i++){
       int class_id = detectionMat.at<float>(i, 1);
       float confidence = detectionMat.at<float>(i, 2);
       
       // Check if the detection is of good quality
       if (confidence > 0.4){
           int box_x = static_cast<int>(detectionMat.at<float>(i, 3) * image.cols);
           int box_y = static_cast<int>(detectionMat.at<float>(i, 4) * image.rows);
           int box_width = static_cast<int>(detectionMat.at<float>(i, 5) * image.cols - box_x);
           int box_height = static_cast<int>(detectionMat.at<float>(i, 6) * image.rows - box_y);
           rectangle(image, Point(box_x, box_y), Point(box_x+box_width, box_y+box_height), Scalar(255,255,255), 2);
           putText(image, class_names[class_id-1].c_str(), Point(box_x, box_y-5), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,255,255), 1);
       }
   }   
  
   imshow("image", image);
   imwrite("image_result.jpg", image);
   waitKey(0);
   destroyAllWindows();

 

  • 루프 내부에서 for먼저 현재 감지된 객체의 신뢰도 점수를 추출합니다. 앞서 설명한 대로, 인덱스 위치 2에서 이 점수를 얻을 수 있습니다.
  • 다음으로, 감지된 객체의 신뢰도가 특정 임계값을 초과하는지 확인하는 블록이 있습니다 if. 신뢰도가 0.4를 초과하는 경계 상자를 그리는 단계로만 진행합니다.
  • 클래스 ID를 가져와 MS COCO 클래스 이름에 매핑합니다. 그런 다음 현재 클래스에 대한 단일 색상을 가져와 경계 상자를 그리고 클래스 레이블 텍스트를 경계 상자 위에 배치합니다.
  • 그런 다음 경계 상자의 x, y 좌표와 경계 상자의 너비와 높이를 추출합니다. 이 값들을 이미지의 너비와 높이에 각각 곱하면 사각형을 그리는 데 필요한 정확한 값을 얻을 수 있습니다.
  • 마지막 몇 단계에서는 경계 상자 사각형을 그리고, 그 위에 클래스 텍스트를 쓰고, 결과 이미지를 시각화합니다.

 

OpenCV DNN을 사용하여 이미지에서 객체를 감지하는 데 필요한 모든 코드입니다. 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

 

MobileNet SSD를 이용한 객체 감지 결과입니다. 이 모델은 이미지의 거의 모든 객체를 감지하고 있습니다. 그러나 일부 감지 결과는 부정확합니다.

 

 

 

그림 8. MobileNet SSD를 이용한 객체 감지 결과. 이 모델은 이미지의 거의 모든 객체를 감지하고 있습니다. 그러나 일부 객체 감지 결과는 부정확합니다.

 

위 이미지에서 결과가 좋은 것을 확인할 수 있습니다. 이 모델은 눈에 보이는 거의 모든 물체를 감지하고 있습니다. 하지만 몇 가지 잘못된 예측도 있습니다. 예를 들어, MobileNet SSD 모델은 오른쪽에 있는 자전거를 오토바이로 감지하고 있습니다. MobileNet SSD는 실시간 애플리케이션을 위해 만들어졌고, 정확도를 높이기 위해 속도를 희생했기 때문에 이러한 오류가 발생하는 경향이 있습니다.

 

이 글에서는 OpenCV DNN을 사용하여 이미지에서 객체 감지를 구현합니다. 이 블로그 게시물에서는 학습 과정을 개선하기 위한 마지막 작업으로 비디오에서 객체 감지를 살펴보겠습니다.

 

OpenCV DNN을 사용한 비디오에서의 객체 감지

 

비디오 객체 감지 코드는 이미지 객체 감지 코드와 매우 유사합니다. 이미지 대신 비디오 프레임을 기반으로 예측을 수행하므로 몇 가지 변경 사항이 있습니다.

 

몇 줄의 코드는 이미지 객체 감지와 동일합니다. 먼저 해당 부분을 완성해 보겠습니다.

 

파이썬

 

import cv2
import time
import numpy as np
  
# load the COCO class names
with open('object_detection_classes_coco.txt', 'r') as f:
   class_names = f.read().split('\n')
  
# get a different color array for each of the classes
COLORS = np.random.uniform(0, 255, size=(len(class_names), 3))
  
# load the DNN model
model = cv2.dnn.readNet(model='frozen_inference_graph.pb', config='ssd_mobilenet_v2_coco_2018_03_29.pbtxt.txt',framework='TensorFlow')
  
# capture the video
cap = cv2.VideoCapture('../../input/video_1.mp4')
# get the video frames' width and height for proper saving of videos
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
# create the `VideoWriter()` object
out = cv2.VideoWriter('video_result.mp4', cv2.VideoWriter_fourcc(*'mp4v'), 30, (frame_width, frame_height))

 

C++

 

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/dnn/all_layers.hpp>
  
using namespace std;
using namespace cv;
using namespace dnn;
  
  
int main(int, char**) {
   std::vector<std::string> class_names;
   ifstream ifs(string("../../../input/object_detection_classes_coco.txt").c_str());
   string line;
   while (getline(ifs, line))
   {
       class_names.push_back(line);
   } 
   
   // load the neural network model
   auto model = readNet("../../../input/frozen_inference_graph.pb",
"../../../input/ssd_mobilenet_v2_coco_2018_03_29.pbtxt.txt","TensorFlow");
  
   // capture the video
   VideoCapture cap("../../../input/video_1.mp4");
   // get the video frames' width and height for proper saving of videos
   int frame_width = static_cast<int>(cap.get(3));
   int frame_height = static_cast<int>(cap.get(4));
   // create the `VideoWriter()` object
   VideoWriter out("video_result.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'), 30, Size(frame_width, frame_height));

 

대부분의 코드가 동일하다는 것을 알 수 있습니다. 동일한 MS COCO 클래스 파일과 MobileNet SSD 모델을 로드하고 있습니다.

 

여기서는 이미지 대신 VideoCapture() 객체를 사용하여 비디오를 캡처합니다. 또한 결과 비디오 프레임을 제대로 저장하기 위해 VideoWriter() 객체도 생성합니다.

 

비디오 프레임을 반복하고 각 프레임에서 객체 감지

 

이제 비디오와 MobileNet SSD 모델이 준비되었습니다. 다음 단계는 각 비디오 프레임을 반복하여 객체 감지를 수행하는 것입니다. 이렇게 하면 각 프레임을 이미지처럼 처리할 수 있습니다.

 

파이썬

 

# detect objects in each frame of the video
while cap.isOpened():
   ret, frame = cap.read()
   if ret:
       image = frame
       image_height, image_width, _ = image.shape
       # create blob from image
       blob = cv2.dnn.blobFromImage(image=image, size=(300, 300), mean=(104, 117, 123), swapRB=True)
       # start time to calculate FPS
       start = time.time()
       model.setInput(blob)
       output = model.forward()       
       # end time after detection
       end = time.time()
       # calculate the FPS for current frame detection
       fps = 1 / (end-start)
       # loop over each of the detections
       for detection in output[0, 0, :, :]:
           # extract the confidence of the detection
           confidence = detection[2]
           # draw bounding boxes only if the detection confidence is above...
           # ... a certain threshold, else skip
           if confidence > .4:
               # get the class id
               class_id = detection[1]
               # map the class id to the class
               class_name = class_names[int(class_id)-1]
               color = COLORS[int(class_id)]
               # get the bounding box coordinates
               box_x = detection[3] * image_width
               box_y = detection[4] * image_height
               # get the bounding box width and height
               box_width = detection[5] * image_width
               box_height = detection[6] * image_height
               # draw a rectangle around each detected object
               cv2.rectangle(image, (int(box_x), int(box_y)), (int(box_width), int(box_height)), color, thickness=2)
               # put the class name text on the detected object
               cv2.putText(image, class_name, (int(box_x), int(box_y - 5)), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
               # put the FPS text on top of the frame
               cv2.putText(image, f"{fps:.2f} FPS", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
       
       cv2.imshow('image', image)
       out.write(image)
       if cv2.waitKey(10) & 0xFF == ord('q'):
           break
   else:
       break
  
cap.release()
cv2.destroyAllWindows()

 

C++

 

while (cap.isOpened()) {
       Mat image;
       bool isSuccess = cap.read(image);
       if (! isSucess) break;
       
       int image_height = image.cols;
       int image_width = image.rows;
       //create blob from image
       Mat blob = blobFromImage(image, 1.0, Size(300, 300), Scalar(127.5, 127.5, 127.5),
                               true, false);
       //create blob from image
       model.setInput(blob);
       //forward pass through the model to carry out the detection
       Mat output = model.forward();
       
       Mat detectionMat(output.size[2], output.size[3], CV_32F, output.ptr<float>());
       
       for (int i = 0; i < detectionMat.rows; i++){
           int class_id = detectionMat.at<float>(i, 1);
           float confidence = detectionMat.at<float>(i, 2);
  
           // Check if the detection is of good quality
           if (confidence > 0.4){
               int box_x = static_cast<int>(detectionMat.at<float>(i, 3) * image.cols);
               int box_y = static_cast<int>(detectionMat.at<float>(i, 4) * image.rows);
               int box_width = static_cast<int>(detectionMat.at<float>(i, 5) * image.cols - box_x);
               int box_height = static_cast<int>(detectionMat.at<float>(i, 6) * image.rows - box_y);
               rectangle(image, Point(box_x, box_y), Point(box_x+box_width, box_y+box_height), Scalar(255,255,255), 2);
               putText(image, class_names[class_id-1].c_str(), Point(box_x, box_y-5), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,255,255), 1);
           }
       }
       
       imshow("image", image);
       out.write(image);
       int k = waitKey(10);
       if (k == 113){
           break;
       }
   }
  
cap.release();
destroyAllWindows();
}

 

위 코드 블록에서 모델은 비디오에서 반복할 프레임이 없을 때까지 각 프레임에서 객체를 감지합니다. 몇 가지 중요한 사항은 다음과 같습니다.

 

  • 탐지 시작 전의 시작 시간을 start 변수에 저장하고, 탐지 종료 후의 종료 시간을 저장합니다.
  • 위의 시간 변수들은 FPS(초당 프레임 수)를 계산하는 데 도움이 됩니다. FPS를 계산하여 fps에 저장합니다. 
  • 코드의 마지막 부분에서는 계산된 FPS를 현재 프레임 위에 출력하여 OpenCV DNN 모듈로 MobileNet SSD 모델을 실행할 때 예상되는 속도를 파악할 수 있도록 합니다.
  • 마지막으로 각 프레임을 화면에 시각화하고 이를 디스크에도 저장합니다.

 

위 코드를 실행하면 다음과 같은 출력이 나옵니다.

 

 

 

비디오 1. SSD MobileNetV2 모델을 사용한 OpenCV DNN 모듈로 객체 감지 출력.

 

i7 12세대 노트북 CPU에서 대부분의 프레임이 40FPS 이상을 기록하고 있습니다. 감지 횟수를 고려하면 나쁘지 않은 결과입니다. 이 모델은 거의 모든 사람, 차량, 신호등을 감지할 수 있습니다. 하지만 핸드백이나 백팩과 같은 작은 물체를 감지할 때는 다소 성능이 떨어집니다. CPU에서 40FPS를 제공하는 것은 정확도는 떨어지지만 작은 물체 감지 횟수는 줄어든다는 대가입니다.

 

GPU에서의 추론

 

모든 분류 및 감지 추론을 GPU에서 실행할 수도 있습니다. GPU를 사용하여 OpenCV DNN 모듈 소스를 컴파일해야 합니다.

 

Ubuntu를 사용하는 경우 LearnOpenCV.com의 이 게시물을 방문하여 GPU로 OpenCV를 컴파일하세요.

Windows를 사용하는 경우 이 링크를 방문하여 GPU로 OpenCV를 컴파일하세요.

 

GPU에서 추론을 실행하려면 C++ 및 Python 코드를 변경하기만 하면 됩니다.

 

파이썬:

 

net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)

net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)

 

C++:

 

net.setPreferableBackend(DNN_BACKEND_CUDA);

net.setPreferableTarget(DNN_TARGET_CUDA);

 

디스크에서 신경망 모델을 로드한 후 위 두 줄의 코드를 추가해야 합니다. 첫 번째 줄은 DNN 모듈이 CUDA GPU 모델을 지원하는 경우 신경망이 CUDA 백엔드를 사용하도록 합니다.

 

두 번째 코드 줄은 모든 신경망 계산이 CPU가 아닌 GPU에서 수행됨을 나타냅니다. CUDA 지원 GPU를 사용하면 CPU보다 객체 감지 비디오 추론에서 더 높은 FPS를 얻을 수 있습니다. 이미지의 경우에도 추론 시간은 CPU보다 훨씬 짧아야 합니다.

 

요약

 

OpenCV의 DNN 모듈을 소개하고 DNN 모듈을 선택한 이유를 설명했습니다. 성능을 비교하는 막대 그래프도 살펴보았습니다. 또한 OpenCV DNN이 지원하는 다양한 딥러닝 기능, 모델, 프레임워크도 살펴보았습니다.

 

OpenCV의 DNN 모듈을 활용한 이미지 분류 및 객체 감지 작업에 대해 실습을 통해 알아보았습니다. 또한, OpenCV DNN을 활용한 비디오 객체 감지 방법도 살펴보았습니다.

 

주요 내용

 

1. 신경망과 딥러닝은 컴퓨터가 사물을 정확하게 이해하고 인식할 수 있는 단계에 도달했습니다. 심지어 특정 사용 사례에서는 인간을 능가하기도 합니다.

 

2. OpenCV DNN 모듈:

 

  • 특히 Intel CPU에서 모델 추론을 위한 선호되는 선택입니다.
  • 설치가 쉽습니다.
  • 대부분의 사용 사례에 맞는 기성품으로 바로 사용 가능한 모델과 알고리즘이 제공됩니다.
  • DNN 모듈에는 학습 기능은 없지만 에지 장치에 대한 배포 지원은 여전히 ​​뛰어납니다.

 

블로그를 즐겁게 읽으시고 OpenCV DNN 모듈의 기본 사항을 익히셨기를 바랍니다. 댓글 섹션에 여러분의 경험을 공유해 주세요.

 

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩