·对于部分图像,会出现整体较暗或较亮的情况,这是由于图片的灰度值范围较小,即对比度低。实际应用中,通过绘制图片的灰度直方图,可以很明显的判断图片的灰度值分布,区分其对比度高低。对于对比度较低的图片,可以通过一定的算法来增强其对比度。常用的方法有线性变换,伽马变换,直方图均衡化,局部自适应直方图均衡化等。
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/