三通道的RGB 和 BGR 的图像数据在 NumPy 数组中的形状都是 [H, W, 3],它们的核心区别在于 通道顺序

示例:一个像素值为 [255, 0, 0] 的数组:

  • 在 RGB 中表示红色(Red=255, Green=0, Blue=0)
  • 在 BGR 中表示蓝色(Blue=255, Green=0, Red=0)

在图像处理中直方图用来表示图像中像素值的分布情况

像素值定义:

像素是数字图像的最小单位,每个像素包含颜色或灰度信息。

  • 表现形式
    • 灰度图像:每个像素用一个数值(整数)表示亮度(如0=纯黑,255=纯白)。
    • 彩色图像:每个像素由多个通道的值组成(如RGB图像的红、绿、蓝三通道)。
    • 直方图的横轴代表像素值,纵轴代表该像素值出现的频率(即有多少个像素具有该值)。 根据图像类型不同,直方图分为:

      (1) 灰度图像直方图
      ht = np.histogram(data, bins)
      '''功能:使用NumPy计算输入图像数据(data)的直方图。
      参数:
      data:输入图像(通常为灰度图像,像素值范围0~255)。
      bins:直方图的区间数(例如256个灰度级)。
      返回值:
      ht 是一个元组,包含两个数组:
      ht[0]:每个bin的频数(即有多少像素落在该区间)。
      例如,ht表示灰度值在[0, 1)范围内的像素数量,实际也就是落在0上的数量。
      ht[1]:每个bin的边界值(长度为bins + 1)。
      例如,ht = [0, 1, 2, ..., 255](若bins=255)。'''
    • 横轴:灰度级(通常0~255,0为黑,255为白)。
    • 纵轴:对应灰度级的像素数量。
    • 示例
      • 若直方图左侧(低灰度值)峰值高,说明图像偏暗。
      • 右侧(高灰度值)峰值高,说明图像偏亮。
    • (2) 彩色图像直方图
    • 通常分别绘制 R、G、B三个通道的直方图,表示各颜色分量的分布。
    • 也可能包含亮度直方图(将RGB转换为灰度后的分布)。
    • 对比度衡量的是图像中最亮部分(高光)与最暗部分(阴影)之间的亮度差异。差异越大,对比度越高;

    • 高对比度:亮部非常亮,暗部非常暗,界限分明(如黑白分明的剪影)。
    • 低对比度:亮部和暗部接近,整体显得灰蒙蒙(如雾天拍摄的照片)。
    • 1.半径计算权重参数矩阵

    • 半径计算权重参数矩阵指的是:在图像处理中,根据一个给定的半径(Radius)值,来计算一个核(Kernel),这个核本身就是一个包含了所有权重参数的矩阵,用于后续的滤波操作。

      2. 什么是“半径”(Radius)?

      ·在图像处理中,半径定义了核的大小以及模糊的程度。

      •物理意义​:它决定了在模糊一个像素时,需要参考其周围多大范围内的像素。

      •数学关系​:核的尺寸,通常与半径 r的关系是:size = 2 * r + 1。

      •例如:半径 r = 2,则核的大小是 5x5。

      •半径越大,参与的像素范围越广,​模糊效果越强烈。

      3. 什么是“权重参数矩阵”?

      这就是核本身。它是一个矩阵,矩阵中的每一个值代表一个权重。这个权重决定了对应位置的像素对中心像素最终值的“贡献”有多大。权重越高,该像素的影响就越大。

      1

      1

      0

      1

      1

      # 根据半径计算权重参数矩阵
      g_para = {}#定义了一个全局字典 g_para用于缓存已经计算过的权重矩阵,避免重复计算。
      
      def getPara(radius=5):
          global g_para
          m = g_para.get(radius, None)
          if m is not None:       #如果该半径的矩阵已经计算过(在g_para中),则直接返回。
              return m
          size = radius * 2 + 1
          m = np.zeros((size, size))      #创建一个大小为 (2radius+1) x (2radius+1) 的矩阵m,并初始化为0。
          for h in range(-radius, radius + 1):#然后遍历这个矩阵的每一个位置(相对于中心点的偏移h和w),计算每个位置的权重值(计算时将矩阵的中心点位置当作(0,0)。
              for w in range(-radius, radius + 1):        
                  if h == 0 and w == 0:
                      continue
                  m[radius + h, radius + w] = 1.0 / math.sqrt(h ** 2 + w ** 2)  #sqrt(h² + w²) 计算当前点 (h,w) 到中心点 (0,0) 的几何距离。
      #物理意义:距离越近的像素对中心点影响越大,符合图像局部相关性原理,权重值等于几何距离的倒数
          m /= m.sum()    #然后对矩阵进行归一化(除以所有元素之和),目的:卷积操作后图像整体亮度不变。
          g_para[radius] = m
          return m        #存储计算结果到全局缓存,返回生成的权重矩阵
    • 三、zmIce

      1、ACE自适应对比度增强

      是一种用于提升图像视觉质量的局部图像处理技术,它特别擅长增强低对比度图像中的细节

      •局部自适应​:不同于全局拉伸或直方图均衡化,ACE能根据图像不同区域的特性进行自适应增强,效果通常更自然。

      •细节突出​:能有效增强低对比度图像中的细节信息,提高图像的视觉清晰度。

    • # 常规的ACE实现
      '''还有一些其他场景1. 手机摄影增强
      ○ 参数:ratio=4, radius=7, clip=30
      ○ 效果:提升暗部细节同时抑制高光过曝
      2.医学影像处理
      ○ 参数:ratio=1.8, radius=3, clip=15
      ○效果:增强微血管对比度而不放大噪声
      3. 工业检测
      ○ 参数:ratio=5.0, radius=2, clip=50
      ○效果:突出产品表面划痕缺陷'''
      def zmIce(I, ratio=4, radius=300,clip=1):  #函数的目标是实现一种自适应的图像对比度增强,
          para = getPara(radius)  #这里调用之前定义的getPara函数,根据给定的半径radius生成一个(2radius+1) x (2radius+1)的权重矩阵。
          height, width = I.shape
          zh = []
          zw = []     #创建两个列表zh和zw,分别用于行和列的索引扩展。
          n = 0
      '''
          while n < radius:
              zh.append(0)        #在zh列表的前面添加radius个0(即镜像了第0行radius次)
              zw.append(0)        #在zw列表的前面添加radius个0(即镜像了第0列radius次)
              n += 1
          for n in range(height):
              zh.append(n)        #然后,将原始图像的高度索引(0到height - 1)添加到zh中
          for n in range(width):
              zw.append(n)        #将原始图像的宽度索引(0到width - 1)添加到zw中
          n = 0
          while n < radius:
              zh.append(height - 1)       #最后,在zh列表的后面添加radius个height - 1(即镜像了最后一行radius次)
              zw.append(width - 1)        #在zw列表的后面添加radius个width - 1(即镜像了最后一列radius次)
              n += 1
      
          Z = I[np.ix_(zh, zw)]   #使用扩展后的行列索引创建新的扩展图像Z:尺寸为(height+2*radius, width+2*radius)实例见图一'''
          res = np.zeros(I.shape)
          for h in range(radius * 2 + 1):
              for w in range(radius * 2 + 1):
                  if para[h][w] == 0:
                      continue                            '''为什么处理之后就能达到去雾的效果?利用大半经radius=300生成的Z当背景(若为雾图像则此时Z的亮度亮度主要由雾决定)
                       ,然后通过原始图片减去背景(雾的亮度)得到原始图片比雾亮/暗多少,ratio 增强多少倍效果,np.clip(..., -1, 1) 是防止调整过度,说明见图二'''
                  res += (para[h][w] * np.clip((I - Z[h:h + height, w:w + width]) * ratio, -clip, clip))
          return res  #res的物理意义① 对比度增强量:正值:表示该像素需要变亮   负值:表示该像素需要变暗    绝对值大小:表示增强强度
      
      '''# 单通道ACE快速增强实现,代码逻辑见图三'''
      def zmIceFast(I, ratio, radius):        #输入参数:图像 I,增强系数 ratio,邻域半径 radius。
          height, width = I.shape[:2]
          if min(height, width) <= 2:
              return np.zeros(I.shape) + 0.5  #如果图像的最小边长小于等于2,直接返回一个0.5的常数矩阵(避免递归到太小图像)
          Rs = cv2.resize(I, (int((width + 1) / 2), int((height + 1) / 2)))   #下采样:将图像缩小到大约一半的大小
          Rf = zmIceFast(Rs, ratio, radius)   # 递归调用,对缩小后的图像递归调用快速ACE算法。
          Rf = cv2.resize(Rf, (width, height))
          Rs = cv2.resize(Rs, (width, height))    #将递归结果Rf和之前下采样的图像Rs都上采样到原始尺寸。
          return Rf + zmIce(I, ratio, radius) - zmIce(Rs, ratio, radius)

    •    图一

    •  tu

    • 图二

    • 图三

    • ---------------------------------------------------------------------------------------------------------------------

    •  d = np.cumsum(ht[0]) / float(data.size)
      '''累积分布函数(CDF)其主要目的是将图像的像素值分布进行重新映射,以改善对比度或实现特定增强效果。
      数学原理: 新像素值=round((L−1)×CDF(原像素值))
      L 是灰度级数(如256),CDF是累积概率(范围0~1) 核心目的:直方图均衡化'''
      '''功能:计算直方图的累积分布函数(CDF),表示像素值的累积概率分布。
      步骤分解:
      np.cumsum(ht[0]):
      对直方图频数ht计算累积和。
      例如,若ht = [10, 20, 5],则np.cumsum(ht) = [10, 30, 35]。
      / float(data.size):
      将累积和除以图像总像素数(data.size),归一化为概率值(范围0~1)。
      例如,若总像素数为100,则d = [0.1, 0.3, 0.35]。d[i]表示灰度值 ≤ i 的像素在图像中的占比'''

      ---------------------------------------------------------------------------------------------------------------------

    • 完整的图像去雾代码

    • import cv2
      import numpy as np
      import math
      
      # 线性拉伸处理
      # 去掉最大最小0.5%的像素值 线性拉伸至[0,1]
      def stretchImage(data, s=0.005, bins=2000):
          ht = np.histogram(data, bins)   #使用numpy的histogram函数计算输入图像数据的直方图。
                                          #返回值ht是一个元组,包含两个数组:
                                          #第一个数组是每个bin的频数(即有多少个像素落在该区间)
                                          #第二个数组是bin的边界(长度为bins+1)
          d = np.cumsum(ht[0]) / float(data.size) #计算直方图的累积分布函数(CDF)。
                                                  #np.cumsum(ht[0])计算频数的累积和。
                                                  #除以总像素数(data.size)得到累积概率分布,即每个bin的累积概率。
          lmin = 0
          lmax = bins - 1
          while lmin < bins:
              if d[lmin] >= s:
                  break
              lmin += 1
          while lmax >= 0:
              if d[lmax] <= 1 - s:
                  break
              lmax -= 1
          return np.clip((data - ht[1][lmin]) / (ht[1][lmax] - ht[1][lmin]), 0, 1)
                  #对图像数据进行线性拉伸变换:
                  #将原图像数据减去找到的最小边界值(ht[1][lmin],即第lmin个bin的左边界)
                  #然后除以(最大边界值ht[1][lmax]减去最小边界值)的差,将像素值映射到[0,1]区间。
                  #np.clip函数将超出[0,1]范围的值截断到0和1之间。
      # 根据半径计算权重参数矩阵
      g_para = {}#定义了一个全局字典 g_para用于缓存已经计算过的权重矩阵,避免重复计算。
      
      def getPara(radius=5):
          global g_para
          m = g_para.get(radius, None)
          if m is not None:       #如果该半径的矩阵已经计算过(在g_para中),则直接返回。
              return m
          size = radius * 2 + 1
          m = np.zeros((size, size))      #创建一个大小为 (2radius+1) x (2radius+1) 的矩阵m,并初始化为0。
          for h in range(-radius, radius + 1):#然后遍历这个矩阵的每一个位置(相对于中心点的偏移h和w),计算每个位置的权重值(计算时将矩阵的中心点位置当作(0,0)。
              for w in range(-radius, radius + 1):        
                  if h == 0 and w == 0:
                      continue
                  m[radius + h, radius + w] = 1.0 / math.sqrt(h ** 2 + w ** 2)  #sqrt(h² + w²) 计算当前点 (h,w) 到中心点 (0,0) 的几何距离。
      #物理意义:距离越近的像素对中心点影响越大,符合图像局部相关性原理,权重值等于几何距离的倒数
          m /= m.sum()    #然后对矩阵进行归一化(除以所有元素之和),目的:卷积操作后图像整体亮度不变。
          g_para[radius] = m
          return m        #存储计算结果到全局缓存,返回生成的权重矩阵
      
      # 常规的ACE实现
      def zmIce(I, ratio=4, radius=300):  #函数的目标是实现一种自适应的图像对比度增强,
          para = getPara(radius)  #这里调用之前定义的getPara函数,根据给定的半径radius生成一个(2radius+1) x (2radius+1)的权重矩阵。
          height, width = I.shape
          zh = []
          zw = []     #创建两个列表zh和zw,分别用于行和列的索引扩展。
          n = 0
      '''
          while n < radius:
              zh.append(0)        #在zh列表的前面添加radius个0(即镜像了第0行radius次)
              zw.append(0)        #在zw列表的前面添加radius个0(即镜像了第0列radius次)
              n += 1
          for n in range(height):
              zh.append(n)        #然后,将原始图像的高度索引(0到height - 1)添加到zh中
          for n in range(width):
              zw.append(n)        #将原始图像的宽度索引(0到width - 1)添加到zw中
          n = 0
          while n < radius:
              zh.append(height - 1)       #最后,在zh列表的后面添加radius个height - 1(即镜像了最后一行radius次)
              zw.append(width - 1)        #在zw列表的后面添加radius个width - 1(即镜像了最后一列radius次)
              n += 1
      
          Z = I[np.ix_(zh, zw)]   #使用扩展后的行列索引创建新的扩展图像Z:尺寸为(height+2*radius, width+2*radius)实例见图一'''
          res = np.zeros(I.shape)
          for h in range(radius * 2 + 1):
              for w in range(radius * 2 + 1):
                  if para[h][w] == 0:
                      continue                            '''为什么处理之后就能达到去雾的效果?利用大半经radius=300生成的Z当背景(若为雾图像则此时Z的亮度亮度主要由雾决定)
                       ,然后通过原始图片减去背景(雾的亮度)得到原始图片比雾亮/暗多少,ratio 增强多少倍效果,np.clip(..., -1, 1) 是防止调整过度,说明见图二图三'''
                  res += (para[h][w] * np.clip((I - Z[h:h + height, w:w + width]) * ratio, -1, 1))
          return res  #res的物理意义① 对比度增强量:正值:表示该像素需要变亮   负值:表示该像素需要变暗    绝对值大小:表示增强强度
      
      '''# 单通道ACE快速增强实现,代码逻辑见图四'''
      def zmIceFast(I, ratio, radius):        #输入参数:图像 I,增强系数 ratio,邻域半径 radius。
          height, width = I.shape[:2]
          if min(height, width) <= 2:
              return np.zeros(I.shape) + 0.5  #如果图像的最小边长小于等于2,直接返回一个0.5的常数矩阵(避免递归到太小图像)
          Rs = cv2.resize(I, (int((width + 1) / 2), int((height + 1) / 2)))   #下采样:将图像缩小到大约一半的大小
          Rf = zmIceFast(Rs, ratio, radius)   # 递归调用,对缩小后的图像递归调用快速ACE算法。
          Rf = cv2.resize(Rf, (width, height))
          Rs = cv2.resize(Rs, (width, height))    #将递归结果Rf和之前下采样的图像Rs都上采样到原始尺寸。
          return Rf + zmIce(I, ratio, radius) - zmIce(Rs, ratio, radius)
          
      
      # rgb三通道分别增强 ratio是对比度增强因子 radius是卷积模板半径
      def zmIceColor(I, ratio=4, radius=3):
          res = np.zeros(I.shape)     # 初始形化一个与输入图像I状相同的全零数组res,用于存储增强后的图像。
          for k in range(3):
              res[:, :, k] = stretchImage(zmIceFast(I[:, :, k], ratio, radius))
          return res
      
      """
      整体流程:
      1、启动与输入:程序从 main 函数开始,读取一张名为 foggy.jpg 的输入图像。
      2、预处理:将图像数据从整数范围 [0, 255] 归一化到浮点数范围 [0, 1]。几乎所有图像处理算法(如CNN、Retinex、ACE)都会先将图像归一化到 [0,1] 或 [-1,1]:
      1)统一输入尺度:使算法参数(如 ratio=4、radius=3)在不同分辨率/亮度的图像上效果一致
      2)适配深度学习框架:若后续接入神经网络,归一化是必须的预处理步骤
      3)降低数值敏感:浮点数运算对硬件(CPU/GPU)更友好,不易因整数溢出导致崩溃
      3、分通道处理:将彩色图像拆分为R, G, B三个独立的通道。
      4、核心增强(对每个通道):
          使用快速多尺度ACE算法 (zmIceFast) 对每个通道进行处理。该算法通过递归,高效地计算了每个像素与其非常大邻域内像素的相对关系,从而显著提升了局部对比度。
      5、后处理(对每个通道):
          对增强后的通道数据进行线性拉伸 (stretchImage),剔除极端值,使灰度分布更饱满,覆盖整个 [0, 1] 范围。拉伸后,图像会更清晰,暗部和亮部细节更容易分辨。
      类似于 直方图均衡化,但更加温和,只是做线性拉伸。
      6、合并通道:将处理好的R, G, B三个通道合并回一幅彩色图像。
      7、输出:将最终结果的数据范围从 [0, 1] 转换回 [0, 255],并保存为 out.jpg 文件。
      """
      # 主函数
      if __name__ == '__main__':
          img = cv2.imread('foggy5.jpg')
          res = zmIceColor(img / 255.0) * 255
          cv2.imwrite('out7.jpg', res)
      
      

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐