使用Numpy和Opencv完成影象的基本資料分析(Part III)
引言
本文是使用python進行影象基本處理系列的第三部分,在本人之前的文章裡介紹了一些非常基本的影象分析操作,見文章《 ofollow,noindex" target="_blank">使用Numpy和Opencv完成影象的基本資料分析Part I 》和《 使用Numpy和Opencv完成影象的基本資料分析 Part II 》,下面我們將繼續介紹一些有關影象處理的好玩內容。
本文介紹的內容基本反映了我本人學習的影象處理課程中的內容,並不會加入任何工程專案中的影象處理內容,本文目的是嘗試實現一些基本影象處理技術的基礎知識,出於這個原因,本文繼續使用 SciKit-Image , numpy 資料包執行大多數的操作,此外,還會時不時的使用其他型別的工具庫,比如影象處理中常用的 OpenCV 等:
本系列分為三個部分,分別為part I、part II以及part III。剛開始想把這個系列分成兩個部分,但由於內容豐富且各種處理操作獲得的結果是令人著迷,因此不得不把它分成三個部分。系列所有的原始碼地址: Python" rel="nofollow,noindex" target="_blank">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');
影象底片|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');
對數變換|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');
伽馬校正| 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');
伽馬校正的原因|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,也可以取其它大小的值,但我們關心的是它是否適合輸入影象。
過濾器滑過輸入影象上的所有位置後,我們會發現,我們剩下的是一個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矩陣,現在讓我們看一下:
此外,我們實際上使用的過濾器不止一個,過濾器的數量自己設定,假設過濾器的數量設定為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
,其中已經深入討論了各種型別的核心,並展示了它們之間的差異。
作者資訊
Mohammed Innat,機器學習和資料科學研究者
本文由阿里云云棲社群組織翻譯。
文章原標題《Basic Image Data Analysis Using Numpy and OpenCV – Part 3》,譯者:海棠,審校:Uncle_LLD。
文章為簡譯,更為詳細的內容, 請檢視原文 。