딥러닝과 Loss 함수의 이해

기계학습 2021. 9. 15. 23:04

딥러닝.. 내겐 여전히 블랙박스 같은 존재이다. 하지만 딥러닝도 크게 보면 기계학습 방법의 하나로서 딥네트워크를 이용한 모델링과 파라미터 추정 방법으로 볼 수 있다. 그래서 전통적으로 기계학습을 연구했던 사람들은 아마도 보다 넓은 시야를 가지고 딥러닝을 이해할 수 있을 것으로 생각한다.

 

이 글은 딥러닝에 대한 나름의 이해를 정리한 글이다. Loss 함수, 파라미터 공간, 최적화 기법 등이 어떻게 딥러닝과 연결되는가 주 내용이다. 딥러닝의 세부적인 내용보다는 전체적인 틀을 바라보고 해석한 글로 볼 수 있다.

 

 

딥러닝과 파라미터 추정

 

딥러닝도 크게 보면 x에서 y로의 함수관계 fw(·)를 찾는 문제로 볼 수 있다.

x → fw(·) → y

 

이 때 함수의 구조와 복잡도를 정하는 것이 모델링 과정인데, 딥러닝에서는 어떤 네트워크 아키텍쳐를 사용할 것인지, 몇 개의 층을 사용할 것인지, activation 함수로는 무엇을 사용할 것인지 등을 정하는 것이 모델링에 해당한다. 예를 들어 아키텍쳐로는 VGG, ResNet 등을 사용할 수 있고 activation 함수로는 ReLU나 sigmoid 등을 사용할 수 있다.

 

모델이 정해지면 모델의 파라미터가 정해지는데 딥네트워크에서는 입력층과 출력층 사이에 존재하는 모든 weight들이 모델 파라미터가 된다. 모델 파라미터는 모델에 종속되어 정해지는 것으로서 예를 들어 돌맹이의 궤적을 2차 다항식 f(t) = at2 + bt + c로 근사하기로 결정했다면 모델은 2차 다항식이고 모델 파라미터는 a, b, c가 된다.

 

모델과 모델 파라미터가 정해지면 이제 남은 일은 모델 파라미터의 값을 정하는 것이다. 주어진 입력(x)에 대해 원하는 출력(y)이 나오도록 모델 파라미터 값을 조정하는 것이 목적이다. 수집된 관측값들이 (xi; yi), i = 1, 2, ..., n라면 각각의 i에 대해 최대한 yi = fw(xi)가 되도록 파라미터 값을 찾게 되는데, 이 과정이 딥러닝의 학습과정 또는 파라미터 추정 과정에 해당한다.

 

그런데, 파라미터 추정을 위해서는 먼저 파라미터의 좋고 나쁨을 판단하는 기준이 있어야 한다. 그것이 바로 loss 함수를 정하는 일이다. 앞서 과정에서 모델과 모델 파라미터가 정해지면 일종의 파라미터 공간(parameter space)이 생성된다. 이 공간의 차원은 모델 파라미터의 개수와 동일하며 아직은 차원만 정해진 빈 공간이다. 그런데, loss 함수가 정해지면 이 공간에 산맥이 생기고 골짜기가 생긴다. 즉, Loss 함수에 따라서 각 파라미터 값에 대한 score(or 손실)가 매겨지기 때문에 이 score에 따라서 파라미터 공간에 높고 낮음이 생성된다.

 

딥러닝 학습에 있어서 마지막 선택은 주어진 산맥과 골짜기의 파라미터 공간에서 어떻게 최적의 답을 찾아갈 것인가이다. 파라미터 공간이 정해지면 이미 최적의 답도 같이 정해진다. 하지만 무한한 파라미터 공간을 미리 다 evaluation 해볼 수는 없기 때문에 현실적으로는 시작 위치에서 조금씩 해를 찾아갈 수 밖에 없다. 즉, gradient descent 방법, Gauss-Newton 방법, LM 방법 등과 같은 최적화 기법(optimizer)이 필요하다.

 

※ 일반적으로는 Newton, LM 방법 등의 성능이 더 좋다. 하지만 딥러닝에서는 현실적인 이유(파라미터 공간이 너무 고차원)로 gradient descent, 특히 stochatic gradient descent 류의 방식이 주로 사용된다.

 

이상의 내용을 정리해 보면 다음과 같다.

  • 학습데이터: problem의 domain을 정의
  • 네트워크 구조: 함수의 복잡도와 power를 정의, 모델 파라미터를 정의
  • loss 함수: 파라미터에 대한 evaluation(cost)을 정의
  • optimizer: 파라미터 공간에서의 탐색 방법을 정의

 

이중 어떤 요소가 더 중요할까? optimizer는 해 자체를 바꾸지는 못하지만 optimizer에 따라 해를 잘 찾을 수도 있고 못 찾을 수도 있다. 네트워크의 구조와 복잡도를 정하는 것은 비유컨데 닭 잡는데 소 잡는 칼을 쓸 것인지 아니면 소 잡는데 닭 잡는 칼을 쓸 것인지를 정하는 문제로 볼 수 있다. 물론 적당한 규격의 칼이 좋을 것이다. 학습데이터와 loss 함수는 선택된 칼의 규격 내에서 최적의 칼이 되기 위한 기준을 잡아준다.

 

 

Loss 함수의 역할

 

Loss는 영어 단어로는 상실, 손실을 의미한다. 그리고 기계학습에서의 Loss는 어떤 학습된 모델을 실제 데이터에 적용했을 때 모델의 추정 오차로 인해 발생하는 손실을 의미한다. 학습된 모델을 fw(·), 관측 데이터를 (xi; yi)라 하자. 이 때, 입력 xi에 대한 모델 추정값과 실제 관측값 사이의 오차를 residual이라 부르며 xi에 대한 residual은 ri = yi - fw(xi)로 계산된다. Residual이 단순히 모델 추정값과 참값(관측값) 사이의 차이를 말하는 반면에, Loss는 이 차이로 인해 발생하는 실제 계산상의 손실을 의미한다. 사실 Loss는 우리가 마음데로 정할 수 있으며 예를 들어 residual의 절대값을 Loss로 정하거나(L(xi) = ‖yi - fw(xi)‖), residual의 제곱을 Loss로 할 수도 있다(L(xi) = ‖yi - fw(xi)‖2). 이 외에도 문제나 응용에 따라서 focal loss, triplet loss 등 다양한 형태의 Loss 함수 정의가 가능하다.

 

앞서 Loss 함수는 파라미터에 대한 evaluation(cost)을 정의한다고 했다. 아래 두 파라미터 추정 문제의 예를 살펴보자. 식 (1)은 일반적인 L2-norm을 Loss로 정의한 경우이고, 식 (2)는 L2-norm에 파라미터 크기를 함께 고려하여 Loss로 정의한 경우이다.

$$\min_{w} \; {\left \|  y - f_w (x) \right \|^2}\tag{1}$$

$$\min_{w} \; {\left \|  y - f_w (x) \right \|^2 \!+\lambda \left \| w \right \|^2} \tag{2}$$

그림1. Rideg Regression (그림출처: http://primo.ai/index.php?title=Ridge_Regression)

 

식 (1)과 같이 Loss를 정의하면 정답(학습데이터)에만 초점을 맞추어 파라미터를 추정하게 된다. 그런데, 파라미터 공간은 워낙 고차원이기 때문에 그림1(좌) 경우처럼 식 (1)을 만족하는 해(w)가 너무 많아서 오히려 수렴이 어려울 수도 있다.  또는 반대로 파라미터 공간이 너무 굴곡이 심해서 쉽게 발산할 수도 있다. 하지만, 식 (2)와 같이 Loss를 정의하면 가능한 해들 중에서 크기가 최소화되는 해를 찾기 때문에 그림1(우)처럼 파라미터 공간이 안정화되어 상대적으로 쉽게 해를 찾을 수도 있다.

 

※ 기계학습 분야에서는 식 (2)와 같은 regularization term을 추가한 파라미터 추정 기법을 ridge regression이라 부르는데, ridge를 추가하는 것이 아니라 제거하기 때문에 ridge regression이라 부른다.

 

위 예는 기계학습 or 파라미터 추정에서 Loss 함수가 미치는 영향을 설명하기 위한 하나의 예시이며 이 외에도 다양한 형태의 Loss 함수 정의가 가능하다. Loss 함수를 어떻게 정의하느냐에 따라서 파라미터에 대한 evaluation 즉, 학습의 지향점이 달라지기 때문에 기계학습을 한다면 한 번쯤은 Loss 함수에 대해 고민해 볼 필요가 있다.

 

※ Loss 함수가 중요하긴 하지만 그렇다고 파라미터 공간을 마음대로 바꾸는 것은 아니다. 잘 생각해보면 학습에 사용되는 관측 데이터 자체가 이미 답을 제시한다. 즉, xi에 대해서는 yi가 나오도록 하는 파라미터를 찾아라고 알려준다. 따라서, 보다 정확하게 말하면 학습 데이터는 기준점을 제시하고 Loss 함수는 기준점에서 벗어날을 때의 패널티와 부가적인 제약조건을 정의한다고 볼 수 있다. 그리고 부가적인 제약조건에 따라서 학습의 지향점이 조금씩 달라질 수 있다.

 

 

딥러닝 Loss 함수

 

마지막으로 딥러닝에서 사용되는 Loss 함수들에 대해 간단히 살펴보려고 한다. 사실 Loss 함수도 워낙 종류가 많기 때문에 대표적인 몇 가지만 살펴보도록 하자. 먼저, yolo에서는 weighted least squares loss를 사용한다. 기본적으로는 L2-norm인데 요소(추정된 box와 ground truth box와의 중심거리, 박스 크기, 박스 overlap 등)별로 다른 가중치를 적용한다. 그리고 classification 문제에서는 대부분 cross entropy loss라는 것을 사용한다. 멀티 클래스 classification 문제에서는 클래스 개수만큼의 출력층 노드가 존재하는데, 학습시 입력 데이터의 클래스에 대응되는 출력노드의 출력은 1, 그 외의 출력노드의 값은 0이 되도록 학습하는 것이 일반적이다. 만일 입력 학습데이터의 클래스가 Ci이고 Ci에 대응되는 출력노드의 출력값을 pi라고 하면 x∈Ci에 대한 cross entropy loss는 L = -log(pi)로 계산된다 (일반적으로 딥러닝에서는 출력층 값에 softmax를 적용하기 때문에 pi는 항상 0 ~ 1 사이의 값을 갖는다). Cross entropy loss를 개선한 버전들도 존재하는데, balanced cross entropy loss는 클래스간 샘플 데이터의 불균형을 해결하기 위해 희소 클래스일수록 높은 가중치를 주도록 x∈Ci에 대해 L = -1/αi * log(pi)로 Loss를 계산한다 (αi: 전체 학습 데이터들 중에서 클래스 Ci에 속하는 데이터들의 비율). 그리고 hard example에 대해 좀더 높은 가중치를 주는 focal loss도 발표된 바 있다 (“Focal Loss for Dense Object Detection.” ICCV 2017).

 

그외 얼굴인식이나 image retrieval 분야에서는 ranking loss라는 것을 사용하는데, 입력 query 영상에 대해 정답 영상들(positives)과는 거리가 가깝게 하고 정답이 아닌 영상들(negatives)과는 거리가 멀게 하도록 Loss 함수를 설계한다. 예를 들어, 얼굴인식 응용에서 철수 얼굴 영상들을 p1, p2, ..., pn, 다른 사람들의 얼굴들은 q1, ..., qm라고 했을 때, pi들끼리는 거리를 최소화하고 pi와 qj 사이의 거리는 크게 하도록 Loss 함수를 정의한다. 이러한 응용에서는 query영상과 가장 가까운 DB 영상을 정답으로 찾는 것이 목적이기 때문에 계산된 거리값(distance) 자체보다는 그 거리값들의 상대적인 순서를 맞추는 것이 중요하다(pi들끼리의 거리가 pi와 qj 사이의 거리보다 작기만 하면 된다). 그래서 ranking loss라고 부르며 그 대표적인 예로는 triplet loss나 pairwise loss 등이 있다 (ranking loss도 굉장히 많은 변형들이 있다).

 

이 글에서는 개별 Loss 함수들을 설명하는 것이 목적이 아니기 때문에 기존의 Loss 함수들에 대해서는 간단히만 설명하였다. 중요한 것은 Loss 함수의 의미를 이해하고 활용하는 것이라 생각한다. 그리고 나아가서 자신의 문제에 맞는 Loss 함수를 직접 만들 수 있다면 더 좋을 것이다.

 

by 다크 프로그래머

'기계학습' 카테고리의 다른 글

딥러닝과 Loss 함수의 이해  (4) 2021.09.15
YOLO와 성능지표(mAP, AP50)  (7) 2020.09.07
YOLO 윈도우즈(windows) 버전  (63) 2017.09.01
최적화 기법의 직관적 이해  (110) 2015.06.02
  • asd 2021.09.27 16:00 ADDR 수정/삭제 답글

    안녕하세요! 글 잘보고있어요. LeRU가 아니라 ReLU 가 맞는거 같습니다

  • BlogIcon 딩딩기 2021.09.28 17:34 ADDR 수정/삭제 답글

    오랜만입니다. 선생님 글 기다리고 있었습니다 ㅋㅋ

  • BlogIcon DataMayno 2021.10.01 00:07 신고 ADDR 수정/삭제 답글

    오랜만에 글 올리시네요. 좋은 글 감사합니다^^

상관계수와 cross correlation

수학 이야기 2021. 9. 12. 16:33

상관계수(correlation coefficient)는 통계학 쪽에서 사용되는 용어이고, cross correlation은 신호처리 분야의 용어이다. 그리고 NCC(normalized cross correlation)도 신호처리 분야 용어이다.

 

수식적으로 보면 상관계수 계산식과 NCC 계산식은 매우 유사하다 ($\bar{x}$: X의 평균, $\sigma_x$: X의 표준편차, $n$: 데이터 개수, $x_i$: i번째 관측 데이터의 X 속성 값, $f(x), g(x)$: x에서의 두 신호 값).

$$상관계수(X, Y) = \frac{1}{n} \sum_{i=1}^{n}{\frac{(x_i - \bar{x})(y_i - \bar{y})}{\sigma_x \sigma_y}}\tag{1}$$

$$NCC(f, g) = \frac{1}{n} \sum_{i=1}^{n}{\frac{f(x_i) g(x_i)}{\sigma_f \sigma_g}} \text{ or } \frac{1}{n} \sum_{i=1}^{n}{\frac{(f(x_i)-\bar{f})(g(x_i)-\bar{g})}{\sigma_f \sigma_g}} \tag{2}$$

 

그런데, 두 식이 사용되는 목적이나 응용은 조금 다르다. 상관계수(correlation coefficient)는 두 속성 or 변량의 상호 연관성을 -1 ~ +1 사이의 값으로 수치화한 것이다. 예를 들어, 키와 몸무게의 관계, 수업 참여도와 성적의 관계, 환율과 금리의 관계 등 두 변인의 연관관계를 분석하는 것이 목적이다. 반면에, cross correlation이나 normalized cross correlation은 두 신호(음성신호, 영상신호 등)의 형태적 유사성을 단지 수치적으로 계산한 것이다. 여기서 'cross'라는 용어는 이동, 교차의 의미로서 두 신호를 서로 겹쳐서 correlation을 계산하기에 붙여진 이름이다. 그리고 두 신호가 겹쳐지는 시점은 한 신호를 shift시킴으로써 다양하게 조정될 수 있다. cross correlation이 사용되는 대표적인 예로 템플릿 매칭을 생각할 수 있다. 식은 거의 같지만 적용에 있어서 어감이나 느낌은 상당히 다름을 알 수 있다.

 

이 글은 상관계수와 cross correlation에 대해 최근 깨달은 것들은 정리한 글이다. 또한, 공분산(covariance), 분산(variance), correlation과 covariance 차이, auto correlation 등 관련 용어들도 같이 정리한다.

 

사실 이 글을 쓰게 된 계기는 아내 때문이다. 아내가 요즘 '연구방법론'(객관적 연구를 위한 과학적 방법론)을 공부하면서 t-검정, 상관분석 등을 공부하고 있다. 그러면서 책을 봐도 무슨 말인지 모르겠다고 어려워한다. 그래서 이래 저래 설명해 주게 되었는데, 사실은 설명을 하면서 스스로 배우고 깨달은 것들이 더 많다. 막연하게만 알고 있던 covariance(공분산)의 의미를 다시 깨닫게 되었고 correlation과 covariance의 차이도 보다 확실히 알게 되었다. 그리고 상관계수나 공분산(covariance)에서 두 변량의 값이 서로 페어링(pairing)된 값이라는 점도 새로운 깨달음이다.

 

 

공분산(covariance)

 

데이터 분포의 평균, 분산, 표준편차는 통계에서 가장 기초적인 개념이다. 이들 중 분산(variance)은 측정된 데이터들이 평균을 중심으로 얼마나 모이고 흩어져 있는지를 나타내는 지표로서 "(편차)2의 평균"으로 계산된다 (편차 = 데이터값 - 평균). 그리고 분산에 제곱근을 취하면 표준편차가 된다.

$$Var(X) = \frac{1}{n} \sum_{i=1}^{n}{(x_i - \bar{x})^2}\tag{3}$$

$$Std(X) = \sqrt{Var(X)}\tag{4}$$

 

이 때, 한가지 주의해야 할 점은 평균, 분산, 표준편차는 데이터 분포에 대한 특성값이고, 편차는 데이터 각각에 대한 특성값이라는 점이다. 예를 들어, 전국 학생들의 키를 분석한다고 했을 때 측정된 학생들의 키의 평균은 얼마, 분산은 얼마라고 말할 수 있겠지만 데이터 각각(철수의 키, 영희의 키, ...)에 대해 평균, 분산, 표준편차가 얼마다고 말하는 것은 이치에 맞지 않는다.

 

분산이 데이터의 흩어진 정도를 나타내는 값이라면 공분산(covariance)는 어떤 의미일까? 일단, 분산이 하나의 변수(e.g. 키)에 대해 계산되는 반면에 공분산(covariance)은 두 변량(e.g. 키, 몸무게)에 대해 계산된다. 관측된 데이터가 n개일 때, 두 변수 X, Y 사이의 공분산 Cov(X, Y)는 아래와 같이 계산된다.

$$Cov(X, Y) = \frac{1}{n} \sum_{i=1}^{n}{(x_i - \bar{x})(y_i - \bar{y})}\tag{5}$$

 

식 (3)과 (5)를 자세히 살펴보자. X의 분산은 자신의 편차에 편차를 곱한 평균이다. 반면에 X와 Y의 공분산은 X의 편차에 Y의 편차를 곱한 평균이다. X의 편차에 Y의 편차를 곱한다는 말의 의미는 무엇일까? 편차는 +, - 부호를 가진 값이기 때문에 두 편차를 곱한 결과는 +일 수도 있고 -일 수도 있다. 분산(variance)는 항상 양수(0 이상)이지만 공분산(covariance) 값은 +일 수도 -일 수도 있음에 주의하자. X와 Y의 공분산은 데이터들의 X 편차와 Y 편차의 부호가 서로 같을수록 큰 +값을 갖고 부호가 서로 다를수록 -로 큰 값을 갖는다. 그리고 X의 자체 분산과 Y의 자체 분산이 클수록 X, Y의 공분산의 절대값도 증가한다.

 

공분산을 이해하기 위해서는 계산에 사용되는 두 변량의 값이 서로 페어링(pairing)된 값이라는 것을 인지하는 것이 중요하다. 식 (5)에서 보면 $x_i$에는 $y_i$가 곱해지지 $y_j$가 곱해지지 않는다. 이것이 앞서 중요하게 깨달았다고 했던 두 변량이 서로 페어링(pairing)된다는 것과 관련된다. 페어링 관점에서 보면 식 (5)에서 $x_i$는 i번째 데이터의 X 속성값, $y_i$는 동일한 데이터의 Y 속성값으로 이해할 수 있다. 예를 들어 모집단=전국학생, X=키, Y=몸무게로 생각해 보자. 그리고, 측정된 표본집단에서 키와 몸무게의 공분산을 구하는 문제를 생각해 보자. 이 때, 페어링 관점에서 철수의 키에 대해서는 철수의 몸무게와 비교하고 영희의 키는 영희의 몸무게를 비교해야지 철수의 키와 영희의 몸무게를 비교해서는 안된다. 그리고 이러한 페어링 관계는 공분산 뿐만 아니라 상관계수 계산에도 동일하게 적용된다.

 

※ 신호처리 분야에서는 2차원 평면에 점들이 있을 때 이 점들의 X좌표와 Y좌표의 공분산을 구하는 문제를 생각할 수 있다. 이 때, 각각의 점들을 데이터로 볼 수 있고 이 점들의 X좌표와 Y좌표는 서로 페어링된 값으로 볼 수 있다.

 

 

correlation과 covariance

 

두 변수의 correlation은 두 변량의 곱의 평균으로 정의된다.

$$Corr(X, Y) = \frac{1}{n} \sum_{i=1}^{n}{x_i y_i}\tag{6}$$

 

correlation이란 단어 뜻으로 보면 '상호연관'이 떠오른다. 그런데, 곱해서 합하면 왜 상호연관이 측정되는지는 언뜻 이해가 어렵다. 그런데, X의 값의 범위가 한정되어 있고 Y의 값도 범위가 한정되어 있는 경우를 생각해 보면, 곱의 합은 X의 분포와 Y의 분포가 서로 일치할수록 증가한다 (모든 i에 대해 xi = yi일 때 최대).

 

correlation은 두 데이터 분포의 유사도를 측정하는 도구로서 두 분포가 일치할 때 가장 높은 값을 갖는다. 그런데, 두 분포가 서로 위치적으로 떨어져 있는 경우에는 분포의 형태가 유사하더라도 낮은 correlation 값을 갖게 된다. 따라서, 두 분포의 형태 or 경향성의 유사도를 측정할 때에는 각 데이터 분포에서 평균을 뺀 후에 correlation을 계산하는 것이 효과적이다 (zero-normalized correlation).

$$\text{zero-normalized }Corr(X, Y) = \frac{1}{n} \sum_{i=1}^{n}{(x_i - \bar{x})(y_i - \bar{y})}\tag{7}$$

 

그런데, zero-normalized correlation 식을 보면 식 (5)의 covariance 식과 동일함을 알 수 있다. 즉, 바꾸어 말하면 covariance는 zero-normalized correlation으로 볼 수 있다.

 

그림1. 두 분포의 correlation과 covariance

※ 분야에 따라서는 correlation과 covariance의 용어가 혼용되는 경우가 많다.

 

 

상관계수와 covariance

 

상관계수(correlation coefficient)는 통계학 분야에서 나오는 용어이다. 흔히, 통계라고 하면 수학 쪽으로만 생각하기 쉬운데 통계를 '사례를 모으고 분석해서 현상을 이해하는 연구 방법론'으로 본다면 통계는 경제학, 심리학, 사회과학, 의학 등 전 분야에 걸친 방법론이다.

 

상관계수(correlation coefficient)는 두 변량 X, Y 사이의 correlation 정도를 -1 ~ +1 사이의 값으로 수치화한 척도이다 ($\bar{x}$: X의 평균, $\sigma_x$: X의 표준편차, $n$: 데이터 개수, $x_i$: i번째 데이터의 X 속성 값, $y_i$: i번째 데이터의 Y 속성 값)

$$상관계수(X, Y) = \gamma = \frac{1}{n} \sum_{i=1}^{n}{\frac{(x_i - \bar{x})(y_i - \bar{y})}{\sigma_x \sigma_y}}\tag{8}$$

 

그런데 상관계수 식을 잘 보면 식 (7)의 공분산 식과 연관이 큼을 알 수 있다. 앞서, 공분산은 zero-normalized correlation으로 볼 수 있다고 했다. 그런데, zero-normalization을 했더라도 두 분포가 가지고 있는 값의 크기의 스케일(scale)에 따라서 correlation 값이 달라질 수 있다. 따라서 순수하게 상관도만 분석하기 위해서는 각 분포의 스케일을 1로 정규화한 후에 correlation을 계산할 필요가 있으며 이렇게 정규화된 두 분포의 상관도를 계산한 것이 상관계수(correlation coefficient)이다. 여기서 scale이란 값의 단위 또는 크기의 범위를 말한다. 예를 들어, 한국 중학생들의 키와 몸무게의 상관도를 분석하기 위해 키는 미터(meter) 단위로 몸무게는 킬로그램(kg) 단위로 측정했다고 하자. 그러면 평균을 보상한 키는 -0.3 ~ 0.3 m 사이의 값을, 몸무게는 -20 ~ 20 kg 사이의 값을 가질 것이다. 이렇게 값의 단위가 서로 다른 두 변량을 그대로 correlation을 취하면 그 결과는 예측이 힘들며 분석도 어렵게 된다. 하지만, 각 변량을 표준편차로 나누어 정규화한 후에 correlation을 계산하면 그 결과는 항상 -1 ~ +1 사이의 값을 갖게 된다 (Why? Cauchy-Schwarz inequality).

 

정리하면 상관계수(correlation coefficient)는 평균과 스케일을 보상한(평균은 0으로, 표준편차는 1이 되도록 정규화한) 후 계산한 두 변량의 correlation이다.

 

상관계수 계산값 r은 항상 -1 ~ +1 사이의 값을 가지는데, r>0이면 양의 상관관계(X가 클수록 Y도 증가), r<0이면 음의 상관관계(X가 클수록 Y는 작은 값), r = 0이면 X, Y가 서로 아무 관계가 없다는 것을 의미한다. 그리고 상관계수를 계산할 때 두 변량이 항상 페어링(pairing)된다는 점도 잊어서는 안된다.

그림2. 상관계수의 해석

 

상관계수의 해석: (댓글을 읽다보니 상관계수의 의미에 대해 오해가 있을 수 있겠다 싶어서 내용을 덧붙입니다) 상관계수가 높다는 것은 두 대상의 경향성이 유사하다는 것이지 값이 같다는 의미는 아니다. 예를 들어, 예측값과 관측값의 상관계수를 계산한다고 했을 때, 예측된 값이 40, 50, 60일 때 실제 관측된 값이 90, 100, 110이라고 해 보자. 이 경우 예측값과 실제값은 상당한 오차가 있다. 하지만 상관계수를 계산해 보면 매우 높은 값을 갖는다. 상관계수는 평균과 스케일(표준편차)을 정규화한 후에 계산하기 때문이다. 따라서, 응용에 따라 경향성 뿐만 아니라 값 자체도 유사해야 한다면 식 (6)처럼 평균, 표준편차 차이를 제거하지 말고 원본 값에 대해 correlation을 계산해야 한다.

 

 

cross correlation과 correlation

 

이제 신호처리 분야로 돌아와 보자. 사회과학, 심리상담학, 경제학 등에서의 correlation 분석은 두 변인 사이의 상호 연관도를 분석하기 위한 것으로서 그 데이터 값들이 서로 페어링(pairing)된 것이 특징이다. 반면에, 신호처리 분야에서는 단지 두 신호가 얼마나 형태적으로 유사한지를 수치적으로 계산한 것이다. 그리고 두 신호를 서로 겹쳐서 계산하기 때문에 cross correlation이라 부르며 두 신호를 어떻게 겹치는지(displacement)에 따라서 pairing 관계가 달라진다.

 

연속된 두 신호 f(t), g(t)가 있을 때 cross correlation은 일반적으로 다음과 같이 정의된다 ($\tau$는 displacement 파라미터).

$$f \star g(\tau) = \int_{-\infty }^{\infty } f(t) g(t+\tau) dt \tag{9}$$

 

그리고 이를 이미지 프로세싱에 적용하면 영상신호 f(x,y)와 템플릿 t(x,y)에 대해 cross correlation을 아래와 같이 계산할 수 있다.

$$\frac{1}{n} \sum_{x,y} f(x,y)t(x,y) \tag{10}$$

 

만일 영상 밝기 차이를 보상한 후에 correlation을 구하고 싶다면 아래와 같이 계산할 수 있다 (zero-normalized cross correlation).

$$\frac{1}{n} \sum_{x,y} (f(x,y) - \bar{f})(t(x,y) - \bar{t}) \tag{11}$$

 

또는 아래와 같이 각각의 신호를 표준편차로 나누어 스케일 차이를 보상한 normalized cross correlation을 구할 수도 있다.

$$NCC = \frac{1}{n} \sum_{x, y} \frac{f(x, y)t(x, y)}{\sigma_f \sigma_t} \tag{12}$$

 

그리고 평균과 스케일을 모두 보상한 normalized cross covariance를 구할 수도 있다 (계산 결과는 -1 ~ +1 사이의 값을 갖는다).

$$\frac{1}{n} \sum_{x, y} \frac{(f(x, y) - \bar{f})(t(x, y) - \bar{t})}{\sigma_f \sigma_t} \tag{13}$$

 

한 가지 하고 싶은 말은 용어나 수식에 너무 집착하지 말자는 것이다. 정말 중요한 것은 그 '의미'이다. NCC 식은 식 (12)가 맞겠지만 식 (13)을 NCC로 사용해도 큰 문제는 없다. 의미만 알면 자신이 원하는 목적에 맞게 식을 골라 쓰거나 변형해서 사용할 수 있다.

 

마지막으로 신호처리 분야에 auto correlation이라는 게 있다. auto correlation은 서로 다른 두 신호가 아니라 자신과 자신을 correlation하는 것을 의미한다.

$$f \star f(\tau) = \int_{-\infty }^{\infty } f(t) f(t+\tau) dt \tag{14}$$

 

by 다크 프로그래머

'수학 이야기' 카테고리의 다른 글

상관계수와 cross correlation  (3) 2021.09.12
벡터 외적을 이용한 직선의 교점 구하기  (0) 2020.09.10
구구단 암산법  (4) 2018.10.08
아이와 수학 공부하기  (5) 2017.11.15
  • ㅇㅇ 2021.09.13 11:10 ADDR 수정/삭제 답글

    글 내용과는 연관이 반정도만 있을 것 같은데 한가지 궁금해서 여쭤봅니다 ㅠㅠ

    제가 연속형 예측값과 실제값을 correlation을 통해서 비교하여 '높으면 높을수록 모형을 사용하기 좋다고 결론내릴 수 있다'라고 고객사를 설득해보려고 하는데 이게 실제로 사용 예가 있던지, 아니면 의미가 있는 행동일까요? 희안한게 모형을 통해서 나온 예측값과 실제값을 비교하면 RMSE나 MAE 같은 수치들은 영 안좋게 나오는데 이게 시각화를 해 보면 얼추 상관계수 자체는 0.8 이상이 나오더라고요. 최종적으로 결과를 보정해서 사용하는 근거로 실제값과 예측값의 correlation이 높다, 라고 해보려고 하는데 이게 맞는 결론인지를 모르겠네요..

    • BlogIcon 다크pgmr 2021.09.13 12:29 신고 수정/삭제

      상관계수를 계산할 때에는 평균과 표준편차 차이를 제거한 후에 correlation을 계산합니다. 따라서 두 값이 차이가 크더라도 상관계수는 높게 나올 수 있습니다. 즉 경향성의 연관을 보는 것이지 값 자체가 동일한지를 판단하는 것이 아님을 구분하시면 좋을 것 같습니다. 예를 들어 실제값이 100인데 예측값이 40이 나왔습니다. 그리고 실제값이 110일때, 예측값이 50이 나왔다고 하겠습니다. 이 경우 예측 오차는 엄청 큽니다. 하지만 경향성은 유사하기 때문에 상관계수는 높게 나올 수 있습니다.
      상관계수의 의미에 대해서는 말씀드릴 수 있지만 이걸 이용한 결론에 대해서는 제가 판단할 수 있는 문제는 아닌 것 같습니다.

  • 지나가는 2021.10.07 12:20 ADDR 수정/삭제 답글

    이거에 대해서는 통계의 함정이라고 하는 논문이 있습니다. 아이스크림이 팔리는 양과 바다에 상어가 출몰하는 횟수가 비슷하다고해서 아이스크림이 많이 팔리면 상어가 많이 나타나는건가? 라는 말이죠 노벨 경제학상을 타신분의 이론이였는데 찾아보시면 이해하는데 큰 도움이 될 거 같아서 남겨드립니다.

OpenCV + CUDA 직접 빌드하기 (Windows/Linux 종합)

프로그래밍 2021. 6. 25. 15:38

최근에 opencv에 있는 dnn을 한번 써보려고 직접 소스를 받아서 빌드(build)해 보았다. 역시나 엄청난 삽질의 연속이고 할 때마다 이것 저것 해결책을 검색하느라 많은 시간을 소모한다 (삽질은 누구나 한다..).

 

그동안 opencv 빌드를 한두 번 해본 것이 아닌데도 매번 이렇다. 이게 다 앉았다 돌아서면 잊어버릴 나이인데도 기록을 잘 하지 않았기 때문이다. 이번 기회에 opencv 빌드에 필요한 내용들을 다시 한번 정리해 둔다. 먼저, windows 빌드부터 적고 linux 빌드는 끝에서 잠깐 설명한다.

 

1. Windows에서 OpenCV+CUDA 빌드

 

준비사항 (Requirements)

 

opencv git에는 주요 핵심 모듈들만 들어있기 때문에 opencv의 모든 기능을 사용하기 위해서는 opencv contrib도 같이 다운로드 받는다. cmake는 가급적 가장 최신 버전을 사용하는게 좋고, CUDA는 자신의 그래픽카드(GPU)를 지원하는 버전으로 다운로드 한다(https://en.wikipedia.org/wiki/CUDA#GPUs_supported에서 확인). cuDNN은 사용할 CUDA 버전에 맞는 것을 다운로드한다. VTK는 opencv의 viz 기능을 이용할 경우에만 다운로드 받는다.

 

CUDA와 cuDNN 설치

  • CUDA는 설치 파일만 실행하면 알아서 설치된다. 단, 중간에 설치 옵션을 물어보는데 나처럼 성질이 까칠한 사람은 빠른설치(권장) 옵션이 아닌 사용자 정의 옵션을 선택할 것이다. 이 때, 다른 것은 다 제외시키더라도 development, runtime, visual studio integration는 꼭 선택해 준다. 그리고 driver도 같이 선택해 주는게 좋겠다. (다른 것들은 설치 안해도 관계없다)
  • cuDNN은 설치파일이 아니고 단순 압축파일이다. 압축을 풀어서 CUDA가 설치된 폴더에 파일들만 복사해 주면 된다. cuDNN 압축을 풀면 bin, include, lib\x64 3개의 폴더가 나온다. 만일 CUDA가 설치된 폴더 위치가 CUDA_ROOT = "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.3"라 하면 bin에 있는 파일들은 CUDA_ROOT\bin 폴더에, include에 있는 파일들은 CUDA_ROOT\include 폴더에, 그리고 lib\x64에 있는 파일들은 CUDA_ROOT\lib\x64 폴더에 복사해 준다.

 

VTK 빌드

opencv를 빌드할 때 가장 피곤한 부분이 VTK를 빌드하는 것이다. VTK는 opencv의 viz를 사용할 때만 필요하기 때문에 viz를 사용하지 않는다면 바로 다음의 OpenCV 빌드로 넘어가면 된다. 개인적으로는 어쩔 수 없이 viz를 사용해야 하는데 opencv를 빌드할 때마다 VTK 쪽에서 문제를 일으킨다. 그리고 VTK는 빌드(컴파일+링크) 시간도 오래 걸린다. 빌드는 CMake(사용법은 https://darkpgmr.tistory.com/102 참조)로 하면 되는데, VTK 자체는 별 문제없이 빌드되지만 항상 opencv와 연결할 때 문제가 생긴다. CMake로 프로젝트를 생성할 때 BUILD_SHARED_LIBS를 제외하고 대부분 기본설정을 사용한다.

  • BUILD_SHARED_LIBS = OFF (체크 해제)

 

opencv에 static으로 링크할 생각이므로 BUILD_SHARED_LIBS는 OFF(체크해제)로 설정한다. 만일, 이 옵션을 ON시키면 수많은(약 120개) dll 파일들이 생성되고, opencv를 사용할 때 이 dll 파일들을 항상 같이 가지고 다녀야 하는 불상사가 발생한다.

 

OpenCV 빌드 (main + contrib을 하나의 world 파일로)

목표는 opencv의 모든 기능(contrib 포함)을 하나의 파일(opencv_worldxxx.dll, lib)로 빌드하는 것이다. 그 방법은 비교적 간단하다.

  • opencv_contrib\modules\ 밑에 있는 모든 내용을 opencv\modules\로 이동시킨다. --> 요즘은 이렇게 안해도 OPENCV_EXTRA_MODULES_PATH 만 설정하면 알아서 하나로 합쳐주는지는 잘 모르겠다..
  • opencv 빌드용 폴더(e.g. opencv_build)를 하나 만들고 cmake를 이용해서 필요한 설정을 한 후 프로젝트를 생성한다.
  • opencv_build\OpenCV.sln을 열어서 ALL_BUILD 프로젝트를 빌드한다.
  • 마지막으로 INSTALL 프로젝트를 빌드한다 (ALL_BUILD로 생성된 include, lib, dll 파일들을 한 곳에 모아주는 역할). 이 때, build만 하고 절대로 rebuild를 하면 안된다. 자신도 모르게 전체 프로젝트를 rebuild하는 불상사가 발생할 수 있다.

 

cmake gui에서 빌드 설정은 개인 취향껏 하면 되지만, 중요한 것들을 정리해 보면 다음과 같다.

 

기타 개인적으로 설정하는 옵션들 (나머지는 기본값 사용)

  • BUILD_PACKAGE = OFF
  • BUILD_PERF_TESTS = OFF
  • BUILD_TESTS = OFF
  • BUILD_opencv_apps = OFF
  • VTK_DIR = "..." (VTK의 빌드 폴더 root)

 

※ CPU_BASELINE

opencv cmake 옵션들 중 CPU_BASELINE은 사용할 CPU 명령어 셋을 선택하는 것으로서 SSE1 < SSE2 < SSE3 < SSE4 < AVX < AVX2 < AVX-512 순으로 향상된(빠른) 명령어 셋을 의미한다. 명령어 셋은 컴퓨터(CPU)에서 지원을 해야만 사용 가능하기 때문에 빌드한 opencv를 여러 컴퓨터에서 사용하고자 한다면 낮은 버전(SSE4 등)을 CPU_BASELINE으로 설정하고, 자신만 사용할 것이라면 자신 컴퓨터에서 지원하는 최대 명령어 셋을 CPU_BASELINE으로 설정하면 된다. CPU 버전 별 지원 명령어 셋은 https://en.wikipedia.org/wiki/Advanced_Vector_Extensions를 참조한다 (인텔의 경우 https://www.intel.co.kr/content/www/kr/ko/support/articles/000005779/processors.html). 요즘 대부분의 PC는 AVX2까지는 지원하기 때문에 공용 빌드의 경우에는 AVX나 AVX2 정도로 설정하면 적당할 듯 싶다.

 

※ CUDA_ARCH_BIN

CUDA_ARCH_BIN에는 사용할 그래픽카드(GPU)의 compute capability 값을 적어준다. GPU별 compute capability 값은 https://en.wikipedia.org/wiki/CUDA#GPUs_supported를 참조한다 (예를 들어, RTX 2080은 7.5, RTX 3080은 8.6).  빌드한 opencv를 여러 컴퓨터나 GPU에서 사용할 예정이라면 지원할 GPU compute capability 값을 모두 적어주면 된다. 단, 여러 값을 적을수록 빌드 시간은 엄청나게 늘어나기 때문에 필요한 정도만 적는 것이 좋다 (지금 몇 시간째 빌드 중인데 끝날 기미가 안보인다..)

 

※ 참고 사항 (빌드시간 단축)

WITH_CUDA, OPENCV_DNN_CUDA을 활성화시키면 opencv 빌드시간이 정말 오래 걸린다 (몇 시간씩). 따라서, CUDA 쪽을 사용할 것이 아니면 이 옵션은 OFF시키고 빌드하는 것이 좋다. 하지만, CUDA 쪽을 사용해야만 하는 경우 빌드시간을 조금이나마 줄이기 위해 참고할 만한 내용은 다음과 같다.

  • CUDA_ARCH_BIN 개수를 최소화
  • visual studio에서 Tools/Options/Projects and Solutions/Build and Run/maximum number of parallel project builds 값을 자신 컴퓨터 CPU의 논리 프로세서 수로 설정 (작업관리자 - 성능 탭에서 확인 가능)
  • visual studio에서 debug 버전과 release 버전을 하나씩 빌드 (Build/Batch Build... 기능을 이용해서 debug, release를 같이 빌드할 경우에는 빌드 시간이 증가하는 것으로 보임).
  • 디스크 입출력 속도가 중요하므로 SSD 등에서 빌드 (내 경우는 아예 전용 파티션을 하나 생성해서 사용함)

 

OpenCV + VTK 문제 해결 (OpenCV 4.5.2 기준)

VTK 때문에 opencv와 vtk를 몇 번을 다시 빌드하고 지우고 했는지 모르겠다. 현재 VTK 홈페이지에서는 VTK 9.0.1, 8.2.0, 7.1.1의 3가지 버전을 다운로드 받을 수 있으며 각 버전별 문제 및 해결 방법은 다음과 같다.

 

[VTK 9.0.1 + OpenCV]

opencv 4.5.2의 경우 cmake 설정창에서 VTK_DIR을 VTK 빌드 폴더로 설정하고 configure를 실행하면 TARGET opencv_viz를 찾을 수 없다는 CMake Error가 발생한다(get_property could not find TARGET opencv_viz). 단, 이 에러는 BUILD_opencv_world = ON 설정시에만 나타나고, OFF시에는 발생하지 않는다. 즉, opencv_viz를 개별 dll로 빌드할 경우에는 문제가 발생하지 않는다. BUILD_opencv_world = ON을 유지하고 싶은 경우에 한 가지 해결책은 opencv_world에서 viz만 제외시키는 것이다 (viz를 제외한 다른 모든 모듈들은 opencv_world로 통합).

if(BUILD_opencv_world AND OPENCV_MODULE_${the_module}_IS_PART_OF_WORLD AND NOT "${the_module}" STREQUAL "opencv_viz")

 

[VTK 8.2.0 + OpenCV]

opencv의 cmake 설정창에서 VTK_DIR을 VTK 빌드 폴더로 설정한 후 configure를 실행하면 "VTK is not found. Please set -DVTK_DIR in CMake to VTK build directory, or to VTK install subdirectory with VTKConfig.cmake file"라는 에러메시지와 함께 VTK 설정이 실패한다. 분명히 VTK 빌드 폴더를 제대로 지정해 주었음에도 이 에러는 계속 발생한다. cmake에서 VTK를 발견하지 못했다는 것인데, 인터넷 검색으로는 도무지 해결이 안되어서 결국 cmake 파일들의 내용을 모두 살펴보고 해결책을 찾았다.

  • 해결책: opencv 소스에서 opencv/cmake/OpenCVDetectVTK.cmake파일의 L1~L15를 아래와 같이 주석처리 (단, 9.x.x 대의 VTK 버전을 사용할 경우에는 주석 처리하면 안됨)
# VTK 9.0
#if(NOT VTK_FOUND)
#  find_package(VTK 9 QUIET NAMES vtk COMPONENTS
#    FiltersExtraction
#    FiltersSources
#    FiltersTexture
#    IOExport
#    IOGeometry
#    IOPLY
#    InteractionStyle
#    RenderingCore
#    RenderingLOD
#    RenderingOpenGL2
#    NO_MODULE)
#endif()

# VTK 6.x components
if(NOT VTK_FOUND)
  find_package(VTK QUIET COMPONENTS vtkInteractionStyle vtkRenderingLOD vtkIOPLY vtkFiltersTexture vtkRenderingFreeType vtkIOExport NO_MODULE)
  IF(VTK_FOUND)
    IF(VTK_RENDERING_BACKEND) #in vtk 7, the rendering backend is exported as a var.
      find_package(VTK QUIET COMPONENTS vtkRendering${VTK_RENDERING_BACKEND} vtkInteractionStyle vtkRenderingLOD vtkIOPLY vtkFiltersTexture vtkRenderingFreeType vtkIOExport vtkIOGeometry NO_MODULE)
    ELSE(VTK_RENDERING_BACKEND)
      find_package(VTK QUIET COMPONENTS vtkRenderingOpenGL vtkInteractionStyle vtkRenderingLOD vtkIOPLY vtkFiltersTexture vtkRenderingFreeType vtkIOExport NO_MODULE)
    ENDIF(VTK_RENDERING_BACKEND)
  ENDIF(VTK_FOUND)
endif()
  • 원인: opencv/cmake/OpenCVDetectVTK.cmake의 내용은 먼저 VTK 9.x.x를 찾고 실패하면 하위 버전 순서로 VTK를 찾는 것인데, VTK 9.x.x에 실패하면 사용자가 설정한 VTK_DIR 환경변수가 초기화가 되어서 이후 하위 버전 탐색이 정상 실행되지 않는 것으로 추측됨
  • VTK 7.1.1 버전에서도 동일한 문제가 발생하며 해결책도 동일함

 

2. Linux에서 OpenCV + CUDA 빌드 (한글 지원 포함)

앞서 설명한 내용들은 윈도우즈 빌드를 대상으로 하지만, 일부 내용들(cmake 설정 등)은 linux 빌드에도 동일하게 적용된다. 리눅스에서의 빌드는 shell script 형태로 간단하게 설명한다.

 

CUDA, cuDNN 설치

여기서는 여러 버전의 CUDA 라이브러리를 시스템에 같이 설치하는 것을 예로 한다 (필요할 때마다 버전을 바꿔가면서 사용할 수 있도록)

 

1. 시스템 그래픽 드라이버 설치 (최신버전으로 설치됨)

  • $ sudo apt update
  • $ sudo apt install cuda-drivers
  • 설치후 반드시 시스템 재부팅

 

2. CUDA 라이브러리 설치 (e.g. 10.2, 11.3를 같이 설치하는 경우)

  • $ sudo apt update
  • $ sudo apt install cuda-toolkit-10-2
  • $ sudo apt install cuda-toolkit-11-3

 

3. cuDNN 설치

먼저, CUDA 버전에 맞는 cuDNN 파일들을 다운로드한다 (https://developer.nvidia.com/rdp/cudnn-archive)

  • cudnn-10.2-linux-x64-v8.2.0.53.tgz
  • cudnn-11.3-linux-x64-v8.2.0.53.tgz

 

다음과 같은 설치 script 파일을 생성한 후 실행 (e.g. cudnn_install.sh)

  • $ chmod a+x cudnn_install.sh
  • $ ./cudnn_install.sh
#cudnn_install.sh
tar -xzvf cudnn-10.2-linux-x64-v8.2.0.53.tgz
sudo cp cuda/include/cudnn*.h /usr/local/cuda-10.2/include
sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda-10.2/lib64
sudo chmod a+r /usr/local/cuda-10.2/include/cudnn*.h /usr/local/cuda-10.2/lib64/libcudnn*
rm -rf cuda

tar -xzvf cudnn-11.3-linux-x64-v8.2.0.53.tgz
sudo cp cuda/include/cudnn*.h /usr/local/cuda-11.3/include
sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda-11.3/lib64
sudo chmod a+r /usr/local/cuda-11.3/include/cudnn*.h /usr/local/cuda-11.3/lib64/libcudnn*
rm -rf cuda

 

시스템의 default CUDA 설정 (10.2를 default로 설정할 경우)

  • $ cd /usr/local
  • $ sudo rm cuda
  • $ sudo ln -s cuda-10.2 cuda

 

시스템 환경변수 등록 (CUDA 라이브러리 탐색 순서 설정)

  • $ sudo vi /etc/profile.d/cuda.sh # cuda.sh 파일 생성
  • 아래 내용으로 해당 파일 내용을 생성 후 저장
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/usr/local/cuda-10.2/lib64:/usr/local/cuda-11.3/lib64

 

OpenCV + CUDA 빌드

CUDA가 준비되면 이제 opencv를 빌드해 보자. Linux에서의 opencv 빌드는 shell script 파일 형식으로 설명한다. 일부 내용은 자신에 맞게 수정한다.

# 참고한 사이트
# 1. https://linuxize.com/post/how-to-install-opencv-on-ubuntu-18-04/
# 2. https://webnautes.tistory.com/1030
# 3. https://kkokkal.tistory.com/1325

# 과거 설치된 opencv 버전이 있을 경우 제거
sudo apt purge libopencv* python-opencv
sudo apt autoremove # 위험할 수 있으니 내용을 잘 확인하고 실행 (opencv 관련만 삭제하는지..)

# update ubuntu system to latest
sudo apt update
#sudo apt upgrade # 시스템 전체를 업그레이드하는 것임 (필요한 경우만 실행)

# install relevant packages (libfreetype, libharfbuzz는 opencv에서 한글지원 위해서 설치)
sudo apt install build-essential cmake git pkg-config libqt4-dev \
libavcodec-dev libavformat-dev libswscale-dev libv4l-dev v4l-utils \
libxvidcore-dev libx264-dev libxine2-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev \
gfortran openexr libatlas-base-dev python3-dev python3-numpy \
libgl1-mesa-dri libqt4-opengl-dev libeigen3-dev \
libfreetype6-dev libharfbuzz-dev      # 한글 지원을 위해 필요

# download opencv
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git

# cmake setup (설정은 자신이 원하는대로 수정하거나 추가, 삭제)
cd ./opencv
mkdir build && cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D WITH_TBB=OFF \
-D WITH_IPP=OFF \
-D WITH_1394=OFF \
-D BUILD_WITH_DEBUG_INFO=OFF \
-D BUILD_DOCS=OFF \
-D BUILD_EXAMPLES=OFF \
-D BUILD_TESTS=OFF \
-D BUILD_PERF_TESTS=OFF \
-D WITH_CUDA=ON \
-D WITH_CUDNN=ON \
-D OPENCV_DNN_CUDA=ON \
-D CUDA_FAST_MATH=ON \
-D CUDA_ARCH_BIN=7.5 \    # 자신 GPU의 compute capability 값
-D WITH_CUBLAS=ON \
-D WITH_CUFFT=ON \

-D WITH_QT=ON \
-D WITH_GTK=OFF \
-D WITH_OPENGL=ON \
-D WITH_V4L=ON \
-D WITH_FFMPEG=ON \
-D WITH_XINE=ON \
-D BUILD_NEW_PYTHON_SUPPORT=ON \
-D INSTALL_C_EXAMPLES=OFF \
-D INSTALL_PYTHON_EXAMPLES=OFF \
-D OPENCV_GENERATE_PKGCONFIG=ON \
-D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
-D OPENCV_ENABLE_NONFREE=ON \
-D BUILD_EXAMPLES=OFF ..

# check number of cores of your system
nproc # 자신 시스템의 코어 수 확인

# build (modify the core number '12' after option -j accordingly)
make -j12 # 자신 시스템 코어 수에 맞게 -j 다음의 숫자를 변경

# install
sudo make install

# check if it is installed successfully
pkg-config --modversion opencv4

설정값 중에서, GPU별 compute capability 값은 https://en.wikipedia.org/wiki/CUDA#GPUs_supported를 참조한다 (예를 들어, RTX 2080은 7.5, RTX 3080은 8.6).

 

위 내용을 shell script 파일로 만들어서 아래와 같이 실행하면 GPU 지원 opencv가 설치된다 (e.g. opencv_install.sh라는 이름으로 스크립트를 생성했을 경우). 만일 CUDA를 사용하지 않을 경우에는 설정에서 WITH_CUDA부터 WITH_CUFFT까지를 모두 삭제하거나 OFF로 바꾼다.

$ nproc					# 자신의 시스템 코어 수 확인
$ nvidia-smi				# 자신의 GPU 확인
$ vi opencv_install.sh			# opencv_install.sh 내용 편집
 --> make -j12에서 12를 자신의 시스템 코어 수로 변경
 --> CUDA_ARCH_BIN 값을 자신 GPU의 compute capability 값으로 변경
 --> 기타 필요한 설정 등을 변경
$ chmod a+x opencv_install.sh		# 실행 가능한 파일로 변경
$ ./opencv_install.sh			# 스크립트 실행
$ sudo vi /etc/ld.so.conf.d/opencv.conf	# opencv conf 파일 생성
 --> /usr/local/lib 라인 추가 (opencv 설치된 라이브러리 폴더를 알려주는 것)
$ sudo ldconfig -v			# 생성한 opencv conf 반영

 

OpenCV에서 이미지에 한글 출력하기

opencv에서는 기본적으로 cv::putText로 이미지에 한글을 출력하면 글자가 깨진다. 하지만, 위와 같이 libfreetype6-dev, libharfbuzz-dev를 먼저 설치한 후에 opencv를 빌드하면 한글 출력이 가능해진다. 단, linux 빌드의 경우만 해당되며 윈도우즈에서도 한글 출력되도록 빌드해 보고자 했지만 결국 실패하였다. 어쨌든, 한글 출력 사용법 예제는 다음과 같다.

 

먼저, 한글 폰트를 다운로드해서 시스템에 저장한다. (e.g. test/font/)

  • 한글폰트 다운로드: font_korean.zip
  • test/font/gulim.ttf
  • test/font/batang.ttf

 

예제 코드(참고사이트: https://kkokkal.tistory.com/1325):

#include "opencv2/freetype.hpp"
int main(void)
{
    Mat src = imread("sample.bmp", IMREAD_COLOR);

    // FreeType2 객체 생성
    cv::Ptr<cv::freetype::FreeType2> ft2 = cv::freetype::createFreeType2();

    // 글꼴 불러오기
    ft2->loadFontData("test/font/gulim.ttf", 0);

    // 문자열 출력
    ft2->putText(src, u8"Hello?", Point(50, 50), 50, Scalar(255, 255, 255), -1, LINE_AA, false);
    ft2->putText(src, u8"안녕하세요!", Point(50, 120), 50, Scalar(0, 255, 255), -1, LINE_AA, false);

    imshow("src", src);
    waitKey(0);

    return 0;
}

 

☞ 후기

사실 이런 괜한 삽질을 한 이유는 최근 yolo 사이트를 둘러보다가 opencv dnn을 이용해서 yolo를 돌리면 속도가 크게 증가한다는 표를 봤기 때문이다(608x608 이미지의 경우 darknet은 53fps, opencv fp16은 115fps라고 나온다). 직접 빌드해서 돌려보니 내 경우는 opencv가 좀더 빠른 것은 맞지만 그 정도는 아니다 (opencv의 경우, image blob 생성 및 non-max-suppression 시간 등은 제외하고 순수 network forward 시간만 측정한 것 같다). 실제 돌려보면 이미지 입력부터 결과 출력까지 608x608 기준으로 darknet은 42 fps, opencv fp16은 54 fps 정도가 나온다. 416x416 기준으로는 darknet은 75 fps, opencv fp16도 75 fps로 동일하게 나온다. 고해상도로 갈수록 속도가 빨라지는 것 같다. 어쨌든 빨라졌으니 좋은 것이긴 한데, 문제는 opencv dnn 버전은 yolov3, yolov4로는 동작하지만 yolov4-csp 등에서는 동작하지 않는다는 점이다. 윈도우즈 버전으로 테스트한 것이기 때문에 linux에서는 결과가 다를지도 모르겠다. 참고로 fp16은 floating point 데이터 표현 및 연산을 16비트로 처리한다는 의미이다(기본은 32비트). opencv dnn을 이용한 yolo 사용은 YashasSamaga/main.cpp를 참고한다.

darknet yolo opencv dnn fp16
다양한 yolo 버전을 모두 지원(yolo, yolo-csp, yolo-mesh 등)
별도로 darknet을 빌드해서 사용해야 함

yolov3, ylov4 등 기본 버전만 지원
darknet 빌드 없이 opencv 자체에서 yolo 수행
dnn빌드로 opencv dll 파일의 크기가 커짐 (428MB)
속도가 조금 더 빠름 (정확도는 darknet yolo와 동일)

 

by 다크 프로그래머

'프로그래밍' 카테고리의 다른 글

OpenCV + CUDA 직접 빌드하기 (Windows/Linux 종합)  (12) 2021.06.25
Matlab 핸드북 (참고용)  (0) 2017.11.21
프로그래밍 공부 방법  (16) 2014.07.31
  • 지나가던나그네 2021.07.09 11:27 ADDR 수정/삭제 답글

    안녕하세요 다크님! 지난번 이후로 계속 잘 보고 있습니다.
    뭔가 이번글은 저도 굉장히 도움이 되는 글이네요...ㅎㅎ
    저도 yolov4를 쓰고 있는데 opencv dnn으로 yolo를 쓰면 약간이나마 속도가 빨라진다는 정보는 굉장히 필요한 정보였습니다.

    한가지 여쭤보고 싶은게 있습니다.
    OpenCV viz를 사용하신다고 들었습니다. 저는 아직 한번도 viz를 써본적이 없습니다. 3차원 데이터를 이용시에는 주로 PCL을 사용하여 작업 및 viewing을 했기 때문에 viz의 성능에대해서 잘 모르는데요...혹시 PCl과 비교했을때 성능적인 부분에서 어느정도 차이가 있나요? 예를들면 버벅임이라던가..UI라던가...그런 측면에서요. 파일(예를들면 ply, las 등)이 생성되면 주로 현재는 CloudCompare나 Meshlab으로 확인했었습니다.

    참고로 예전에는 OpenGL로도 해봤었는데 PCL과 비교했을때 GL보다는 PCL이 조금 더 좋다고 생각해서 PCL로 통합해서 썼었습니다. viz는...해본적이 없네요...

    • BlogIcon 다크pgmr 2021.07.10 23:30 신고 수정/삭제

      안녕하세요. 제가 viz를 쓰는 것은 3d 작업용은 아니고 카메라 시점이나 간단한 도형들을 이쁘게 보여주기 위함 정도입니다. 저는 pcl은 써보지 않아서 잘 모릅니다.

  • 메모광 2021.07.13 14:21 ADDR 수정/삭제 답글

    좋은 정보 공유 언제나 감사합니다!

  • 여몽 2021.07.17 12:28 ADDR 수정/삭제 답글

    다크님 대학원 학생입니다.
    주성분 분석 관련 올리신 글 잘 봤습니다.
    관련해서 전화로나 메일로 질문 좀 드려도 될까요?
    부탁드립니다.

  • 공대생 2021.08.09 00:05 ADDR 수정/삭제 답글

    진짜 검색하면 이사이트에 다 있네요 ㅎㅎ 심지어 2013년 포스트... 도데체 얼마나 앞서는 것입니까? 최신동향까지 올려주시니 감사할 따름 입니다. 늘 잘 보고 있습니다.

  • BlogIcon 야니 2021.08.25 10:49 ADDR 수정/삭제 답글

    오랜만입니다. 몇년전에 과속카메라 만든다고 했던 사람입니다.
    거리측정까지는 했습니다. 그런데. 확산이 안되는군요!
    다른분들이 먼저 열심히 실패를 해버렸습니다. ㅠ.ㅠ

    • BlogIcon 다크pgmr 2021.08.26 09:22 신고 수정/삭제

      안녕하세요, 잘 기억하고 있습니다.
      열심히 만드셨는데 보급이 잘 안된다니 안타깝네요. 그래도 기술에 성공하신 것은 축하드립니다.

  • 김강사 2021.09.26 17:39 ADDR 수정/삭제 답글

    안녕하세요? YOLO 공부해보려고 검색 했다가 여기 까지 왔습니다.
    CUDA, cuDNN은 어렵지 않게 설치 했습니다. 현재 C#에서 OpenCV 사용 하고 있는데. OpenCV를 따로 라이브러리를 만들어서 사용해야 되나요? 링크 해주신곳의 OpenCV를 Zip 화일로 받아서 CMake 에서 DLL로 만들어서 사용 하면 되는건지? 그냥 지금 처럼 C#에서 OpenCV 를 불러 와서 그냥 사용 해도 되는건지 이해가 잘 안됩니다. 도움 부탁 드립니다.

    • BlogIcon 다크pgmr 2021.09.26 19:42 신고 수정/삭제

      저는 C#은 알지 못합니다. C++에서는 OpenCV 사이트에서 제공하는 기본 빌드는 CUDA를 지원하지 않기 때문에 부가적인 기능(GPU 사용, dnn 등의 extra 모듈 사용 등)을 사용하기 위해서 별도로 빌드하는 것입니다. YOLO가 목적이시라면 굳이 OpenCV를 직접 빌드할 필요는 없습니다. OpenCV와 무관하게 YOLO만 빌드해서 사용하면 됩니다.

  • 김강사 2021.09.27 10:34 ADDR 수정/삭제 답글

    YOLO라는걸 배워야 된다고 해서 이제 막 용어 검색해서 알아보고 다운 받을거 받고 하는중인데 접근이 쉽지 않네요 ^.^ 만일 OpenCV를 라이브러리고 해야 된다면 님이 링크 올리신 곳의 자료를 다운 받아서 CMake로 만드렁 주면 되나요?

    • BlogIcon 다크pgmr 2021.09.27 15:45 신고 수정/삭제

      김강사님, YOLO는 opencv와는 무관해서 YOLO 때문에 opencv로 빌드하실 필요는 없습니다. https://github.com/AlexeyAB/darknet/에 가시면 yolo 설치 및 사용법에 대한 자세한 설명이 나오니, 여길 참고하시면 좋을 것 같습니다.

  • 김강사 2021.09.27 20:46 ADDR 수정/삭제 답글

    오늘 다크님이 만드신 글 보면서 CMake로 OpenCV 라이브러리 하는거 성공 했습니다.

    이제 알려 주신거 한번 더 찾아 보면 될거 같아요. 제가 하고자 하는건 카메라로 꽃을 인식해서 화면 정 중앙에 오게 기구를 움직이게 해보고 싶은겁니다. 아마 OpenCV 도 필요 할거라고 해서 같이 해보는 중입니다. 모터 돌리는것도 해야 되고 할게 많은 프로젝트 입니다 ^.^