1. 程式人生 > >OpenCV計算機視覺學習(3)——影象灰度線性變換與非線性變換(對數變換,伽馬變換)

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