이번 포스팅은 k-means 알고리즘에 대해 알아보고
OpenCV에서 사용하는 k-means Clustering에 대해 알아보겠습니다.
포스팅 관련 내용은 아래에서 출처를 얻어 사용하였습니다.
https://docs.opencv.org/4.5.2/d1/d5c/tutorial_py_kmeans_opencv.html
k-평균(k-means) 알고리즘
- k-means 알고리즘은 대표적인 Clustering 알고리즘 중 하나입니다.
1) 각 군집은 하나의 중심(centroid)을 가집니다.
2) 각 개체는 가장 가까운 중심에 할당됩니다.
3) 같은 중심에 할당된 개체들이 모여 하나의 군집을 형성하는 알고리즘입니다.
- k(군집 수)를 정해야 알고리즘을 실행할 수 있으며 이는 k가 하이퍼파라미터라는 의미입니다.
이를 수식으로 나타내면 아래와 같습니다.
OpenCV 에서 k-means 알고리즘 동작
- OpenCV에서 k-means Clustering을 사용하기 위한 함수는 cv2.kmeans입니다.
- cv2.kmeans(data, K, bestLabels, criteria, attempts, flags, centers=None)
입력 매개변수
- samples : np.float32 데이터 타입이 여야 하며, 각 피쳐(Feature)는 단일 열(Column)으로 저장되어 있어야 합니다.
- nclusters(K) : 군집화할 개수
- criteria : 반복을 종료할 기준입니다. 이 기준이 충족되면 알고리즘의 반복은 중지됩니다. 실제로는 3개의 매개변수로 구성된 튜플이어야 하며, (type, max_iter, epsilon)입니다.
- type : 종료 기준의 유형이며 3개의 플래그가 있습니다.
- - cv2.TERM_CRITERIA_EPS : 주어진 정확도(epsilon 인자)에 도달하면 반복을 중단하고, -
- - cv2.TERM_CRITERIA_MAX_ITER : max_iter 인자에 지정된 횟수만큼 반복하고 중단합니다.
- - cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER : 위의 조건이 하나라도 만족하면 반복 중지
- max_iter : 최대 반복할 횟수(정수형 타입)
- epsilon : 필요한 정확도
- attempts : 다른 초기 라벨을 사용하여 알고리즘이 실행되는 횟수를 지정하는 플래그, 최상의 compactness를 생성하는 라벨을 반환합니다. 이 compactness는 출력으로 반환
- flags : 초기값을 잡을 중심에 대한 플래그로써 cv2.KMEANS_PP_CENTERS와 cv2.KMEANS_RANDOM_CENTERS 중 하나가 사용됩니다.
출력 매개변수
1. compactness : 각 점에서 해당 중심까지의 거리 제곱의 합
2. label : 라벨에 대한 배열, 0, 1로 표시된 레이블 배열
3. Centers : 클리스터의 중심이 저장된 배열
OpenCV에서 어떤 함수로 k-means 알고리즘이 사용되는지 알아보았고 이제 어떻게 사용되는지 확인해봅시다.
1. 하나의 특징(Feature)을 가지는 데이터
1차원 같이 하나의 기능만 있는 데이터 집합이 있다고 가정하겠습니다.
예를 들면 사람들의 키만 가지고 티셔츠의 크기를 결정하는 문제입니다.
먼저 데이터를 생성하고 matplotlib를 사용하여 그래프를 표시했습니다.
import numpy as np
import cv2
import matplotlib.pyplot as plt
x = np.random.randint(25,100,25)
y = np.random.randint(175,255,25)
z = np.hstack((x,y))
z = z.reshape((50,1))
z = np.float32(z)
plt.hist(z,256,[0,256]),plt.show()
x와 y는 ndarray 타입의 랜덤 한 int형 숫자를 각각 범위에 맞게 25개씩 만들게 했고
만들어진 x와 y로 z를 만들었는데, z는 (50, 1) 크기의 float32형의 배열로 만들었습니다.
왜냐하면 cv2.kmeans의 입력 데이터는 float32형 이어야 하며 단일 컬럼(열)의 데이터여야 하기 때문입니다.
결과)
x와 y의 범위의 랜덤한 int형 숫자들이 표현된 것을 볼 수 있고
랜덤 했기 때문에 2개가 있는 숫자도 있다! 라고 보면 될 것 같습니다.
이제 cv2.kmeans 함수를 사용하여 알고리즘을 적용해봅시다.
알고리즘 종료에 대한 기존 조건을 정해야 하며 저는 10번의 반복과 epsilon(정확도)를 1.0으로 정했으며
flags로 군집화 중심을 랜덤 하게 잡게 했습니다.
# Define criteria = ( type, max_iter = 10 , epsilon = 1.0 )
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
flags = cv2.KMEANS_RANDOM_CENTERS
compactness, labels, centers = cv2.kmeans(z, 2, None, criteria, 10, flags)
앞서 말했던 것처럼 출력 매개변수로 compactness, labels, centers를 반환 값으로 받으며
print(centers)
centers가 어떤 출력이 나오는지 궁금하여 확인했습니다.
랜덤 하게 center를 잡게 하였으니 실행마다 다르게 나오는 것을 확인할 수 있었습니다.
X = z[labels == 0]
Y = z[labels == 1]
plt.hist(X, 256, [0,256], color = 'r')
plt.hist(Y, 256, [0,256], color = 'b')
plt.hist(centers, 32, [0,256], color = 'y')
plt.show()
labels의 경우 각 요소에 대해 0과 1로 분류되었기 때문에 라벨 값을 기반으로 X와 Y로 나누었고
그 결과를 X는 빨간색, Y는 파란색으로 그리고 중심이 되는 centers를 노란색으로 표시했습니다.
결과)
2. 여러 개의 특징(Feature)을 가지는 데이터
- 이제는 키와 몸무게, 두 가지 특성을 사용하겠습니다! 앞의 예에서 하나의 특징만으로는 단일 열 벡터로 만들었습니다. 각 특징은 열로 구성되어 있어야 합니다. 기능은 열, 입력 데이터 샘플은 행에 해당됩니다.
예를 들어 50명의 키와 몸무게를 50x2의 행열로 테스트 데이터를 설정했습니다.
이미지를 확인하시면 이해가 쉽게 됩니다.
코드)
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
X = np.random.randint(25, 50, (25,2))
Y = np.random.randint(60, 85, (25,2))
Z = np.vstack((X,Y))
# print(X)
# np.float32로 변환
Z = np.float32(Z)
# print(Z.shape)
# kmeans 매개변수 정의
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret, label, center=cv.kmeans(Z, 2, None, criteria, 10, cv.KMEANS_RANDOM_CENTERS)
# 데이터 분리
A = Z[label.ravel()==0]
B = Z[label.ravel()==1]
# 데이터 가시화
plt.scatter(A[:,0], A[:,1])
plt.scatter(B[:,0], B[:,1], c = 'r')
plt.scatter(center[:,0], center[:,1], s = 80, c = 'y', marker = 's')
plt.xlabel('Height')
plt.ylabel('Weight')
plt.show()
앞에서 했던 것과 별 다른 건 없지만 X, Y를 정의할 때 (25, 2)로 만들어 아래의 그림처럼 결과가 나오게 되며
X와 Y를 합쳐 Z를 만들게 됩니다.
결과)
3. 색상 양자화(Color Quantization)
색상 양자화란 이미지에서 색상 수를 줄이는 처리를 말합니다. 이 처리를 하는 이유는 메모리를 줄여 연산 속도를 빠르게 하기 위해서 사용합니다. 지금은 k-means Clustering을 사용해 색상 양자화를 해보겠습니다.
앞서 했던 내용과 다른 점은 바로 이미지의 R, G, B입니다. 이미지의 RGB 데이터를 Mx3 배열로 만들어야 하는데
M은 이미지의 화소 개수입니다. 클러스터링 이후 중심점(RGB) 값으로 군집되는 화소 값으로 변경합니다.
그리고 Mx3 배열을 원래 이미지 크기로 변경해야 합니다.
코드)
import numpy as np
import cv2
img = cv2.imread('./images/clustering.jpg')
img = cv2.resize(img, (480, 480))
z = img.reshape((-1, 3))
z = np.float32(z)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
k3 = 3
k6 = 6
k9 = 9
ret3, label3, center3 = cv2.kmeans(z, k3, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
ret6, label6, center6 = cv2.kmeans(z, k6, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
ret9, label9, center9 = cv2.kmeans(z, k9, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
center3 = np.uint8(center3)
res3 = center3[label3.flatten()]
res3 = res3.reshape(img.shape)
center6 = np.uint8(center6)
res6 = center6[label6.flatten()]
res6 = res6.reshape(img.shape)
center9 = np.uint8(center9)
res9 = center9[label9.flatten()]
res9 = res9.reshape(img.shape)
cv2.imshow('Original', img)
cv2.imshow('K = 3', res3)
cv2.imshow('K = 6', res6)
cv2.imshow('K = 9', res9)
cv2.waitKey(0)
cv2.destroyAllWindows()
k 값에 따라 비교하기 위해 각각 따로 표시해서 코드를 작성했습니다.
결과)
k 값이 늘어가면서 색의 구별이 더 잘되는 것을 볼 수 있었습니다.
k-means 알고리즘에 대해 간략하게 정리했고
이 알고리즘이 OpenCV에서 어떻게 쓰이는지에 대해 공부했습니다.
다음 포스팅으로 찾아오겠습니다
감사합니다 :)
'영상처리' 카테고리의 다른 글
Python - OpenCV (10) : Otsu's Binarization (0) | 2021.07.15 |
---|---|
Python - OpenCV (9) : Image Thresholding (0) | 2021.07.14 |
Python - OpenCV (7) : Background Subtraction (0) | 2021.07.11 |
Python - OpenCV (6) : Image Inpainting (0) | 2021.07.09 |
Python - OpenCV (5) : 허프 변환 (Hough Transformation) (0) | 2021.05.25 |