[Computer Vision] 딥러닝 & OpenCV

2023. 10. 11. 00:09Run/Computer Vision

1. 딥러닝

  • 신경망(neural network): 사람의 뇌 신경 세포(neuron)에서 일어나는 반응을 모델링하여 만들어진 머신러닝 알고리즘
    • = 인공신경망(artificial neural network)
  • 딥러닝(deep learning): 신경망을 여러 계층으로 쌓아서 만든 머신러닝 알고리즘 중 하나
    • = 심층 신경망(deep neural network)
    • 보통 은닉층 2개 이상인 경우 딥러닝 알고리즘으로 간주

 

머신러닝 & 딥러닝

  • 컴퓨터 비전 분야에서 딥러닝이 주목받고 있는 이유는 객체 인식, 분할 등 영역에서 기존 컴퓨터 비전 기반 기술보다 월등한 성능 보이기 때문
  • 머신러닝: 특징(일반 pixel, HOG, ...)을 사람이 추출하여 입력으로 전달, 특징이 적합하지 않으면 좋은 성능 가지기 어려움
  • 딥러닝: 특징 추출 및 학습을 모두 딥러닝이 알아서 수행, 대신 데이터는 훨씬 많이 필요

 

퍼셉트론

  • 퍼셉트론(perceptron): 신경망의 가장 기초적인 형태 (1950년대 개발)
  • 다수의 입력으로부터 가중합 계산하여 하나의 출력 만들어내는 구조
  • 정점(node, vertex): 입력 노드 & 출력 노드
  • 간선(edge): 가중치(weight)
  • 편향(bias)

 

다층 퍼셉트론

  • 입력 노드, 출력 노드 개수 증가하고 여러 은닉층(hidden layer) 있는 형태로 좀 더 복잡한 문제 해결
  • 신경망이 주어진 문제 제대로 해결하려면 신경망 구조가 적절해야 하고, 적절한 weight 부여되어야 함
    • 적절한 weight, bias 찾기 위해 경사하강법(gradient descent), 오류 역전파(error backpropagation) 등 사용
  • 학습 = 훈련 데이터셋 이용하여 적절한 weight, bias 찾는 과정

 

  • 2000년대 초반까지 신경망 크게 발전 X (은닉층 많을수록 학습 시간 오래 걸리고 학습 제대로 되지 않음)
  • 2000년대 후반부터 심층 신경망, 딥러닝 이름으로 크게 발전
    • 1) 딥러닝 알고리즘 개선 → 은닉층 많아도 학습 제대로 이루어짐
    • 2) 하드웨어(특히 GPU) 발전, GPU 이용한 학습 방법 → 훈련 시간 단축
    • 3) 인터넷 발전에 따른 빅데이터(Pascal VOC, Coco, ImageNet, ...) 활용, 경쟁 및 공유 활발

 

일반적인 CNN 구조 (filtering & pooling & flatten & confidence)

  • 다양한 딥러닝 구조 중 영상을 입력으로 사용하는 영상 인식, 객체 검출 등 분야에서는 CNN 구조가 널리 사용됨
  • 보통 Convolution layer(합성곱 레이어. 2차원 영상에서 특징 추출) 여러 개, 맨 뒤에 FC layer(추출된 특징 분류)로 구성
  • Convolution layer: 영상의 지역적인 특징 추출
  • Pooling: 비선형 down sampling 수행하여 데이터 양 줄이고 일부 특징 강조 (max pooling, ...)
  • FC layer: 앞서 추출된 특징 이용하여 출력값 결정
  • Convolution 단계에서 사용하는 kernel 다양한 크기로 구성(1x1, 3x3, 5x5, ...), 레이어 사이 연결 방식도 다양
  • 자연어 분야에서 사용하던 Transformer 구조(attention)를 컴퓨터비전 분야에 적용한 딥러닝 알고리즘이 우수한 성능 내고 있음

 

2. OpenCV DNN 모듈

  • 이미 만들어진 네트워크에서 순방향 실행 (훈련 X 오로지 추론만)
  • PyTorch, Tensorflow 등 다른 프레임워크에서 학습 진행하고, OpenCV DNN 모듈로 학습된 모델 불러와 추론
  • OpenCV DNN 모듈은 C/C++ 기반이기 때문에 프로그램 이식성 높음
  • OpenCV 3.3부터 기본 모듈에 포함
  • 지원하는 딥러닝 프레임워크 (PyTorch는 ONNX 형식으로 변환 후 사용 가능)
  • 최근 새롭게 개발되고 있는 딥러닝 네트워크도 지속적으로 추가 지원
    • 영상 인식: AlexNet, GoogLeNet, VGG, ResNet, SqueezeNet, DenseNet, ShuffleNet, MobileNet, Darknet, ...
    • 객체 검출: VGG-SSD, MobileNet-SSD, Faster-RCNN, R-FCN, Mask-RCNN, EAST, YOLOv3, tiny YOLOv4, YOLOv4, ...
    • OpenPose, Colorization, OpenFace, ...
    • But, 대중적이지 않은 모델 업데이트 안하는 경우도 있음

 

  • readNet(): 모델 읽어오는 함수
  • model만으로는 모델 모양 정확히 알 수 없어 config를 넣기도 함

 

cv::dnn::Net 클래스

  • cv::dnn::Net: 딥러닝 네트워크 표현
    • dnn 모듈에 포함되어 있고 cv::dnn 네임스페이스 안에 정의되어 있음
    • 다양한 레이어로 구성된 네트워크 구조 표현
    • 네트워크에서 특정 입력에 대한 순방향 실행 지원 (훈련 지원 X)
  • Net::empty(): 초기화 잘 되었는지 확인, true면 문제 있는 것
  • Net::forward(): 네트워크 입력 설정
  • Net::setInput(): 네트워크 순방향 실행
  • Net::setPreferableBackend(): 선호하는 백엔드 지정 (ex. OpenCV)
  • Net::setPreferableTarget(): 선호하는 타겟 디바이스 지정 (ex. CPU)

 

  • Net 클래스 객체 직접 생성하지 않고 readNet() 사용하여 생성 (readNetFromXXX()도 사용 가능)
  • 미리 학습된 딥러닝 모델, 네트워크 구성 파일 이용하여 생성
  • model에 네트워크 훈련 가중치와 네트워크 구조 함께 저장되어 있으면 config 생략 가능 (ex. ONNX)
  • model 또는 config 확장자 통해 framework 구분 가능한 경우 framework 생략 가능 (그냥 보통 생략)

 

  • 네트워크에 Mat 타입 2차원 영상 그대로 입력하지 않고 blob 형식으로 변경해야 함
  • Blob: 영상 등 데이터 포함할 수 있는 다차원 데이터 표현 방식, OpenCV에서는 Mat 타입 4차원 행렬
    • N: 영상 개수 / C: 채널 개수 / H, W: 영상 세로, 가로 크기
  • blobFromImage(): Mat 영상으로부터 blob 생성
  • blobFromImages(): Batch(= 영상 여러 장) 변환하는 경우 사용
  • blob 출력 포맷 = 딥러닝 모델 입력 포맷

 

  • setInput(): Blob 객체 생성한 후 네트워크 입력으로 설정
  • blobFromImage()와 마찬가지로 scalefactor, mean 인자 있음 (추가적으로 픽셀 값 조정 가능)
  • mean은 채널별로 빼줄 수 있지만, scalefactor는 불가능함

 

  • forward(): 네트워크 입력 설정한 후 네트워크 순방향으로 실행하여 결과 예측 (추론)

 

  • Backend, Target 실행환경에 맞추어 설정
net = cv::dnn::readNet(modelFile, cfgFile);

// Case 1 (default) : 기본 OpenCV Backend & CPU 이용하여 추론
net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);

// Case 2 : CUDA Backend & GPU 이용하여 추론
net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);

// Case 3 : CUDA Backend & GPU(Float 16 연산) 이용하여 추론
net.setPreferableBackend(cv::dnn::DNN_BACKEND_)CUDA;
net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);

 

OpenCV DNN 추론 과정

 

3. MNIST 데이터셋 & Tensorflow 모델로 필기체 숫자 인식

  • 각 숫자 영상 크기 28x28, 픽셀 값 0~1 실수 값, 그레이스케일
  • Tensorflow로 학습한 네트워크 mnist_cnn.pb 불러와 사용 (학습 결과 & 네트워크 구성 정보 저장되어 있음)

 

#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;
using namespace cv::dnn;

Point ptPrev(-1, -1);

void on_mouse(int event, int x, int y, int flags, void* userdata);

int main() {
    Net net = readNet("models/mnist_cnn.pb"); // 네트워크 생성
    if (net.empty()) {
        cerr << "Network load failed!" << endl;
        return -1;
    }

    Mat img = Mat::zeros(400, 400, CV_8UC1);
    imshow("img", img);
    setMouseCallback("img", on_mouse, (void*)&img);

    while (true) {
        int c = waitKey(0);
        // ESC 누르면 종료
        if (c == 27) {
            break;
        }
        // Space 누르면 글씨 인식, 결과 출력
        else if (c == ' ') {
            /* 추론 */
            Mat inputBlob = blobFromImage(img, 1/255.f, Size(28, 28)); // blob 생성
            net.setInput(inputBlob);
            Mat prob = net.forward(); // 추론

            /* 예측 결과 확인 */
            double maxVal;
            Point maxLoc;
            minMaxLoc(prob, NULL, &maxVal, NULL, &maxLoc); // 가장 높은 값, 낮은 위치/값 구함 (내장함수)
            int digit = maxLoc.x;
            cout << digit << " (" << maxVal * 100 << "%)" << endl;

            /* 초기화 */
            img.setTo(0); // 행렬 0으로 초기화
            imshow("img", img);
        }
    }
    return 0;
}

void on_mouse(int event, int x, int y, int flags, void* userdata) {
    Mat img = *(Mat*)userdata;

    if (event == EVENT_LBUTTONDOWN) {
        ptPrev = Point(x, y);
    }
    else if (event == EVENT_LBUTTONUP) {
        ptPrev = Point(-1, -1);
    }
    else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {
        line(img, ptPrev, Point(x, y), Scalar::all(255), 40, LINE_AA, 0);
        ptPrev = Point(x, y);
        imshow("img", img);
    }
}
  • 입력 그레이스케일 영상 img (0~255) → Blob (0~1, 1x1x28x28)
  • 반환되는 행렬 prob: 10x1, CV_32FC1 (각 0~9에 해당할 확률)
  • 인식 결과: 최댓값 위치 행 번호 / 해당 위치의 원소 값: 확률
  • 머신러닝과 달리 인식 확률도 확인해볼 수 있음