一、直方图
是一个反映图像像素分布的统计图,其横坐标就是图像像素的取值, 个数的统计。 纵坐标是该像
二、直方图均衡化作用
1. 增强对比度:可以使图像中的灰度分布更加均匀,从而增强图像的对比度, 使得图像中的细节更加清晰。
2. 提高图像质量:直方图均衡化可以改善图像的视觉效果,使得图像更加鲜明生动。
三、直方图均衡的应用场景
1. 医学影像处理:X光片、CT扫描、核磁共振图像等都会用到直方图均衡化。
2. 摄影:可以改善照片的对比度和色彩平衡,使图像更加生动,色彩更鲜艳。
3. 卫星图像处理:用来增强图像质量,提高图像解译的准确性。
四、直方图均衡化
一副效果好的图像通常在直方图上的分布比较均匀,直方图均衡化就是用来改善图像的全局亮度和对比 度。通俗的讲,就是遍历图像的像素统计出灰度值的个数、比例与累计比例,并重新映射到0-255范围 (也可以是其他范围)内,其实从观感上就可以发现,下面两幅图中前面那张图偏白,后面那幅图分布更均匀。
可以看到均衡化后图片的亮度和对比度效果明显好于原图。
对于直方图均衡化这个组件,其参数hist_method代表直方图均衡化方法,它有两个取值: equalizeHist,createCLAHE。下面会分别说明这两个参数: equalizeHist:原始的直方图均衡化,通过调整图像像素值的分布,使得图像的对比度和亮度得到改善。
该方法适用于图像的灰度分布不均匀,且灰度分布集中在更窄的范围,图像的细节不够清晰且对比度较 低的情况,然而,传统的直方图均衡化方法会引入噪声,并导致图像中出现过度增强的区域。
直方图均衡化方法不会考虑到图像的局部特征和全局对比度的差异
很明显,均衡化全局调整亮度和对比度的原因,脸部太亮,大部分细节都丢失了。自适应均衡化就是用来 解决这一问题的:它在每一个小区域内(默认8×8)进行直方图均衡化。当然,如果有噪点的话,噪点 会被放大,需要对小区域内的对比度进行了限制。
在网格中,在给定像素值附近的对比度放大由变换函数的斜率给出。这与邻域累积分布函数(CDF)的斜率成正比,因此与该像素值处的直方图值成正比。CLAHE通过在计算CDF之前将直方图裁剪到预定义值来限制放大。这限制了CDF的斜率,因此也限制了变换函数的斜率。直方图被裁剪的值,即所谓的剪切极限,取决于直方图的归一化,因此也取决于邻域的大小。公共值将产生的放大限制在3到4之间。最好不要丢弃直方图中超过剪辑限制的部分,而是在所有直方图箱中均匀地重新分配。
重新分配将再次推动一些箱子超过剪辑限制(图中区域为绿色阴影),从而产生一个有效的剪辑限制,该限制大于规定的限制,其确切值取决于图像。如果不希望出现这种情况,则可以递归地重复重新分配过程,直到多余部分可以忽略为止。
所以这个算法全称叫:对比度受限的自适应直方图均 衡化CLAHE(Contrast Limited Adaptive Histogram Equalization)。
其主要步骤为:
1. 将图像分为大小相同的矩形块,块分的越细,图像对比度调整效果相对越好。
2. 统计每个块的图像直方图信息,并生成均衡化直方图H。
3. 根据设置的限制参数强度,对直方图信息进行重新分配:
1. 设置一个阈值T,将均衡化后直方图,从H[0]到H[255]上依次遍历,把大于阈值T的都全部减 去,并统计一共减去了多少像素。
2. 将减去的像素值的加和除以缩放的范围,就是每个灰度值要添加的像素点。
4. 然后重复2、3步骤,直到只有可忽略的部分会超过阈值为止。如果出现无法平均分配的,就在直方 图上等间距分配即可。
5. 为了保证每个区域块之间的颜色的连续性,需要通过插值算法来进行计算每个像素点的像素值, 即:
1. 对分割好的每个块,找到其中心点,如下图里的黑色点就是其周围黑色实线边框的中心点,然 后利用上面的方法计算出中心点均衡化之后的像素值。
2. 对整个图像来说,将其中的所有的像素点分为三大类,第一类是处于四个顶点位置的点(也就 是下图中红色区域的点,其周围只有一个黑色中心点),该区域内的点是使用上面方法所得到 的结果。第二类是处于边缘区域的点(也就是下图中绿色区域的点,其周围有两个黑色中心像 素点),该区域内的点就是采用单线性插值来得到其均衡化的结果。第三类是处于中间区域的 点(也就是下图中蓝色区域的点,其周围有四个黑色中心像素点),该区域内的点根据周围的 四个黑色中心像素点采用双线性插值得到均衡化的结果。
五、均衡化
5.1、直方图均衡化
导入模块
import cv2
import numpy as np
from matplotlib import pyplot as plt
输入图像
img=cv2.imread('hailu.jpg')
灰度化
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
绘制直方图
def calcAndDrawHist(img_gray):
img_hist = cv2.calcHist([img_gray], [0], None, [256], [0, 256])
minVal,maxVal,minLoc,maxLoc=cv2.minMaxLoc(img_hist)
histImg=np.zeros((256,256,3),dtype=np.uint8)
temp=int(256*0.9)
for i in range(256):
intensity = int(temp * img_hist[i].item() / maxVal)
cv2.line(histImg,(i,256),(i,256-intensity),(255,0,0))
return histImg
img_gray_hist=calcAndDrawHist(img_gray)
或
plt.hist(img_gray.ravel(), 256)
plt.show()
均衡化
img_equalizeHist=cv2.equalizeHist(img_gray)
绘制均衡化直方图
img_equalizeHist_hist=calcAndDrawHist(img_equalizeHist)
输出图像
cv2.imshow('img_gray',img_gray)
cv2.imshow('img_gray_hist',img_gray_hist)
cv2.imshow('img_equalizeHist',img_equalizeHist)
cv2.imshow('img_equalizeHist_hist',img_equalizeHist_hist)
cv2.waitKey(0)
完整代码
import cv2 # 导入OpenCV库,用于图像处理
import numpy as np # 导入NumPy库,用于数值计算
from matplotlib import pyplot as plt # 导入matplotlib库用于绘图
# 定义计算并绘制直方图的函数
def calcAndDrawHist(img_gray):
# 计算灰度图像的直方图
img_hist = cv2.calcHist([img_gray], [0], None, [256], [0, 256])
# 获取直方图的最小值、最大值及其位置
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(img_hist)
# 创建一个空的图像用于绘制直方图(256x256像素,3个通道颜色)
histImg = np.zeros((256, 256, 3), dtype=np.uint8)
temp = int(256 * 0.9) # 设置直方图绘制的高度比例
# 绘制直方图
for i in range(256):
intensity = int(temp * img_hist[i].item() / maxVal) # 根据最大值调整线条高度
cv2.line(histImg, (i, 256), (i, 256 - intensity), (255, 0, 0)) # 使用红色绘制直方图线条
return histImg # 返回绘制后的直方图图像
# 读取图像
img = cv2.imread('hailu.jpg')
# 将彩色图像转换为灰度图像
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 计算并绘制灰度图像的直方图
img_gray_hist = calcAndDrawHist(img_gray)
# 对灰度图像进行直方图均衡化
img_equalizeHist = cv2.equalizeHist(img_gray)
# 计算并绘制均衡化后的图像直方图
img_equalizeHist_hist = calcAndDrawHist(img_equalizeHist)
# 使用matplotlib绘制灰度图像的直方图
plt.hist(img_gray.ravel(), 256)
# 显示灰度图像
cv2.imshow('img_gray', img_gray)
# 显示灰度图像的直方图
cv2.imshow('img_gray_hist', img_gray_hist)
# 显示均衡化后的灰度图像
cv2.imshow('img_equalizeHist', img_equalizeHist)
# 显示均衡化后的图像的直方图
cv2.imshow('img_equalizeHist_hist', img_equalizeHist_hist)
# 显示使用matplotlib绘制的直方图
plt.show()
# 等待按键事件
cv2.waitKey(0)
5.2、自适应均衡化
导入模块
import cv2
import numpy as np
from matplotlib import pyplot as plt
输入图像
img=cv2.imread('hailu.jpg')
灰度化
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
绘制直方图
def calcAndDrawHist(img_gray):
img_hist = cv2.calcHist([img_gray], [0], None, [256], [0, 256])
minVal,maxVal,minLoc,maxLoc=cv2.minMaxLoc(img_hist)
histImg=np.zeros((256,256,3),dtype=np.uint8)
temp=int(256*0.9)
for i in range(256):
intensity = int(temp * img_hist[i].item() / maxVal)
cv2.line(histImg,(i,256),(i,256-intensity),(255,0,0))
return histImg
img_gray_hist=calcAndDrawHist(img_gray)
或
plt.hist(img_gray.ravel(), 256)
plt.show()
自适应均衡化
clahe=cv2.createCLAHE(2,(8,8))
img_clahe=clahe.apply(img_gray)
绘制均衡化直方图
img_clahe_hist=calcAndDrawHist(img_clahe)
输出图像
plt.hist(img_gray.ravel(), 256)
plt.hist(img_clahe.ravel(), 256)
cv2.imshow('img_gray',img_gray)
cv2.imshow('img_gray_hist',img_gray_hist)
cv2.imshow('img_clahe',img_clahe)
cv2.imshow('img_clahe_hist',img_clahe_hist)
plt.show()
cv2.waitKey(0)
完整代码
import cv2 # 导入OpenCV库,用于图像处理
import numpy as np # 导入NumPy库,用于数值计算
from matplotlib import pyplot as plt # 导入matplotlib库用于绘图
# 定义计算并绘制灰度图像直方图的函数
def calcAndDrawHist(img_gray):
# 计算灰度图像的直方图
img_hist = cv2.calcHist([img_gray], [0], None, [256], [0, 256])
# 获取直方图的最小值、最大值及其位置
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(img_hist)
# 创建一个空的图像用于绘制直方图(256x256像素,3个通道颜色)
histImg = np.zeros((256, 256, 3), dtype=np.uint8)
temp = int(256 * 0.9) # 设置直方图绘制的高度比例
# 绘制直方图
for i in range(256):
# 计算每个强度值的显示高度
intensity = int(temp * img_hist[i].item() / maxVal)
# 使用红色绘制直方图线条
cv2.line(histImg, (i, 256), (i, 256 - intensity), (255, 0, 0))
return histImg # 返回绘制后的直方图图像
# 读取图像
img = cv2.imread('hailu.jpg')
# 将彩色图像转换为灰度图像
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 计算并绘制灰度图像的直方图
img_gray_hist = calcAndDrawHist(img_gray)
# 创建CLAHE对象,设置对比度限制和网格大小
clahe = cv2.createCLAHE(2, (8, 8))
# 应用CLAHE进行图像增强
img_clahe = clahe.apply(img_gray)
# 计算并绘制均衡化后图像的直方图
img_clahe_hist = calcAndDrawHist(img_clahe)
# 使用matplotlib绘制原灰度图像和CLAHE均衡化图像的直方图
plt.hist(img_gray.ravel(), 256, color='blue', alpha=0.5, label='Original')
plt.hist(img_clahe.ravel(), 256, color='red', alpha=0.5, label='CLAHE')
plt.legend() # 显示图例
# 显示灰度图像
cv2.imshow('img_gray', img_gray)
# 显示灰度图像的直方图
cv2.imshow('img_gray_hist', img_gray_hist)
# 显示经过CLAHE处理后的图像
cv2.imshow('img_clahe', img_clahe)
# 显示CLAHE处理后图像的直方图
cv2.imshow('img_clahe_hist', img_clahe_hist)
# 显示使用matplotlib绘制的直方图
plt.show()
# 等待按键事件
cv2.waitKey(0)
六、库函数
6.1、calcHist()
计算一组数组的直方图。
函数 cv::calcHist 计算一个或多个数组的直方图。用于递增直方图箱的元组元素取自同一位置的相应输入数组。
cv.calcHist( images, channels, mask, histSize, ranges[, hist[, accumulate]] ) -> hist
方法 | 描述 |
---|---|
images | 源数组。它们都应该具有相同的深度、CV_8U、CV_16U 或 CV_32F 以及相同的大小。它们中的每一个都可以有任意数量的通道。 |
nimages | 源图像的数量。 |
channels | 用于计算直方图的 dims 通道列表。第一个数组通道从 0 到 images[0].channels()-1 ,第二个数组通道从 images[0].channels() 计数到 images[0].channels() + images[1].channels()-1,依此类推。 |
mask | 可选掩码。如果矩阵不为空,则它必须是与 images[i] 大小相同的 8 位数组。非零掩码元素标记直方图中计数的数组元素。 |
hist | 输出直方图,它是一个密集或稀疏的 dims -dimensional 数组。 |
dims | 直方图维数必须为正且不大于 CV_MAX_DIMS(等于当前 OpenCV 版本中的 32)。 |
histSize | 每个维度中的直方图大小数组。 |
ranges | 每个维度中直方图箱边界的 dims 数组的数组。当直方图是均匀的( uniform =true)时,对于每个维度 i,指定第 0 个直方图 bin 的下边界(包括L_0)和最后一个直方图 bin histSize[i]-1 的上(不含)边界 \(U_{\texttt{histSize}[i]-1}\) 就足够了。也就是说,在统一直方图的情况下,每个 ranges[i] 都是 2 个元素的数组。当直方图不均匀时 ( uniform=false ),则每个范围 [i] 都包含 histSize[i]+1 元素:\(L_0, U_0=L_1, U_1=L_2, ..., U_{\texttt{histSize[i]}-2}=L_{\texttt{histSize[i]}-1}, U_{\texttt{histSize[i]}-1}\) 。不在 \(L_0\) 和 \(U_{\texttt{histSize[i]}-1}\) 之间的数组元素不计入直方图。 |
uniform | 指示直方图是否均匀的标志 |
accumulate | 累积标志。如果已设置,则在分配直方图时,不会在开始时清除直方图。此功能使您能够从多组数组中计算单个直方图,或及时更新直方图。 |
6.2、minMaxLoc()
查找数组中的全局最小值和最大值。
函数 cv::minMaxLoc 查找最小和最大元素值及其位置。在整个数组中搜索极值,或者,如果 mask 不是空数组,则在指定的数组区域中搜索极值。
在 C++ 中,如果输入是多通道的,则应省略 minLoc、maxLoc 和 mask 参数(即分别将它们保留为 NULL、NULL 和 noArray())。多通道 input 数组不支持这些参数。如果使用多通道输入,并且你需要 minLoc、maxLoc 或 mask 参数,那么首先使用 Mat::reshape 将数组重新解释为单通道。或者,您可以使用 extractImageCOI、mixChannel 或 split 提取特定通道。
在 Python 中,由于绑定生成过程的限制(无法将 minLoc 和 maxLoc 设置为 NULL),因此根本不支持多通道输入。一种解决方法是单独对每个通道进行作或使用 NumPy 实现相同的功能。
cv.minMaxLoc( src[, mask] ) -> minVal, maxVal, minLoc, maxLoc
方法 | 描述 |
---|---|
src | input单通道数组 |
minVal | 指向返回的最小值的指针;如果不需要,则使用 NULL |
maxVal | 指向返回的最大值的指针;如果不需要,则使用 NULL。 |
minLoc | 指向返回的最小位置的指针(在 2D 情况下);如果不需要,则使用 NULL |
maxLoc | 指向返回的最大位置的指针(在 2D 情况下);如果不需要,则使用 NULL。 |
mask | 可选 mask 用于选择子数组 |
6.3、line()
绘制连接两点的线段。
函数 line 在图像中的 pt1 和 pt2 点之间绘制线段。该线被图像边界剪切。对于具有整数坐标的非抗锯齿线,使用 8 连通或 4 连通 Bresenham 算法。粗线绘制有圆角结尾。抗锯齿线是使用高斯过滤绘制的。
cv.line( img, pt1, pt2, color[, thickness[, lineType[, shift]]] ) -> img
方法 | 描述 |
---|---|
img | 图像 |
pt1 | 线条的第一个点 |
pt2 | 线段的第二个点。 |
color | 线条颜色 |
thickness | 线条粗细。 |
lineType | 线路的类型。请参见 LineTypes。 |
shift | 点坐标中的小数位数 |
6.4、equalizeHist()
cv.equalizeHist( src[, dst] ) -> dst
方法 | 描述 |
---|---|
src | 源 8 位单通道图像。 |
dst | 与 src 大小和类型相同的目标图像。 |
6.5、createCLAHE()
创建指向 cv::CLAHE 类的智能指针并对其进行初始化。
cv.createCLAHE( [, clipLimit[, tileGridSize]] ) -> retval
方法 | 描述 |
---|---|
clipLimit | 对比度限制的阈值。 |
tileGridSize | 直方图均衡的网格大小。输入图像将被划分为大小相等的矩形图块。tileGridSize 定义行和列中的图块数。 |
class CLAHE(Algorithm):
# Functions
@_typing.overload
def apply(self, src: cv2.typing.MatLike, dst: cv2.typing.MatLike | None = ...) -> cv2.typing.MatLike: ...
@_typing.overload
def apply(self, src: UMat, dst: UMat | None = ...) -> UMat: ...
def setClipLimit(self, clipLimit: float) -> None: ...
def getClipLimit(self) -> float: ...
def setTilesGridSize(self, tileGridSize: cv2.typing.Size) -> None: ...
def getTilesGridSize(self) -> cv2.typing.Size: ...
def collectGarbage(self) -> None: ...
6.6、ravel()
ravel方法返回一个连续的数组,它尝试以最低的复制操作来返回展平后的数组
numpy.ndarray.ravel(order='C')
函数 | 说明 |
---|---|
order | 可选参数,‘C’ 表示按行,‘F’ 表示按列 |
ravel和flatten的区别:
flatten 方法返回的是原数组的副本。这意味着返回的新数组与原数组是两个独 立的对象,对新数组的修改不会影响原数组。
ravel 方法返回的是原数组的视图(view)或副本(copy),这取决于数组的 顺序。如果数组是连续的(C-style,行优先),则 ravel 返回的是视图,这意 味着对返回数组的修改会影响到原数组。如果数组不是连续的,则 的是副本。可以通过传递 order='F' 参数来请求列优先的视图。