·對於部分圖像,會出現整體較暗或較亮的情況,這是由於圖片的灰度值范圍較小,即對比度低。實際應用中,通過繪制圖片的灰度直方圖,可以很明顯的判斷圖片的灰度值分布,區分其對比度高低。對於對比度較低的圖片,可以通過一定的算法來增強其對比度。常用的方法有線性變換,伽馬變換,直方圖均衡化,局部自適應直方圖均衡化等。
1. 灰度直方圖及繪制
灰度直方圖用來描述每個像素在圖像矩陣中出現的次數或概率。其橫坐標一般為0-255個像素值,縱坐標為該像素值對應的像素點個數。如下圖所示的圖像矩陣(單通道灰度圖,三通道時可以分別繪制),可以統計每個像素值出現的次數,也可以統計概率,統計像素值出現次數的灰度直方圖如下所示。
灰度直方圖繪制
a, 可以利用opencv的calcHist()統計像素值出現次數,通過matploblib的plot()繪制
b, 可以直接利用matploblib的hist()方法
cv2.calcHist() 參數: img:輸入圖像,為列表,如[img] channels: 計算的通道,為列表,如[0]表示單通道,[0,1]統計兩個通道 mask: 掩模,和輸入圖像大小一樣的矩陣,為1的地方會進行統計(與圖像邏輯與后再統計);無掩模時為None histSize: 每一個channel對應的bins個數,為列表,如[256]表示256個像素值 ranges: bins的邊界,為列表,如[0,256]表示像素值范圍在0-256之間 accumulate: Accumulation flag. If it is set, the histogram is not cleared in the beginning when it is allocated. This feature enables you to compute a single histogram from several sets of arrays, or to update the histogram in time.
如下圖所示,分別繪制了灰度分布曲線圖,灰度分布直方圖和兩者疊加圖形,代碼如下:

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\maze.png",0) hist = cv.calcHist([img],[0],None,[256],[0,256]) plt.subplot(1,3,1),plt.plot(hist,color="r"),plt.axis([0,256,0,np.max(hist)]) plt.xlabel("gray level") plt.ylabel("number of pixels") plt.subplot(1,3,2),plt.hist(img.ravel(),bins=256,range=[0,256]),plt.xlim([0,256]) plt.xlabel("gray level") plt.ylabel("number of pixels") plt.subplot(1,3,3) plt.plot(hist,color="r"),plt.axis([0,256,0,np.max(hist)]) plt.hist(img.ravel(),bins=256,range=[0,256]),plt.xlim([0,256]) plt.xlabel("gray level") plt.ylabel("number of pixels") plt.show()
c.通過np.histogram()和plt.hist()也可以計算出灰度值分布

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\maze.png",0) histogram,bins = np.histogram(img,bins=256,range=[0,256]) print(histogram) plt.plot(histogram,color="g") plt.axis([0,256,0,np.max(histogram)]) plt.xlabel("gray level") plt.ylabel("number of pixels") plt.show()

import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\maze.png",0) rows,cols = img.shape hist = img.reshape(rows*cols) histogram,bins,patch = plt.hist(hist,256,facecolor="green",histtype="bar") #histogram即為統計出的灰度值分布 plt.xlabel("gray level") plt.ylabel("number of pixels") plt.axis([0,255,0,np.max(histogram)]) plt.show()
2. 對比度增強
對比度增強,即將圖片的灰度范圍拉寬,如圖片灰度分布范圍在[50,150]之間,將其范圍拉升到[0,256]之間。這里介紹下 線性變換,直方圖正規化,伽馬變換,全局直方圖均衡化,限制對比度自適應直方圖均衡化等算法。
2.1 線性變換
通過函數y=ax+b對灰度值進行處理,例如對於過暗的圖片,其灰度分布在[0,100], 選擇a=2,b=10能將灰度范圍拉伸到[10, 210]。可以通過np或者opencv的convertScaleAbs()函數來實現,對應參數列表如下:
cv2.convertScaleAbs(src,alpha,beta) src: 圖像對象矩陣
dst:輸出圖像矩陣 alpha:y=ax+b中的a值 beta:y=ax+b中的b值 (對於計算后大於255的像素值會截斷為255)
使用示例代碼和效果圖如下:

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\dark.jpg") print(img) img_bright = cv.convertScaleAbs(img,alpha=1.5,beta=0) print(img_bright) cv.imshow("img",img) cv.imshow("img_bright",img_bright) cv.waitKey(0) cv.destroyAllWindows()

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\dark.jpg") a=1.5 b=0 y = np.float(a)*img+b y[y>255]=255 y = np.round(y) img_bright= y.astype(np.uint8) cv.imshow("img",img) cv.imshow("img_bright",img_bright) cv.waitKey(0) cv.destroyAllWindows()
(實用性:numpy自定義實現時,可以針對不同區間像素點,采用不同系數a,b來動態改變像素值)
2.2 直方圖正規化
對於上述線性變換,系數a,b需要自己摸索設置。直方圖正規化的系數固定,一般將原圖片的像素值范圍映射到[0,255]范圍內。假設原圖片的像素值分布范圍為Input:[min, max], 映射后的范圍為Output:[0,255], 則對應的系數a=(255-0)/(max-min), 系數b=0。即計算公式:
opencv提供了normalize()函數來實現灰度正規化,對應參數列表如下:
cv2.normalize(src,dst,alpha,beta,normType,dtype,mask) 參數: src: 圖像對象矩陣 dst:輸出圖像矩陣(和src的shape一樣) alpha:正規化的值,如果是范圍值,為范圍的下限 (alpha – norm value to normalize to or the lower range boundary in case of the range normalization.) beta:如果是范圍值,為范圍的上限;正規化中不用到 ( upper range boundary in case of the range normalization; it is not used for the norm normalization.) norm_type:normalize的類型 cv2.NORM_L1:將像素矩陣的1-范數做為最大值(矩陣中值的絕對值的和) cv2.NORM_L2:將像素矩陣的2-范數做為最大值(矩陣中值的平方和的開方) cv2.NORM_MINMAX:將像素矩陣的∞-范數做為最大值 (矩陣中值的絕對值的最大值) dtype: 輸出圖像矩陣的數據類型,默認為-1,即和src一樣 mask:掩模矩陣,只對感興趣的地方歸一化
(對於alpha的值,不是很清楚含義,經過試驗,應該是一個錨點,用像素矩陣中的范數計算出來的比例和alpha相乘,當dtype為cv2.NORM_MINMAX時,其計算公式類似如下:
使用示例代碼和效果圖如下:

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\dark.jpg") img_norm=cv.normalize(img,dst=None,alpha=350,beta=10,norm_type=cv.NORM_MINMAX) cv.imshow("img",img) cv.imshow("img_norm",img_norm) cv.waitKey(0) cv.destroyAllWindows()

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\dark.jpg") out_min=0 out_max=255 in_min = np.min(img) in_max = np.max(img) a=float(out_max-out_min)/(in_max-in_min) b=out_min-a*in_min img_norm = img*a+b img_norm = img_norm.astype(np.uint8) cv.imshow("img",img) cv.imshow("img_norm",img_norm) cv.waitKey(0) cv.destroyAllWindows()
2.3 伽馬變換
將輸入圖像的像素值除以255,歸一化到[0,1]區間,然后計算其γ次方值,用公式表示如下,其中I(r,c)為歸一化后的像素值,當γ=1時原像素值不影響,當0<γ<1時,增大像素值,提高圖片對比度;反之γ>1時能降低圖片對比度。
實現代碼和示例如下:

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\dark.jpg") img_norm = img/255.0 #注意255.0得采用浮點數 img_gamma = np.power(img_norm,0.4)*255.0 img_gamma = img_gamma.astype(np.uint8) cv.imshow("img",img) cv.imshow("img_gamma",img_gamma) cv.waitKey(0) cv.destroyAllWindows()
2.4 全局直方圖均衡化
直方圖均衡化的目的是將原圖片每個像素值的像素點個數進行重新分配到[0,255]的256個像素值上,使得每個像素值對應的像素點個數近似相等,即重新分配后,0-255的每個像素值對應的像素點個數近似為(rows*cols/256),(直方圖均衡化對應的數學原理參考:https://blog.csdn.net/superjunenaruto/article/details/52431941)。opencv里面equalizeHist()函數實現了相應的功能,只能處理單通道數據,參數列表如下:
cv2.equalizeHist(src,dst)
src: 圖像對象矩陣,必須為單通道的uint8類型的矩陣數據
dst:輸出圖像矩陣(和src的shape一樣)
實現代碼和示例如下:

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np import math img = cv.imread(r"C:\Users\Administrator\Desktop\dark.jpg",0) img_equalize = cv.equalizeHist(img) cv.imshow("img",img) cv.imshow("img_equalize",img_equalize) cv.waitKey(0) cv.destroyAllWindows()

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np import math #統計灰度分布 def calc_hist(img): rows,cols = img.shape[:2] hist=np.zeros(256,np.uint64) #注意此處的數據格式不要用np.uint8,會溢出但不報錯 for r in range(rows): for c in range(cols): hist[img[r,c]]+=1 return hist def equalize_hist(img): rows,cols = img.shape[:2] hist = calc_hist(img) #計算灰度累積分布 hist_sum=np.zeros([256],np.uint32) #注意數據類型為np.uint32,防止溢出 for i in range(256): if i==0: hist_sum[i]=hist[i] else: hist_sum[i] = hist[i]+hist_sum[i-1] #輸出圖像的灰度分布 output_hist = np.zeros(256,np.uint8) cofficient= 256.0/(rows*cols) for i in range(256): q = cofficient*float(hist_sum[i])-1 if q>=0: output_hist[i]=math.floor(q) else: output_hist[i]=0 #輸出圖像的像素值 output_img=np.zeros([rows,cols],np.uint8) for r in range(rows): for c in range(cols): output_img[r,c]=output_hist[img[r,c]] return output_img if __name__=="__main__": img = cv.imread(r"C:\Users\Administrator\Desktop\dark.jpg",0) img_equalize=equalize_hist(img) cv.imshow("img",img) cv.imshow("img_equalize",img_equalize) cv.waitKey(0) cv.destroyAllWindows()
(通過numpy實現equalizeHist的算法思路參見直方圖均衡化的數學原理,這里沒寫出。。。。。)
2.5 限制對比度自適應直方圖均衡化
相比全局直方圖均衡化,自適應直方圖均衡化將圖像划分為不重疊的小塊,在每一小塊進行直方圖均衡化,但若小塊內有噪聲,影響很大,需要通過限制對比度來進行抑制,即限制對比度自適應直方圖均衡化。如果限制對比度的閾值設置會40,在局部直方圖分布中某個像素值出現次數為45,那么多出的5次像素點會被去掉,平均成其他像素值,如圖所示:
opencv通過createCLAHE()和apply()函數來實現,其對應參數如下:
clahe=cv2.createCLAHE(clipLimit,tileGridSize) clipLimit:限制對比度的閾值,默認為40,直方圖中像素值出現次數大於該閾值,多余的次數會被重新分配 tileGridSize:圖像會被划分的size, 如tileGridSize=(8,8),默認為(8,8) calhe.apply(img) #對img進行限制對比度自適應直方圖均衡化
代碼示例和效果如下:(實際使用中可以先去噪聲,再進行對比度增強)

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np import math img = cv.imread(r"C:\Users\Administrator\Desktop\dark.jpg",0) clahe = cv.createCLAHE(3,(8,8)) dst = clahe.apply(img) cv.imshow("img",img) cv.imshow("dst",dst) cv.waitKey(0) cv.destroyAllWindows()
參考:
官方文檔:https://www.docs.opencv.org/4.1.0/