原文地址:https://realpython.com/python-opencv-color-spaces/
這可能是一個深度學習和大數據的時代,在這個時代,復雜的算法通過顯示數百萬幅圖像來分析圖像,但是顏色空間對於圖像分析仍然非常有用。簡單的方法仍然是強大的。
在本文中,您將學習如何使用OpenCV基於Python中的顏色從圖像中簡單地分割對象。OpenCV是一個流行的計算機視覺庫,用c/c++編寫,帶有Python綁定,提供了操作顏色空間的簡單方法。
雖然你不需要已經熟悉OpenCV或本文中使用的其他助手包,但我們假設你至少對Python中的編碼有了基本的了解。
什么是顏色空間?
在最常見的顏色空間RGB(紅、綠、藍)中,顏色以其紅、綠、藍三種成分表示。在更專業的術語中,RGB將顏色描述為三個成分的元組。每個組件可以取0到255之間的值,其中元組(0,0,0)表示黑色,(255,255,255)表示白色。
RGB被認為是一個附加顏色空間,顏色可以想象為由大量的紅色、藍色和綠色光線照射到黑色背景上而產生,以下是RGB顏色的一些例子:
RGB是五種主要顏色空間模型之一,每種模型都有許多分支。有這么多顏色空間,因為不同的顏色空間對於不同的目的是有用的。
在印刷領域,CMYK非常有用,因為它描述了從白色背景產生顏色所需的顏色組合。RGB中的0元組是黑色的,而CMYK中的0元組是白色的。我們的打印機包含青色、品紅色、黃色和黑色墨盒。
在某些類型的醫療領域,裝有染色組織樣本的載玻片被掃描並保存為圖像。它們可以在HED空間中進行分析,HED空間是應用於原始組織的染色類型——蘇木精、曙紅和DAB——飽和度的表示。
HSV和HSL是色調、飽和度和亮度的描述,對於識別圖像中的對比度特別有用。這些顏色空間經常用於軟件和網頁設計中的顏色選擇工具。
實際上,顏色是一個連續的現象,意味着有無限多的顏色。然而,顏色空間通過離散結構(固定數量的整數數值)來表示顏色,這是可以接受的,因為人眼和感知也是有限的。顏色空間完全能夠代表我們能夠區分的所有顏色。
既然我們理解了顏色空間的概念,我們可以繼續在OpenCV中使用它們。
使用顏色空間進行簡單分割
為了演示顏色空間分割技術,我們在[real-Python材料庫][3]中提供了一個尼莫魚圖像數據集,供您下載和玩耍。小丑魚很容易被它們明亮的橙色識別,所以它們是好的分割候選。讓我們看看在一張圖片中找到尼莫魚有多精確。你需要遵循的關鍵Python包是NumPy—Python中最重要的科學計算包,matplolib—繪圖庫,當然還有OpenCV。
顏色空間和使用opencv讀取圖像
首先,你需要設置你的環境。本文將假設您的系統上安裝了Python 3.x。請注意,雖然OpenCV的當前版本是3.x,但是要導入的包的名稱仍然是cv2,通過`pip3 install opencv-python`命令進行安裝(沒有pip3,用pip也行)。 ```python >>> import cv2 ``` 成功導入OpenCV后,您可以查看OpenCV提供的所有顏色空間轉換,並將它們全部保存到變量中: ```python >>> flags = [i for i in dir(cv2) if i.startswith('COLOR_')] ``` 根據您的OpenCV版本,標志的列表和數量可能略有不同,但是不管怎樣,會有很多標志!查看您有多少個可用的標志: ```python >>> len(flags) 258 >>> flags[40] 'COLOR_BGR2RGB' ``` COLOR_后面的第一個字符表示原始顏色空間,2后面的字符表示目標顏色空間。此標志表示從BGR(藍色、綠色、紅色)到RGB的轉換。正如你所看到的,這兩個顏色空間非常相似,只有第一個和最后一個通道交換。你需要matplotlib.pyplot來查看圖像,需要NumPy來處理一些圖像。如果尚未安裝Matplotlib或NumPy,則在嘗試導入之前,您需要pip3安裝Matplotlib和pip3安裝NumPy:
>>> import matplotlib.pyplot as plt
>>> import numpy as np
現在,您可以加載和檢查圖像了。請注意,如果您是從命令行或終端工作,您的圖像將出現在彈出窗口中。如果你在Jupyter筆記本或類似的東西上工作,它們會簡單地顯示在下面。不管您的設置如何,您都應該看到show()命令生成的圖像:
>>> nemo = cv2.imread('./images/nemo0.jpg')
>>> plt.imshow(nemo)
>>> plt.show()
嘿,尼莫……還是多莉?你會注意到,藍色和紅色的頻道似乎已經混在一起了。事實上,默認情況下,OpenCV讀取BGR格式的圖像。您可以使用cvtColor (圖像、標志)和我們在上面看到的標志來解決這個問題:
>>> nemo = cv2.cvtColor(nemo, cv2.COLOR_BGR2RGB)
>>> plt.imshow(nemo)
>>> plt.show()
現在尼莫看起來更像他自己。
在RGB顏色空間可視化小丑魚
HSV是按顏色分割顏色空間的一個很好的選擇,但是為了了解原因,讓我們通過可視化其像素的顏色分布來比較RGB和HSV顏色空間中的圖像。3D圖很好地顯示了這一點,每個軸代表顏色空間中的一個通道。如果您想知道如何制作3D繪圖,請查看下面部分: 要繪制該圖,您還需要幾個Matplotlib庫: ```python >>> from mpl_toolkits.mplot3d import Axes3D >>> from matplotlib import cm >>> from matplotlib import colors ``` 這些庫提供了繪圖所需的功能。您希望根據每個像素的組件將每個像素放置在其位置,並根據其顏色對其進行着色。cv2.split()在這里非常方便;它將圖像分割成其分量通道。這幾行代碼分割圖像並設置3D繪圖: ```python >>> r, g, b = cv2.split(nemo) >>> fig = plt.figure() >>> axis = fig.add_subplot(1, 1, 1, projection="3d") ``` 既然已經設置了繪圖,就需要設置像素顏色。為了根據每個像素的真實顏色為其上色,需要進行一些整形和歸一化。它看起來很凌亂,但實際上你需要將圖像中每個像素對應的顏色展平成一個列表並歸一化,這樣它們就可以傳遞到Matplotlib scatter()的facecolors參數。歸一化只是指根據facecolors參數的要求,將顏色范圍從0-255縮小到0-1。最后,facecolors想要一個列表,而不是一個NumPy數組:
>>> pixel_colors = nemo.reshape((np.shape(nemo)[0]*np.shape(nemo)[1], 3))
>>> norm = colors.Normalize(vmin=-1.,vmax=1.)
>>> norm.autoscale(pixel_colors)
>>> pixel_colors = norm(pixel_colors).tolist()
現在,我們已經准備好繪制所有組件:每個軸的像素位置及其對應的顏色,按照facecolors期望的格式。您可以構建散點圖並查看它:
>>> axis.scatter(r.flatten(), g.flatten(), b.flatten(), facecolors=pixel_colors, marker=".")
>>> axis.set_xlabel("Red")
>>> axis.set_ylabel("Green")
>>> axis.set_zlabel("Blue")
>>> plt.show()
從這個圖中,你可以看到圖像的橙色部分跨越了幾乎整個范圍的紅色、綠色和藍色值。由於Nemo的一部分延伸到整個情節,根據RGB值的范圍在RGB空間分割Nemo並不容易。
在HSV顏色空間可視化小丑魚
我們在RGB空間看到尼莫,所以現在讓我們在HSV空間看到他並進行比較。 正如上面簡要提到的,HSV代表色調、飽和度和值(或亮度),是一個圓柱色空間。顏色或色調被建模為圍繞中心垂直軸旋轉的角度尺寸,這表示值通道。值從暗(底部為0 )到亮(頂部為0 )。第三個軸“飽和度”定義了色調的深淺,從垂直軸上的最不飽和到離中心最遠的最飽和: ![image_1cos8vt42i0h4cu1mdn1of71fcl2t.png-310.5kB][7] 要將圖像從RGB轉換為HSV,可以使用cvtColor(): ```python >>> hsv_nemo = cv2.cvtColor(nemo, cv2.COLOR_RGB2HSV) ``` 現在,HSV_Nemo在HSV中存儲了尼莫的表示。使用與上面相同的技術,我們可以查看HSV中的圖像圖,HSV中顯示圖像的代碼與RGB中的代碼相同。請注意,您使用相同的pixel_colors變量為像素着色,因為Matplotlib希望這些值以RGB為單位: ```python >>> h, s, v = cv2.split(hsv_nemo) >>> fig = plt.figure() >>> axis = fig.add_subplot(1, 1, 1, projection="3d")axis.scatter(h.flatten(), s.flatten(), v.flatten(), facecolors=pixel_colors, marker=".")
axis.set_xlabel("Hue")
axis.set_ylabel("Saturation")
axis.set_zlabel("Value")
plt.show()
![image_1cos95797og1pci1m01lcl1f13a.png-267.2kB][8]
在HSV空間中,尼莫的橙色更加本地化,視覺上也更加分離。橙子的飽和度和價值確實有所不同,但它們大多位於色調軸上的小范圍內。這是可用於分段的關鍵點。
<h3>
<a id="B4">
選取范圍
</a>
</h3>
讓我們根據一系列簡單的橙色來判斷尼莫的閾值。你可以通過觀察上面的圖或者在線使用顏色挑選應用程序來選擇范圍,比如這個[RGB到HSV工具][9]。這里選擇的色板是淺橙色和深橙色,幾乎是紅色:
```python
>>> light_orange = (1, 190, 200)
>>> dark_orange = (18, 255, 255)
在Python中顯示顏色的一個簡單方法是制作所需顏色的小正方形圖像,並在Matplotlib中繪制。matplotlib只解釋RGB中的顏色,但是為主要顏色空間提供了方便的轉換功能,以便我們可以在其他顏色空間繪制圖像:
>>> from matplotlib.colors import hsv_to_rgb
然后,構建小的10x10x3正方形,填充相應的顏色。您可以使用NumPy輕松地用顏色填充正方形:
>>> lo_square = np.full((10, 10, 3), light_orange, dtype=np.uint8) / 255.0
>>> do_square = np.full((10, 10, 3), dark_orange, dtype=np.uint8) / 255.0
最后,通過將它們轉換為RGB進行查看,您可以將它們繪制在一起:
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(hsv_to_rgb(do_square))
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(hsv_to_rgb(lo_square))
>>> plt.show()
產生這些圖像,用選擇的顏色填充:
一旦你獲得了合適的顏色范圍,你可以使用cv2.inrange()來嘗試閾值Nemo,inRange()采用三個參數:圖像、較低范圍和較高范圍。它返回圖像大小的二進制掩碼(ndarray為1和0),其中值1表示范圍內的值,零值表示范圍外的值:
>>> mask = cv2.inRange(hsv_nemo, light_orange, dark_orange)
要在原始圖像的頂部加上遮罩,可以使用cv2.bittage_and(),使遮罩中的對應值為1:
>>> result = cv2.bitwise_and(nemo, nemo, mask=mask)
要查看到底做了什么,讓我們查看遮罩和頂部帶有遮罩的原始圖像:
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(mask, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(result)
>>> plt.show()
這已經很好地捕捉了魚的橙色部分。唯一的問題是尼莫也有白色條紋……幸運的是,添加第二個尋找白色的遮罩與你已經用橙色做的非常相似:
>>> light_white = (0, 0, 200)
>>> dark_white = (145, 60, 255)
一旦指定了顏色范圍,就可以查看您選擇的顏色:
>>> lw_square = np.full((10, 10, 3), light_white, dtype=np.uint8) / 255.0
>>> dw_square = np.full((10, 10, 3), dark_white, dtype=np.uint8) / 255.0
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(hsv_to_rgb(lw_square))
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(hsv_to_rgb(dw_square))
>>> plt.show()
我在這里選擇的上限是非常藍的白色,因為白色在陰影中有藍色的色彩。讓我們制作第二個遮罩,看看它是否捕捉到尼莫的條紋。您可以像構建第一個遮罩一樣構建第二個遮罩:
>>> mask_white = cv2.inRange(hsv_nemo, light_white, dark_white)
>>> result_white = cv2.bitwise_and(nemo, nemo, mask=mask_white)
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(mask_white, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(result_white)
>>> plt.show()
不錯!現在你可以組合這些遮罩了。將兩個遮罩加在一起,無論哪里有橙色或白色,都會產生1個值,這正是所需要的。讓我們一起添加遮罩並繪制結果:
>>> final_mask = mask + mask_white
>>> final_result = cv2.bitwise_and(nemo, nemo, mask=final_mask)
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(final_mask, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(final_result)
>>> plt.show()
本質上,你已經在HSV顏色空間中粗略地分割了Nemo。你會注意到分割邊界上有一些雜散像素,如果你喜歡,你可以使用高斯模糊來清理小的錯誤檢測。
高斯模糊是一種圖像過濾器,它使用一種叫做高斯的函數來變換圖像中的每個像素。它具有平滑圖像噪聲和減少細節的效果。以下是對我們的圖像應用模糊的情況:
>>> blur = cv2.GaussianBlur(final_result, (7, 7), 0)
>>> plt.imshow(blur)
>>> plt.show()
這個分割是否可以泛化到小丑魚的親屬
為了好玩,讓我們看看這種分割技術推廣到其他小丑魚圖像的效果如何。在這個資料庫中,有六張谷歌尼莫魚圖片可供公眾使用。圖像在子目錄中,索引為nemoi.jpg,其中I是0-5的索引。首先,將尼莫的所有親戚載入一個列表: ```python path = "./images/nemo"nemos_friends = []
for i in range(6):
friend = cv2.cvtColor(cv2.imread(path + str(i) + ".jpg"), cv2.COLOR_BGR2RGB)
nemos_friends.append(friend)
你可以將上面用來分割一條魚的所有代碼組合成一個函數,該函數將圖像作為輸入並返回分割的圖像。如下所示:
```python
def segment_fish(image):
''' Attempts to segment the clownfish out of the provided image '''
# Convert the image into HSV
hsv_image = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
# Set the orange range
light_orange = (1, 190, 200)
dark_orange = (18, 255, 255)
# Apply the orange mask
mask = cv2.inRange(hsv_image, light_orange, dark_orange)
# Set a white range
light_white = (0, 0, 200)
dark_white = (145, 60, 255)
# Apply the white mask
mask_white = cv2.inRange(hsv_image, light_white, dark_white)
# Combine the two masks
final_mask = mask + mask_white
result = cv2.bitwise_and(image, image, mask=final_mask)
# Clean up the segmentation using a blur
blur = cv2.GaussianBlur(result, (7, 7), 0)
return blur
有了這個有用的功能,你可以分割所有的魚:
results = [segment_fish(friend) for friend in nemos_friends]
讓我們通過在一個循環中繪制結果來查看所有結果:
for i in range(1, 6):
plt.subplot(1, 2, 1)
plt.imshow(nemos_friends[i])
plt.subplot(1, 2, 2)
plt.imshow(results[i])
plt.show()
總的來說,這種簡單的分割方法已經成功地找到了尼莫的大多數親戚。然而,很明顯,用特定的光照和背景分割一條Nemo魚未必能很好地推廣到分割所有Nemo魚。
總結
在本教程中,您已經看到了幾個不同的顏色空間,一幅圖像是如何分布在RGB和HSV顏色空間中的,以及如何使用OpenCV在顏色空間之間進行轉換和分割范圍。總之,您已經了解了如何使用OpenCV中的顏色空間來執行圖像中的對象分割,並希望看到它在執行其他任務方面的潛力。在控制照明和背景的情況下,例如在實驗環境中或者在更均勻的數據集上,這種分割技術簡單、快速、可靠。