机器学习篇---数字图像增强(灰度变换与直方图均衡)
一、模块导入与环境准备
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.jpg, log_over.jpg |
| 任务3 | 线性拉伸与均衡化 | (x-min)/(max-min)*255 + equalizeHist |
低照度图 + 过曝图 | low_linear.jpg, low_eq.jpg, over_linear.jpg, over_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 |
对比度不足 | 线性展开灰度范围 |
| 直方图均衡化 | 累计分布函数映射 | 对比度不足 | 非线性展平灰度分布 |
更多推荐



所有评论(0)