訂閱
糾錯
加入自媒體

關(guān)于圖像處理和Python深度學(xué)習(xí)的教程:第一部分

介紹

在這篇文章中,我們將學(xué)習(xí)如何執(zhí)行圖像處理。在整篇文章中,我們使用到的庫是Scikit Image。

基礎(chǔ)知識

1、什么是圖像?

圖像數(shù)據(jù)可能是文本之后最常見的數(shù)據(jù)。那么,電腦如何理解你在埃菲爾鐵塔前的自拍呢?

它使用一個稱為像素的小正方形網(wǎng)格。像素覆蓋一個小區(qū)域,并具有表示顏色的值。圖像中的像素越多,其質(zhì)量越高,存儲所需的內(nèi)存越多。

就是這樣。圖像處理主要是處理這些單獨的像素(有時是像素組),以便計算機(jī)視覺算法可以從中提取更多信息。

2、NumPy和Skimage的圖像基礎(chǔ)

在Matplotlib和Skimage中,圖像都作為NumPy ndarray加載。

from skimage.io import imread  # pip install scikit-image

image = imread("images/colorful_scenery.jpg")

>>> type(image)

numpy.ndarray

NumPy數(shù)組帶來靈活性、速度和力量。圖像處理也不例外。

Ndarrays可以輕松檢索圖像的一般詳細(xì)信息,例如圖像的尺寸:

>>> image.shape

(853, 1280, 3)

>>> image.ndim

3

# The number of pixels

>>> image.size  # 853 * 1280 * 3

3275520

我們的圖像高度為853像素,寬度為1280像素。第三維表示RGB(紅、綠、藍(lán))顏色通道的值。最常見的圖像格式是3D。

你可以通過常規(guī)NumPy索引檢索單個像素值。下面,我們嘗試索引圖像以檢索三個顏色通道中的每一個通道:

red = image[:, :, 0]

compare(image, red, "Red Channel of the Image", cmap_type="Reds_r")

green = image[:, :, 1]

compare(image, green, "Green Channel of the Image", "Greens_r")

blue = image[:, :, 2]

compare(image, blue, "Blue Channel of the Image", "Blues_r")

0表示紅色,1表示綠色,2表示藍(lán)色通道-非常簡單。

創(chuàng)建了兩個函數(shù),show和compare,它們顯示一個圖像或并排顯示其中兩個進(jìn)行比較。在整個教程中,我們將廣泛使用這兩個函數(shù)。

按照約定,ndarray的第三維用于顏色通道,但并不總是遵循此約定。Skimage通常提供參數(shù)來指定這種行為。

圖像與通常的Matplotlib繪圖不同。它們的原點不位于左下角,而是位于左上角的位置(0,0)。

>>> show(image, axis=True)

當(dāng)我們在Matplotlib中繪制圖像時,軸表示像素的順序,但我們通常會隱藏它們。

3、常見轉(zhuǎn)換

我們將要執(zhí)行的最常見的圖像轉(zhuǎn)換是將彩色圖像轉(zhuǎn)換為灰度。許多圖像處理算法需要灰度圖像。因為顏色不是圖片的定義特征,沒有它,計算機(jī)仍然可以提取足夠的信息。

from skimage.color import rgb2gray

image = imread("images/grayscale_example.jpg")

# Convert image to grayscale

gray = rgb2gray(image)

compare(image, gray, "Grayscale Image")

>>> gray.shape

(853, 1280)

當(dāng)將圖像轉(zhuǎn)換為灰度時,它們會丟失其第三維度-顏色通道。相反,圖像數(shù)組中的每個單元格現(xiàn)在表示uint8類型的整數(shù)。它們的范圍從0到255,提供256種灰度。

你還可以使用np.flipud或者np.fliplr之類的NumPy函數(shù),隨心所欲地以任何方式操縱圖像。

kitten = imread("images/horizontal_flip.jpg")

horizontal_flipped = np.fliplr(kitten)

compare(kitten, horizontal_flipped, "Horizontally Flipped Image")

ball = imread("images/upside_down.jpg")

vertically_flipped = np.flipud(ball)

compare(ball, vertically_flipped, "Vertically Flipped Image")

在“顏色”模塊中,你可以找到許多其他變換函數(shù)來處理圖像中的顏色。

4、顏色通道直方圖

有時,查看每個顏色通道的強(qiáng)度有助于了解顏色分布。我們可以通過切片每個顏色通道并繪制它們的直方圖來實現(xiàn)這一點。以下是執(zhí)行此操作的函數(shù):

def plot_with_h(yuǎn)ist_channel(image, channel):
 

  channels = ["red", "green", "blue"]

  channel_idx = channels.index(channel)

  color = channels[channel_idx]

  extracted_channel = image[:, :, channel_idx]
   

  fig, (ax1, ax2) = plt.subplots(

      ncols=2, figsize=(18, 6)

  )

  ax1.imshow(image)

  ax1.a(chǎn)xis("off")
   

  ax2.hist(extracted_channel.ravel(), bins=256, color=color)
   

  ax2.set_title(f"{channels[channel_idx]} histogram")

除了Matplotlib的一些細(xì)節(jié)之外,你還應(yīng)該注意hist函數(shù)的調(diào)用。提取顏色通道及其數(shù)組后,我們將其展平為1D數(shù)組,并將其傳遞給hist函數(shù)。

bin數(shù)量應(yīng)該是256個,每個像素值對應(yīng)一個-0表示黑色,255表示完全白色。

讓我們使用彩色風(fēng)景圖像:

colorful_scenery = imread("images/colorful_scenery.jpg")

plot_with_h(yuǎn)ist_channel(colorful_scenery, "red")

>>> plot_with_h(yuǎn)ist_channel(colorful_scenery, "green")

>>> plot_with_h(yuǎn)ist_channel(colorful_scenery, "blue")

還可以使用直方圖在將圖像轉(zhuǎn)換為灰度后找出圖像中的亮度:

gray_color_scenery = rgb2gray(colorful_scenery)

plt.hist(gray_color_scenery.ravel(), bins=256);

大多數(shù)像素的值較低,因為景物圖像較暗。

我們將在以下部分探討直方圖的更多應(yīng)用。

過濾器

1、手動閾值

現(xiàn)在,我們來看看有趣的東西——過濾圖像。我們將學(xué)習(xí)的第一個操作是閾值化。讓我們加載一個示例圖像:

stag = imread("images/binary_example.jpg")

>>> show(stag)

閾值分割在圖像分割、目標(biāo)檢測、邊緣或輪廓提取等方面有著廣泛的應(yīng)用,它主要用于區(qū)分圖像的背景和前景。

閾值處理在高對比度灰度圖像上效果最好:

# Convert to graysacle

stag_gray = rgb2gray(stag)

>>> show(stag_gray)

我們將從基本的手動閾值設(shè)置開始,然后轉(zhuǎn)到自動閾值設(shè)置。

首先,我們查看灰度圖像中所有像素的平均值:

>>> stag_gray.mean()

0.20056262759859955

請注意,通過將所有灰度圖像的值除以256,上述灰度圖像的像素在0和1之間歸一化。

我們得到的平均值為0.2,這為我們提供了可能要使用的閾值的初步想法。

現(xiàn)在,我們使用這個閾值來進(jìn)行掩碼操作。如果像素值低于閾值,否則其值將變?yōu)?-黑色或1-白色。換句話說,我們得到一個黑白二值圖像:

# Set threshold

threshold = 0.35

# Binarize

binary_image = stag_gray > threshold

compare(stag, binary_image, "Binary image")

在這個版本中,我們可以更清楚地區(qū)分鹿的輪廓。我們可以反轉(zhuǎn)遮罩,使背景變?yōu)榘咨?/p>

inverted_binary = stag_gray <= threshold

>>> compare(stag, inverted_binary, "Binary image inverted")

2、閾值-全局

雖然嘗試不同的閾值并觀察它們對圖像的影響可能很有趣,但我們通常使用比我們的眼球估計更穩(wěn)健的算法來執(zhí)行閾值分割。

有很多閾值算法,所以可能很難選擇一種。在這種情況下,skimage具有try_all_threshold函數(shù),該函數(shù)在給定的灰度圖像上運行七種閾值算法。讓我們加載一個示例并進(jìn)行轉(zhuǎn)換:

flower = imread("images/global_threshold_ex.jpg")

flower_gray = rgb2gray(flower)

compare(flower, flower_gray)

我們將看看是否可以使用閾值優(yōu)化郁金香的特征:

from skimage.filters import try_all_threshold

fig, ax = try_all_threshold(  

flower_gray, figsize=(10, 8), verbose=False

正如你所看到的,一些算法在這張圖像上工作得更好,而其他算法則很糟糕。otsu算法看起來更好,所以我們將繼續(xù)使用它。

在這一點上,我想提請你注意郁金香的原始圖像:

>>> show(flower)

圖像背景不均勻,因為有太多光線從后面的窗口射進(jìn)來。我們可以通過繪制灰色郁金香的直方圖來證實這一點:

>>> plt.hist(flower_gray.ravel(), bins=256);

正如預(yù)期的那樣,大多數(shù)像素的值都位于直方圖的遠(yuǎn)端,這證實了它們大部分都是明亮的。

為什么這很重要?根據(jù)圖像的亮度,閾值算法的性能也會發(fā)生變化。因此,閾值算法通常有兩種類型:

全局-適用于具有均勻、統(tǒng)一背景的照片

局部-用于不同圖片區(qū)域中具有不同亮度級別的圖像。

郁金香圖像屬于第二類,因為右側(cè)部分比另一半亮得多,使其背景不均勻。我們不能在其上使用全局閾值算法,這就是為什么try_all_threshold中所有算法的性能都很差的原因。

稍后我們將回到郁金香示例和局部閾值。現(xiàn)在,我們將加載另一個亮度更精確的實例,并嘗試自動設(shè)置閾值:

spiral = imread("images/otsu_example.jpg")

spiral_gray = rgb2gray(spiral)

compare(spiral, spiral_gray)

我們將在Skimage中使用通用的全局閾值算法threshold_otsu:

from skimage.filters import threshold_otsu

# Find optimal threshold with `threshold_otsu`

threshold = threshold_otsu(spiral_gray)

# Binarize

binary_spiral = spiral_gray > threshold

compare(spiral, binary_spiral, "Binarized Image w. Otsu Thresholding")

它工作得更好!

3、閾值-局部

現(xiàn)在,我們將使用局部閾值算法。

局部算法不關(guān)注整個圖像,而是關(guān)注像素鄰域,以解釋不同區(qū)域的亮度不均勻。skimage中常見的局部算法為threshold_local函數(shù):

from skimage.filters import threshold_local

local_thresh = threshold_local(flower_gray, block_size=3, offset=0.0002)

binary_flower = flower_gray > local_thresh

compare(flower, binary_flower, "Tresholded flower image")

你必須使用offset參數(shù)來找到符合你需要的最佳圖像。offset是從局部像素鄰域的平均值中減去的常數(shù)。該“像素鄰域”由local_threshold中的block_size參數(shù)確定,該參數(shù)表示算法在每個方向上圍繞每個點查看的像素數(shù)。

顯然,同時調(diào)整offset和block_size是一個缺點,但局部閾值是唯一比手動或全局閾值產(chǎn)生更好結(jié)果的選項。

讓我們再舉一個例子:

from skimage.filters import threshold_local

handwriting = imread("images/chalk_writing.jpg")

handwriting_gray = rgb2gray(handwriting)

# Find optimal threshold using local

local_thresh = threshold_local(handwriting_gray, offset=0.0003)

# Binarize

binary_h(yuǎn)andwriting = handwriting_gray > local_thresh

compare(handwriting, binary_h(yuǎn)andwriting,

   "Binarized image with local thresholding")

正如你所看到的,經(jīng)過閾值處理后,黑板上的筆跡更加精細(xì)。

4、邊緣檢測

邊緣檢測在很多方面都很有用,例如識別對象、從中提取特征、對其進(jìn)行計數(shù)等等。

我們將從基本的Sobel濾波器開始,它在灰度圖像中查找對象的邊緣。我們將加載一張硬幣圖片,并對其使用Sobel濾波器:

from skimage.filters import sobel

coins = imread("images/coins_2.jpg")

coins_gray = rgb2gray(coins)

coins_edge = sobel(coins_gray)

compare(coins, coins_edge, "Images of coins with edges detected")

sobel很直截了當(dāng);你只需在灰色圖像上調(diào)用它即可獲得如上所述的輸出。我們將在后面的部分中看到Sobel的更復(fù)雜版本。

5、平滑

另一種圖像過濾技術(shù)是平滑。許多像下面的雞一樣的圖像可能包含隨機(jī)噪聲,而對ML和DL算法沒有任何有價值的信息。

例如,雞周圍的毛發(fā)會給圖像添加噪聲,這可能會使ML模型的注意力偏離主要對象本身。在這種情況下,我們使用平滑來模糊噪聲或邊緣并降低對比度。

chickens = imread("images/chickens.jpg")

>>> show(chickens)

高斯平滑是最流行和最強(qiáng)大的平滑技術(shù)之一:

from skimage.filters import gaussian

smoothed = gaussian(chickens, multichannel=True, sigma=2)

compare(chickens, smoothed, "An image smoothed with Gaussian smoothing")

你可以通過調(diào)整sigma參數(shù)來控制模糊的效果。如果你正在處理RGB圖像,請不要忘記將multichannel設(shè)置為True。

如果圖像分辨率太高,平滑效果可能肉眼看不到,但仍然有效。

       原文標(biāo)題 : 關(guān)于圖像處理和Python深度學(xué)習(xí)的教程:第一部分

聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權(quán)或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內(nèi)容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

    人工智能 獵頭職位 更多
    掃碼關(guān)注公眾號
    OFweek人工智能網(wǎng)
    獲取更多精彩內(nèi)容
    文章糾錯
    x
    *文字標(biāo)題:
    *糾錯內(nèi)容:
    聯(lián)系郵箱:
    *驗 證 碼:

    粵公網(wǎng)安備 44030502002758號