Python----计算机视觉处理(Opencv:直方图均衡化)

发布于:2025-03-25 ⋅ 阅读:(28) ⋅ 点赞:(0)

一、直方图

        是一个反映图像像素分布的统计图,其横坐标就是图像像素的取值, 个数的统计。 纵坐标是该像

二、直方图均衡化作用

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' 参数来请求列优先的视图。