如果我們知道圖像或對象最常見的是哪種顏色,那麼可以解決圖像處理中的幾個用例,例如在農業領域,我們可能需要確定水果的成熟度。我們可以簡單地檢查一下水果的顏色是否在預定的範圍內,看看它是成熟的,腐爛的,還是未成熟的。
與往常一樣,我們可以使用Python和簡單但功能強大的庫(如Numpy、Matplotlib和OpenCV)來解決這個問題。我將演示如何使用這些軟體包在圖像中找到最常見的顏色的幾種方法。
步驟1-加載包
import cv2 as cvimport numpy as npimport matplotlib.pyplot as pltimport PIL%matplotlib inline在這裡加載基本包,後續會繼續加載更多的包。另外,由於我們是用Jupyter編程的,所以不要忘了包含%matplotlib內聯命令。
步驟2-加載並顯示示例圖像
在本教程中,我們將展示兩個並排的圖像。所以,讓我們用一個helper函數來實現。
def show_img_compar(img_1, img_2 ): f, ax = plt.subplots(1, 2, figsize=(10,10)) ax[0].imshow(img_1) ax[1].imshow(img_2) ax[0].axis('off') # 隱藏軸 ax[1].axis('off') f.tight_layout() plt.show()接下來,我們將加載一些在本教程中使用的示例圖像,並使用上面的函數演示它們。
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 = (500, 300)# 圖像大小自定義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)
現在我們準備好了。接下來要找出這些圖像中最常見的顏色了。
方法1-平均值
第一種方法是最簡單的(但無效的),找到平均像素值。
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)使用numpy的average函數,我們可以很容易地得到行和寬度軸的平均像素值axis=(0,1)。
我們可以看出,平均法可以給出誤導或不準確的結果,因為它給出的最常見的顏色與實際偏離,這是因為平均值結合了所有像素值。當我們有高對比度的圖像(在一個圖像中「光」和「暗」),如在第二幅圖中,這個問題更明顯。
它給了我們一種在圖像中不明顯的新顏色。
方法2-最高像素頻率
第二種方法要比第一種方法精確一些,我們只需計算每個像素值中出現的次數。
幸運的是,numpy給了我們另一個函數,這個函數給出了精確的結果。但首先,我們必須重塑圖像數據結構,使其僅給出3個值的列表(每個R、G和B通道強度各一個)。
可以簡單地使用numpy的reshape函數來獲得像素值的列表。
現在我們有了正確結構的數據,我們可以開始計算像素值的頻率。只需使用numpy的unique函數,參數return_counts=True。
完成了,接下來在圖像上運行。
img_temp = img.copy()unique, counts = np.unique(img_temp.reshape(-1, 3), 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(-1, 3), 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)
這比第一個更有意義,對吧?最常見的顏色是黑色區域。但我們可以更進一步,如果不只取一種最常見的顏色,而是多取一種呢?用同樣的方法,我們可以選擇前N種最常見的顏色。但是如果你看第一張圖片,頻率最高的許多顏色很可能是相鄰的顏色,可能只有幾個像素的差別。
換言之,我們要取最常見的,不同顏色的簇。
方法3-使用K均值聚類
我們使用Scikit-Learn來實現。我們可以使用K-Means聚類將顏色組聚集在一起。
現在,我們只需要一個函數來顯示上面的顏色簇並立即顯示出來。
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 paletteclt_1 = clt.fit(img.reshape(-1, 3))show_img_compar(img, palette(clt_1))clt_2 = clt.fit(img_2.reshape(-1, 3))show_img_compar(img_2, palette(clt_2))我們只需創建一個高度為50、寬度為300像素的圖像來顯示顏色組/調色板。對於每個顏色簇,將其指定給調色板。
K-Means聚類在檢測圖像中最常見的顏色方面給出了很好的結果。在第二張圖中,我們可以看到調色板中有太多的棕色陰影。這很可能是因為我們選擇了太多的簇。讓我們看看是否可以通過選擇較小的k值來修復它。
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 paletteclt_3 = KMeans(n_clusters=3)clt_3.fit(img_2.reshape(-1, 3))show_img_compar(img_2, palette(clt_3))
是的,解決了。
由於我們使用K-Means聚類,仍然需要自己確定適當的k。3似乎是個不錯的選擇。
但我們仍然可以在這些結果的基礎上改進。我們也展示一下這些簇在整個圖像中所佔的比例如何?
方法3.1-K均值+比例顯示
我們需要做的就是修改調色板函數。我們不使用固定的步長,而是將每個簇的寬度更改為與該簇中的像素數成比例。
from collections import Counterdef 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_) # 計算每個簇有多少像素 perc = {} for i in counter: perc[i] = np.round(counter[i]/n_pixels, 2) perc = dict(sorted(perc.items())) # 用於日誌記錄 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 paletteclt_1 = clt.fit(img.reshape(-1, 3))show_img_compar(img, palette_perc(clt_1))clt_2 = clt.fit(img_2.reshape(-1, 3))show_img_compar(img_2, palette_perc(clt_2))
這樣好多了,它不僅給了我們圖像中最常見的顏色。它還提供了每個像素出現的比例。
它也有助於告訴我們應該使用多少個簇。在上面的圖像中,兩到四個簇似乎是合理的。在第二張圖像中,我們至少需要兩個簇。不使用一個簇(k=4)的原因是可能會遇到與平均方法相同的問題。
結論
我們介紹了使用Python和一些著名的庫來獲取圖像中最常見顏色的幾種技術。另外,我們也看到了這些技術的優缺點。到目前為止,使用K>1的K-Means找到最常見的顏色是在圖像中找到最常見顏色的最佳解決方案之一(至少與我們已經使用的其他方法相比)。
Github倉庫代碼:https://github.com/mrakelinggar/data-stuffs/tree/master/frequent_color。