使用Numpy和Opencv完成圖像的基本數據分析(Part III)

摘要: 使用Numpy和Opencv完成圖像的基本數據分析第三部分,主要包含圖像變換、卷積操作等。


使用Numpy和Opencv完成圖像的基本數據分析(Part III)


引言

本文是使用python進行圖像基本處理系列的第三部分,在本人之前的文章裡介紹了一些非常基本的圖像分析操作,見文章《使用Numpy和Opencv完成圖像的基本數據分析Part I》和《使用Numpy和Opencv完成圖像的基本數據分析 Part II》,下面我們將繼續介紹一些有關圖像處理的好玩內容。

本文介紹的內容基本反映了我本人學習的圖像處理課程中的內容,並不會加入任何工程項目中的圖像處理內容,本文目的是嘗試實現一些基本圖像處理技術的基礎知識,出於這個原因,本文繼續使用 SciKit-Image,numpy數據包執行大多數的操作,此外,還會時不時的使用其他類型的工具庫,比如圖像處理中常用的OpenCV等:

本系列分為三個部分,分別為part I、part II以及part III。剛開始想把這個系列分成兩個部分,但由於內容豐富且各種處理操作獲得的結果是令人著迷,因此不得不把它分成三個部分。系列所有的源代碼地址:GitHub-Image-Processing-Python。

在上一篇文章中,我們已經完成了以下一些基本操作。為了跟上今天的內容,回顧一下之前的基本操作:

  • 導入圖像並觀察其屬性
  • 拆分圖層
  • 灰度處理
  • 對像素值使用邏輯運算符
  • 使用邏輯運算符進行掩碼

現在開始本節的內容:

強度變換|Intensity Transformation

首先導入一張圖像作為開始:

%matplotlibinline
import imageio
import matplotlib.pyplot as plt
import warnings
import matplotlib.cbook
warnings.filterwarnings("ignore",category=matplotlib.cbook.mplDeprecation)
pic=imageio.imread('img/parrot.jpg')
plt.figure(figsize=(6,6))
plt.imshow(pic);
plt.axis('off');


使用Numpy和Opencv完成圖像的基本數據分析(Part III)


圖像底片|Image Negative

強度變換函數在數學上定義為:

S = T(r)

其中r是輸入圖像的像素,S是輸出圖像的像素,T是一個轉換函數,它將r的每個像素值映射到s中對應的像素值。

負變換,即恆等變換的逆。在負變換中,輸入圖像的每個像素值從L-1中減去並映射到輸出圖像上。

在這種情況下,完成以下轉換:

S =(L-1)-r

因此,每個像素值都減去255。這樣的操作導致的結果是,較亮的像素變暗,較暗的圖像變亮,類似於圖像底片。

negative =255- pic # neg = (L-1) - img
plt.figure(figsize= (6,6))
plt.imshow(negative);
plt.axis('off');


使用Numpy和Opencv完成圖像的基本數據分析(Part III)


對數變換|Log transformation

對數轉換可以通過以下公式定義:

s = c *log(r + 1)

其中s和r是輸出和輸入圖像的像素值,c是常數。輸入圖像的每個像素值都會加1,之後再進行對數操作,這是因為如果圖像中的像素值為0時,log(0)的結果等於無窮大。因此,為了避免這種情況的發生,輸入圖像中的每個像素值都加1,使最小像素值至少為1。

在對數變換過程中,與較高像素值相比,圖像中的低像素被擴展。較高的像素值在對數變換中被壓縮,這導致圖像增強。

對數變換中的c值調整了我們想要的增強程度:

%matplotlibinline
import imageio
import numpyasnp
import matplotlib.pyplotasplt
pic=imageio.imread('img/parrot.jpg')
gray=lambdargb:np.dot(rgb[...,:3],[0.299,0.587,0.114])
gray=gray(pic)
'''
log transform
-> s = c*log(1+r)
So, we calculate constant c to estimate s
-> c = (L-1)/log(1+|I_max|)
'''
max_=np.max(gray)
def log_transform():
return(255/np.log(1+max_))*np.log(1+gray)
plt.figure(figsize=(5,5))
plt.imshow(log_transform(),cmap=plt.get_cmap(name='gray'))
plt.axis('off');


使用Numpy和Opencv完成圖像的基本數據分析(Part III)


伽馬校正| Gamma Correction

伽馬校正,或通常簡稱為伽瑪,是用於對視頻或靜止圖像系統中的亮度或三刺激值進行編碼和解碼的非線性操作,伽瑪校正也稱為冪律變換。首先,圖像的像素值大小範圍必須從0~255被縮放至0~1.0。然後,通過應用以下等式獲得伽馬校正後的輸出圖像:

Vo = Vi ^(1 / G)

其中Vi是我們的輸入圖像,G是設置的伽瑪值,然後將輸出圖像Vo縮放回0-255範圍。

對於伽馬值而言,G <1有時被稱為編碼伽瑪,並且利用該壓縮冪律非線性進行編碼的過程被稱為伽馬壓縮; Gamma值小於1會將圖像移向光譜的較暗端。

相反,伽馬值G> 1被稱為解碼伽馬,並且膨脹冪律非線性的應用被稱為伽馬展開。Gamma值大於1將使圖像顯得更亮。將伽瑪值設置為G = 1時對輸入圖像沒有影響:

import imageio
import matplotlib.pyplotasplt
# Gamma encoding
pic=image io.imread('img/parrot.jpg')
gamma=2.2# Gamma < 1 ~ Dark ; Gamma > 1 ~ Bright
gamma_correction=((pic/255)**(1/gamma))
plt.figure(figsize=(5,5))
plt.imshow(gamma_correction)
plt.axis('off');


使用Numpy和Opencv完成圖像的基本數據分析(Part III)


伽馬校正的原因|Reason for Gamma Correction

我們應用伽馬校正的原因是,由於我們的眼睛感知顏色和亮度這一過程與數碼相機中的傳感器的工作原理不同。當數碼相機上的傳感器獲得兩倍的光子量時,信號會加倍。但是,我們人類的眼睛的工作原理與這不同,當我們的眼睛感知兩倍的光量時,視野中只有一小部分顯得更亮。因此,數碼相機在亮度之間具有線性關係,而我們人類的眼睛具有非線性關係。為了解釋這種關係,我們應用伽瑪校正。

還有一些其他的線性變換函數,比如:

  • 對比度拉伸(Contrast Stretching)
  • 強度切片(Intensity-Level Slicing)
  • 位平面切片(Bit-Plane Slicing)

卷積|Convolution

在上一篇文章中,對卷積操作作了簡要討論。當計算機看到圖像時,它看到不是一整幅圖像,它的眼裡看到的只是一個像素值數組。假設讀取一個32X32大小的彩色圖像,根據圖像的分辨率和大小,計算機它將看到一個32 x 32 x 3維的數字數組,其中3表示RGB值或三通道。假設現在我們有一個PNG格式的彩色圖像,它的大小是480 x 480。將其讀入後,其表示數組將是480 x 480 x 3維。數組中的所有的每個數字值範圍都在0到255之間,它描述的是那個點的像素強度。

就像我們剛才提到的那樣,假設輸入圖像是一個32 x 32 x 3的像素值數組,解釋卷積的最佳方法是想象一個閃爍在圖像左上方的手電筒。假設手電筒照射區域大小為3 x 3。現在,讓我們假設這個手電筒滑過輸入圖像的所有區域。在機器學習術語中,這個手電筒被稱為過濾器(filter)或內核(kernel),或者有時被稱為權重(weights) 或 掩模(mask),它所照射的區域稱為 感受野(receptive field)。

現在,此過濾器也是一個數字數組,數組中的數字稱為權重或參數,在這裡要著重注意一點,此過濾器的深度必須與輸入圖像的深度相同,即通道數相同,因此此過濾器的尺寸為3 x 3 x 3。

圖像內核 或過濾器是一個小矩陣,用於應用我們可能在Photoshop或Gimp中找到的效果,例如模糊、銳化、輪廓或浮雕等。此外,它們還被用於在機器學習中進行圖像特徵提取(CNN),這是一種用於確定圖像最重要部分的技術。更多相關信息,請查看Gimp關於使用Image kernel的文檔,我們可以該文檔中找到最常見的內核列表 。

現在,讓我們將過濾器放在圖像的左上角。當濾波器圍繞輸入圖像滑動或卷積時,它將濾波器中的值乘以圖像的原始像素值(也稱為計算元素乘法)。這些乘法操作最後都會求和,所以卷積操作後只得到一個數字值。請記住,此數字僅代表過濾器位於圖像的左上角。現在,我們對輸入圖像上的每個位置重複此過程,移動過濾器使其與圖像矩陣的每個像素值進行卷積操作,這個過程需要設置移動步幅,依此類推,完成整幅圖像的卷積操作。輸入圖中的每個唯一位置都會生成一個數字。步幅的取值一般為1,也可以取其它大小的值,但我們關心的是它是否適合輸入圖像。


使用Numpy和Opencv完成圖像的基本數據分析(Part III)


過濾器滑過輸入圖像上的所有位置後,我們會發現,我們剩下的是一個30 x 30 x 1的數組,我們將其稱為激活圖 或特徵圖。將3 x 3過濾器可以放在32 x 32輸入圖像上,可以得到30 x 30大小的陣列,原因是有300個不同的位置,這900個數字映射到30 x 30陣列。我們可以通過以下方式計算卷積圖像後圖像的大小:

  • 卷積:(N-F)/ S + 1

其中N和F分別代表輸入圖像大小和卷積核大小,S代表步幅或步長。因此,對於上述情況,輸出圖像的大小將是

  • 32-31 + 1 = 30

假設我們有一個3x3濾波器,在5x5大小的矩陣上進行卷積,根據等式,我們應該得到一個3x3矩陣,現在讓我們看一下:


使用Numpy和Opencv完成圖像的基本數據分析(Part III)


此外,我們實際上使用的過濾器不止一個,過濾器的數量自己設定,假設過濾器的數量設置為n,則我們的輸出將是28x28xn大小(其中n是特徵圖的數量 )。

通過使用更多的過濾器,我們能夠更好地保留空間維度信息。

然而,對於圖像矩陣邊界上的像素,卷積核的一些元素移動時會出現在圖像矩陣之外,因此不具有來自圖像矩陣的任何對應元素。在這種情況下,我們可以消除這些位置的卷積運算,最終輸出矩陣大小將會小於輸入圖像,或者我們可以對輸入圖像矩陣進行填充(padding),以保證輸出圖像大小維度不變。

為了保持本系列的簡潔而保持內容的完整性,本文提供了全部的資源鏈接,在其中更詳細地解釋了有關內容。

下面,讓我們首先將一些自定義卷積核個數的窗口應用於圖像中,這可以通過平均每個像素值與附近的像素值來處理圖像:

%%time
import numpy as np
import imageio
import matplotlib.pyplot as plt
from scipy.signal import convolve2d
def Convolution(image, kernel):
conv_bucket= []
for d in range(image.ndim):
conv_channel= convolve2d(image[:,:,d], kernel,
mode="same", boundary="symm")
conv_bucket.append(conv_channel)
returnnp.stack(conv_bucket, axis=2).astype("uint8")
kernel_sizes= [9,15,30,60]
fig, axs=plt.subplots(nrows=1, ncols=len(kernel_sizes), figsize=(15,15));
pic =imageio.imread('img:/parrot.jpg')
for k, ax in zip(kernel_sizes, axs):
kernel =np.ones((k,k))
kernel /=np.sum(kernel)
ax.imshow(Convolution(pic, kernel));
ax.set_title("Convolved By Kernel: {}".format(k));
ax.set_axis_off();
Wall time: 43.5 s


使用Numpy和Opencv完成圖像的基本數據分析(Part III)


更多內容可以在此查看,其中已經深入討論了各種類型的內核,並展示了它們之間的差異。

作者信息

Mohammed Innat,機器學習和數據科學研究者

本文由阿里云云棲社區組織翻譯。

文章原標題《Basic Image Data Analysis Using Numpy and OpenCV – Part 3》,譯者:海棠,審校:Uncle_LLD。

相關推薦

推薦中...