一、模块导入与环境准备

import cv2
import numpy as np
import matplotlib.pyplot as plt
import os
  • cv2:OpenCV 库,提供图像读取、写入、颜色空间转换、直方图均衡化等函数。

  • numpy:科学计算库,用于高效的数组运算,图像本质上就是 NumPy 数组。

  • matplotlib.pyplot:绘图库,用于显示图像和绘制直方图。

  • os:操作系统接口库,用于创建输出目录。


二、准备工作:创建输出目录

output_dir = 'output_1'
os.makedirs(output_dir, exist_ok=True)
  • output_dir:定义输出文件夹名称为 'output_1'

  • os.makedirs():递归创建目录,exist_ok=True 表示若目录已存在不报错,保证每次运行都能正常保存结果。


三、自定义工具函数:读取灰度图像

def load_gray_image(path):
  • 定义函数 load_gray_image,接收一个参数 path(图像文件路径),负责将任意图像统一读取为灰度图。

    img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
  • cv2.imread():读取图像文件。

  • cv2.IMREAD_UNCHANGED:保留原始图像的通道数和位深度,不自动转换。

    if img is None:
        raise FileNotFoundError(f"图像文件 {path} 未找到,请检查路径")
  • 若图像读取失败(返回 None),抛出一个明确的文件未找到异常,方便定位错误。

    if len(img.shape) == 3:
        print(f"[提示] {path} 是彩色图,已自动转换为灰度图")
        return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  • len(img.shape) == 3:判断是否为三通道彩色图像(形状为 (高, 宽, 3))。

  • 若是彩色图,打印提示信息,并用 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 将 BGR 彩色图转换为单通道灰度图后返回。

   return img
  • 若已是灰度图(二维数组),直接返回。


四、任务1:反色处理

def task1_invert(original_gray):
  • 定义函数 task1_invert,接收正常光照的灰度图像,实现底片效果的反色处理。

    inverted = 255 - original_gray      # s = 255 - r
  • 反色公式s = 255 - r,对每个像素值 r 用 255 减去,暗变亮、亮变暗,呈现底片效果。利用 NumPy 广播机制对整幅图像一次性完成运算。

    plt.figure(figsize=(10, 4))
    plt.subplot(1, 2, 1)
    plt.imshow(original_gray, cmap='gray', vmin=0, vmax=255)
    plt.title('Original Gray Image')
    plt.axis('off')
  • 创建 10×4 英寸画布,1行2列布局,左侧显示原图。

  • vmin=0, vmax=255:固定灰度显示范围,确保0为黑、255为白。

    plt.subplot(1, 2, 2)
    plt.imshow(inverted, cmap='gray', vmin=0, vmax=255)
    plt.title('Inverted Image (255 - r)')
    plt.axis('off')
    plt.tight_layout()
    plt.show()
  • 右侧显示反色图像,tight_layout() 调整间距,show() 渲染显示。

    cv2.imwrite(os.path.join(output_dir, 'inverted.jpg'), inverted)
    print("[任务1] 反色图像已保存")
  • cv2.imwrite():将反色图像保存为 JPG 文件到输出目录,并打印完成提示。


五、任务2:对数变换

def task2_log_transform(low_light, over_exposed):
  • 定义函数,接收低照度图像 low_light 和曝光过度图像 over_exposed 两幅灰度图。

5.1 内部辅助函数:对数变换

    def log_transform(img):
        img_float = img.astype(np.float32)
        c = 255.0 / np.log(1 + np.max(img_float))
        log_img = c * np.log(1 + img_float)
        return np.clip(log_img, 0, 255).astype(np.uint8)
  • img.astype(np.float32):将 uint8 类型转为 float32,避免对数运算溢出。

  • c:缩放常数,计算公式为 255 / log(1 + max),使变换后最大像素值恰好映射到 255。

  • np.log(1 + img_float):对数变换核心,加1防止对0取对数导致负无穷。

  • np.clip(log_img, 0, 255):裁剪到 [0, 255] 范围,防止越界。

  • .astype(np.uint8):转回 8 位无符号整数类型,用于显示和保存。

    log_low = log_transform(low_light)
    log_over = log_transform(over_exposed)
  • 分别对低照度和过度曝光图像执行对数变换。

5.2 显示与直方图绘制(以低照度为例)

    plt.figure(figsize=(12, 8))
    plt.subplot(2, 2, 1)
    plt.imshow(low_light, cmap='gray', vmin=0, vmax=255)
    plt.title('Low Light Original')
    plt.axis('off')
  • 2行2列布局,左上角显示低照度原图。

    plt.subplot(2, 2, 2)
    plt.imshow(log_low, cmap='gray', vmin=0, vmax=255)
    plt.title('After Log Transform')
    plt.axis('off')
  • 右上角显示对数变换后的低照度图像。

    plt.subplot(2, 2, 3)
    plt.hist(low_light.ravel(), bins=256, range=[0, 256], color='gray', alpha=0.7)
    plt.title('Low Light Histogram')
  • plt.hist():绘制直方图。

  • .ravel():将二维图像数组展平为一维,便于统计。

  • bins=256:256 个柱,对应 0-255 每个灰度级。

  • range=[0, 256]:统计范围为 [0, 256)。

  • alpha=0.7:设置透明度。

    plt.subplot(2, 2, 4)
    plt.hist(log_low.ravel(), bins=256, range=[0, 256], color='gray', alpha=0.7)
    plt.title('Log Transformed Histogram')
    plt.tight_layout()
    plt.show()
  • 右下角绘制变换后的直方图,展示对数变换对灰度分布的扩展效果。

曝光过度图像的显示逻辑完全相同,仅替换数据源。

5.3 保存结果

    cv2.imwrite(os.path.join(output_dir, 'log_low.jpg'), log_low)
    cv2.imwrite(os.path.join(output_dir, 'log_over.jpg'), log_over)
    print("[任务2] 对数变换结果已保存")

六、任务3:线性拉伸与直方图均衡化

def task3_histogram_enhancement(low_light, over_exposed):
  • 定义函数,对低照度和过度曝光图像分别进行线性拉伸和直方图均衡化,并对比展示。

6.1 内部辅助函数

    def linear_stretch(img):
        min_val, max_val = np.min(img), np.max(img)
        if max_val == min_val:
            return img
        return ((img - min_val) / (max_val - min_val) * 255).astype(np.uint8)
  • 线性拉伸公式(x - min) / (max - min) * 255,将原图灰度范围线性映射到 [0, 255]。

  • max_val == min_val:若图像灰度值全部相同(常量图像),直接返回原图避免除零错误。

    def equalize(img):
        return cv2.equalizeHist(img)
  • cv2.equalizeHist():OpenCV 的直方图均衡化函数,自动重新分布灰度值使直方图尽可能平坦,增强全局对比度。

6.2 执行增强

    low_linear = linear_stretch(low_light)
    low_eq = equalize(low_light)
    over_linear = linear_stretch(over_exposed)
    over_eq = equalize(over_exposed)
  • 分别生成四幅增强结果。

6.3 三组对比显示(以低照度为例)

    plt.figure(figsize=(15, 10))
  • 2行3列布局,上排三幅图(原图、线性拉伸、均衡化),下排对应直方图。

    plt.subplot(2, 3, 1)
    plt.imshow(low_light, cmap='gray', vmin=0, vmax=255)
    plt.title('Low Light Original')
    plt.axis('off')
  • 左上:低照度原图。

    plt.subplot(2, 3, 2)
    plt.imshow(low_linear, cmap='gray', vmin=0, vmax=255)
    plt.title('Linear Stretch')
    plt.axis('off')
  • 中上:线性拉伸结果。

    plt.subplot(2, 3, 3)
    plt.imshow(low_eq, cmap='gray', vmin=0, vmax=255)
    plt.title('Histogram Equalization')
    plt.axis('off')
  • 右上:直方图均衡化结果。

    plt.subplot(2, 3, 4)
    plt.hist(low_light.ravel(), bins=256, range=[0, 256], color='gray', alpha=0.7)
    plt.title('Original Histogram')
  • 左下:原图直方图,反映灰度集中在暗区的特点。

    plt.subplot(2, 3, 5)
    plt.hist(low_linear.ravel(), bins=256, range=[0, 256], color='gray', alpha=0.7)
    plt.title('Linear Stretch Histogram')
  • 中下:线性拉伸后直方图,分布被均匀拉开但保持原有相对形状。

    plt.subplot(2, 3, 6)
    plt.hist(low_eq.ravel(), bins=256, range=[0, 256], color='gray', alpha=0.7)
    plt.title('Equalized Histogram')
    plt.tight_layout()
    plt.show()
  • 右下:均衡化后直方图,分布更加均匀平坦。

曝光过度图像的三组对比采用完全相同的布局。

6.4 保存结果

    cv2.imwrite(os.path.join(output_dir, 'low_linear.jpg'), low_linear)
    cv2.imwrite(os.path.join(output_dir, 'low_eq.jpg'), low_eq)
    cv2.imwrite(os.path.join(output_dir, 'over_linear.jpg'), over_linear)
    cv2.imwrite(os.path.join(output_dir, 'over_eq.jpg'), over_eq)
    print("[任务3] 线性拉伸与直方图均衡化结果已保存")

七、主程序入口

if __name__ == "__main__":
  • Python 的惯用写法,确保该脚本被直接运行时才执行以下代码,被 import 时不执行。

    img_normal_path = './DIP2026/DIP2026/bird.jpg'
    img_low_path    = './DIP2026/DIP2026/night.jpg'
    img_over_path   = './DIP2026/DIP2026/car_1.jpg'
  • 定义三幅测试图像的路径变量,分别为正常光照、低照度、曝光过度图像。

    original = load_gray_image(img_normal_path)
    low      = load_gray_image(img_low_path)
    over     = load_gray_image(img_over_path)
  • 调用 load_gray_image() 读取并统一转换为灰度图。

    task1_invert(original)
    task2_log_transform(low, over)
    task3_histogram_enhancement(low, over)
  • 按顺序执行三个实验任务。

    print(f"\n所有实验完成,结果保存在 '{output_dir}' 文件夹中。")
  • 打印完成提示。


八、整体流程总结

任务 功能 核心算法 输入 输出文件
任务1 反色处理 s = 255 - r 正常光照图 inverted.jpg
任务2 对数变换 c·log(1+r) 低照度图 + 过曝图 log_low.jpglog_over.jpg
任务3 线性拉伸与均衡化 (x-min)/(max-min)*255 + equalizeHist 低照度图 + 过曝图 low_linear.jpglow_eq.jpgover_linear.jpgover_eq.jpg

每个任务都包含处理 → 可视化对比(图像 + 直方图) → 保存三个环节,便于观察灰度变换对图像质量和像素分布的影响。

框图结构说明

层级 节点 说明
准备阶段 创建输出目录 确保 output_1 文件夹存在
数据加载 三路读取 load_gray_image() 自动将彩色图转灰度
任务1 反色处理 一路输入(正常图),公式简单直接,输出1个文件
任务2 对数变换 两路输入(低照度+过曝),内部定义 log_transform(),每路显示4幅图(原图、变换图、两直方图),输出2个文件
任务3 线性拉伸与均衡化 两路输入,每路执行 linear_stretch() 和 cv2.equalizeHist() 两种算法,每路显示6幅图(3图+3直方图),输出4个文件
收尾 结束 三个任务按顺序串行执行完毕后程序终止

三种灰度变换算法对比

算法 公式 适用场景 效果
反色 s = 255 - r 正常光照 亮暗反转,底片效果
对数变换 s = c·log(1+r) 低照度 / 过曝 压缩高灰度、拉伸低灰度
线性拉伸 (x-min)/(max-min)×255 对比度不足 线性展开灰度范围
直方图均衡化 累计分布函数映射 对比度不足 非线性展平灰度分布
Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐