技術背景
在機器視覺等領域,最基本的圖像處理處理操作,可以通過opencv
這個庫來實現。opencv提供了python的接口,所需安裝的庫為opencv-python
,但是在庫的導入的時候一般用的是import cv2
,因此很多也把opencv-python簡稱為cv2
。
cv2的安裝
如果是使用anaconda所搭建的python的編程環境,一般會事先安裝好cv2這個倉庫。在上面的超鏈接中可以找到適合自己本地環境的anaconda環境進行安裝,這是一個非常常用的python包集成管理工具,其中預安裝了很多python庫,使得我們不需要去手動安裝各種的第三方庫,我們知道自己取手動安裝的過程中,很容易就會遇到一些報錯,解決起來也非常的麻煩。
如果系統中沒有這個庫,可以通過pip來進行安裝和管理:
[dechin@dechin-manjaro cv2]$ python3 -m pip install opencv-python
Requirement already satisfied: opencv-python in /home/dechin/anaconda3/lib/python3.8/site-packages (4.5.1.48)
Requirement already satisfied: numpy>=1.17.3 in /home/dechin/anaconda3/lib/python3.8/site-packages (from opencv-python) (1.20.1)
需要注意的是,這里雖然安裝的時候是使用opencv-python
這個名字,但是在python代碼中調用的時候是用的cv2
這個名字:
[dechin@dechin-manjaro cv2]$ ipython
Python 3.8.5 (default, Sep 4 2020, 07:30:14)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.19.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import cv2
In [2]: quit()
cv2基本圖像操作
首先假定我們已經獲取了這么一個圖片,接下來我們要對這個圖片進行各式各樣的處理(圖片來自於參考鏈接1):
重構大小
我們可以對輸入的圖片進行大小調整,由於大小被改變,因此會涉及到一些插值算法。cv2內置的有線性插值和最近鄰插值等,我們可以直接使用:
# cv2_reshape.py
import cv2
import numpy as np
width = 400
height = 200
img = cv2.imread('test.png') # 讀取圖像
print ('The shape of initial graph is: {}'.format(img.shape)) # 打印原圖大小
img = cv2.resize(img, (width, height), interpolation=cv2.INTER_NEAREST) # 最近鄰插值縮放
print ('The changed shape of graph is: {}'.format(img.shape)) # 打印更改后圖片大小
cv2.imwrite('new_logo.png', img) # 保存圖片
在這個案例中,我們首先讀取了一個516×254的圖片,由於是RGB格式的,因此會有三層圖像。然后通過cv2
將該圖像重構成一個400×200的圖像。上述代碼的執行結果如下:
[dechin@dechin-manjaro cv2]$ python3 cv2_reshape.py
The shape of initial graph is: (254, 516, 3)
The changed shape of graph is: (200, 400, 3)
同時會在當前的目錄下生成一個新的圖像,這個圖像就是經過我們縮放重構之后的圖像:
圖像翻轉
圖像的翻轉也是一種常用的基本操作,cv2里面提供了三種模式的翻轉:編碼為1的橫向翻轉,編碼為0的縱向翻轉,以及編碼為-1的同時翻轉,這里我們演示其中的一種縱向翻轉:
# cv2_rotate.py
import cv2
import numpy as np
img = cv2.imread('test.png')
print ('The shape of initial graph is: {}'.format(img.shape))
img = cv2.flip(img, 0)
print ('The changed shape of graph is: {}'.format(img.shape))
cv2.imwrite('rotate_logo.png', img)
執行完成后,因為是翻轉操作,所以並不會影響圖像大小:
[dechin@dechin-manjaro cv2]$ python3 cv2_rotate.py
The shape of initial graph is: (254, 516, 3)
The changed shape of graph is: (254, 516, 3)
同樣的,會在當前目錄下生成一個翻轉之后的圖像:
灰度圖
在很多圖像特征提取的場景中,其實並不需要RGB配色。比如我們判斷一個圖片中的動物是貓還是狗,這跟貓和狗身上的顏色並沒有太大的關系,因此我們需要一個灰度圖就夠了:
# cv2_color.py
import cv2
import numpy as np
img = cv2.imread('test.png')
print ('The shape of initial graph is: {}'.format(img.shape))
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
print ('The changed shape of graph is: {}'.format(img.shape))
cv2.imwrite('gray_logo.png', img)
因為提取的灰度圖並沒有顯含RGB的配色,因此得到的圖片沒有3層,只有1層:
[dechin@dechin-manjaro cv2]$ python3 cv2_color.py
The shape of initial graph is: (254, 516, 3)
The changed shape of graph is: (254, 516)
同時在本地目錄下會生成一個新的灰度圖:
卷積與滑窗
卷積操作在卷積神經網絡中有重要的應用,其本質是通過滑窗的方式,對原本的圖像進行小范圍內的指定操作,而這個小范圍內的指定操作,則是由卷積核來定義的。我們先來看一下三個卷積核的使用案例,這些卷積核的作用是進行邊緣檢測。並且這三個卷積核都是3×3的大小,也就是說,原圖像經過卷積核操作之后,在橫向和縱向兩個維度的大小都會減去2。
# convolution.py
import cv2
import numpy as np
img = cv2.imread('test.png')
print ('The shape of input img is: {}'.format(img.shape))
conv_img = np.zeros((int(img.shape[0])-2,
int(img.shape[1])-2,
int(img.shape[2])))
for i in range(int(img.shape[0])-2):
for j in range(int(img.shape[1])-2):
conv_img[i][j] = img[i][j] - img[i+2][j] - img[i][j+2] + img[i+2][j+2]
print ('The shape of output img is: {}'.format(conv_img.shape))
cv2.imwrite('conv.png', conv_img)
這個案例所對應的卷積核為:
執行結果如下:
[dechin@dechin-manjaro cv2]$ python3 convolution.py
The shape of input img is: (254, 516, 3)
The shape of output img is: (252, 514, 3)
我們可以看到圖像的最終大小是符合我們所預期的,再看看生成的圖像:
我們可以明顯的發覺,原本圖像中一些不太重要的信息就被忽略了,僅保留了一些邊緣的信息。那么在一些圖像特征識別的場景下,就可以先用卷積層轉換成這種邊緣圖像,再結合池化層和潛藏層構成一個卷積神經網絡,對圖像進行分辨和識別。由於卷積核並不是唯一固定的,因此我們可以對比以下另外兩種卷積核:
# convolution1.py
import cv2
import numpy as np
img = cv2.imread('test.png')
print ('The shape of input img is: {}'.format(img.shape))
conv_img = np.zeros((int(img.shape[0])-2,
int(img.shape[1])-2,
int(img.shape[2])))
for i in range(int(img.shape[0])-2):
for j in range(int(img.shape[1])-2):
conv_img[i][j] = img[i][j+1] + img[i+1][j] + img[i+2][j+1] + img[i+1][j+2] - 4*img[i+1][j+1]
print ('The shape of output img is: {}'.format(conv_img.shape))
cv2.imwrite('conv1.png', conv_img)
這個案例所對應的卷積核為:
執行結果如下:
[dechin@dechin-manjaro cv2]$ python3 convolution1.py
The shape of input img is: (254, 516, 3)
The shape of output img is: (252, 514, 3)
得到的新圖像與第一種卷積核有顯著的差異:
再看看另外一種卷積和:
# convolution2.py
import cv2
import numpy as np
img = cv2.imread('test.png')
print ('The shape of input img is: {}'.format(img.shape))
conv_img = np.zeros((int(img.shape[0])-2,
int(img.shape[1])-2,
int(img.shape[2])))
for i in range(int(img.shape[0])-2):
for j in range(int(img.shape[1])-2):
conv_img[i][j] = -img[i][j] - img[i][j+1] - img[i][j+2] -\
img[i+1][j] + 8*img[i+1][j+1] - img[i+1][j+2] -\
img[i+2][j] - img[i+2][j+1] - img[i+2][j+2]
print ('The shape of output img is: {}'.format(conv_img.shape))
cv2.imwrite('conv2.png', conv_img)
這個案例所對應的卷積核為:
執行結果如下所示:
[dechin@dechin-manjaro cv2]$ python3 convolution2.py
The shape of input img is: (254, 516, 3)
The shape of output img is: (252, 514, 3)
最終生成的圖像與前兩種卷積和都截然不同:
最后還要介紹一種可以銳化圖像的卷積核,與前面介紹的邊緣檢測的卷積核不同的是,銳化的卷積核保留了大部分的圖像特征,只是更加顯著的突出了圖像的的邊緣:
# convolution3.py
import cv2
import numpy as np
img = cv2.imread('test.png')
print ('The shape of input img is: {}'.format(img.shape))
conv_img = np.zeros((int(img.shape[0])-2,
int(img.shape[1])-2,
int(img.shape[2])))
for i in range(int(img.shape[0])-2):
for j in range(int(img.shape[1])-2):
conv_img[i][j] = - img[i][j+1] - img[i+1][j] - img[i+2][j+1] - img[i+1][j+2] + 5*img[i+1][j+1]
print ('The shape of output img is: {}'.format(conv_img.shape))
cv2.imwrite('conv3.png', conv_img)
這個案例所對應的卷積核為:
執行結果如下:
[dechin@dechin-manjaro cv2]$ python3 convolution3.py
The shape of input img is: (254, 516, 3)
The shape of output img is: (252, 514, 3)
可以看到跟其他其中卷積核相比,銳化的卷積核是最接近於原始圖像的:
在上述的幾個輸出圖像中,我們可以大致評估,第一種卷積邊緣檢測的方法有效的去除了很多無用的背景信息,可以在這種類型下的圖像中進行使用,我們可以針對不同的場景選擇不同的操作。
平均池化
在上面所介紹的卷積核中,我們使用的滑窗步長都是1,但是在實際場景中,增大滑窗的步長不僅可以達到很好的效果,還可以很大程度上介紹需要處理的圖像的大小。這里介紹的池化,可以認為是一種特殊的卷積運算。常用的池化方法有最大池化和平均池化,顧名思義,最大池化就是取卷積/池化區域中的最大值,而平均池化則是取平均值。這里我們展示一個平均池化的示例:
# avg_pooling.py
import cv2
import numpy as np
img = cv2.imread('test.png')
print ('The shape of input img is: {}'.format(img.shape))
pooling_img = np.zeros((int(int(img.shape[0])/2),
int(int(img.shape[1])/2),
int(img.shape[2])))
for i in range(int(int(img.shape[0])/2)):
for j in range(int(int(img.shape[1])/2)):
pooling_img[i][j] = (img[2*i][2*j] + img[2*i][2*j+1] + img[2*i+1][2*j] + img[2*i+1][2*j+1])/4
print ('The shape of output img is: {}'.format(pooling_img.shape))
cv2.imwrite('pooling.png', pooling_img)
該平均池化如果用卷積核來表示的化,大概是如下的形式:
上述代碼的輸出結果如下:
[dechin@dechin-manjaro cv2]$ python3 avg_pooling.py
The shape of input img is: (254, 516, 3)
The shape of output img is: (127, 258, 3)
我們發現由於這里的滑窗步長設置為了2,滑窗大小變為了2×2,因此得到的結果圖像縮小了一半。最終得到的池化的圖像如下:
在這個池化的圖片中我們其實並沒有得到太多的信息,更多的作用還是等效的去壓縮一個圖像的信息,尤其是最大池化,可以很好的保留原圖像中的顯著特征。
總結概要
本文介紹了使用opencv-python對輸入圖像進行處理的基本操作,包括圖像讀取、圖像變換等。有了這些基礎的操作支撐后,我們可以執行跟高層次的圖像處理,比如常用於深度學習的卷積和池化操作,這里我們也作了簡單介紹,並給出了使用示例。
版權聲明
本文首發鏈接為:https://www.cnblogs.com/dechinphy/p/cv2.html
作者ID:DechinPhy
更多原著文章請參考:https://www.cnblogs.com/dechinphy/