乾貨 | OpenCV看這篇就夠了,9段程式碼詳解影像變換基本操作
- 2019 年 10 月 7 日
- 筆記
來源 | 大數據(ID:hzdashuju)
【導讀】OpenCV是一個以BSD許可證開源的、跨平台的電腦視覺庫。它提供了Python、C++、Java、Matlab等多種程式語言介面。它集成了很多電腦視覺演算法,具有非常強大的功能,是電腦視覺中最為著名的一個庫。在本文中,我們將要介紹OpenCV的一些基本用法。
01 OpenCV介紹
OpenCV是使用C++進行編寫的、以BSD許可證開放源程式碼的、跨平台的電腦視覺庫。它提供了上百種電腦視覺、機器學習、影像處理等相關演算法,新版本的OpenCV支援Tensorflow、Caffe等深度學習框架。
OpenCV的底層優化處理得很好,能夠支援多核處理,能夠利用硬體實現加速。由於該庫是以BSD許可證進行開源的,因此可以被免費應用在科學研究與商業應用中。
OpenCV庫是由英特爾公司下屬的俄羅斯技術團隊發起的,由於優異的性能、免費、開源、演算法豐富、使用簡單等優點,自從項目被發起後便得到迅猛發展,越來越多的組織和個人加入到源程式碼的貢獻隊伍中,這也在客觀進一步促進了OpenCV庫的發展。
OpenCV在諸多領域得到了廣泛的應用,例如物體檢測、影像識別、運動跟蹤、增強現實(AR)、機器人等場景。我們在本書中需要對影像進行處理時,需要用到OpenCV庫。
OpenCV的安裝也比較簡單,在Python中,通過pip包管理工具就可以實現安裝:
pip install opencv-python
如果在anaconda環境中安裝OpenCV,則通過下面的方法進行安裝:
conda install opencv
安裝完畢OpenCV後,可以通過下述方法來查看是否安裝成功:
# 查看引入OpenCV庫時是否報錯 import cv2 # 查看安裝的版本 cv2.__version__ # 我這裡顯示的版本資訊是 '3.4.1'
以下介紹OpenCV基本操作。
02 存取數據
OpenCV中的圖片以RGB的形式存儲,只不過再OpenCV中的顏色通道順序不是RGB而是BGR。這可以歸結為一個歷史遺留原因。因為OpenCV庫的研發歷史比較「悠久」,在那個時代,BGR是數位相機設備的主流表示形式。這一點伴隨著OpenCV的發展一直沒有被改變,我們在後面編寫程式碼的時候應該注意到通道順序的問題。
我們看一下OpenCV中存儲圖片的形式,圖4-2是按照BGR順序存儲的RGB顏色模型的圖片,對於相同的數據,我們也可以將其分別拆分為藍色、綠色、紅色的顏色矩陣,如圖4-3所示。

▲圖4-2 OpenCV中以BGR形式存儲的彩色圖片

▲圖4-3 將彩色圖片拆分成三個顏色通道存儲的形式
通過圖4-2和圖4-3,我們知道了OpenCV存儲圖片的形式。那麼在Python環境中的OpenCV庫底層存儲這顏色的數據結構就是array類型。OpenCV將圖片讀取進來,經過解碼後以array形式存儲。通過下面的例子,我們看一下OpenCV中圖片的讀取和存儲方法。
- 程式碼清單① OpenCV中圖片的讀取和存儲
import cv2 import numpy as np # 使用imread()方法讀入一個圖片 image = cv2.imread("lena.jpg") # 看一下數據的存儲維度 image.shape # 返回:(121, 121, 3) # 將讀入的數據image列印出來 print(image) ''' 如果讀入數據失敗,返回值將不是一個array類型,而是None 我們可以看到圖片數據的存儲形式: [[[113 152 227] [109 153 230] [104 152 230] ..., [ 58 93 166] [119 156 212] [149 182 232]] [[107 149 224] [103 149 226] [ 97 149 225] ..., [ 79 112 175] [ 77 108 159] [ 65 91 137]] [[101 148 222] [ 96 146 222] [ 91 146 221] ..., [ 56 80 132] [ 3 22 65] [ 0 3 40]] ..., [[ 21 40 45] [ 24 37 45] [ 34 41 50] ..., [ 20 34 57] [ 7 24 50] [ 8 27 54]] [[ 17 35 36] [ 20 32 38] [ 22 29 38] ..., [ 13 29 52] [ 28 45 72] [ 41 59 90]] [[ 15 31 30] [ 19 31 35] [ 21 28 37] ..., [ 13 29 52] [ 48 64 93] [ 71 90 123]]] ''' # 將存儲圖片數據的image變數寫到磁碟中,寫出的文件名為lena.bak.jpg # 其返回值結果為True代表寫入成功,反之代表失敗 cv2.imwrite("lena.bak.jpg",image)
這裡面有一個問題需要注意:OpenCV判斷圖片的格式是通過擴展名來實現的,如果我們寫出的文件名為 lena.jpg.bak 那麼可能會報錯:
could not find a writer for the specified extension in function cv::imwrite_
所以,我們在使用OpenCV時候要注意圖片文件的擴展名。
OpenCV相比於其他的庫,最大的特點是對影像的處理功能非常完備。OpenCV能夠實現對圖片顏色和形狀的變換。
03 顏色變換
圖片的顏色變換可以有很多種類,譬如可以對彩色圖片進行灰度化處理,調節圖片的亮度和對比度,將圖片轉換成負片的形式等。這些操作都是表現在對圖片的顏色處理上,下面我們介紹幾種圖片的常用顏色變換。
1. 灰度化
我們在平時接觸到的圖片大多都是彩色圖片,存儲的顏色模型一般也都是RGB模型。對於彩色圖片我們前面提到過它的存儲形式,相當於三個顏色通道分別用各自的顏色矩陣來記錄數據。對於灰度影像來講,它自然沒有三個通道的說法,它的表現形式是一張矩陣,沒有RGB三個不同的顏色通道。
彩色圖片是可以轉換為灰度影像的,雖然在轉換為灰度影像的過程中丟失了顏色資訊,但是卻保留了圖片的紋理、線條、輪廓等特徵,這些特徵往往比顏色特徵更重要。
將彩色圖片轉換為灰度圖片後,存儲的數據量自然而然也隨之減少,這樣就會帶來一個明顯的好處:對圖片進行處理時的計算量也將會減少很多,這一點在工程實踐中非常重要,大家會在後面的內容中進一步體會。下面我們簡述一下在OpenCV中將彩色圖片轉換為灰度圖片的過程。
- 程式碼清單② 使用OpenCV將圖片灰度化處理
import numpy as np import cv2 img = cv2.imread("lena.jpg") print(img.shape) # (121, 121, 3) # 使用cv2.cvtColor() 方法將彩色圖片轉換為灰度圖片 gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) print(gray_img.shape) # (121, 121) # 將轉換後的灰度圖片回復成BGR形式 img2 = cv2.cvtColor(gray_img,cv2.COLOR_GRAY2BGR) print(img2.shape) # (121, 121, 3) # 輸出彩色圖片img的內容 print(img) ''' [[[113 152 227] [109 153 230] [104 152 230] ..., [ 58 93 166] [119 156 212] [149 182 232]] [[107 149 224] [103 149 226] [ 97 149 225] ..., [ 79 112 175] [ 77 108 159] [ 65 91 137]] [[101 148 222] [ 96 146 222] [ 91 146 221] ..., [ 56 80 132] [ 3 22 65] [ 0 3 40]] ..., [[ 21 40 45] [ 24 37 45] [ 34 41 50] ..., [ 20 34 57] [ 7 24 50] [ 8 27 54]] [[ 17 35 36] [ 20 32 38] [ 22 29 38] ..., [ 13 29 52] [ 28 45 72] [ 41 59 90]] [[ 15 31 30] [ 19 31 35] [ 21 28 37] ..., [ 13 29 52] [ 48 64 93] [ 71 90 123]]] ''' # 輸出將灰度圖片重新轉換為BGR形式圖片後的內容 print(img2) ''' [[[170 170 170] [171 171 171] [170 170 170] ..., [111 111 111] [169 169 169] [193 193 193]] [[167 167 167] [167 167 167] [166 166 166] ..., [127 127 127] [120 120 120] [102 102 102]] [[165 165 165] [163 163 163] [162 162 162] ..., [ 93 93 93] [ 33 33 33] [ 14 14 14]] ..., [[ 39 39 39] [ 38 38 38] [ 43 43 43] ..., [ 39 39 39] [ 30 30 30] [ 33 33 33]] [[ 33 33 33] [ 32 32 32] [ 31 31 31] ..., [ 34 34 34] [ 51 51 51] [ 66 66 66]] [[ 29 29 29] [ 31 31 31] [ 30 30 30] ..., [ 34 34 34] [ 71 71 71] [ 98 98 98]]] '''
在上面的例子中,我們看到使用cvtColor() 函數可以將彩色圖片轉換為灰度圖片,經過轉換後的圖片shape屬性減少了一個維度,所以這個過程也可以看作是一個降維的過程。
在cvtColor() 函數取convert color 之意,第二個參數表示的是轉換操作的類別。這裡我們是將BGR形式的圖片轉換為灰度圖片,所以使用 cv2.COLOR_BGR2GRAY 常量來表示,當然如果將灰度圖片轉換為BGR形式的圖片,也可以傳入cv2.COLOR_GRAY2BGR 常量。
在程式碼清單②中做了一個實驗:嘗試將灰度圖片gray_img 再次轉換為BGR形式的彩色圖片,發現轉換後的圖片無法恢復原先不同顏色通道的數值,OpenCV所採用的方法是將所有的顏色通道全都置成相同的數值,這個數值就是該點的灰度值。
這也說明了從彩色圖片轉換到灰度圖片的計算是單向的,使用簡單的演算法將灰度圖片恢復為彩色圖片是很難的,OpenCV中所採用的轉換過程只是形式上的轉換,並不是真正將灰度圖片轉換為彩色形式。目前有效果比較好的將灰度圖片轉換為彩色圖片的演算法多是結合機器學習的方法來實現的。
2. 負片轉換
負片是攝影中會經常接觸到的一個詞語,在最早的膠捲照片沖印中是指經曝光和顯影加工後得到的影像。負片操作在很多影像處理軟體中也叫反色,其明暗與原影像相反,其色彩則為原影像的補色。
例如顏色值A與顏色值B互為補色,則其數值的和為255。即RGB影像中的某點顏色為(0,0,255) 則其補色為 (255,255,0)。
由於負片的操作過程比較簡單,OpenCV並沒有單獨封裝負片函數,這裡我們需要將一張圖片拆分為各個顏色通道矩陣,然後分別對每一個顏色通道矩陣進行處理,最後再將其重新組合為一張圖片,示例程式碼如下。
- 程式碼清單③ 負片功能實現
import numpy as np import cv2 # 讀入圖片 img = cv2.imread("lena.jpg") # 獲取高度和寬度,注意索引是高度在前,寬度在後 height = img.shape[0] width = img.shape[1] # 生成一個空的三維張量,用於存放後續三個通道的數據 negative_file = np.zeros((height,width,3)) # 將BGR形式存儲的彩色圖片拆分成三個顏色通道,注意顏色通道的順序是藍、綠、紅 b,g,r = cv2.split(img) # 進行負片化處理,求每個通道顏色的補色 r = 255 - r b = 255 - b g = 255 - g # 將處理後的結果賦值到前面生成的三維張量中 negative_file[:,:,0] = b negative_file[:,:,1] = g negative_file[:,:,2] = r # 看一下生成圖片的數據 negative_file ''' array([[[ 142., 103., 28.], [ 146., 102., 25.], [ 151., 103., 25.], ..., [ 197., 162., 89.], [ 136., 99., 43.], [ 106., 73., 23.]], [[ 148., 106., 31.], [ 152., 106., 29.], [ 158., 106., 30.], ..., [ 176., 143., 80.], [ 178., 147., 96.], [ 190., 164., 118.]], [[ 154., 107., 33.], [ 159., 109., 33.], [ 164., 109., 34.], ..., [ 199., 175., 123.], [ 252., 233., 190.], [ 255., 252., 215.]], ..., [[ 234., 215., 210.], [ 231., 218., 210.], [ 221., 214., 205.], ..., [ 235., 221., 198.], [ 248., 231., 205.], [ 247., 228., 201.]], [[ 238., 220., 219.], [ 235., 223., 217.], [ 233., 226., 217.], ..., [ 242., 226., 203.], [ 227., 210., 183.], [ 214., 196., 165.]], [[ 240., 224., 225.], [ 236., 224., 220.], [ 234., 227., 218.], ..., [ 242., 226., 203.], [ 207., 191., 162.], [ 184., 165., 132.]]]) ''' # 將生成的圖片保存起來,注意存儲圖片文件名中的擴展名 cv2.imwrite("negative_lena.jpg",negative_file)
經過上述程式碼的對影像的處理,我們可以看到經過處理的影像如圖4-4b所示,原始影像如圖4-4a所示。

▲圖4-4 原始影像與經過負片處理後的影像
使用負片對影像進行處理,就是將圖片的顏色進行反轉的過程,這是一個線性變換過程。在影像處理中可以增強暗色區域中的白色或灰色細節。在這個例子中,我們應該同時熟悉對彩色圖片中三個不同顏色通道的拆分以及重新構建影像的方法。
3. 亮度與對比度轉換
一般來說,影像處理運算元是將一幅或多幅影像作為輸入數據,產生一幅輸出影像的函數。影像變換可分為以下兩種。
- 點運算元:基於像素變換,在這一類影像變換中,僅僅根據輸入像素值(有時可加上某些額外資訊)計算相應的輸出像素值。
- 鄰域運算元:基於影像區域進行變換。
兩種常用的點運算元,是用常數對點的像素值進行乘法或加法運算,可以表示為:
g(i, j)=α·f(i, j)+β
其中,影像中點的位置為(i, j), α值代表增益,β值代表偏置。對影像進行亮度和對比度的變換就是一種點運算元,這兩個參數分別可以用來控制對比度與亮度。
熟悉這個原理之後,我們就可以通過調節這兩個參數的值,來對圖片進行對比度或亮度的調節。即將原影像中的每一個像素點都加上一個偏置常數,則可以使圖片的亮度變大,類似地,可以將原圖片中的像素點乘上一個增益係數,來調整圖片的對比度。
但是要注意,圖片像素點的像素值取值範圍是[0,255],一定不應該讓其溢出,否則圖片將不是我們想要的效果。
程式碼清單④分別演示了實現對圖片的像素點進行計算的兩種方法。
- 程式碼清單④ 對圖片亮度與對比度轉換演示
import cv2 import numpy as np # 方法1:通過addWeighted()函數來實現 def convert_img1(img, alpha, beta): blank = np.zeros(img.shape, img.dtype) # dtpye is uint8 return cv2.addWeighted(img, alpha, blank, 0, beta) # 方法2: 通過for循環手動實現,與addWeighted()函數內部實現原理一樣 def convert_img2(img, alpha, beta): rows, cols, channel = img.shape new_img = np.zeros(img.shape, img.dtype) for i in range(0,rows): for j in range(0,cols): for k in range(0,channel): # np.clip() 將數值限制在[0,255]區間,防止數字溢出 new_img[i,j,k] = np.clip( alpha * img[i,j,k] + beta,0,255) return new_img img = cv2.imread('lena.jpg') cv2.imwrite('converted_lena_1.jpg', convert_img1(img,2.2,50)) cv2.imwrite('converted_lena_2.jpg', convert_img2(img,2.2,50))
在上述程式碼中,convert_img1() 函數的addWeighted() 函數的參數列表分別為:
[img1, alpha, img2, beta, gamma]
代表將兩個圖片進行如下計算:
new_img=alpha·img1+beta·img2+gamma
因此,函數convert_img2() 實現的過程,就是通過for循環修改原始圖片的像素值,與convert_img1() 函數的過程是一樣的,只不過convert_img1() 函數調用addWeighted() 函數的img2參數中圖片的像素值都是0罷了。
可以得到變換前的圖片如圖4-5a所示,變換後的圖片如圖4-5b所示。

▲圖4-5 圖片亮度與對比度轉換示例
04 幾何變換
影像的幾何變換是指對影像中的影像像素點的位置進行變換的一種操作,它將一幅影像中的坐標位置映射到新的坐標位置,也就是是改變像素點的空間位置,同時也要估算新空間位置上的像素值。
經過幾何變換的圖片,直觀來看就是其影像的形態發生了變化,例如常見的影像縮放、平移、旋轉等都屬於幾何變換。
1. 影像裁剪
影像的裁剪實現起來相對容易,即在影像數據的矩陣中裁剪出部分矩陣作為新的影像數據,從而實現對影像的裁剪。例如下面的程式碼段落實現了對圖片的裁剪。
- 程式碼清單⑤ 影像裁剪演示
import cv2 import numpy as np img = cv2.imread('lena.jpg') print(img.shape) # (121, 121, 3) new_img = img[20:120,20:120] cv2.imwrite('new_img.jpg',new_img)
上述程式碼實現的過程是將原始的影像從第(20,20) 個像素點的位置,裁剪到(120,120) 處,裁剪的形狀是矩形的。原始影像如圖4-6a所示,裁剪後的影像如圖4-6b所示,影像尺寸明顯變小了。

▲圖4-6 影像裁剪示例
2. 影像尺寸變換
修改影像的尺寸也就是修改影像的大小,OpenCV的resize() 函數可以實現這樣的功能。對影像進行尺寸變換時,必然會丟失或者增加一些像素點,這些像素點怎麼丟棄或者增加呢?
這就需要插值演算法了,resize() 函數提供了指定插值演算法的參數。在縮放時建議使用區域插值cv2.INTER_AREA, 可以避免波紋出現;在放大時建議使用三次樣條插值cv2.INTER_CUBIC, 但是其計算速度相對較,或者線性插值cv2.INTER_LINEAR, 默認情況下所有改變影像尺寸大小的操作使用的是插值法都是線性插值。
我們可以通過設置縮放因子或者直接給出變換後影像的尺寸,則resize() 函數就可以為我們自動生成變換後的影像了。
- 程式碼清單⑥ 使用OpenCV變換影像尺寸
import cv2 import numpy as np img = cv2.imread('lena.jpg') print(img.shape) # (121, 121, 3) new_img = cv2.resize(img,(40,40),interpolation=cv2.INTER_AREA) cv2.imwrite('new_img1.jpg', new_img) print(new_img.shape) # (40, 40, 3) new_img2 = cv2.resize(img,None,fx=0.5, fy=0.6,interpolation=cv2.INTER_AREA) print(new_img2.shape) # 注意影像的寬對應的是列數,高對應的是行數 # (73, 60, 3) cv2.imwrite('new_img2.jpg',new_img2)
如圖4-7所示,原圖如圖4-7a所示,new_img1與new_img2分別如圖4-7b與圖4-7c所示。

▲圖4-7 影像尺寸變換示例
3. 影像旋轉
我們在前面介紹過影像的旋轉原理,OpenCV為我們提供了影像的這種操作,旋轉通過getRotationMatrix2D() 函數來實現。
- 程式碼清單⑦ 使用OpenCV實現影像旋轉
import cv2 import numpy as np img = cv2.imread('lena.jpg') rows, cols, _ = img.shape # 第一個參數為旋轉中心,第二個為旋轉角度,第三個為旋轉後的縮放因子 rotated_img = cv2.getRotationMatrix2D((cols/2,rows/2),45,0.4) cv2.imwrite('dst.jpg',dst)
原圖如圖 4-7a 所示,經過旋轉後的影像如圖4-8所示。

▲圖4-8 經過旋轉後的影像
05 影像雜訊處理
我們曾在前面介紹過雜訊,與訊號相比,雜訊是我們不希望得到的,雜訊量越少則表明影像品質越高。由於影像採集設備的性能不同,有的採集設備獲得的雜訊少,有的則會很多,這可能會干擾到影像的處理。
因此,我們在這裡介紹一下雜訊的消減方法,可以用在影像的預處理上。與此同時,對訓練數據添加適量雜訊,可以使訓練後的模型更加魯棒,對模型的性能提升有一定幫助。因此,為影像添加雜訊可以起到數據增強的作用。
1. 添加雜訊
下面我們演示一下對影像添加兩種常用雜訊的方法,一種是椒鹽雜訊,另一種是高斯雜訊,它們的實現程式碼如程式碼清單⑧所示。
- 程式碼清單⑧ 為影像添加雜訊
import cv2 import numpy as np import random # 添加椒鹽雜訊 def salt_and_pepper_noise(img, percentage): rows, cols = img.shape num = int(percentage * rows * cols) for i in range(num): x = random.randint(0,rows - 1) y = random.randint(0,cols - 1) if random.randint(0,1) == 0: img[x,y] = 0 # 黑色噪點 else: img[x,y] = 255 # 白色噪點 return img # 添加高斯隨機雜訊 def gaussian_noise(img, mu, sigma, k): rows, cols = img.shape for i in range(rows): for j in range(cols): # 生成高斯分布的隨機數,與原始數據相加後要取整 value = int(img[i,j] + k * random.gauss(mu=mu, sigma=sigma)) # 限定數據值的上下邊界 value = np.clip(a_max=255,a_min=0,a=value) img[i,j] = value return img img = cv2.imread('lena.jpg') # 轉換為灰度影像 gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) cv2.imwrite('gray_lena.jpg',gray_img) # 需要複製一份,不然是對影像的引用,後面的操作會重疊 gray_img2 = gray_img.copy() # 保存椒鹽雜訊影像 cv2.imwrite('salt_and_pepper.jpg', salt_and_pepper_noise(gray_img,0.3)) # 保存高斯雜訊影像 cv2.imwrite('gaussian.jpg', gaussian_noise(gray_img2, 0, 1, 32))

▲圖4-9 為影像添加雜訊示例
在圖4-9中,圖4-9b是椒鹽雜訊處理後的影像,圖4-9b是高斯雜訊處理後的影像。
2. 模糊與濾波
OpenCV為我們提供了幾種濾波方法,諸如中值濾波、雙邊濾波、高斯模糊、二維卷積等,這些操作的基本方法如程式碼清單⑨所示。
- 程式碼清單⑨ 影像濾波演示
import cv2 import numpy as np import random salt_and_pepper_img = cv2.imread('salt_and_pepper.jpg') gaussian_img = cv2.imread('gaussian.jpg') # 2維卷積 # 得到一個5*5大小的矩陣作為卷積核,矩陣中的每個值都為0.04 kernel = np.ones((5,5),np.float32) / 25 conv_2d_img = cv2.filter2D(salt_and_pepper_img, -1, kernel) cv2.imwrite('filter_2d_img.jpg', conv_2d_img) # 中值濾波 # 參數5表示選擇附近5*5區域的像素值進行計算 median_blur_img = cv2.medianBlur(salt_and_pepper_img,5) cv2.imwrite('median_blur_img.jpg', median_blur_img) # 高斯模糊 # 標準差參數設置為0是指根據窗口大小(5,5)來自行計算高斯函數標準差 gaussian_blur_img = cv2.GaussianBlur(gaussian_img, (5,5), 0) cv2.imwrite('gaussian_blur_img.jpg', gaussian_blur_img) # 雙邊濾波 # cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace) # 9 代表鄰域直徑,兩個參數75分別代表值域與空域標準差 bilateral_filter_img = cv2.bilateralFilter(gaussian_img, 9, 75, 75) cv2.imwrite('bilateral_filter_img.jpg', bilateral_filter_img)
上述操作加入過雜訊的原始影像如圖4-9所示,這兩個帶有雜訊的影像經過濾波處理的結果如圖4-10所示。
- 對添加過椒鹽雜訊圖片經過二維卷積濾波後的結果
- 對添加過椒鹽雜訊圖片進行中值濾波後的結果
- 對經過高斯雜訊污染後的圖片進行高斯濾波後的結果
- 對經過高斯雜訊污染後的圖片進行雙邊濾波後的結果


▲圖4-10 帶有雜訊的影像經過濾波處理後的結果
06 小結
OpenCV是一個非常優秀且使用廣泛的開源電腦視覺庫,該庫核心程式碼採用C++編寫,提供了多種語言介面。在本文中,我們學習了OpenCV的Python介面使用方法,學習使用OpenCV對影像進行操作的基本方法。
(*本文為AI科技大本營轉載文章,轉載請聯繫作者)