이번에도 아주 훌륭하고 멋진 포스트 하나를 번역해보려 합니다. 바로 sklearn 모듈을 사용한 포스트 입니다.

제 홈페이지의 가수 검색 기능을 이 방법을 이용하여 만들었습니다.

그 가수의 사진에서 주된 색상을 얻어 다양한 색상의 표가 자동으로 만들어지는 것입니다.😎

가수 사진의 색을 추출하여 자동으로 표 색상이 구성됩니다!

그렇게 어렵지 않고 쉽고 재밌는 포스트입니다!

 

towardsdatascience.com/finding-most-common-colors-in-python-47ea0767a06a

 

Finding Most Common Colors in Python

A standard, yet crucial, image processing task.

towardsdatascience.com

파이썬을 이용하여 가장 주가 되는 색상찾기

(이미지 처리 작업에 있어 일반적인, 하지만 중요한, 기능)

 

만약 우리가 이미지나 어떤 사물의 가장 주가 되는 색상을 안다면 이미지를 처리하는 몇가지 방법을 적용할 수 있습니다. 예를 들어 농장에서 우리는 과일의 숙성도를 판단해야 합니다. 오렌지나 딸기같은. 우리는 단순하게 과일의 색상이 미리 결정된 범위안에 드는지 확인하고 그것이 잘 여물었는지 썩었는지 아직 덜 자랐는지를 알 수 있습니다.

 

통상적으로 우리는 파이썬과 여전이 단순하지만 강력한 라이브러리인 Numpy, Matplotlib, 그리고 OpenCV를 이용하여 이러한 경우를 해결해 나갈 수 있습니다. 저는 여기서 이러한 패키지를 이용하여 가장 주된 색상을 찾는 몇가지 방법을 소개하고자 합니다.

 

Step1 - Load Packages

1
2
3
4
5
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
import PIL
%matplotlib inline
cs

우리는 여기서 기본적인 패키지를 불러오겠습니다. 그리고 더 많은 패키지를 진행하면서 불러올 겁니다. 또한 우리는 Jupyter를 통해 프로그래밍을 하고 있기 때문에 %matplotlib inline을 넣는 것을 잊지 맙시다.

 

Step2 -  Load and show sample images

 

본 튜토리얼에서 우리는 두 이미지를 계속 비교하며 보겠습니다. 그래서 일단 helper 함수를 만들어봅시다.

1
2
3
4
5
6
7
8
def show_img_compar(img_1, img_2 ):
    f, ax = plt.subplots(12, figsize=(10,10))
    ax[0].imshow(img_1)
    ax[1].imshow(img_2)
    ax[0].axis('off'#hide the axis
    ax[1].axis('off')
    f.tight_layout()
    plt.show()
cs

 

그 다음 우리는 샘플 이미지를 불러와서 위의 함수를 적용합니다.

1
2
3
4
5
6
7
8
9
10
11
img = cv.imread("img/img_1.jpg")
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
img_2 = cv.imread("img/img_2.jpg")
img_2 = cv.cvtColor(img_2, cv.COLOR_BGR2RGB)
 
dim = (500300)
# resize image
img = cv.resize(img, dim, interpolation = cv.INTER_AREA)
img_2 = cv.resize(img_2, dim, interpolation = cv.INTER_AREA)
 
show_img_compar(img, img_2)
cs

이제 준비되었습니다. 이제는 위 이미지들에서 가장 주가 되는 색상을 찾는 시간입니다.

 

Method1 - Average

 

첫번째 방법은 가장 쉬운(하지만 가장 비효율적인), 단순하게 평균 픽셀 값을 찾는 것입니다.

1
2
3
4
5
6
7
8
img_temp = img.copy()
img_temp[:,:,0], img_temp[:,:,1], img_temp[:,:,2= np.average(img, axis=(0,1))
 
img_temp_2 = img_2.copy()
img_temp_2[:,:,0], img_temp_2[:,:,1], img_temp_2[:,:,2= np.average(img_2, axis=(0,1))
 
show_img_compar(img, img_temp)
show_img_compar(img_2, img_temp_2)
cs

 

numpy의 average 함수를 이용, 우리는 쉽게 열과 너비를 통한 평균 픽셀값을 얻을 수 있습니다.- axis=(0,1)

평균 함수를 이용하게 되면 결과를 호도하고 또 부정확한 결과를 얻는다는 것을 알게 되었습니다. 가장 주된 색상들과는 거리가 조금 멀군요. 이러한 결과의 이유는 평균은 모든 픽셀값의 평균을 고려하기 때문입니다. 특히 이러한 결과는 대비가 큰(한 이미지 안에 밝고 어두운) 이미지들을 분석할 때 더욱 큰 문제가 됩니다. 이는 두번째 이미지 안에서 더욱 확실히 알 수 있습니다.

 

결국 이는 우리에게 한 이미지 안에서 명확하거나 알아챌 수 있지 않는 새로운 색상을 줄 뿐입니다.

 

Method 2 - Highest Pixel Freqency

 

두번째 방법은 처음 방법보다 좀 더 정확한 방법일 듯 합니다. 우리는 각 픽셀값의 빈도를 단순히 세 보겠습니다.

 

운좋게도 우리에겐 numpy를 통해 정확히 우리가 의도한 역할을 수행하는 함수를 사용할 수 있습니다. 먼저 우리는 이미지 데이터 구조를 재구성하여 3가지 값(R, G, B 채널의 감도)으로 나타내 봅니다.

우리는 단순히 numpy의 reshape 함수를 이용하여 픽셀값의 리스트를 얻어냈습니다.

그리고 이제 우리는 이 제대로 된 구조를 가진 데이터를 가지고 픽셀값의 빈도수를 세어보기 시작할 것입니다. 우리는 단순히 numpy의 unique함수를 이용하고 이때 파라미터는 return_counts=True로 설정합니다.

됐습니다. 이제 우리의 이미지를 돌려봅시다.

1
2
3
4
5
6
7
8
9
10
img_temp = img.copy()
unique, counts = np.unique(img_temp.reshape(-13), axis=0, return_counts=True)
img_temp[:,:,0], img_temp[:,:,1], img_temp[:,:,2= unique[np.argmax(counts)]
 
img_temp_2 = img_2.copy()
unique, counts = np.unique(img_temp_2.reshape(-13), axis=0, return_counts=True)
img_temp_2[:,:,0], img_temp_2[:,:,1], img_temp_2[:,:,2= unique[np.argmax(counts)]
 
show_img_compar(img, img_temp)
show_img_compar(img_2, img_temp_2)
cs

위 결과는 처음 것보다 더 그럴듯 해 보이지 않나요? 가장 주된 색들은 검은색 구역입니다. 하지만 좀 더 심화하여 봅시다. 만약 그저 하나의 주된 색상을 선택하는 것이 아니라 그 이상이라면요? 동일한 컨셉을 적용하여 우리는 가장 주된 N개의 색상을 추출해 낼 수 있습니다. 예외적으로 처음 이미지를 본다면 많은 색들이 다 인접한 색과 비슷하게 배치되어 있고 아주 적은 픽셀들의 차이가 수반되어 있습니다.

 

즉, 우리는 가장 빈번한, 하지만 다른 색상군을 추출하길 원합니다.

 

Method 3 - Using K-Means clustering

 

Scikit-learn 패키지가 우리를 구해주러 왔습니다. 우리는 악명높은 K-Means clustering을 이용 색상 군을 얻을 수 있습니다.

 

쉽지 않나요? 이제 우리가 해야할 일은 색상군을 보여주는 함수를 사용하는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def palette(clusters):
    width=300
    palette = np.zeros((50, width, 3), np.uint8)
    steps = width/clusters.cluster_centers_.shape[0]
    for idx, centers in enumerate(clusters.cluster_centers_): 
        palette[:, int(idx*steps):(int((idx+1)*steps)), :] = centers
    return palette
 
  
clt_1 = clt.fit(img.reshape(-13))
show_img_compar(img, palette(clt_1))
 
clt_2 = clt.fit(img_2.reshape(-13))
show_img_compar(img_2, palette(clt_2))
cs

우리는 단순하게 높이가 50이며 너비가 300픽셀인 이미지에 색 그룹/팔레트를 보여줍니다. 그리고 각 색상군을 팔레트에 보여줍니다.

아름답지 않나요? K-Means clustering은 이미지의 가장 주된 색상을 추출할 때 우리에게 훌륭한 결과를 줍니다. 두번째 이미지에서는 너무 많은 갈색 계통의 색들이 있습니다. 이경우 우리는 너무 많은 색상군을 추출했습니다.  다시 이경우 k 값을 좀 더 작은 수로 수정해 봅시다.

1
2
3
4
5
6
7
8
9
10
11
def palette(clusters):
    width=300
    palette = np.zeros((50, width, 3), np.uint8)
    steps = width/clusters.cluster_centers_.shape[0]
    for idx, centers in enumerate(clusters.cluster_centers_): 
        palette[:, int(idx*steps):(int((idx+1)*steps)), :] = centers
    return palette
 
clt_3 = KMeans(n_clusters=3)
clt_3.fit(img_2.reshape(-13))
show_img_compar(img_2, palette(clt_3))
cs

해결했습니다. 우리가 K-Means clustering을 이용하는 이상 우리는 적정 클러스터 수를 우리가 결정해야 합니다. 위 사진의 경우 3개의 클러스터가 가장 좋은 선택 같습니다.

 

하지만 여기서 우리는 좀더 결과를 개선해볼 수 있을 것 같습니다.

 

전체 이미지에서 각 색상군의 비중을 보여주는 것은 어떨까요?

 

Method 3.1 - K-Means + Proportion display

 

우리가 해야할 것은 단지 palette 함수를 수정하는 것입니다. 정해진 과정 대신에 우리는 각 군의 너비값을 픽셀 수에 비례하여 변경할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from collections import Counter
 
def palette_perc(k_cluster):
    width = 300
    palette = np.zeros((50, width, 3), np.uint8)
    
    n_pixels = len(k_cluster.labels_)
    counter = Counter(k_cluster.labels_) # count how many pixels per cluster
    perc = {}
    for i in counter:
        perc[i] = np.round(counter[i]/n_pixels, 2)
    perc = dict(sorted(perc.items()))
    
    #for logging purposes
    print(perc)
    print(k_cluster.cluster_centers_)
    
    step = 0
    
    for idx, centers in enumerate(k_cluster.cluster_centers_): 
        palette[:, step:int(step + perc[idx]*width+1), :] = centers
        step += int(perc[idx]*width+1)
        
    return palette
    
clt_1 = clt.fit(img.reshape(-13))
show_img_compar(img, palette_perc(clt_1))
 
clt_2 = clt.fit(img_2.reshape(-13))
show_img_compar(img_2, palette_perc(clt_2))
cs

훨씬 낫습니다.

 

그저 우리에게 가장 주된 색상을 주는 것 뿐이 아니라 각 픽셀의 빈도수의 비중도 나타내 줍니다.

 

그것은 또한 얼마나 많은 군들을 우리가 사용해야 할지도 알려줍니다. 가장 위에 있는 이미지의 경우 두개에서 네개의 군이 적당해 보이고 두번째 이미지의 경우 최소 두개의 군이 필요할 듯 합니다. 하나의 클러스터 (k = 4)를 이용하지 않는 이유는 average 방법에서 발생했던 동일한 문제가 발생하게 됩니다.

결론

우리는 파이썬과 잘 알려져있는 몇가지 라이브러리를 이용하여 가장 주된 색상을 얻는 몇가지 기술들을 익혔습니다. 더하여 우리는 또한 이러한 기술들의 장단점을 알 수 있게 되었습니다. 지금까지 K가 1보다 큰 K-Means를 이용하여 가장 주된 색상들을 얻는 방법이 최고의 방법인듯 합니다.(적어도 비교한 다른 방법들에 비해서는요.)

 

문제가 있다면 댓글로, 또는 제 Github에 남겨주세요.

 

+ Recent posts