1. 为什么水下图像需要专属的“体检报告”?

如果你玩过水下摄影,或者看过一些海洋纪录片,一定会发现一个头疼的问题:拍出来的照片总是蓝绿蓝绿的,模模糊糊,颜色也怪怪的。这可不是你的相机坏了,而是水这个“滤镜”在捣乱。水会吸收和散射光线,尤其是红色光,跑不了几米就被“吃”得差不多了,所以水下照片普遍偏蓝、偏绿,对比度低,细节糊成一片。

那么问题来了,当我们开发一个水下图像增强算法,比如想把一张蓝蒙蒙的珊瑚照片变得清晰鲜艳,怎么判断这个算法到底好不好呢?总不能每次都靠人眼凭感觉说“嗯,这张好像更顺眼一点”吧?我们需要一个客观、可量化的“裁判”来打分。这就是水下图像质量评价指标存在的意义。

在众多“裁判”中,UIQM 是一个被广泛认可和使用的无参考评价指标。所谓“无参考”,就是不需要一张完美的“标准答案”图来对比,它自己就能根据图像本身的特性给出一个分数。这对于真实的水下场景太重要了,因为现实中我们往往没有那个理想的“清晰原图”作为参照。UIQM 就像一位经验丰富的质检员,通过分析图像的色彩、清晰度和对比度这三个核心维度,综合给出一个质量分数。分数越高,通常意味着图像质量越好,色彩更自然,细节更清晰,层次更分明。

我刚开始接触水下图像处理时,也试过用一些通用的图像质量指标,比如PSNR、SSIM,但发现它们经常“水土不服”。一张经过增强、人眼觉得明显变好的水下图片,这些通用指标的分数可能反而下降了。后来用了UIQM,发现它的打分结果和人的主观感受吻合度要高得多,这才算是找到了靠谱的“裁判”。接下来,我就带你彻底搞懂这位“裁判”是怎么工作的,并手把手教你用Python把它实现出来。

2. 拆解UIQM:三位一体的评价体系

UIQM 的全称是 Underwater Image Quality Measure。它不是一个单一的公式,而是一个由三个子指标加权组合而成的综合分数。这个设计非常巧妙,因为它抓住了水下图像退化的三个关键方面。它的计算公式如下:

UIQM = c1 * UICM + c2 * UISM + c3 * UIConM

这里的 c1, c2, c3 是经过大量实验确定的权重系数,通常取值为 c1 = 0.0282, c2 = 0.2953, c3 = 3.5753。你可以看到,UIConM(对比度度量)的权重最高,这反映了对比度恢复对于水下视觉感知至关重要。下面我们来逐一拆解这三个核心部件。

2.1 UICM:给色彩失衡“把个脉”

UICM 衡量的是图像的色彩质量。水下图像最大的色偏就是失去红色,整体笼罩在蓝绿色调中。UICM 的计算思路很直观:它分别分析图像中 红色-绿色(RG)黄-蓝(YB) 这两个颜色对立通道的统计特性。

具体来说,它先计算每个像素的 RG = R - G 和 YB = (R + G)/2 - B 值。然后,它不是简单地对所有像素求平均,而是采用了一种截断处理。它会剔除掉 RG 和 YB 值中最小和最大的各10%(这个比例由参数 alpha 控制,通常为0.1),只对中间80%的数据计算均值。这样做的好处是能减少极端噪声点(比如高光或死黑区域)对整体色彩评价的干扰,让结果更稳健。

接着,它会计算这两个通道的方差。最终,UICM 的值由这两个通道的均值和方差的平方根组合而成。一个健康的、色彩平衡的图像,其 RG 和 YB 的均值应该接近0(没有严重色偏),同时方差适中(色彩丰富但不怪异)。公式中的系数使得 UICM 值越大,通常表示色彩质量越好。

我打个比方,UICM 就像在检查图像的“血色”。一张健康的水下图像应该“面色红润”(RG通道平衡),而不是“脸色发青”(过度偏向YB通道)。

2.2 UISM:看看图像“近视”了没

UISM 衡量的是图像的清晰度或锐度。模糊是水下图像的另一个顽疾。UISM 的实现基于一个叫做 EME 的指标。EME 的全称是 “Enhancement Measure by Entropy”,但它本质上衡量的是图像局部区域的对比度变化。

它的计算方法是:把图像的单个颜色通道(红、绿、蓝)划分成许多不重叠的小块。对于每一个小块,找出其最大和最小像素值。如果最小值不为零,就计算 log(最大值 / 最小值);如果最小值为零(避免除零错误),则直接用最大值。最后,对所有小块的这个对数值求平均。

这个逻辑很好理解:一个清晰、细节丰富的图像区域,其明暗对比强烈,最大值和最小值的比值会很大,取对数后的值也大。反之,一个模糊的、灰蒙蒙的区域,明暗对比弱,这个比值就小。分别计算 R, G, B 三个通道的 EME 值,然后按照人眼对不同颜色的敏感度进行加权求和(权重分别为 0.299, 0.144, 0.587),就得到了 UISM。UISM 值越高,说明图像越清晰,细节越丰富。

你可以想象一下,UISM 就是在给图像做“视力测试”。它把图像分成一个个小格子,检查每个格子里最亮和最暗的部分差别大不大。差别越大,说明“视力”越好,看得越清楚。

2.3 UIConM:找回丢失的层次感

UIConM 是 UIQM 中权重最高的部分,它衡量的是图像的对比度。这里用到了一个基于 PLIP 模型的对比度测量方法。PLIP 模型是专门为模仿人类视觉感知而设计的,比简单的像素差值更能反映人眼感受到的对比度。

它的计算同样基于图像分块。在每个小块内,它用块内的最大值和最小值,通过 PLIP 特有的加法和减法运算,计算出一个局部对比度值 phi。这个计算过程涉及对像素值进行一种非线性变换(以1026为模),使其更符合人眼的响应。最后,对所有小块的 phi 值进行平均,并再次通过 PLIP 模型的反变换得到一个最终的 UIConM 值。

这个过程听起来有点绕,但其核心思想是:评估图像中亮部和暗部之间的差异是否明显、自然。一个好的水下增强算法,应该能拉开图像的动态范围,让该亮的地方亮起来,该暗的地方沉下去,从而恢复出物体的立体感和层次感。UIConM 就是量化这个“拉开”的程度。

3. 手把手实现:从公式到Python代码

理论说再多,不如一行代码。我们直接来看如何用 Python 和 OpenCV 把 UIQM 的三个子指标以及总分计算出来。我会对原始代码进行优化和详细注释,确保你能看懂每一行在做什么。

首先,确保你安装了必要的库:opencv-pythonnumpy。可以通过 pip install opencv-python numpy 来安装。

import cv2
import math
import numpy as np

# 权重系数
c1 = 0.0282
c2 = 0.2953
c3 = 3.5753

3.1 实现 UICM

def uicm(img):
    """
    计算水下图像色彩度量 (Underwater Image Colorfulness Measure)
    参数:
        img: 输入图像,BGR格式,numpy数组
    返回:
        Uicm: 色彩度量值
    """
    # 1. 分离通道。注意OpenCV默认是BGR顺序,不是RGB。
    b, g, r = cv2.split(img) # 这里修正了原始代码的变量命名顺序,更符合BGR拆分
    # 计算两个对立色通道
    RG = r - g               # 红-绿通道
    YB = (r + g) / 2 - b     # 黄-蓝通道 (Y = (R+G)/2)

    # 2. 获取图像尺寸和总像素数
    m, n = r.shape           # 图像高和宽
    K = m * n                # 总像素数

    # 3. 截断参数,剔除两端各10%的极端值
    alpha_L = 0.1
    alpha_R = 0.1
    T_alpha_L = math.ceil(alpha_L * K)   # 向上取整,左侧剔除数量
    T_alpha_R = math.floor(alpha_R * K)  # 向下取整,右侧剔除数量

    # 4. 处理RG通道
    RG_list = RG.flatten()               # 展平为一维数组
    RG_list_sorted = np.sort(RG_list)    # 排序
    # 计算截断后的均值 (剔除前T_alpha_L个和后T_alpha_R个)
    sum_RG = np.sum(RG_list_sorted[T_alpha_L: K - T_alpha_R])
    U_RG = sum_RG / (K - T_alpha_R - T_alpha_L)
    # 计算整个RG通道的方差
    sigma2_RG = np.mean(np.square(RG_list - U_RG))

    # 5. 处理YB通道 (逻辑同上)
    YB_list = YB.flatten()
    YB_list_sorted = np.sort(YB_list)
    sum_YB = np.sum(YB_list_sorted[T_alpha_L: K - T_alpha_R])
    U_YB = sum_YB / (K - T_alpha_R - T_alpha_L)
    sigma2_YB = np.mean(np.square(YB_list - U_YB))

    # 6. 组合均值和方差,计算最终的UICM
    # 公式: -0.0268 * sqrt(mean^2) + 0.1586 * sqrt(variance)
    Uicm = -0.0268 * np.sqrt(U_RG**2 + U_YB**2) + 0.1586 * np.sqrt(sigma2_RG + sigma2_YB)
    return Uicm

关键点解释

  • math.ceilmath.floor 是为了确保剔除的像素数量是整数,且处理方式一致。
  • 先排序再切片求和,是计算截断均值的标准做法。
  • 方差计算用的是整个数据集(未截断)的方差,这是原始论文的定义。
  • 最终公式中的系数 (-0.0268 和 0.1586) 是经验值,确保 UICM 对色彩平衡和丰富度做出合理响应。

3.2 实现 UISM 所需的 EME 函数

def EME(channel, L=8):
    """
    计算单个通道的EME值 (Enhancement Measure by Entropy)
    参数:
        channel: 单通道图像数据 (如R, G, B通道)
        L: 分块大小,默认为8x8。块越小,对局部细节越敏感。
    返回:
        E_sum: 该通道的EME值
    """
    m, n = channel.shape
    number_m = m // L  # 纵向可以分成多少块
    number_n = n // L  # 横向可以分成多少块

    E = 0.0
    m1 = 0
    for i in range(number_m):
        n1 = 0
        for t in range(number_n):
            # 提取当前块
            block = channel[m1:m1+L, n1:n1+L]
            rbg_min = np.min(block)
            rbg_max = np.max(block)

            # 计算块内对比度。加一个极小值防止除零。
            if rbg_min > 0:
                rbg_ratio = rbg_max / rbg_min
            else:
                rbg_ratio = rbg_max
            # 累加对数对比度
            E += np.log(rbg_ratio + 1e-5)  # 加1e-5确保log参数为正且稳定
            n1 += L
        m1 += L

    # 计算所有块的平均值,并乘以2(原始论文中的因子)
    E_sum = 2 * (E / (number_m * number_n))
    return E_sum

关键点解释

  • // 是整除,确保我们只处理完整的图像块,忽略边缘剩余部分。
  • 循环遍历所有块,计算每个块的 max/min 比值。这个比值反映了该块的局部对比度。
  • np.log(rbg_ratio + 1e-5):对数变换将乘性对比度转化为加性度量,更符合感知。加 1e-5 是为了数值稳定性。
  • 最终乘以2是原始EME定义中的缩放因子。

3.3 实现 UIConM

def UICONM(img, L=8):
    """
    计算水下图像对比度度量 (Underwater Image Contrast Measure)
    基于PLIP模型。
    参数:
        img: 输入图像,BGR格式
        L: 分块大小,默认为8x8
    返回:
        logAMEE: 对比度度量值
    """
    # 如果输入是彩色图,转换为灰度图。UIConM通常在灰度空间计算。
    if len(img.shape) == 3:
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    else:
        gray = img

    m, n = gray.shape
    number_m = m // L
    number_n = n // L

    logAMEE = 0.0
    m1 = 0
    for i in range(number_m):
        n1 = 0
        for t in range(number_n):
            # 提取当前块
            block = gray[m1:m1+L, n1:n1+L]
            rbg_min = int(np.min(block))  # 转换为整数,用于PLIP计算
            rbg_max = int(np.max(block))

            # PLIP 加法和减法运算 (模数取1026,对应8-bit图像的255*4+6)
            # PLIP 加法: μ + ν = μ + ν - (μ * ν) / 1026
            plip_add = rbg_max + rbg_min - (rbg_max * rbg_min) / 1026.0
            # PLIP 减法: |μ - ν|,当分母不为零时
            if (1026 - rbg_min) > 0:
                plip_del = 1026 * (rbg_max - rbg_min) / (1026.0 - rbg_min)
            else:
                plip_del = 1026 * (rbg_max - rbg_min) / 1e-5  # 避免除零

            # 计算局部对比度 phi
            if plip_del > 0 and plip_add > 0:
                local_a = plip_del / plip_add
                local_b = math.log(plip_del / plip_add)
                phi = local_a * local_b
                logAMEE += phi
            n1 += L
        m1 += L

    # 最终的全局对比度度量计算
    logAMEE = 1026 - 1026 * ((1 - logAMEE / 1026) ** (1 / (number_n * number_m)))
    return logAMEE

关键点解释

  • PLIP 模型用非线性运算模拟人眼对光强的响应,其加法和减法与常规算术不同。
  • 1026 这个模数是为了将8位像素值(0-255)映射到PLIP的运算空间。
  • 局部对比度 phiplip_del/plip_add 的比值及其对数构成,反映了块内明暗差异的相对强度。
  • 最后一行公式是对所有局部对比度进行聚合,得到全局对比度评分。值越大表示对比度越好。

3.4 组合成完整的UIQM

现在,我们把三个部分组合起来,计算最终的 UIQM 分数。

def calculate_uiqm(img_path, L=8):
    """
    计算给定图像路径的UIQM分数
    参数:
        img_path: 图像文件路径
        L: 分块大小,用于UISM和UIConM
    返回:
        uiqm_score: UIQM总分
        scores: 字典,包含各子项分数
    """
    # 读取图像
    img = cv2.imread(img_path)
    if img is None:
        raise FileNotFoundError(f"无法读取图像: {img_path}")

    # 1. 计算 UICM
    Uicm = uicm(img)

    # 2. 计算 UISM (需要分别计算RGB通道的EME)
    b, g, r = cv2.split(img)
    EME_r = EME(r, L)
    EME_g = EME(g, L)
    EME_b = EME(b, L)
    # 加权求和,权重基于人眼对不同颜色的敏感度
    Uism = 0.299 * EME_r + 0.144 * EME_b + 0.587 * EME_g

    # 3. 计算 UIConM
    Uiconm = UICONM(img, L)

    # 4. 加权计算最终UIQM
    uiqm_score = c1 * Uicm + c2 * Uism + c3 * Uiconm

    scores = {
        'UICM': Uicm,
        'UISM': Uism,
        'UIConM': Uiconm,
        'UIQM': uiqm_score
    }
    return uiqm_score, scores

# 使用示例
if __name__ == '__main__':
    # 替换为你的图像路径
    image_path = 'your_underwater_image.jpg'
    try:
        final_score, detail_scores = calculate_uiqm(image_path)
        print(f"图像质量评估结果:")
        print(f"  UICM (色彩): {detail_scores['UICM']:.4f}")
        print(f"  UISM (清晰度): {detail_scores['UISM']:.4f}")
        print(f"  UIConM (对比度): {detail_scores['UIConM']:.4f}")
        print(f"  UIQM (综合得分): {detail_scores['UIQM']:.4f}")
    except Exception as e:
        print(f"计算出错: {e}")

运行这段代码,你就能得到一张水下图像的UIQM综合得分及其三个维度的详细评分。通常,一个经过良好增强的水下图像,其UIQM分数会比原始退化图像有显著提升。

4. 实战应用:用UIQM给增强算法“打分”

理解了原理,实现了代码,UIQM 到底怎么用呢?这里我分享几个在项目中实际应用的场景。

场景一:算法调参与比较 假设你正在开发或测试一个水下图像增强算法,比如基于Retinex的或者基于深度学习的方法。你手头有一批处理前和处理后的图像。直接肉眼看太主观,而且数量一多就眼花缭乱。这时,你可以写一个脚本,批量计算所有处理前后图像的UIQM分数。

import os

def batch_evaluate(input_dir, output_dir):
    """批量评估一个目录下所有处理前后图像对的UIQM提升"""
    input_images = [f for f in os.listdir(input_dir) if f.endswith(('.jpg', '.png'))]
    results = []
    for img_name in input_images:
        input_path = os.path.join(input_dir, img_name)
        output_path = os.path.join(output_dir, img_name)

        if not os.path.exists(output_path):
            continue

        score_in, _ = calculate_uiqm(input_path)
        score_out, _ = calculate_uiqm(output_path)
        improvement = score_out - score_in
        results.append((img_name, score_in, score_out, improvement))
        print(f"{img_name}: 原始={score_in:.3f}, 增强后={score_out:.3f}, 提升={improvement:.3f}")

    # 可以进一步分析平均提升、最大提升等
    if results:
        avg_imp = np.mean([r[3] for r in results])
        print(f"\n平均UIQM提升: {avg_imp:.3f}")
    return results

通过这样的批量评估,你可以量化地比较不同算法、或同一算法不同参数下的效果。比如,你可能会发现算法A在大多数图像上能将UIQM从2.5提升到4.0,而算法B只能提升到3.5,那么算法A的综合表现就更优。这比“我觉得A更好看”有说服力得多。

场景二:监控增强过程的收敛性 在一些迭代式的增强算法中,比如某些基于优化的方法,UIQM可以作为目标函数或监控指标。你可以在每次迭代后计算当前输出图像的UIQM,观察它是否随着迭代而增加并趋于稳定。这能帮助你判断算法是否在向好的方向优化,以及何时可以停止迭代。

场景三:融合算法的权重决策 有些高级的增强方法会融合多个初步结果。例如,对同一张图用白平衡、直方图均衡、锐化等多种方法处理,得到多张中间结果。如何将它们融合成一张最好的图?你可以用UIQM来指导融合权重的学习,让融合后的图像UIQM分数最高,从而自动得到视觉质量更佳的结果。

5. 深入思考:UIQM的局限与注意事项

虽然UIQM非常有用,但把它当作金科玉律也会踩坑。在我多年的使用经验中,总结了以下几点需要注意的地方:

第一,UIQM是一个无参考指标,但它不是“真理”。它的设计基于对水下图像退化模型的统计理解和人眼视觉特性的模拟。然而,人眼的主观评价是复杂且带有上下文偏好的。有时,一个算法为了追求极高的UIQM分数,可能会过度增强对比度或饱和度,导致图像看起来不自然、有噪点或出现色块。UIQM分数高,并不绝对等于人眼觉得好看。它更像一个强有力的辅助工具,而不是最终判决。

第二,分块大小 L 是个重要参数。在 EMEUICONM 函数中,我们都用到了分块。默认的 L=8 是一个常用值,但它并非放之四海而皆准。对于高分辨率图像(如4K),8x8的块可能太小,计算量大且对噪声敏感;对于低分辨率图像,8x8的块可能又太大,无法捕捉细微的局部对比度。我的经验是,可以尝试 L=4, 8, 16 等不同值,观察UIQM分数对同一组图像的排序是否稳定。通常,L[4, 16] 之间变化时,趋势应该是一致的。

第三,UIQM对均匀色偏敏感,但对局部伪影不敏感。UICM衡量的是全局色彩分布。如果一个算法成功纠正了全局的蓝绿色偏,UICM分数会显著提高。但是,如果算法在纠正过程中引入了局部的不自然色斑或光晕,UIQM可能无法有效捕捉到这些缺陷。这时就需要结合其他指标(比如专门检测伪影的指标)或人眼观察来综合判断。

第四,计算效率。对于实时性要求高的应用(如水下机器人视觉导航),逐帧计算UIQM可能会带来较大计算负担。尤其是 UICONM 函数中的循环和PLIP计算。在实际部署时,可能需要考虑优化代码(如向量化运算)、降低图像分辨率或减少分块数量来平衡精度和速度。

最后,记住一点:没有哪个指标是完美的。UIQM是水下图像质量评估领域一个里程碑式的工作,它为我们提供了一个客观、可计算、且与主观评价相关性较高的工具。但在实际研究和开发中,我通常会结合多个指标(如UCIQE、NIQE等)以及最重要的——人的主观评价——来对算法性能进行综合评估。把UIQM当作你工具箱里的一把好尺子,用它来测量、比较、优化,但最终,别忘了用你的眼睛去感受图像是否真的“变好了”。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐