雷鋒網 AI 科技評論按,隨著人工智慧技術的逐年火熱,越來越多的人投入到計算機視覺領域的相關研究中。而圖像分割是圖像處理中非常重要的一個步驟,它是把圖像分成若干個特定的、具有獨特性質的區域,並提取出感興趣目標的技術。
近日,工程師 Parul Pandey 發表了一篇博文,在博文中,她介紹了用 python 的 scikit-image 庫進行圖像分割的方法。具體涉及 scikit-image 的安裝,圖像導入以及用監督算法和無監督算法進行圖像分割的方法。雷鋒網 AI 科技評論編譯整理如下。
遲早有一天,所有的一切都是數字,包括圖像。
看過《終結者》的人肯定會認為這是那個時代最偉大的科幻電影。在這部電影中,James Cameron 引入了一個有趣的視覺效果概念,它可以使觀眾有可能躲在被稱為終結者的電子人的眼睛後面。這種效應被稱為「終結者視覺」,在某種程度上,它將人與背景分割開來。在當時,這聽起來完全是天方夜譚,但在如今,圖像分割已經成為了許多圖像處理技術的重要組成部分。
圖像分割
我們都很清楚,Photoshop 或類似的圖形編輯器提供了無限的可能性,可以將一個人從一張圖片中帶到另一張圖片中。然而,要這樣做,首先需要確定那個人在源圖像中的位置,這就需要用到圖像分割技術了。有許多庫是為圖像分析而編寫的。在本文中,我們將詳細討論基於 python 的圖像處理庫 scikit-image。
完整的代碼也可以從與本文關聯的 Github 存儲庫中訪問。
Scikit-image
SciKit Image 是一個專門用於圖像處理的 python 包。
安裝
可以按如下方式安裝 scikit-image:
pip install -U scikit-image(Linux and OSX)pip install scikit-image(Windows)# For Conda-based distributionsconda install scikit-image
python 中的圖像處理概述
在使用圖像分割技術之前,有必要先了解 scikit image 以及它是如何處理圖像的。
從 skimage 庫導入灰度圖像
skimage 數據模塊包含一些內置示例數據集,這些數據集通常以 jpeg 或 png 格式存儲。
from skimage import dataimport numpy as npimport matplotlib.pyplot as pltimage = data.binary_blobs()plt.imshow(image, cmap='gray')
從 skimage 庫導入彩色圖像
from skimage import dataimport numpy as npimport matplotlib.pyplot as pltimage = data.astronaut()plt.imshow(image)
從外部源導入圖像
# The I/O module is used for importing the imagefrom skimage import dataimport numpy as npimport matplotlib.pyplot as pltfrom skimage import ioimage = io.imread('skimage_logo.png')plt.imshow(image);
加載多個圖像
images = io.ImageCollection('../images/*.png:../images/*.jpg')print('Type:', type(images))images.filesOut[]: Type: <class 『skimage.io.collection.ImageCollection』>
保存圖像
#Saving file as 'logo.png'io.imsave('logo.png', logo)
圖像分割
現在我們已經了解了 scikit-image,接下來讓我們來詳細了解圖像分割。圖像分割本質上是將數字圖像分割成多個片段的過程,以簡化或將圖像的表示方式更改為更有意義和更易於分析的內容。
在本文中,我們結合監督算法和無監督算法來處理分割過程。
scikit-image 庫中可用的一些分割算法
監督分割算法:一些可能來自人類輸入的先驗知識被用來指導算法。
無監督分割算法:不需要先驗知識。這些算法試圖將圖像自動細分到有意義的區域。用戶仍然可以通過調整某些設置以獲得想要的輸出。
讓我們從最簡單的閾值分割算法開始吧。
閾值算法
通過選擇高於或低於某個閾值的像素,將對象從從背景中分割出來是最簡單的方法。在北京分割中,這通常是一個非常有用的方法。了解更多可以查看:http://scikit-image.org/docs/dev/auto_examples/xx_applications/plot_thresholding.html
讓我們在 scikit-image 數據集的一張教科書圖像上試試這個。
基本輸入
import numpy as npimport matplotlib.pyplot as pltimport skimage.data as dataimport skimage.segmentation as segimport skimage.filters as filtersimport skimage.draw as drawimport skimage.color as color
繪製圖像的簡單函數:
defimage_show(image, nrows=1, ncols=1, cmap='gray'): fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(14, 14))ax.imshow(image, cmap='gray')ax.axis('off')return fig, ax
圖像
text = data.page()image_show(text)
這個圖像有點暗,但我們仍然可以選擇一個值,它可以合理的分割圖像,而不需要用到任何先進的算法。為了得到這個分割閾值,我們將使用直方圖。
直方圖是一種顯示圖像中不同強度值的像素數的圖。簡單地說,直方圖是一個圖表,其中 X 軸顯示圖像中的所有像素值,而 Y 軸顯示這些值的頻率。
fig, ax = plt.subplots(1, 1)ax.hist(text.ravel(), bins=32, range=[0, 256])ax.set_xlim(0, 256);
我們的示例恰好是一張 8-bit 圖像,因此在 X 軸上總共有 256 個可能的值。在圖像中,0 表示黑色,255 表示白色,我們觀察到有些像素值很集中。這很可能是由於我們的文本背景比較淡,而其他部分則有點模糊不清。一個理想的分割直方圖應該是有兩個峰值,且兩個峰值隔的較遠,以便我們可以選擇在這兩個峰值中間選擇一個數字作為閾值。現在,讓我們試著根據簡單的閾值法來分割圖像。
有監督閾值
因為閾值是我們自己選擇的,所以我們稱之為監督閾值。
text_segmented = text > (value concluded from histogram i.e 50,70,120 )image_show(text_segmented);
文本>50
文本>70
文本>120
我們沒有得到理想的結果,因為左邊的陰影會造成問題,接下來讓我們嘗試無監督的閾值。
無監督閾值
scikit-image 有許多自動閾值設定方法,在選擇最佳閾值時不需要手動輸入。其中常用的方法有 otsu, li, local 等等。
text_threshold = filters.threshold_ # Hit tab with the cursor after the underscore to get all the methods.image_show(text < text_threshold);
otsu 算法分割效果圖
li 算法分割效果圖
在 local 算法中,我們還需要指定 block 的大小。offset 有助於調整圖像,以獲得更好的效果。
text_threshold = filters.threshold_local(text,block_size=51, offset=10) image_show(text > text_threshold);
local 閾值法分割效果圖
這是一種很好的方法,它在很大程度上消除了噪聲。
監督分割
閾值分割是一個非常基本的分割方法,但是它在高對比度圖像中效果不是很好,因此我們需要採用更加先進的算法。
在本節中,我們將使用一個免費的示例圖像,並嘗試使用監督分割技術分割圖像中人的頭部。
# import the imagefrom skimage import ioimage = io.imread('girl.jpg') plt.imshow(image);
源圖像
小 tip:在對圖像進行任何分割之前,最好使用一些濾波器對其進行去噪。
但是,在我們的例子中,圖像中的噪聲很小,因此我們直接對其進行處理。接下來我們將要做的是使用 rgb2gray 將圖像轉換成灰度圖。
image_gray = color.rgb2gray(image) image_show(image_gray);
我們將使用兩種原理完全不同的分割方法。
活動輪廓分割(Active Contour segmentation)
活動輪廓分割使用用戶定義的輪廓或線在感興趣的區域周圍進行初始化,然後該輪廓慢慢收縮。
對於我們的示例圖像,讓我們圍繞人的頭部畫一個圈來初始化輪廓。
def circle_points(resolution, center, radius):"""Generate points which define a circle on an image.Centre refers to the centre of the circle""" radians = np.linspace(0, 2*np.pi, resolution)c = center[1] + radius*np.cos(radians)#polar co-ordinatesr = center[0] + radius*np.sin(radians)return np.array([c, r]).T# Exclude last point because a closed path should not have duplicate pointspoints = circle_points(200, [80, 250], 80)[:-1]
上面對圓環邊緣點的 x 坐標和 y 坐標進行了計算。我們設置解析度值的為 200,那麼將計算 200 個這樣的點。
fig, ax = image_show(image)ax.plot(points[:, 0], points[:, 1], '--r', lw=3)
然後,該算法通過將閉合曲線擬合到人臉的邊緣,將人臉與圖像的其餘部分分割開來。
snake = seg.active_contour(image_gray, points)fig, ax = image_show(image)ax.plot(points[:, 0], points[:, 1], '--r', lw=3)ax.plot(snake[:, 0], snake[:, 1], '-b', lw=3);
我們可以調整參數 alpha 和 beta。alpha 值越高,輪廓的收縮速度越快,而 beta 越大收縮越緩慢。
snake = seg.active_contour(image_gray, points,alpha=0.06,beta=0.3)fig, ax = image_show(image)ax.plot(points[:, 0], points[:, 1], '--r', lw=3)ax.plot(snake[:, 0], snake[:, 1], '-b', lw=3);
隨機 walker 分割
在這種方法中,用戶以交互方式標記少量的像素,這些像素稱為標籤。然後假設每個未標記的像素釋放一個隨機 walker,然後可以確定隨機 walker 從每個未標記像素開始到達一個預標記像素的概率。通過將每個像素分配給計算出來的概率值最大的標籤,可以獲得高質量的分割圖像。
更多相關資料可以閱讀參考文獻:https://ieeexplore.ieee.org/document/1704833。
我們將在這裡重新使用前面示例中的種子值。為了簡單起見,讓我們繼續使用圓。
image_labels = np.zeros(image_gray.shape, dtype=np.uint8)
隨機 Walker 算法需要一個標籤圖像作為輸入。所以我們會有一個更大的圓,它包圍了人的整個臉,還有一個靠近臉中間的小圓。
indices = draw.circle_perimeter(80, 250,20)#from hereimage_labels[indices] = 1image_labels[points[:, 1].astype(np.int), points[:, 0].astype(np.int)] = 2image_show(image_labels);
現在,讓我們使用隨機 walker,並觀察發生了什麼。
image_segmented = seg.random_walker(image_gray, image_labels)# Check our resultsfig, ax = image_show(image_gray)ax.imshow(image_segmented == 1, alpha=0.3);
它並沒有如我們所預期的那樣描繪出臉的邊緣。為了解決這個問題,我們可以調整 beta 參數,直到得到所需的結果。經過幾次嘗試後,可以得到,當 beta 值為 3000 時,分割效果不錯。
image_segmented = seg.random_walker(image_gray, image_labels, beta = 3000)# Check our resultsfig, ax = image_show(image_gray)ax.imshow(image_segmented == 1, alpha=0.3);
以上就是監督分割,在這種算法中,我們必須提供某些輸入,也必須調整某些參數。然而,我們不可能總是讓人先看一張圖像,然後再決定輸入什麼或者從哪裡開始。幸運的是,對於這種情況,我們可以採用無監督分割技術。
無監督分割
無監督分割不需要事先了解圖像。在一張圖像太大的情況下,同時考慮所有像素是不可能的。因此,在這種情況下,無監督分割可以將圖像分解為幾個子區域,你可以使用數十到數百個區域來代替數百萬像素。下面是兩個無監督分割算法:
SLIC(簡單線性迭代聚類)
SLIC 算法實際上使用了一種叫做 k-means 的機器學習算法。它接收圖像的所有像素值,並嘗試將它們分離到給定數量的子區域中。
更多相關內容可以閱讀相關資料:https://ieeexplore.ieee.org/document/6205760。
SLIC 是處理彩色圖像的,所以我們將使用原始圖像。
image_slic = seg.slic(image,n_segments=155)
我們所做的只是將圖像的每個子圖像或子區域像素設置為該區域像素的平均值。
# label2rgb replaces each discrete label with the average interior colorimage_show(color.label2rgb(image_slic, image, kind='avg'));
我們已經將此圖像從 512*512=262000 個像素減少到 155 個區域。
Felzenszwalb 算法
該算法也使用了一種機器學習算法,即最小生成樹聚類算法。Felzenszwaib 算法並沒有告訴我們圖像將被分割成多少個集群。它將運行並生成儘可能多的適合它的集群。相關的參考文件可以在這裡查閱。
image_felzenszwalb = seg.felzenszwalb(image) image_show(image_felzenszwalb);
有很多區域,我們計算相互獨立的區域數。
np.unique(image_felzenszwalb).size3368
現在讓我們使用區域像素平均值對它們重新著色,就像我們在 SLIC 算法中所做的那樣。
image_felzenszwalb_colored = color.label2rgb(image_felzenszwalb, image, kind='avg')image_show(image_felzenszwalb_colored);
現在我們將圖像分成了合適的小區域。如果我們想要將圖像分成更少的區域,可以更改比例參數或者繼續組合它們。這種方法有時被稱為過度分割。
這看起來更像是一個拆分後的圖像,其本質上只是減少了顏色的數量。要再次組合它們,可以使用區域鄰接圖(RAG),但這超出了本文的範圍。
結論
圖像分割是圖像處理中非常重要的一個步驟。它是一個熱門的研究領域,應用非常廣泛,從計算機視覺到醫學圖像、從交通和視頻監控等領域都有涉及。Python scikit-image 提供了一個非常強大的庫,該庫具有大量用於圖像處理的算法。它是免費的,沒有任何限制,在其背後有一個活躍的社區。你可以查看他們的文檔,了解關於庫及其用例的更多信息。
via:https://towardsdatascience.com/image-segmentation-using-pythons-scikit-image-module-533a61ecc980
雷鋒網 AI 科技評論編譯。