OpenCV計算機視覺學習(3)——影象灰度線性變換與非線性變換(對數變換,伽馬變換)
如果需要處理的原圖及程式碼,請移步小編的GitHub地址
傳送門:請點選我
如果點選有誤:https://github.com/LeBron-Jian/ComputerVisionPractice
下面主要學習影象灰度化的知識,結合OpenCV呼叫 cv2.cvtColor()函式實現影象灰度化,使用畫素處理方法對影象進行灰度化處理。
1. 影象灰度化
1.1 影象灰度化原理
影象灰度化是將一幅彩色影象轉換為灰度化影象的過程。彩色影象通常包括R、G、B三個分量,分別顯示出紅綠藍等各種顏色,灰度化就是使彩色影象的R、G、B三個分量相等的過程。灰度影象中每個畫素僅具有一種樣本顏色,其灰度是位於黑色與白色之間的多級色彩深度,灰度值大的畫素點比較亮,反之比較暗,畫素值最大為255(表示白色),畫素值最小為0(表示黑色)。
假設某點的顏色由RGB(R,G,B)組成,常見灰度處理演算法如下表所示(盜圖:https://blog.csdn.net/Eastmount/article/details/88785768)
上表中Gray表示灰度處理之後的顏色,然後將原始RGB(R,G,B)顏色均勻地替換成新顏色RGB(Gray,Gray,Gray),從而將彩色圖片轉化為灰度影象。
一種常見的方法是加權平均灰度處理,這種效果是最好的。是將RGB三個分量求和再取平均值,但更為準確的方法是設定不同的權重,將RGB分量按不同的比例進行灰度劃分。比如人類的眼睛感官藍色的敏感度最低,敏感最高的是綠色,因此將RGB按照0.299、0.587、0.114比例加權平均能得到較合理的灰度影象,如公式所示:
下面程式碼是呼叫cvtColor()函式將影象進行灰度化處理的程式碼:
#_*_coding:utf-8_*_ import cv2 import numpy as np import matplotlib.pyplot as plt #讀取原始圖片 src1 = cv2.imread('irving.jpg') src2 = cv2.cvtColor(src1, cv2.COLOR_BGR2RGB) #影象灰度化處理 grayImage = cv2.cvtColor(src1, cv2.COLOR_RGB2GRAY) grayImage1 = cv2.imread('irving.jpg', 0) # #顯示影象 # cv2.imshow("src", src) # cv2.imshow("result", grayImage) # #等待顯示 # cv2.waitKey(0) # cv2.destroyAllWindows() plt.subplot(2,2,1), plt.imshow(src1) plt.xticks([]), plt.yticks([]) plt.title('origin image BGR') plt.subplot(2,2,2), plt.imshow(src2) plt.xticks([]), plt.yticks([]) plt.title('origin image RGB') plt.subplot(2,2,3), plt.imshow(grayImage) plt.xticks([]), plt.yticks([]) plt.title('BGR2gray image') plt.subplot(2,2,4), plt.imshow(grayImage1) plt.xticks([]), plt.yticks([]) plt.title('gray image') plt.show()
處理結果如下:
注意:灰度化的圖,在matplotlib裡顯示影象不是灰色的,這裡因為通道轉換問題,這裡我們在OpenCV中顯示則正常。
2,基於畫素操作的影象灰度化處理
基於畫素操作的影象灰度化處理方法,主要是最大值灰度處理、平均灰度處理和加權平均灰度處理方法。其實之前也學習過,這裡再整理一遍。
2.1 最大值灰度處理方法
該方法的灰度值等於彩色影象R、G、B三個分量中的最大值,其公式如下:
其方法灰度化處理後的灰度圖亮度很高(即灰度偏亮)。
程式碼如下:
#_*_coding:utf-8_*_ import cv2 import numpy as np import matplotlib.pyplot as plt #讀取原始影象 img = cv2.imread('irving.jpg') src = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #獲取影象高度和寬度 height = img.shape[0] width = img.shape[1] #建立一幅影象 grayimg = np.zeros((height, width, 3), np.uint8) #影象最大值灰度處理 for i in range(height): for j in range(width): #獲取影象R G B最大值 gray = max(img[i,j][0], img[i,j][1], img[i,j][2]) #灰度影象素賦值 gray=max(R,G,B) grayimg[i,j] = np.uint8(gray) # #顯示影象 # cv2.imshow("src", img) # cv2.imshow("gray", grayimg) # #等待顯示 # cv2.waitKey(0) # cv2.destroyAllWindows() plt.subplot(1,3,1), plt.imshow(img) plt.xticks([]), plt.yticks([]) plt.title('origin image BGR') plt.subplot(1,3,2), plt.imshow(src) plt.xticks([]), plt.yticks([]) plt.title('origin image RGB') plt.subplot(1,3,3), plt.imshow(grayimg) plt.xticks([]), plt.yticks([]) plt.title('max gray image') plt.show()
圖如下:
2.2 平均灰度處理方法
該方法的灰度值等於彩色影象 R,G,B 是哪個分量灰度值的求和平均值,其計算公式如下所示:
平均灰度處理方法實現程式碼如下所示:
#_*_coding:utf-8_*_ import cv2 import numpy as np import matplotlib.pyplot as plt #讀取原始影象 img = cv2.imread('irving.jpg') src = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #獲取影象高度和寬度 height = img.shape[0] width = img.shape[1] #建立一幅影象 grayimg = np.zeros((height, width, 3), np.uint8) #影象平均灰度處理方法 for i in range(height): for j in range(width): #灰度值為RGB三個分量的平均值 gray = (int(img[i,j][0]) + int(img[i,j][1]) + int(img[i,j][2])) / 3 grayimg[i,j] = np.uint8(gray) # #顯示影象 # cv2.imshow("src", img) # cv2.imshow("gray", grayimg) # #等待顯示 # cv2.waitKey(0) # cv2.destroyAllWindows() plt.subplot(1,3,1), plt.imshow(img) plt.xticks([]), plt.yticks([]) plt.title('origin image BGR') plt.subplot(1,3,2), plt.imshow(src) plt.xticks([]), plt.yticks([]) plt.title('origin image RGB') plt.subplot(1,3,3), plt.imshow(grayimg) plt.xticks([]), plt.yticks([]) plt.title('mean gray image') plt.show()
效果如下:
2.3 加權平均灰度處理方法
該方法根據色彩重要性,將三個分量以不同的權值進行加權平均。由於人眼對綠色的敏感最高,對藍色敏感最低,因此,按下式對RGB三分量進行加權平均能得到較合理的灰度影象。
加權平均灰度處理方法實現程式碼如下:
#_*_coding:utf-8_*_ import cv2 import numpy as np import matplotlib.pyplot as plt #讀取原始影象 img = cv2.imread('irving.jpg') src = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #獲取影象高度和寬度 height = img.shape[0] width = img.shape[1] #建立一幅影象 grayimg = np.zeros((height, width, 3), np.uint8) #加權平均灰度處理方法 for i in range(height): for j in range(width): #灰度加權平均法 gray = 0.30 * img[i,j][0] + 0.59 * img[i,j][1] + 0.11 * img[i,j][2] grayimg[i,j] = np.uint8(gray) # #顯示影象 # cv2.imshow("src", img) # cv2.imshow("gray", grayimg) # #等待顯示 # cv2.waitKey(0) # cv2.destroyAllWindows() plt.subplot(1,3,1), plt.imshow(img) plt.xticks([]), plt.yticks([]) plt.title('origin image BGR') plt.subplot(1,3,2), plt.imshow(src) plt.xticks([]), plt.yticks([]) plt.title('origin image RGB') plt.subplot(1,3,3), plt.imshow(grayimg) plt.xticks([]), plt.yticks([]) plt.title('weighted mean gray image') plt.show()
效果如下:
3,影象灰度線性變換原理
影象的灰度線性變換是通過建立灰度對映來調整原始影象的灰度,從而改善影象的質量,凸顯影象的細節,提高影象的對比度。灰度線性變換的計算公式如下所示:
該公式中DB表示灰度線性變換後的灰度值,DA表示變換前輸入影象的灰度值,α和b為線性變換方程f(D)的引數,分別表示斜率和截距。
- 當α=1,b=0時,保持原始影象
- 當α=1,b!=0時,影象所有的灰度值上移或下移
- 當α=-1,b=255時,原始影象的灰度值反轉
- 當α>1時,輸出影象的對比度增強
- 當0<α<1時,輸出影象的對比度減小
- 當α<0時,原始影象暗區域變亮,亮區域變暗,影象求補
如下圖所示,顯示了影象灰度線性變換對應的效果圖:
下面一一學習。
3.1 影象灰度上移變換
該演算法將實現影象灰度值的上移,從而提升影象的亮度,由於影象的灰度值位於0到255區間之內,所以需要對灰度值進行溢位判斷。其實現程式碼如下所示:
import cv2 import numpy as np import matplotlib.pyplot as plt # 讀取原始影象 img = cv2.imread('irving.jpg') # 影象灰度轉換 grayimage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 獲取影象高度和寬度 height = grayimage.shape[0] width = grayimage.shape[1] # 建立一幅影象 result = np.zeros((height, width), np.uint8) # 影象灰度上移變換 DB = DA + 50 for i in range(height): for j in range(width): if (int(grayimage[i, j] + 50) > 255): gray = 255 else: gray = int(grayimage[i, j] + 50) result[i, j] = np.uint8(gray) # 顯示影象 cv2.imshow("Gray Image", grayimage) cv2.imshow("Result", result) # 等待顯示 cv2.waitKey(0) cv2.destroyAllWindows()
其輸出結果如下所示:
我們可以看到,影象的所有灰度值上移 50,影象變得更白了。(注意:純黑色對應的灰度值為0,純白色的對應的灰度值為255)
3.2 影象對比度增強變換
該演算法將增強影象的對比度。Python實現程式碼如下:
import cv2 import numpy as np import matplotlib.pyplot as plt # 讀取原始影象 img = cv2.imread('irving.jpg') # 影象灰度轉換 grayimage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 獲取影象高度和寬度 height = grayimage.shape[0] width = grayimage.shape[1] # 建立一幅影象 result = np.zeros((height, width), np.uint8) # 影象對比度增強變換 DB = DA * 1.5 for i in range(height): for j in range(width): if (int(grayimage[i, j] * 1.5) > 255): gray = 255 else: gray = int(grayimage[i, j] * 1.5) result[i, j] = np.uint8(gray) # 顯示影象 cv2.imshow("Gray Image", grayimage) cv2.imshow("Result", result) # 等待顯示 cv2.waitKey(0) cv2.destroyAllWindows()
結果如下:
結果就是影象所有的灰度值增強 1.5倍,我們發現變亮了。
3.3 影象對比度減弱變換
該演算法將減弱影象的對比度,Python實現程式碼如下所示:
import cv2 import numpy as np import matplotlib.pyplot as plt # 讀取原始影象 img = cv2.imread('irving.jpg') # 影象灰度轉換 grayimage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 獲取影象高度和寬度 height = grayimage.shape[0] width = grayimage.shape[1] # 建立一幅影象 result = np.zeros((height, width), np.uint8) # 影象對比度減弱變換 DB = DA * 0.8 for i in range(height): for j in range(width): if (int(grayimage[i, j] * 0.8) > 255): gray = 255 else: gray = int(grayimage[i, j] * 0.8) result[i, j] = np.uint8(gray) # 顯示影象 cv2.imshow("Gray Image", grayimage) cv2.imshow("Result", result) # 等待顯示 cv2.waitKey(0) cv2.destroyAllWindows()
結果如下所示:
我們從結果可以看出,影象的所有灰度值減弱,影象變得更暗。
3.4 影象灰度反色變換
反色變換又稱線性灰度求補變換,它是對原影象的畫素值進行反轉,即黑色變為白色,白色變為黑色的過程。其Python實現程式碼如下所示:
import cv2 import numpy as np import matplotlib.pyplot as plt # 讀取原始影象 img = cv2.imread('irving.jpg') # 影象灰度轉換 grayimage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 獲取影象高度和寬度 height = grayimage.shape[0] width = grayimage.shape[1] # 建立一幅影象 result = np.zeros((height, width), np.uint8) # 影象灰度反色變換 DB = 255 - DA for i in range(height): for j in range(width): gray = 255 - grayimage[i, j] result[i, j] = np.uint8(gray) # 顯示影象 cv2.imshow("Gray Image", grayimage) cv2.imshow("Result", result) # 等待顯示 cv2.waitKey(0) cv2.destroyAllWindows()
其結果如下所示:
我們發現,影象處理前後的灰度值是互補的。
注意:影象灰度反色變換在醫學影象處理中有一定的應用,如下圖所示:
4,影象灰度非線性變換
影象的灰度非線性變換主要包括對數變換,冪次變換,指數變換,分段函式變換,通過非線性關係對影象進行灰度處理,下面學習三種常見型別的灰度非線性變換。
4.1 影象灰度非線性變換:DB = DA * DA / 255
下面我們將原始影象的灰度值按照DB = DA * DA / 255 的公式進行非線性變換,其程式碼如下:
import cv2 import numpy as np import matplotlib.pyplot as plt # 讀取原始影象 img = cv2.imread('irving.jpg') # 影象灰度轉換 grayimage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 獲取影象高度和寬度 height = grayimage.shape[0] width = grayimage.shape[1] # 建立一幅影象 result = np.zeros((height, width), np.uint8) # 影象灰度非線性變換 DB = DA * DA / 255 for i in range(height): for j in range(width): gray = int(grayimage[i, j]) * int(grayimage[i, j]) / 255 result[i, j] = np.uint8(gray) # 顯示影象 cv2.imshow("Gray Image", grayimage) cv2.imshow("Result", result) # 等待顯示 cv2.waitKey(0) cv2.destroyAllWindows()
結果如下:
4.2 影象灰度對數變換
影象灰度的對數變換一般表示如公式所示:
其中 c 為尺度比較常數,DA為原始影象灰度值,DB為變換後的目標灰度值。如下圖所示,它表示對數曲線下的灰度值變換情況。
由於對數曲線在畫素值較低的區域斜率大,在畫素值高的區域斜率較小,所以影象經過對數變換後,較暗區域的對比度將有所提升。這種變換可用於增強影象的暗部細節,從而用來擴充套件被壓縮的高值影象中的較暗畫素。
對數變換實現了擴充套件低灰度值而壓縮高灰度值的效果,被廣泛的應用於頻譜影象的顯示中。一個典型的應用是傅立葉頻譜,其動態範圍可能寬達 0 ~ 106 直接顯示頻譜時,影象顯示裝置的動態範圍往往不能滿足要求,從而丟失大量的暗部細節;而在使用對數變換之後,影象的動態範圍被合理的非線性壓縮,從而可以清晰地顯示。在下圖中,未經變換的頻譜經過對數變換後,增加了低灰度區域的對比度,從而增強暗部的細節。(關於傅立葉變換,我後面會學習)
下面程式碼實現了影象灰度的對數變換:
import cv2 import numpy as np import matplotlib.pyplot as plt # 繪製曲線 def log_plot(c): x = np.arange(0, 256, 0.01) y = c * np.log(1 + x) plt.plot(x, y, 'r', linewidth=1) # 正常顯示中文標籤 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.title(u'對數變換函式') plt.xlim(0, 255), plt.ylim(0, 255) plt.show() # 對數變換 def log(c, img): output = c * np.log(1.0 + img) output = np.uint8(output + 0.5) return output # 讀取原始影象 img = cv2.imread('irving.jpg') # 繪製對數變換曲線 log_plot(42) # 影象灰度對數變換 result = log(42, img) # 顯示影象 cv2.imshow("Image", img) cv2.imshow("Result", result) # 等待顯示 cv2.waitKey(0) cv2.destroyAllWindows()
結果如下圖:
對應的對數函式曲線如圖所示:
對數變換對於整體對比度偏低並且灰度值偏低的影象增強效果較好。
4.3 影象灰度伽馬變換
gamma矯正通常用於電匯和監視器系統中重現攝像機拍攝的畫面,在影象處理中也可用於調節影象的對比度,減少影象的光照不均和區域性陰影。
gamma變換又稱為指數變換或冪次變換,是另外一種常用的灰度非線性變換。影象灰度的伽馬變換一般表示如下:
gamma矯正示意圖:
結合上圖看gamma矯正的作用:
- 1,當 gamma<1時,如虛線所示,在低灰度值區域內,動態範圍變大,進而影象對比度增強(當 x € [0, 0.2] 時,y的範圍從 [0, 0.218] 擴大到 [0, 0.5]);在高灰度值區域內,動態範圍變小,影象對比度降低(當 x € [ 0.8, 1]時,y的範圍從 [0.8, 1] 縮小到 [0.9, 1],同時,影象整體的灰度值變大)。簡單來說:會拉伸影象中灰度級較低的區域,壓縮灰度級較高的部分。
- 2,當 gamma >1時,如實線所示,低灰度值區域的動態範圍變小,高灰度值區域在動態範圍內變大,降低了低灰度值區域影象對比度。提高了高灰度值區域影象對比度。同時,影象整體的灰度值變小。簡單來說:會拉伸體現中灰度級較高的區域,壓縮灰度級較低的部分。
- 3,當 gamma=1時,該灰度變換是線性的,此時通過線性方式改變原影象。
當一張圖片的畫素在顏色空間的值都比較大(曝光過度)或者比較小(曝光不足)的情況下,選用合理的R值能減少或者增加其顏色亮度,並使顏色分佈更為均勻和豐富,圖片效果得到明顯的改善。但是這種方法並非對所有在曝光上有缺陷的圖片都適用,這是在使用這個方法的時候必須要注意的。
當一張曝光過度的圖片中存在顏色較暗的區域(比如背光面,陰影,顏色較深的物體),由 gamma函式影象可以看出,當選取較小的R值,過度曝光得不到充分的改善;而當選取較大的R值,該區域將會變成黑乎乎的一片;同樣,當一張曝光不足的圖片存在顏色較亮的區域(比如天空,白色背景,反光物等),如果選取R值較大,則曝光不足得不到改善;而選取較小的R值,這個區域變得更亮,從圖片上看就覺得很“扎眼”。
因此,雖然這種方法對圖片的曝光率具有較好的調整效果,但是如果素材敏感差異較大,在調整較暗或較亮區域時,要注意減少對較暗或較亮區域的影響。事實上可以根據相同的原理,利用插值的方法構造相對應的處理函式,以得到更加精緻的處理效果。
使用Python實現,可以通過除以畫素最大值,先將影象畫素值調整到 0~1之間,然後進行不同的 gamma值的gamma矯正,python程式碼如下:
import cv2 import numpy as np import matplotlib.pyplot as plt # 繪製曲線 def gamma_plot(c, gamma): x = np.arange(0, 256, 0.01) # y = c * x ** gamma y = c * np.power(x, gamma) plt.plot(x, y, 'r', linewidth=1) plt.rcParams['font.sans-serif'] = ['SimHei'] # 正常顯示中文標籤 plt.title(u'伽馬變換函式') plt.xlim([0, 255]), plt.ylim([0, 255]) plt.show() # 伽瑪變換 def gamma(img, c, gamma): # 對映表必須為0~255(改成其他會報錯) gamma_table = c * [np.power(x/255.0, gamma) * 255.0 for x in range(256)] # Numpy陣列預設資料型別為 int32,需要將資料型別轉換為opencv影象適合使用的無符號八位整形 # round() 方法返回浮點數x的四捨五入值。 gamma_table = np.round(np.array(gamma_table)).astype(np.uint8) output_img = cv2.LUT(img, gamma_table) return output_img def gamma_1(img, c, gamma): # 對映表必須為0~255(改成其他會報錯) output_img = c * np.power(img / float(np.max(img)), gamma) * 255.0 output_img = np.uint8(output_img) return output_img # 讀取原始影象 img = cv2.imread('irving.jpg') # 將影象轉換為灰度,我們需要使用灰度圖做gamma矯正 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 繪製伽瑪變換曲線 gamma_plot(1, 4.0) # 影象灰度伽瑪變換 result = gamma(gray, 1, 0.4) result1 = gamma_1(gray, 1, 0.4) # 顯示影象 cv2.imshow("Image", img) cv2.imshow("Result", result) cv2.imshow("Result 1", result1) # 等待顯示 cv2.waitKey(0) cv2.destroyAllWindows()
除以最大值的目的是歸一化,將畫素值調整到0~1之間。
我們看結果:
4.3 opencv中 cv2.LUT() 函式
此函式主要用來其到突出的有用資訊,增強影象的光對比度的作用。通過對input的灰度畫素的改變,可以通過對映的關係得到需要輸出的灰度畫素矩陣 output。
或者這樣講:使用查詢表中的值填充輸出陣列。我們看原始碼解析:
def LUT(src, lut, dst=None): # real signature unknown; restored from __doc__ """ LUT(src, lut[, dst]) -> dst . @brief Performs a look-up table transform of an array. 對陣列執行查詢錶轉換。 . . The function LUT fills the output array with values from the look-up table. Indices of the entries . are taken from the input array. That is, the function processes each element of src as follows: 函式LUT使用查詢表中的值填充輸出陣列。 條目的索引取自輸入陣列。 也就是說,該函式按以下方式處理src的每個元素: . \f[\texttt{dst} (I) \leftarrow \texttt{lut(src(I) + d)}\f] . where . \f[d = \fork{0}{if \(\texttt{src}\) has depth \(\texttt{CV_8U}\)}{128}{if \(\texttt{src}\) has depth \(\texttt{CV_8S}\)}\f] . @param src input array of 8-bit elements. 8位元素的輸入陣列。 . @param lut look-up table of 256 elements; in case of multi-channel input array, the table should . either have a single channel (in this case the same table is used for all channels) or the same . number of channels as in the input array. 256個元素的查詢表; 如果是多通道輸入陣列,則該表應具有單個通道 (在這種情況下,所有通道都使用相同的表)或與輸入陣列中的通道數相同。 . @param dst output array of the same size and number of channels as src, and the same depth as lut. 輸出陣列,其大小和通道數與src相同,深度與lut相同。 . @sa convertScaleAbs, Mat::convertTo """ pass
參考文獻:
https://blog.csdn.net/Rothwale/article/details/79189032
https://blog.csdn.net/Eastmount/article/details/88785768
https://blog.csdn.net/akadiao/article/details/79679306
https://blog.csdn.net/Eastmount/article/details/88858696
https://blog.csdn.net/Eastmount/article/details/8892