OpenCV--Python 影象增強
OpenCV--Python 影象增強
影象增強主要解決由於影象的灰度級範圍較小造成的對比度較低的問題,目的就是將輸出影象的灰度級放大到指定的程度,使得影象中的細節看起來增加清晰。對比度增強有幾種常用的方法,如線性變換、分段線性變換、伽馬變換、直方圖正規化、直方圖均衡化、區域性自適應直方圖均衡化等。
1.灰度直方圖
在講解影象增強的方法之前先來認識一下灰度直方圖,灰度直方圖是影象灰度級的函式,用來描述每個灰度級在影象矩陣中的畫素個數或者佔有率。接下來使用程式實現直方圖:
import cv2 as cv import numpy as np import matplotlib.pyplot as plt def calcGrayHist(I): # 計算灰度直方圖 h, w = I.shape[:2] grayHist = np.zeros([256], np.uint64) for i in range(h): for j in range(w): grayHist[I[i][j]] += 1 return grayHist img = cv.imread("../testImages/4/img1.jpg", 0) grayHist = calcGrayHist(img) x = np.arange(256) # 繪製灰度直方圖 plt.plot(x, grayHist, 'r', linewidth=2, c='black') plt.xlabel("gray Label") plt.ylabel("number of pixels") plt.show() # cv.imshow("img", img) # cv.waitKey()
Matplotlib本身也提供了計算直方圖的函式hist,以下由matplotlib實現直方圖的生成:
import cv2 as cv import numpy as np import matplotlib.pyplot as plt img = cv.imread("../testImages/4/img1.jpg", 0) h, w = img.shape[:2] pixelSequence = img.reshape([h * w, ]) numberBins = 256 histogram, bins, patch = plt.hist(pixelSequence, numberBins, facecolor='black', histtype='bar') plt.xlabel("gray label") plt.ylabel("number of pixels") plt.axis([0, 255, 0, np.max(histogram)]) plt.show() cv.imshow("img", img) cv.waitKey()
影象的對比度是通過灰度級範圍來度量的,而灰度級範圍可通過觀察灰度直方圖得到,灰度級範圍越大代表對比度越高;反之對比度越低,低對比度的影象在視覺上給人的感覺是看起來不夠清晰,所以通過演算法調整影象的灰度值,從而調整影象的對比度是有必要的。最簡單的一種對比度增強的方法是通過灰度值的線性變換實現的。
2.線性變換
假設輸入影象為,寬為,高為,輸出影象記為,影象的線性變換可以用以下公式定義:
如下圖所示,當a=1,b=0時,為的一個副本;如果a>1,則輸出影象的對比度比有所增大;如果0<a<1,則的對比度比有所減小。而b值的改變,影響的是輸出影象的亮度,當b>0時,亮度增加;當b<0時,亮度減小。下面程式碼實現:
import numpy as np
a = np.array([[0, 200], [23, 4]], np.uint8)
b = 2 * a
print(b.dtype)
print(b)
'''
uint8
[[ 0 144]
[ 46 8]]
'''
在上面程式碼中,輸入的是一個uint8型別的ndarray,用數字2乘以該陣列,返回的ndarray的資料型別是uint8。注意輸出第0行第1列,200*2應該等於400,但是400超出了uint8的資料範圍,Numpy是通過模運算歸到uint8範圍的,即400%256=144,從而轉換成uint8型別。如果將常數2改為2.0,雖然這個常數只是整型和浮點型的區別,但是結果卻不一樣。程式碼如下:
import numpy as np
a = np.array([[0, 200], [23, 4]], np.uint8)
b = 2.0 * a
print(b.dtype)
print(b)
'''
float64
[[ 0. 400.]
[ 46. 8.]]
'''
可以發現返回的ndarray的資料型別變成了float64,也就是說,相乘的常數是2和2.0會導致返回的ndarray的資料型別不一樣,就會造成200*2的返回值是144,而200*2.0的值卻是400;而對8點陣圖進行對比度增強來說,線性變換計算出的輸出值可能要大於255,需要將這些值截斷為255,而不是取模運算,所以不能簡單地只是用“*”運算來實現線性變換。具體程式碼如下:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 繪製直方圖函式
def grayHist(img):
h, w = img.shape[:2]
pixelSequence = img.reshape([h * w, ])
numberBins = 256
histogram, bins, patch = plt.hist(pixelSequence, numberBins,
facecolor='black', histtype='bar')
plt.xlabel("gray label")
plt.ylabel("number of pixels")
plt.axis([0, 255, 0, np.max(histogram)])
plt.show()
img = cv.imread("../testImages/4/img4.jpg", 0)
out = 2.0 * img
# 進行資料截斷,大於255的值截斷為255
out[out > 255] = 255
# 資料型別轉換
out = np.around(out)
out = out.astype(np.uint8)
# 分別繪製處理前後的直方圖
# grayHist(img)
# grayHist(out)
cv.imshow("img", img)
cv.imshow("out", out)
cv.waitKey()
以上線性變換是對整個灰度級範圍使用了相同的引數,有的時候也需要針對不同灰度級範圍進行不同的線性變換,這就是常用的分段線性變換,經常用於降低較亮或較暗區域的對比度來增強灰度級處於中間範圍的對比度,或者壓低中間灰度級處的對比度來增強較亮或者較暗區域的對比度。從下圖(a)的灰度直方圖(b)可以看出,影象的灰度主要集中在 [100,150]之間,可以通過以下分段線性變換將主要的灰度級拉伸到[50,230],結果如圖(c)所示,對比度拉伸後顯然比原圖能夠更加清晰地看到更多的細節。
img = cv.imread("../testImages/4/img7.jpg", 0)
img = cv.resize(img, None, fx=0.3, fy=0.3)
h, w = img.shape[:2]
out = np.zeros(img.shape, np.uint8)
for i in range(h):
for j in range(w):
pix = img[i][j]
if pix < 50:
out[i][j] = 0.5 * pix
elif pix < 150:
out[i][j] = 3.6 * pix - 310
else:
out[i][j] = 0.238 * pix + 194
# 資料型別轉換
out = np.around(out)
out = out.astype(np.uint8)
# grayHist(img)
# grayHist(out)
cv.imshow("img", img)
cv.imshow("out", out)
cv.waitKey()
線性變換的引數需要根據不同的應用及影象自身的資訊進行合理的選擇,可能需要進行多次測試,所以選擇合適的引數是相當麻煩的。直方圖正規化就是基於當前影象情況自動選取a和b的值的方法,下面介紹這種方法。
3.直方圖正規化
假設輸入影象為,寬為,高為,代表的第r行第c列的灰度值,將中出現的最小灰度級記為,最大灰度級記為,即,為使輸出影象的灰度級範圍為 ,和做以下對映關係:
這個過程就是直方圖正規化,直方圖正規化是一種自動選取a和b的值的線性變換方法,其中
下面使用Python程式碼實現直方圖正規化:
img = cv.imread("../testImages/4/img6.jpg", 0)
# 計算原圖中出現的最小灰度級和最大灰度級
# 使用函式計算
Imin, Imax = cv.minMaxLoc(img)[:2]
# 使用numpy計算
# Imax = np.max(img)
# Imin = np.min(img)
Omin, Omax = 0, 255
# 計算a和b的值
a = float(Omax - Omin) / (Imax - Imin)
b = Omin - a * Imin
out = a * img + b
out = out.astype(np.uint8)
cv.imshow("img", img)
cv.imshow("out", out)
cv.waitKey()
程式碼中計算原圖中出現的最小灰度級和最大灰度級可以使用OpenCV提供的函式
minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(src[, mask])
返回值分別為:最小值,最大值,最小值的位置索引,最大值的位置索引。
正規化函式normalize: dst=cv.normalize(src, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]])
使用函式normalize對影象進行對比度增強時,經常令引數norm_type=NORM_MINMAX,此函式內部實現和咱們上邊講的計算方法是相同的,引數alpha相當於,引數beta相當於。注意,使用normalize可以處理多通道矩陣,分別對每一個通道進行正規化操作。使用該函式的程式碼如下,實現結果和上邊是相同的:
img = cv.imread("../testImages/4/img6.jpg", 0)
out = np.zeros(img.shape, np.uint8)
cv.normalize(img, out, 255, 0, cv.NORM_MINMAX, cv.CV_8U)
cv.imshow("img", img)
cv.imshow("out", out)
cv.waitKey()
4.伽馬變換
假設輸入影象為,寬為,高為,首先將其灰度值歸一化到[0,1]範圍,對於8點陣圖來說,除以255即可。代表歸一化後的第r行第c列的灰度值,輸出影象記為,伽馬變換就是。當時,影象不變。如果影象整體或者感興趣區域較暗,則令可以增加影象對比度;相反,如果影象整體或者感興趣區域較亮,則令可以降低影象對比度。影象的伽馬變換實質上是對影象矩陣中的每一個值進行冪運算,Numpy提供的冪函式power實現了該功能,程式碼實現如下:
img = cv.imread("../testImages/4/img8.jpg", 0)
# 影象歸一化
fi = img / 255.0
# 伽馬變換
gamma = 0.4
out = np.power(fi, gamma)
cv.imshow("img", img)
cv.imshow("out", out)
cv.waitKey()
伽馬變換在提升對比度上有比較好的效果,但是需要手動調節值。下面介紹一種利用影象的直方圖自動調節影象對比度的方法。
5.全域性直方圖均衡化
對於直方圖均衡化的實現主要分為四個步驟:
1、計算影象的灰度直方圖
2、計算灰度直方圖的累加直方圖
3、輸入灰度級和輸出灰度級之間的對映關係
4、根據對映關係迴圈輸出影象的每一個畫素的灰度級
其中的對映關係是:,其中q為輸出的畫素,p為輸入的畫素。可以這麼理解,這一項相當於是灰度直方圖的累加概率直方圖(範圍在0~1之間),再將此範圍放大至0~255之間便得到輸出影象的畫素。下面使用程式來實現:
def equalHist(img):
# 灰度影象矩陣的高、寬
h, w = img.shape
# 第一步:計算灰度直方圖
grayHist = calcGrayHist(img)
# 第二步:計算累加灰度直方圖
zeroCumuMoment = np.zeros([256], np.uint32)
for p in range(256):
if p == 0:
zeroCumuMoment[p] = grayHist[0]
else:
zeroCumuMoment[p] = zeroCumuMoment[p - 1] + grayHist[p]
# 第三步:根據累加灰度直方圖得到輸入灰度級和輸出灰度級之間的對映關係
outPut_q = np.zeros([256], np.uint8)
cofficient = 256.0 / (h * w)
for p in range(256):
q = cofficient * float(zeroCumuMoment[p]) - 1
if q >= 0:
outPut_q[p] = math.floor(q)
else:
outPut_q[p] = 0
# 第四步:得到直方圖均衡化後的影象
equalHistImage = np.zeros(img.shape, np.uint8)
for i in range(h):
for j in range(w):
equalHistImage[i][j] = outPut_q[img[i][j]]
return equalHistImage
img = cv.imread("../testImages/4/img1.jpg", 0)
# 使用自己寫的函式實現
equa = equalHist(blur)
# grayHist(img, equa)
# 使用OpenCV提供的直方圖均衡化函式實現
# equa = cv.equalizeHist(img)
cv.imshow("img", img)
cv.imshow("equa", equa)
cv.waitKey()
理解了上述程式碼,對於OpenCV提供的函式 equalizeHist() 就可以輕鬆掌握了,使用方法很簡單,只支援對8點陣圖的處理。雖然全域性直方圖均衡化方法對提高對比度很有效,但是均衡化處理以後暗區域的噪聲可能會被放大,變得清晰可見,而亮區域可能會損失資訊。為了解決該問題,提出了自適應直方圖均衡化(Aptive Histogram Equalization)方法。
6.限制對比度的自適應直方圖均衡化
自適應直方圖均衡化首先將影象劃分為不重疊的區域塊,然後對每一個塊分別進行直方圖均衡化。顯然,在沒有噪聲影響的情況下,每一個小區域的灰度直方圖會被限制在一個小的灰度級範圍內;但是如果有噪聲,每一個分割的區域塊執行直方圖均衡化後,噪聲會被放大。為了避免出現噪聲這種情況,提出了“限制對比度”(Contrast Limiting),如果直方圖的bin超過了提前預設好的“限制對比度”,那麼會被裁剪,然後將裁剪的部分均勻分佈到其他的bin,這樣就重構了直方圖。下面介紹OpenCV實現的限制對比度的自適應直方圖均衡化函式,
img = cv.imread("../testImages/4/img3.jpg", 0)
img = cv.resize(img, None, fx=0.5, fy=0.5)
# 建立CLAHE物件
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
# 限制對比度的自適應閾值均衡化
dst = clahe.apply(img)
# 使用全域性直方圖均衡化
equa = cv.equalizeHist(img)
# 分別顯示原圖,CLAHE,HE
cv.imshow("img", img)
cv.imshow("dst", dst)
cv.imshow("equa", equa)
cv.waitKey()
OpenCV提供的函式:cv.createCLAHE([, clipLimit[, tileGridSize]])
引數 | 解釋 |
clipLimit | 對比度限制的閾值,預設為40 |
tileGridSize | 用於直方圖均衡的區域塊大小。 輸入影象將被分成相同大小的矩形塊。 tileGridSize定義行和列中的塊數。預設為(8,8) |
上圖顯示了對原圖(a)進行限制對比度自適應直方圖均衡化(CLAHE)和全域性直方圖均衡化(HE)的效果,會發現,原圖中比較亮的區域,經過HE處理後出現了失真的情況,而且出現了明顯的噪聲,而CLAHE避免了這兩種情況。
7.總結
對比度增強只是影象增強方法中的一種手段,對比度拉伸的方法受影象噪聲的影響會明顯,下一篇介紹去除噪聲的方法,去噪之後再使用對比度增強技術效果會更好。
本篇文章講解了灰度直方圖的視覺化、線性變換、分段線性變換、直方圖正規化、伽馬變換、全域性直方圖均衡化、限制對比度的自適應直方圖均衡化,使用到的函式有 minMaxLoc(),normalize(),equalizeHist(),createCLAHE()。
【轉載】:https://blog.csdn.net/m0_38007695/article/details/82718107