Lesson 16.2 图像的基本操作
二、從卷積到卷積神經網絡
1 圖像的基本表示
深度視覺是處理圖像的學科,因此我們需要從圖像本身開始說起。來看這張孔雀圖像。
在之前的課程中,我為大家展示過圖像數據在計算機中的基本結構。首先,每張圖像都是以一個三維Tensor或者三維矩陣表示,其三個維度分別是(高度 height,寬度 width,通道 channels)。
高度和寬度往往排列在一起,一般是先高后寬的順序,兩者共同決定圖像的尺寸大小。如上圖,高度1707為則說明圖像在豎直方向有1707個像素點(有1707列),同理寬度則代表水平方向的像素點數目(有2560行),因此如上的孔雀圖總共有1707*2560 = 4,369,920個像素點。
通道是單獨的維度,通常排在高度寬度之后,但也有可能是排在第一位。它決定圖像中的輪廓、線條、色彩,基本決定了圖像中顯示的所有內容,尤其是顏色,因此又叫做色彩空間(color space)。
怎么理解通道呢?你可能在很年幼的時候就學過一些基本的色彩知識(感謝九年義務教育中的美術課),例如,自然界中的顏色都是由“三原色”紅黃藍構成的,將紅色和藍色混在一起會得到紫色,將紅色和黃色混在一起會得到橙色,白色的陽光可以經由三棱鏡分解成七彩的光譜等等。計算機的世界中的顏色也是由基本顏色構成的,在計算機的世界里,用于構成其他顏色的基礎色彩,就叫做“通道”。
我們最常用的三種基本顏色是紅綠藍(Red, Green, Blue, 簡寫為RGB),所以最常用的通道就是RGB通道。我們通過將紅綠藍混在一起,創造豐富的色彩。例如,上面的孔雀圖,其實是下面三張紅、綠、藍三色的圖像疊加而成。
不難注意到,通道就是和原圖尺寸一致、像素點數量一致的、只能夠顯示其通道顏色的圖像。例如,紅色通道就只能顯示紅色,綠色通道就只能顯示綠色,藍色同理。
在通道的每一個像素點上,都有[0,255]之間的整數值,這些整數值代表了“該通道上顏色的灰度”。在圖像的語言中,“灰度”就是明亮程度。數字越接近255,就代表顏色明亮程度越高,越接近通道本身的顏色,數字越接近0,就代表顏色的明亮程度越弱,也就是越接近黑色。(仔細觀察孔雀脖子的部分,孔雀脖子在圖像上是藍色的,這種藍色主要由藍色通道和綠色通道構成,幾乎沒有任何紅色通道的元素,因此在紅色通道的圖片中,孔雀脖子幾乎都是黑的。)
在圖像的矩陣中,我們可以使用索引找出任意像素的三個通道上的顏色的明度,例如,對于第0行、第0列的樣本而言,可以看到一個三列的矩陣,這三列就分別代表著紅色、綠色、藍色的像素值。當三個值都不為0時,這個像素在三個通道上都有顏色。相對應的,最純的紅色會顯示為(255,0,0),最純的藍色就會顯示為(0,0,255),綠色可以此類推。當像素值為(0,0,0)時,這個像素點就為黑色,當像素值為(255,255,255),像素點就為白色。通道上像素的灰度,也就是矩陣中的值幾乎100%決定了圖像會呈現出什么樣子。
在圖像的世界中有許多“通道”類型,就和計算機世界有許多編碼類型一樣,較為常見的通道有以下幾種:
灰度通道:灰度在計算機視覺中是指“明暗程度”,而不是指“灰色”,因而灰度通道也不是指圖像是灰色的通道,而是只有一種顏色的通道,同理,灰度圖像是只有一個通道的圖像。所以RGB通道中的任意一個通道單獨拿出來之后,都可以用灰度(明暗)來顯示。就像我們在Fashion-MNIST數據集中所見到的,灰度圖像的shape最后一列為1,索引出來的值中只有一個數字,這個數字就是這種唯一顏色的明度。當你看見圖像的通道數為1,無論可視化之后圖像顯示什么視覺顏色,它都只是表示單一顏色的明度而已。(沒有人懷疑過為什么fashion-MNIST中的圖繪制出來是黃綠色的嗎?你現在了解,其實藍綠色也只是明度的一種表示)。
RGB色彩空間:數字世界最常見的彩色通道,分別表示紅、綠、藍三種電子成像的基本顏色。
CMYK色彩空間:用于彩色打印機成像的通道,由青色(Cyan)、品紅(Magenta)、黃色(Yellow)和黑色(Black)構成,因此是四維通道,在圖像結構中會顯示為(高度,寬度,4)。
HSV(或HSL)色彩空間:HSV通道是為人們描述和解釋顏色而創建的,H代表色相,S代表飽和度,V代表亮度。(H色相這張圖以什么顏色為主,S顏色是否鮮艷,亮度是亮度)
以上三種空間可以自由切換(會產生數據損失),在OpenCV中也有支持切換的函數可以調用。在計算機視覺中,我們可能遇見各種通道類型的圖片,當我們需要對圖像進行特定操作時,我們必須了解這些通道并了解如何在他們之間進行轉換。
另一種非常常見的色彩空間是RGBA,它也擁有四維通道,分別是(紅色,綠色,藍色,透明度alpha)。透明度alpha的取值范圍在0-1之間。當一個像素的RGB顯示為(0,255,0)時,則說明這個像素里是明度最高的綠色,但加上透明度之后,色彩就會變得“透明”。RGBA可以提供更豐富的色彩樣式,讓圖像的色彩變得更加絢麗。
2 OpenCV令像素變化來改變圖像
一張圖像顯示什么內容是由通道中的像素值決定的,只要能夠操作像素,就能夠改變圖像,這是圖像能夠被“處理”的基本條件。在這一節,我們就使用OpenCV來對圖像進行一些改變。首先,先導入一張自己的圖像來看看,大家可以根據自己的喜好導入相應的圖像。
import numpy as np import matplotlib.pyplot as plt import cv2#讀取計算機中的圖像 img = cv2.imread('/Users/zhucan/Desktop/blue-peacock.jpg') #不要有中文,不要有空格 img # array([[[ 20, 27, 30], # [ 22, 29, 32], # [ 24, 31, 34], # ..., # [109, 153, 84], # [108, 152, 83], # [104, 148, 79]],# [[ 23, 30, 33], # [ 26, 33, 36], # [ 28, 35, 38], # ..., # [108, 152, 83], # [108, 152, 83], # [105, 149, 80]],# [[ 20, 27, 30], # [ 24, 31, 34], # [ 26, 33, 36], # ..., # [107, 151, 82], # [107, 151, 82], # [104, 148, 79]],# ...,# [[ 89, 104, 106], # [ 98, 111, 113], # [ 76, 85, 88], # ..., # [111, 133, 169], # [113, 132, 169], # [111, 133, 169]],# [[ 92, 107, 109], # [100, 113, 115], # [ 78, 87, 91], # ..., # [115, 137, 173], # [116, 138, 174], # [116, 138, 174]],# [[ 94, 112, 113], # [102, 117, 119], # [ 80, 91, 95], # ..., # [118, 139, 177], # [118, 141, 179], # [119, 142, 180]]], dtype=uint8) img[0,0,:] #array([20, 27, 30], dtype=uint8) img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #轉換方式 #OpenCV在讀取圖像時會默認圖像通道的順序是BGR img.shape #(1707, 2560, 3) #展示圖片 plt.figure(dpi=150) #畫布,dpi是分辨率 plt.imshow(img) plt.axis('off'); #不顯示坐標軸 img.dtype #dtype('uint8') a = np.array([0,1,255],dtype="uint8") #一個圖像中的一個像素點,一個像素點上的三個通道的值 a #array([ 0, 1, 255], dtype=uint8) a + 10 #array([10, 11, 9], dtype=uint8) a - 10 #array([246, 247, 245], dtype=uint8) img = img*1.0 #變成浮點數 img #array([[[ 30., 27., 20.], # [ 32., 29., 22.], # [ 34., 31., 24.], # ..., # [ 84., 153., 109.], # [ 83., 152., 108.], # [ 79., 148., 104.]], # # [[ 33., 30., 23.], # [ 36., 33., 26.], # [ 38., 35., 28.], # ..., # [ 83., 152., 108.], # [ 83., 152., 108.], # [ 80., 149., 105.]], # # [[ 30., 27., 20.], # [ 34., 31., 24.], # [ 36., 33., 26.], # ..., # [ 82., 151., 107.], # [ 82., 151., 107.], # [ 79., 148., 104.]], # # ..., # # [[106., 104., 89.], # [113., 111., 98.], # [ 88., 85., 76.], # ..., # [169., 133., 111.], # [169., 132., 113.], # [169., 133., 111.]], # # [[109., 107., 92.], # [115., 113., 100.], # [ 91., 87., 78.], # ..., # [173., 137., 115.], # [174., 138., 116.], # [174., 138., 116.]], # # [[113., 112., 94.], # [119., 117., 102.], # [ 95., 91., 80.], # ..., # [177., 139., 118.], # [179., 141., 118.], # [180., 142., 119.]]]) b = np.array([280,-3,250]) np.clip(b,0,255) #用來將數字限制在某個范圍內的函數 #array([255, 0, 250]) img = img/255 img # array([[[0.11764706, 0.10588235, 0.07843137], # [0.1254902 , 0.11372549, 0.08627451], # [0.13333333, 0.12156863, 0.09411765], # ..., # [0.32941176, 0.6 , 0.42745098], # [0.3254902 , 0.59607843, 0.42352941], # [0.30980392, 0.58039216, 0.40784314]],# [[0.12941176, 0.11764706, 0.09019608], # [0.14117647, 0.12941176, 0.10196078], # [0.14901961, 0.1372549 , 0.10980392], # ..., # [0.3254902 , 0.59607843, 0.42352941], # [0.3254902 , 0.59607843, 0.42352941], # [0.31372549, 0.58431373, 0.41176471]],# [[0.11764706, 0.10588235, 0.07843137], # [0.13333333, 0.12156863, 0.09411765], # [0.14117647, 0.12941176, 0.10196078], # ..., # [0.32156863, 0.59215686, 0.41960784], # [0.32156863, 0.59215686, 0.41960784], # [0.30980392, 0.58039216, 0.40784314]],# ...,# [[0.41568627, 0.40784314, 0.34901961], # [0.44313725, 0.43529412, 0.38431373], # [0.34509804, 0.33333333, 0.29803922], # ..., # [0.6627451 , 0.52156863, 0.43529412], # [0.6627451 , 0.51764706, 0.44313725], # [0.6627451 , 0.52156863, 0.43529412]],# [[0.42745098, 0.41960784, 0.36078431], # [0.45098039, 0.44313725, 0.39215686], # [0.35686275, 0.34117647, 0.30588235], # ..., # [0.67843137, 0.5372549 , 0.45098039], # [0.68235294, 0.54117647, 0.45490196], # [0.68235294, 0.54117647, 0.45490196]],# [[0.44313725, 0.43921569, 0.36862745], # [0.46666667, 0.45882353, 0.4 ], # [0.37254902, 0.35686275, 0.31372549], # ..., # [0.69411765, 0.54509804, 0.4627451 ], # [0.70196078, 0.55294118, 0.4627451 ], # [0.70588235, 0.55686275, 0.46666667]]])現在我們在圖像上做一些改變。之前我們說過,像素值越接近255,就表示圖像的“明度”越高,像素值越越接近0,就表示圖像會變得越暗,當我們對圖像歸一化后,像素值越接近1就越亮,越接近0就越暗。所以我們可以通過對像素值做線性變換,來調整圖像的“明暗”程度。
#調亮畫面 img_ = np.clip(img + 100/255,0,1) #np.clip是一個抹掉范圍外值的函數 plt.figure(dpi=100) plt.imshow(img_) plt.axis('off'); #調暗畫面 img_ = np.clip(img - 100/255,0,1) plt.figure(dpi=100) plt.imshow(img_) plt.axis('off'); #讓畫面更鮮艷 img_ = np.clip(img*0.5,0,1) #原本就很大的值會增長得更快,因此原本就很鮮艷的顏色會變得更加鮮艷,增加對比度 plt.figure(dpi=100) plt.imshow(img_) plt.axis('off'); img = cv2.imread('/Users/zhucan/Desktop/blue-peacock.jpg') #OpenCV默認讀取后的圖像通道是BGR,為了調整飽和度,我們直接將通道轉換為HSV img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(img_hsv)#這里分解出的是uint8,要在uint8上進行數值操作則必須先更換為浮點數 #h += np.clip(s*1.0+100,0,255).astype("uint8") # 色相 s += np.clip(s*1.0+100,0,255).astype("uint8") # 飽和度 #v += np.clip(s*1.0+100,0,255).astype("uint8") # 亮度 final_hsv = cv2.merge((h, s, v)) #為了繪圖,這里是轉回RGB,而不是BGR img_s = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2RGB) plt.figure(dpi=100) plt.imshow(img_s) plt.axis('off'); plt.figure(dpi=150) #畫布,dpi是分辨率 plt.imshow(img) plt.axis('off'); #不顯示坐標軸
你發現了嗎?在掌握了一些淺層圖像原理之后,我們就可以通過改變像素值來改變圖像的模樣,這再次證明了,通道上像素的灰度,也就是張量中的值幾乎100%決定了圖像會呈現出什么樣子。因此,只要給與[0,255]之間的值,令其結構形似一張圖像(高,寬,3),我們甚至可以自己“瞎編”出一張圖像來,所以任意矩陣都可以被以圖像的方式進行可視化。
值得注意的是,由于灰度的存在,圖像是可以被表示成二元函數的,通常寫作f(x,y)f(x, y)f(x,y)或f(i,j)f(i, j)f(i,j)。在這個函數中,函數的兩個自變量是圖像的寬度與高度,函數值就是該通道上的灰度。
是不是看起來和梯度下降中梯度的圖像有些類似呢?由于圖像可以被作為二元函數表示,調整亮度等操作也可以用數學的方式來表示:g(i,j)=α?f(i,j)+βg(i, j)=\alpha \cdot f(i, j)+\betag(i,j)=α?f(i,j)+β
其中,g(i,j)g(i,j)g(i,j)表示輸出圖像,f(i,j)f(i,j)f(i,j)表示輸入圖像,α\alphaα控制對比度,β\betaβ控制亮度。這就是視覺領域最簡單的線性變化。
有了函數,我們自然也可以對圖像的函數進行求導、積分等操作。這種表示大大拓寬了我們可以在圖像上進行的操作,只要我們控制像素值的范圍在[0,255],我們就可以在圖像上進行任意的數學變換。計算機視覺研究如何讓計算機從圖像或圖像序列中獲取信息并理解其信息,其主要目的在于從圖像或圖像序列中提取對世界的描述。什么樣的圖像信息,才有利于對世界進行描述呢?在人們探索這個問題的時候,發現了一個很有趣并且有用的數學變化,下一節我們來談談這種對整個計算機視覺領域影響深遠數學變化。
總結
以上是生活随笔為你收集整理的Lesson 16.2 图像的基本操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lesson 15.2 学习率调度在Py
- 下一篇: Lesson 16.3 卷积操作