. 对于部分图像, 会出现整体较暗或较亮的情况, 这是由于图片的灰度值范围较小, 即对比度低. 实际应用中, 通过绘制图片的灰度直方图, 可以很明显的判断图片的灰度值分布, 区分其对比度高低. 对于对比度较低的图片, 可以通过一定的算法来增强其对比度. 常用的方法有线性变换, 伽马变换, 直方图均衡化, 局部自适应直方图均衡化等.
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()
- View Code
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()
- np.histogram()
- 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()
- plt.hist()
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()
- convertScaleAbs()
- #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 实现
(实用性: 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()
- cv.normalize()
- #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()
numpy 实现类似 normalize
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()
numpy 实现伽马变换
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()
- opencv equalizeHist()
- #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
(通过 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()
- createCLAHE()
参考:
官方文档: https://www.docs.opencv.org/4.1.0/
来源: http://www.bubuko.com/infodetail-3092776.html