이번 포스팅은 SVM 알고리즘에 대해 알아보고 OpenCV에서 어떻게 사용하는지 알아보겠습니다.
포스팅 관련 내용은 아래에서 출처를 얻어 사용하였습니다.
https://docs.opencv.org/4.4.0/d1/d73/tutorial_introduction_to_svm.html
GOAL
- SVM이 무엇인지?
- OpenCV에서 SVM의 사용 방법
Theory
- What is SVM?
- SVM(Support Vector Machine)은 레이블이 지정된 훈련 데이터가 주어지면 최적으로 데이터들을 분류하게 만드는 초평면(hyperplane) 출력 알고리즘입니다.
- 빨간색과 파란색 두 클래스를 선형으로 분리 가능한 직선을 구하는 문제가 있을 때 위 그림처럼 다양한 선이 있음을 알 수 있습니다. 저 선 중 어떤 것이 최적일까요? 선의 가치를 정하는 기준은 직관적으로 정의할 수 있습니다!
최적의 선은 모든 데이터로부터 가장 멀리 떨어져서 분류하는 선이라고 할 수 있습니다.
왜일까요? 왜냐면 입력 데이터에 노이즈가 있을 수 도 있기 때문이고 가장 멀리 떨어진 선을 사용하는 것은 그만큼 노이즈에 민감하지 않다는 의미입니다.
결국은 SVM은 이러한 직선 또는 평면, 즉 초평면을 얻는 것입니다. 아래의 그림에 있는 굵은 선이 바로 초평면입니다.
- 그림에 보이는 Optimal hyperplane(최적의 초평면)을 찾기 위해서는 훈련 데이터가 필요합니다.
보이는 전체 데이터를 훈련 데이터로 사용할까요? 상대 클래스와 가까운 데이터만으로도 충분합니다.
위 그림에서는 하나의 파란색 원과 두 개의 빨간색 사각형으로 훈련했습니다.
이 훈련 데이터를 Support Vectors ( 지지 벡터 )라고 하며, 이 데이터를 통과하는, 즉 그림에서의 점선을 Support Planes ( 지지 평면 )이라고 합니다.
- Support Planes ( 지지 평면 )을 구하기 위해 수학적인 모델을 생각해봅시다.
f(x)의 식은 빨간색, 파란색 데이터를 지나는 점선의 식이며
B0는 bias이며 BT는 가중치 벡터(Weight Vector)입니다. bias는 분리하는 경계의 위치를 결정하며 Weight Vector는 분리하는 경계의 방향을 결정합니다. 최종적으로 Optimal hyperplane은 이러한 평면 사이의 중앙을 지나는 것으로 정의될 수 있고 f(x) = 0으로 표현할 수 있습니다.
support vectors의 거리 식에서 2배를 한 것이 위 그림에서의 Margin이며 이 공간이 최대가 되어 그림에서의 Maximum margin을 표현한 것입니다.
Using SVM with OpenCV
- 이제 OpenCV에서 어떻게 SVM을 사용하는지 알아봅시다.
OpenCV에서는 (1) 객체 생성 -> (2) SVM 타입 지정 -> (3) SVM 커널 지정 -> (4) SVM 학습 -> (5) 예측(predict) 순으로 SVM이 실행되며 각 단계마다 사용하는 함수를 알아보겠습니다.
1) SVM 객체 생성
- cv2.ml.SVM_crate()
출력 매개변수
- retval : cv2.ml_SVM 객체
2) SVM 타입 지정
- cv2.ml_SVM.setType(type)
입력 매개변수
- type : SVM 종류 지정, SVM.ml.SVM_으로 시작하는 상수
- cv2.ml.SVM_C_SVC : C-SV 분류, 일반적인 n 클래스 분류에 사용 ( n >= 2 )
- cv2.ml.SVM_NU_SVC : v-SV 분류, C_SVC와 비슷하지만 NU 값이 0 ~ 1로 정규화되어있음
- cv2.ml.SVM_ONE_CLASS : 1-분류 SVM, 데이터 분포 측정에 사용
- cv2.ml.SVM_EPS_SVR : E-SV 회귀
- cv2.ml.SVM_NU_SVR : v-SV 회귀
3) SVM 커널 지정
- cv2.ml_SVM.setKernel(kernelType)
입력 매개변수
- kernelType : 커널 함수 종류 지정, cv2_ml.SVM_으로 시작
4) SVM 학습
- cv2.ml.SVM.train(samples, layout, responses)
입력 매개변수
- samples : 학습 데이터 행렬, dtype = numpy.float32
- layout : 학습 데이터 배치 방법
- cv2.ROW_SAMPLE
- cv2.COL_SAMPLE
- responses : 각 학습 데이터에 대응되는 label 벡터
출력 매개변수
- retval : 학습이 정상적으로 완료되면 True
Code
import cv2
import numpy as np
# 훈련 데이터 설정
labels = np.array([1, -1, -1, -1])
trainingData = np.matrix([[501, 10], [255, 10], [501, 255], [10, 501]], dtype = np.float32)
# SVM 훈련
svm = cv2.ml.SVM_create()
svm.setType(cv2.ml.SVM_C_SVC)
svm.setKernel(cv2.ml.SVM_LINEAR)
svm.setTermCriteria((cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-6))
svm.train(trainingData, cv2.ml.ROW_SAMPLE, labels)
# 시각적 표현을 위한 데이터
width = 512
height = 512
image = np.zeros((height, width, 3), dtype = np.uint8)
# SVM 출력으로 나오는 영역을 표시
green = (0, 255, 0)
blue = (255, 0, 0)
for i in range(image.shape[0]):
for j in range(image.shape[0]):
sampleMat = np.matrix([[j, i]], dtype = np.float32)
response = svm.predict(sampleMat)[1]
if response == 1:
image[i, j] = green
elif response == -1 :
image[i, j] = blue
# 훈련 데이터 표시
thickness = -1
cv2.circle(image, (501, 10), 5, ( 0, 0, 0), thickness)
cv2.circle(image, (255, 10), 5, ( 255, 255, 255), thickness)
cv2.circle(image, (501, 255), 5, ( 255, 255, 255), thickness)
cv2.circle(image, (10, 501), 5, ( 255, 255, 255), thickness)
# 지지 벡터 표시
thickness = 2
sv = svm.getUncompressedSupportVectors()
for i in range(sv.shape[0]):
cv2.circle(image, (int(sv[i,0]), int(sv[i, 1])), 6, (128, 128, 128), thickness)
cv2.imshow('SVM Example', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
결과)
이번 포스팅은 선형으로 분리될 수 있는 데이터만으로 다뤘습니다.
다음 포스팅은 비선형으로 구분해야 하는 데이터들을 어떻게 처리하는지 대해 정리하겠습니다.
틀린 점이나 질문이 있으시면 댓글로 남겨주세요!
감사합니다 :)
'영상처리' 카테고리의 다른 글
Python - OpenCV (15) : Face Detection (0) | 2021.08.03 |
---|---|
Python - OpenCV (14) : Image Pyramid (0) | 2021.07.26 |
Python - OpenCV (12) : Segmentation with Watershed (0) | 2021.07.19 |
Python - OpenCV (11) : Grayscale 함수 비교 (2) | 2021.07.16 |
Python - OpenCV (10) : Otsu's Binarization (0) | 2021.07.15 |