使用顏色空間進行圖像分割

原文地址:https://realpython.com/python-opencv-color-spaces/python

<ul> <li> <a href="#A">什麼是顏色空間?</a></li> <li> <a href="#B">使用顏色空間進行簡單分割 </a></li> <ul> <li> <a href="#B1">顏色空間和使用opencv讀取圖像</a></li> <li> <a href="#B2">在RGB顏色空間可視化小丑魚</a></li> <li> <a href="#B3">在HSV顏色空間可視化小丑魚</a></li> <li> <a href="#B4">選取範圍 </a></li> </ul> <li> <a href="#C">這個分割是否能夠泛化到小丑魚的親屬?</a></li> <li> <a href="#D">總結 </a></li>c++

</ul>算法

這多是一個深度學習和大數據的時代,在這個時代,複雜的算法經過顯示數百萬幅圖像來分析圖像,可是顏色空間對於圖像分析仍然很是有用。簡單的方法仍然是強大的。數組

在本文中,您將學習如何使用OpenCV基於Python中的顏色從圖像中簡單地分割對象。OpenCV是一個流行的計算機視覺庫,用c/c++編寫,帶有Python綁定,提供了操做顏色空間的簡單方法。app

雖然你不須要已經熟悉OpenCV或本文中使用的其餘助手包,但咱們假設你至少對Python中的編碼有了基本的瞭解。ide

<h2> <a id="A"> 什麼是顏色空間? </a> </h2>函數

在最多見的顏色空間RGB(紅、綠、藍)中,顏色以其紅、綠、藍三種成分表示。在更專業的術語中,RGB將顏色描述爲三個成分的元組。每一個組件能夠取0到255之間的值,其中元組(0,0,0)表示黑色,(255,255,255)表示白色。工具

RGB被認爲是一個附加顏色空間,顏色能夠想象爲由大量的紅色、藍色和綠色光線照射到黑色背景上而產生,如下是RGB顏色的一些例子: image_1cos2jf6jbr9dk67kv1cah19d79.png-22kB學習

RGB是五種主要顏色空間模型之一,每種模型都有許多分支。有這麼多顏色空間,由於不一樣的顏色空間對於不一樣的目的是有用的。大數據

在印刷領域,CMYK很是有用,由於它描述了從白色背景產生顏色所需的顏色組合。RGB中的0元組是黑色的,而CMYK中的0元組是白色的。咱們的打印機包含青色、品紅色、黃色和黑色墨盒。

在某些類型的醫療領域,裝有染色組織樣本的載玻片被掃描並保存爲圖像。它們能夠在HED空間中進行分析,HED空間是應用於原始組織的染色類型——蘇木精、曙紅和DAB——飽和度的表示。

HSVHSL是色調、飽和度和亮度的描述,對於識別圖像中的對比度特別有用。這些顏色空間常常用於軟件和網頁設計中的顏色選擇工具。

實際上,顏色是一個連續的現象,意味着有無限多的顏色。然而,顏色空間經過離散結構(固定數量的整數數值)來表示顏色,這是能夠接受的,由於人眼和感知也是有限的。顏色空間徹底可以表明咱們可以區分的全部顏色。

既然咱們理解了顏色空間的概念,咱們能夠繼續在OpenCV中使用它們。

<h2> <a id="B"> 使用顏色空間進行簡單分割 </a> </h2> 爲了演示顏色空間分割技術,咱們在[real-Python材料庫][3]中提供了一個尼莫魚圖像數據集,供您下載和玩耍。小丑魚很容易被它們明亮的橙色識別,因此它們是好的分割候選。讓咱們看看在一張圖片中找到尼莫魚有多精確。

你須要遵循的關鍵Python包是NumPy—Python中最重要的科學計算包,matplolib—繪圖庫,固然還有OpenCV。

<h3> <a id="B1"> 顏色空間和使用opencv讀取圖像 </a> </h3> 首先,你須要設置你的環境。本文將假設您的系統上安裝了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()

image_1cos89diq1833pl61sf11lqp869m.png-323.5kB 嘿,尼莫……仍是多莉?你會注意到,藍色和紅色的頻道彷佛已經混在一塊兒了。事實上,默認狀況下,OpenCV讀取BGR格式的圖像。您可使用cvtColor (圖像、標誌)和咱們在上面看到的標誌來解決這個問題:

>>> nemo = cv2.cvtColor(nemo, cv2.COLOR_BGR2RGB)
>>> plt.imshow(nemo)
>>> plt.show()

image_1cos8di3dk9q1ram1mtp11pno0k1j.png-330.7kB 如今尼莫看起來更像他本身。

<h3> <a id="B2"> 在RGB顏色空間可視化小丑魚 </a> </h3> 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()

image_1cos8s9nnkgetmgvjh8q1ji120.png-274.6kB 從這個圖中,你能夠看到圖像的橙色部分跨越了幾乎整個範圍的紅色、綠色和藍色值。因爲Nemo的一部分延伸到整個情節,根據RGB值的範圍在RGB空間分割Nemo並不容易。

<h3> <a id="B3"> 在HSV顏色空間可視化小丑魚 </a> </h3> 咱們在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()

產生這些圖像,用選擇的顏色填充: image_1cos9goqb13n47h41sm72hq10tn3n.png-16.1kB 一旦你得到了合適的顏色範圍,你可使用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()

image_1cos9ppiv76gpcp1j0o195hohk44.png-44.4kB 這已經很好地捕捉了魚的橙色部分。惟一的問題是尼莫也有白色條紋……幸運的是,添加第二個尋找白色的遮罩與你已經用橙色作的很是類似:

>>> 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()

image_1cos9shag1jcm1e8u1tri1u2m1grk4h.png-15.8kB 我在這裏選擇的上限是很是藍的白色,由於白色在陰影中有藍色的色彩。讓咱們製做第二個遮罩,看看它是否捕捉到尼莫的條紋。您能夠像構建第一個遮罩同樣構建第二個遮罩:

>>> 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()

image_1cosa15amja216ibaf91okg2uk4u.png-30.8kB 不錯!如今你能夠組合這些遮罩了。將兩個遮罩加在一塊兒,不管哪裏有橙色或白色,都會產生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()

image_1cosa2iemsvc10ngo411tti3o5b.png-57.2kB 本質上,你已經在HSV顏色空間中粗略地分割了Nemo。你會注意到分割邊界上有一些雜散像素,若是你喜歡,你可使用高斯模糊來清理小的錯誤檢測。

高斯模糊是一種圖像過濾器,它使用一種叫作高斯的函數來變換圖像中的每一個像素。它具備平滑圖像噪聲和減小細節的效果。如下是對咱們的圖像應用模糊的狀況:

>>> blur = cv2.GaussianBlur(final_result, (7, 7), 0)
>>> plt.imshow(blur)
>>> plt.show()

image_1cosa4srdvs1o49e341k5t1d975o.png-102.8kB

<h2> <a id="C"> 這個分割是否能夠泛化到小丑魚的親屬 </a> </h2> 爲了好玩,讓咱們看看這種分割技術推廣到其餘小丑魚圖像的效果如何。在這個資料庫中,有六張谷歌尼莫魚圖片可供公衆使用。圖像在子目錄中,索引爲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()

image_1cosaeg421baf12vm15r11vksvor65.png-140.7kB image_1cosafeml1c7tiiock03f21k807i.png-146.3kB image_1cosaftnkub9ndl1opc18tnnm7v.png-96.6kB image_1cosagb4m106q28qa4v1s6idum8c.png-130.2kB image_1cosagml6voi1d5a11jejr69mg8p.png-109.8kB 總的來講,這種簡單的分割方法已經成功地找到了尼莫的大多數親戚。然而,很明顯,用特定的光照和背景分割一條Nemo魚未必能很好地推廣到分割全部Nemo魚。

<h2> <a id="D"> 總結 </a> </h2> 在本教程中,您已經看到了幾個不一樣的顏色空間,一幅圖像是如何分佈在RGB和HSV顏色空間中的,以及如何使用OpenCV在顏色空間之間進行轉換和分割範圍。

總之,您已經瞭解瞭如何使用OpenCV中的顏色空間來執行圖像中的對象分割,並但願看到它在執行其餘任務方面的潛力。在控制照明和背景的狀況下,例如在實驗環境中或者在更均勻的數據集上,這種分割技術簡單、快速、可靠。

相關文章
相關標籤/搜索