☰
Python数字图像处理:灰度化、卷积与傅里叶变换全流程实现
2026/9/26 8:57:58 网站建设 项目流程

简介:基于Python的数字图像处理课程设计资料包,面向学习OpenCV与Numpy的计算机视觉初学者,也适合需要课程设计、实验参考或期末项目的在校生。内容覆盖彩色图像灰度化、卷积与相关操作、高斯核滤波、二维傅里叶变换及逆变换、频谱中心化与整数次幂填充等关键知识点,有助于从空间域到频域完整理解图像处理原理,并快速迁移到实际代码中。压缩包共17个文件,约3.57MB,主体为Python脚本与Jupyter Notebook源码,另配9幅TIF/TIFF测试图像及PNG样例、README、License说明等,目录简洁,便于直接运行和对比结果。资源已有825人学习,源码与真实图像数据均可直接复现灰度化、滤波、频域分析等效果,可作为课程报告、实验演示或后续研究的基础工具。

1. 基于Python数字图像处理:灰度化、卷积与傅里叶变换一整套可复现的课程设计

这套课程设计资源拿到手,是一份能直接跑起来的完整工程:一个 ImageProcess.py 主脚本、两个拆分好的 notebook(work_1.ipynb 和 work_2.ipynb),外加十一张标准测试图像,从 cameraman、lena 到月面图像一应俱全。它把数字图像处理课程里最常考、也是面试笔试最爱问的几个模块——彩色转灰度、卷积与相关、高斯核平滑、二维傅里叶变换、频域中心化、频谱可视化、2 的幂次填充——全部用 OpenCV 加 Numpy 从零实现了一遍。适合正在赶课程设计的学生,也适合想快速把冈萨雷斯《数字图像处理》里的原理落成代码的从业者。真正卡人的往往不是算法本身,而是 BGR 通道顺序、filter2D 边界模式、FFT 低频分量这类细节,后面逐一拆开讲。

2. 环境与测试图集:先解决 BGR 通道顺序,再谈灰度化

2.1 用 conda 搭出可复现的 Python 图像处理环境

网上 python 安装教程很多,但图像处理实验我一般不会往系统 Python 里直接塞包,而是用 conda 单独建一个环境,避免 numpy 和 OpenCV 的版本互相打架。推荐 Python 3.10 起步,这个版本对 opencv-python 和 jupyter 的兼容性都稳定。

conda create -n dip python=3.10 -y conda activate dip pip install opencv-python numpy jupyter jupyter notebook

逻辑说明:先创建名为 dip 的独立环境,再安装四个核心依赖。opencv-python 提供 cv2 模块,numpy 负责矩阵运算与 FFT,jupyter 用来打开资源包里的 work_1.ipynb 和 work_2.ipynb。这里刻意只装 opencv-python,不装 opencv-contrib-python,因为 contrib 里包含的 xphoto、optflow 等扩展模块在课程设计里基本用不到,装了反而可能和其他包产生命名冲突。

环境配好后,先把 ImageProcess.py 和两个 notebook 放在同一个目录下,再放测试图像。如果你用的是 VS Code,记得在 .vscode/settings.json 里指定 python.defaultInterpreterPath 指向 conda 环境的 python.exe,否则终端能 import cv2、notebook 里却报 ModuleNotFoundError,这种翻车我在不同机器上见过太多次。验证安装是否成功,在终端跑一句python -c "import cv2, numpy; print(cv2.__version__, numpy.__version__)",能同时打出两个版本号就说明环境没问题。

2.2 十一张测试图像,每张图分别验证什么

资源包里附带的图像选得很有针对性,覆盖了灰度图、彩色图、纹理图、医学图像和月面遥感图几大类。我建议按下面的映射关系去用,而不是每张图都从头到尾跑一遍:

图像文件类型适合验证的内容
cameraman.tif灰度图边缘检测、频域高通滤波的经典素材
lena_gray_512.tif灰度图灰度化对照、高斯平滑、FFT 频谱分析
lena512color.tiff彩色图BGR 与 RGB 通道顺序验证
mandril_color.tif彩色纹理图彩色转灰度、高频细节保留效果
rose512.tif彩色图色彩空间转换、通道分离
house.tif / house02.tif灰度图卷积核边界效应、图像锐化
lunar_surface.tif遥感灰度图16 位深度读取、频谱能量分布
Characters_test_pattern.tif测试图卡2 的幂次填充、频域滤波对比
einstein.tif灰度图混合滤波、去噪前后对照
1.pngPNG 格式读图路径与格式兼容性测试

这张表是我自己整理的习惯用法,不是资源包里的固定要求。核心思路是:跑通用流程用 cameraman 和 lena,测色彩相关操作用 mandril_color 和 rose512,测深度和格式兼容性用 lunar_surface 和 Characters_test_pattern。每完成一个小节,固定用对应图像验证,排查问题时更容易定位到是代码问题还是输入图问题。

2.3 彩色转灰度:cvtColor 与 imread 灰度模式的差异

彩色转灰度在数学上是对 R、G、B 三个通道做加权平均,OpenCV 使用的权重公式是 Y = 0.299R + 0.587G + 0.114B。但这里有个新手必踩的坑:OpenCV 读进来的默认通道顺序是 BGR,不是 RGB。也就是说 cvtColor 内部其实是按 B 通道乘 0.114、G 通道乘 0.587、R 通道乘 0.299 来算的,顺序反了结果完全不对。

import cv2 import numpy as np img_bgr = cv2.imread('mandril_color.tif', cv2.IMREAD_COLOR) gray_cvt = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) gray_imread = cv2.imread('mandril_color.tif', cv2.IMREAD_GRAYSCALE) print('cvtColor 结果 shape:', gray_cvt.shape) print('imread 灰度模式 shape:', gray_imread.shape) print('两种方式结果是否完全一致:', (gray_cvt == gray_imread).all())

逻辑说明:第一行用 IMREAD_COLOR 把彩色图读成三通道 BGR,第二行用 cvtColor 做加权灰度转换,第三行用 IMREAD_GRAYSCALE 直接读灰度图。最后一行对比两个结果是否逐像素一致。

参数说明:IMREAD_COLOR 固定读成三通道,忽略 alpha 通道;IMREAD_GRAYSCALE 在读入阶段就完成灰度化;IMREAD_UNCHANGED 保留原始所有通道与位深,读 16 位 tif 时要用这个标志。

实际运行后你会发现两个结果完全一致,因为 imread 的灰度模式底层调用的就是同一套加权转换。区别在于:如果你后续还要用彩色信息做别的处理,就要先读彩色图再手动 cvtColor;如果只需要灰度图,直接 IMREAD_GRAYSCALE 更省内存。血泪经验:用 IMREAD_UNCHANGED 读 lunar_surface.tif 这类 16 位图,拿到的是 uint16 数据,后续做卷积和 FFT 时数值范围、显示效果和 8 位图完全不同,容易踩坑。

3. 卷积与相关:空间域处理里最容易混淆的两个操作

3.1 卷积核为什么要翻转,以及 filter2D 实际做的是什么

卷积和相关在数学上只差一个操作:卷积要把核旋转 180 度后再做滑动窗口乘法求和,相关则不做翻转。对高斯核这种中心对称的核,两者结果一样;对非对称核就不同了。

数字图像处理课程里经常出现一个让人懵的点:cv2.filter2D 名义上叫 filter2D,实际执行的是相关而不是严格数学意义上的卷积。计算相关时,输出图像每个像素等于核窗口内像素与核系数的加权和,核不翻转;严格卷积需要先翻转核。OpenCV 选择用相关实现,是因为相关运算在数学上更直观,而且对对称核没有区别。

import cv2 import numpy as np img = cv2.imread('lena_gray_512.tif', cv2.IMREAD_GRAYSCALE) sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float32) corr_result = cv2.filter2D(img, -1, sobel_x) conv_result = cv2.filter2D(img, -1, cv2.flip(sobel_x, -1)) diff = np.abs(corr_result.astype(np.int16) - conv_result.astype(np.int16)) print('相关与卷积结果差异像素数:', (diff > 0).sum())

逻辑说明:sobel_x 是经典的横向边缘检测核,中心对称性不成立,所以相关和卷积结果有差异。先用 filter2D 直接做相关,再用 flip 把核在水平和垂直方向都翻转,模拟严格卷积,然后统计两幅结果图像的差异像素数。

参数说明:filter2D 的第二个参数 ddepth 设成 -1 表示输出图像深度和输入一致。这里把结果转成 int16 再做差,是为了防止 uint8 相减出现负数回绕——如果直接用 uint8 相减,负数会变成 255 附近的数值,统计就全乱了。

实际工程里,边缘检测用相关还是卷积差别不大,因为最后往往要取绝对值来获得边缘强度。但如果你做的是模板匹配或方向敏感的特征提取,就必须搞清楚核的朝向。

3.2 高斯核卷积:从数学公式到 filter2D 的参数设置

高斯平滑是去噪最常用的手段,核心思想是用加权平均替代简单平均,权重服从二维高斯分布,离中心越近权重越大,这样在去噪的同时能尽量保留边缘。OpenCV 给高斯平滑开了两个入口:cv2.GaussianBlur 封装好的高层接口,以及 cv2.getGaussianKernel 加 cv2.filter2D 的手工组合。

import cv2 import numpy as np img = cv2.imread('cameraman.tif', cv2.IMREAD_GRAYSCALE) ksize = 5 sigma = 1.2 # 方案A:直接调用封装好的高斯滤波 blur_a = cv2.GaussianBlur(img, (ksize, ksize), sigma) # 方案B:手工生成高斯核,再用 filter2D 做卷积 kernel_x = cv2.getGaussianKernel(ksize, sigma) kernel_2d = kernel_x @ kernel_x.T blur_b = cv2.filter2D(img, -1, kernel_2d, borderType=cv2.BORDER_REFLECT) print('二维高斯核:') print(kernel_2d) print('两种方案结果是否一致:', (blur_a == blur_b).all())

逻辑说明:getGaussianKernel 生成一个一维高斯核,形状是 (ksize, 1),通过矩阵乘法 kernel_x @ kernel_x.T 和二维可分离高斯核等价。GaussianBlur 内部也走同样的计算路径,只是它把核生成和卷积封装到了一起,所以两种方案结果应该完全一致。

参数说明:ksize 必须为正奇数,常见值是 3、5、7,核越大平滑越强但细节损失越多。sigma 是高斯函数的标准差,控制权重的衰减速度。sigma 设得越小,权重越集中在中心点,图像越接近原图;设得越大,平滑范围越广。一个玄学点:sigma 和 ksize 不匹配时,OpenCV 会自动用 sigma 去反推截断范围,所以如果你指定 sigma=1.2 但 ksize=21,实际核中间大部分区域权重几乎为零,白白浪费计算量。

3.3 边界填充:零填充、镜像填充与反射填充对边缘像素的影响

卷积操作的边界处理是最容易被忽略但最影响视觉效果的地方。图像边缘的像素没有完整的邻域,filter2D 必须从外部拿值来补。OpenCV 提供了多种 borderType,课程设计里足够用的是下面这三种:

import cv2 import numpy as np img = cv2.imread('lena_gray_512.tif', cv2.IMREAD_GRAYSCALE) kernel = cv2.getGaussianKernel(5, 1.2) kernel_2d = kernel @ kernel.T edge_zero = cv2.filter2D(img, -1, kernel_2d, borderType=cv2.BORDER_CONSTANT) edge_replicate = cv2.filter2D(img, -1, kernel_2d, borderType=cv2.BORDER_REPLICATE) edge_reflect = cv2.filter2D(img, -1, kernel_2d, borderType=cv2.BORDER_REFLECT) # 检查边缘区域差异 roi_zero = edge_zero[:5, :5].astype(np.int16) roi_reflect = edge_reflect[:5, :5].astype(np.int16) print('左上角 5x5 区域零填充与镜像填充差异:', (roi_zero - roi_reflect).sum())

逻辑说明:三行代码分别用零填充、复制填充和镜像填充做同一高斯卷积,然后对比左上角边缘区域。BORDER_CONSTANT 用固定值(默认黑色)填充外部;BORDER_REPLICATE 把边缘像素值向外复制;BORDER_REFLECT 像照镜子一样反射边缘像素。

参数说明:filter2D 默认的 borderType 是 BORDER_REFLECT_101,它和 BORDER_REFLECT 的差别在于反射时不重复边缘那一行像素。视觉上 BORDER_REFLECT_101 更自然,能避免边缘出现明显的高光条。需要注意,后两者在傅里叶变换里会引入额外的频率分量,如果做完空间域滤波还要做 FFT 频谱分析,最好把边界填充方式也写清楚,否则对比谱图时会有莫名其妙的多余高频成分。

4. 二维傅里叶变换:从空间域到频域的可视化与逆变换

4.1 为什么要做 fftshift 中心化

二维傅里叶变换把图像从空间域变换到频率域,输出是一个和原图同尺寸的复数矩阵。np.fft.fft2 计算完成后,低频分量(也就是 DC 直流分量)分布在矩阵的四个角,高频分量集中在中心附近。这种布局适合数学推导,但不符合视觉习惯,而且做频域滤波时通常希望低频在中心,所以要用 fftshift 把低频移到中央。

import cv2 import numpy as np img = cv2.imread('cameraman.tif', cv2.IMREAD_GRAYSCALE).astype(np.float32) f = np.fft.fft2(img) fshift = np.fft.fftshift(f) print('原始 FFT 四个角的幅度:', np.abs(f[:5, :5]).mean()) print('中心化后中心区域幅度:', np.abs(fshift[128:133, 128:133]).mean())

逻辑说明:cameraman.tif 是 256x256 图像,fft2 后低频分布在四角,fftshift 后低频集中到中心位置,中心区域幅度远高于四角。中心化的物理含义就是把零频移到图像中心。

参数说明:fftshift 默认对所有维度进行移位,二维图像直接调用即可。逆变换时对应使用 ifftshift,顺序不能反。这里先把图像转成 float32 再做 FFT,是因为 FFT 对 uint8 输入会先把数据提升到 float64 运算,但提前转换能避免某些 OpenCV 版本里输入类型判断异常的问题。

4.2 频谱可视化:为什么不能直接显示 FFT 的幅度

FFT 结果是一个复数矩阵,直接取绝对值后数值跨度极大——DC 分量可能达到几千万,而高频细节只有几百。如果用线性映射直接显示,整张图除了中心一个亮点外全部是黑色,细节全部丢失。所以频谱可视化的标准做法是取对数压缩动态范围。

import cv2 import numpy as np img = cv2.imread('cameraman.tif', cv2.IMREAD_GRAYSCALE).astype(np.float32) fshift = np.fft.fftshift(np.fft.fft2(img)) magnitude = np.abs(fshift) magnitude_log = np.log1p(magnitude) # 归一化到 0~255 显示 magnitude_norm = cv2.normalize(magnitude_log, None, 0, 255, cv2.NORM_MINMAX) spectrum = magnitude_norm.astype(np.uint8) cv2.imwrite('spectrum_cameraman.png', spectrum) print('对数变换前幅度最大值:', magnitude.max()) print('对数变换后幅度最大值:', magnitude_log.max())

逻辑说明:np.log1p 是 ln(1+x),加 1 是为了避免 0 值取对数出现负无穷。magnitude_log 把原本几千万的 DC 分量压缩到可显示范围,再用 normalize 做线性拉伸到 0-255。如果不做对数变换,display 出来的图像几乎全黑。

参数说明:normalize 的 NORM_MINMAX 模式会把数组最小值映射到 0、最大值映射到 255。需要注意,归一化之后的数组是 float64,转到 uint8 必须先 astype,否则 imwrite 写出的图片是 16 位深,很多看图软件打不开或者显示异常。如果觉得 log1p 之后图像仍然偏暗,可以再乘一个缩放系数,或者用 np.sqrt 代替 log,两种压缩强度不同。

4.3 逆变换:ifftshift 与 ifft2 的顺序,以及重建误差的来源

频域处理的最后一步通常是把处理后的频域分量逆变换回空间域。顺序是先 ifftshift 把低频分量从中心移回四角,再 ifft2 做逆变换。这个顺序经常被写反,一旦写反,恢复出的图像会出现中心旋转 180 度的错位。

import cv2 import numpy as np img = cv2.imread('cameraman.tif', cv2.IMREAD_GRAYSCALE).astype(np.float32) fshift = np.fft.fftshift(np.fft.fft2(img)) # 光学低通:保留中心区域 rows, cols = img.shape crow, ccol = rows // 2, cols // 2 lowpass_mask = np.zeros((rows, cols), dtype=np.float32) lowpass_mask[crow-30:crow+30, ccol-30:ccol+30] = 1 filtered_shift = fshift * lowpass_mask f_ishift = np.fft.ifftshift(filtered_shift) img_recon = np.fft.ifft2(f_ishift) img_real = np.real(img_recon) img_real_norm = cv2.normalize(img_real, None, 0, 255, cv2.NORM_MINMAX) out = img_real_norm.astype(np.uint8) cv2.imwrite('lowpass_cameraman.png', out)

逻辑说明:这段代码演示了完整的频域滤波链路——计算 FFT、中心化、构造低通掩码、频域乘法、去中心化、逆变换。ifftshift 的位置在乘法之后、逆变换之前,把处理完的中心化频谱还原成四角布局,ifft2 才能正确回到空间域。

参数说明:lowpass_mask 是理想低通滤波的矩形掩码,半径为 30 像素。注意理想矩形截止会产生振铃效应,这是傅里叶变换的性质导致的,不是代码 bug。实际项目中更推荐用高斯低通掩码替代,后面章节展开讲。逆变换输出是复数,直接取实部 np.real 会保留负数值,必须归一化到 0-255 再转 uint8,直接 astype 会导致负值变成 255 数值而产生大片白色噪点。

5. 避坑:数字图像处理课程设计里最常翻车的五个细节

5.1 用 IMREAD_UNCHANGED 读 16 位 tif,后续处理结果全是黑的

现象:读入 lunar_surface.tif 后,imshow 显示正常,但做完灰度转换或 FFT,输出图像黑乎乎一片,数值范围严重超出预期。

原因:lunar_surface.tif 是 16 位深图像,IMREAD_UNCHANGED 读进来是 uint16 类型,数值范围 0-65535。后续卷积核高斯核的输出值都在几千上万的量级,显示时按 0-255 线性映射,大部分像素都超过 255,显示成纯白或纯黑。

解决:读取时直接指定 cv2.IMREAD_GRAYSCALE,或者读进来后先转 float32 再除以 65535 归一化到 0-1 区间。我在处理所有 tif 图像时都强制先打印 img.dtype 和 img.max(),确认数据范围再走后续流程。从那以后这种翻车再没遇到过。

5.2 不调用 fftshift,频谱图能量全部堆积在四个角

现象:对 cameraman.tif 做 FFT 后直接取幅度显示,发现图像四个角各有一个亮斑,中间一片黑,完全看不出频率分布规律。

原因:np.fft.fft2 输出的低频分量在矩阵四角,这是 FFT 算法的自然布局。四角的亮斑就是 DC 分量,中间区域对应高频分量,数值相对太小,线性显示下全被压暗。

解决:在取绝对值之前先执行 np.fft.fftshift,把低频移动到中心。检查是否做对,可以看中心区域幅度是否远大于其他区域。如果用了 fftshift 还是四角亮,检查是不是在 ifftshift 之后又做了一次 fftshift,两个 shift 叠加等于没做。

5.3 频谱图像直接显示一片黑,只有中心一个亮点

现象:fftshift 之后取 np.abs(f),直接 imshow,整张图只有中心一点亮光,周围全是黑色,高频信息完全看不到。

原因:幅度谱的动态范围太大了。DC 分量能达到几千万,高频细微纹理的幅度可能只有几百,线性映射下所有非低频信息都被压缩到不可见。

解决:取幅度后做 np.log1p 压缩动态范围,再用 cv2.normalize 归一化到 0-255。如果做了 log 之后还是偏暗,检查是否用了 np.abs 而不是 np.sqrt 或者是否包含了 DC 分量参与归一化——DC 分量贡献了 log 变换后的主要亮度范围,把它单独抑制掉也是一种常见做法。

5.4 2 的幂次填充后在图像边缘出现规律性条纹

现象:把图像从 256x256 零填充到 512x512 再 FFT 做低通滤波,重建后的图像边缘出现一圈圈明暗交替的条纹,像水波纹。

原因:零填充等于在图像四周制造了从真实像素到 0 的阶跃跳变,这个跳变在频域里对应额外的高频分量。如果再用理想矩形低通掩码滤波,矩形截止在频域上等价于 sinc 函数的卷积,在空间域表现为振铃波纹。两个因素叠加,边缘条纹特别明显。

解决:处理前先把图像值减去均值再做 FFT,从根源上削弱 DC 分量;或者用高斯低通掩码替代理想矩形掩码,高斯在频域平滑截止,不会产生 sinc 振铃。输入里提到的 2 的整数次幂填充,本身是为了 FFT 算法固定 radix-2 尺寸提升速度,但填充方式最好用镜像填充而非零填充。

5.5 逆变换后直接 uint8 强转,重建图像出现大片黑白噪点

现象:ifft2 得到的结果直接调 astype(np.uint8),输出的图像出现很多随机的白色斑点,边缘地方还会出现黑色条纹。

原因:ifft2 输出是复数,存在浮点误差。即使理论上实部应该完全等于原图,实际计算中虚部会有极小的残余值,实部也可能出现负值或超过 255 的值。uint8 转换时,负数映射成 255,超过 255 的值回绕到 0 附近,就产生了随机噪点。

解决:先取 np.real 丢弃虚部,再用 cv2.normalize 把实部数值线性映射到 0-255,最后才转 uint8。如果想评估重建误差,不要直接比较 uint8 图像,而是用 float32 的原图与重建图计算均方误差。我在对比滤波前后图像质量时,固定这套流程,避免把数值类型误差误判为算法缺陷。

6. 把课程设计工程化:ImageProcess.py 的分层写法与命令行参数化

6.1 把读图、处理、保存拆成三个独立函数

课程设计交上去的脚本如果只是把几十行代码堆在 notebook 里,答辩时很难讲清楚。更实际的做法是把整个流程抽象成三个独立函数,每个函数只做一件事,主入口用 argparse 接收命令行参数,这样既能处理单张图像,也能批量处理整个目录。

import argparse import cv2 import numpy as np def read_image(path, gray=True): flag = cv2.IMREAD_GRAYSCALE if gray else cv2.IMREAD_COLOR img = cv2.imread(path, flag) if img.dtype == np.uint16: img = (img / 65535.0 * 255.0).astype(np.uint8) return img def gaussian_smooth(img, ksize=5, sigma=1.2): return cv2.GaussianBlur(img, (ksize, ksize), sigma) def frequency_spectrum(img): fshift = np.fft.fftshift(np.fft.fft2(img.astype(np.float32))) magnitude = np.log1p(np.abs(fshift)) return cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) if __name__ == '__main__': parser = argparse.ArgumentParser(description='数字图像处理课程设计') parser.add_argument('--input', required=True, help='输入图像路径') parser.add_argument('--output', default='output.png', help='输出图像路径') parser.add_argument('--mode', default='gray', choices=['gray', 'blur', 'fft'], help='处理模式:灰度化/高斯平滑/频谱') args = parser.parse_args() image = read_image(args.input) if args.mode == 'gray': result = image elif args.mode == 'blur': result = gaussian_smooth(image) elif args.mode == 'fft': result = frequency_spectrum(image) cv2.imwrite(args.output, result) print(f'已保存: {args.output}')

逻辑说明:read_image 统一入口处理不同位深问题,内部把 uint16 转成 8 位灰度,避免后面每个函数都要判断类型。gaussian_smooth 和 frequency_spectrum 各自负责一个处理模式,主流程只根据 mode 做分发。

参数说明:argparse 的 choices 限制了 mode 只能是 gray、blur、fft 三个值,输入其他值会直接报错而不是静默进入错误分支。这种设计在答辩时要展示哪个模块,直接改 --mode 参数就行,不用改代码。

6.2 批量跑完整个测试集,用文件名后缀区分处理结果

课程设计最花时间的往往不是写算法,而是验证算法在整套测试图像上的表现。手动一张张跑完再手动保存,效率太低。把 ImageProcess.py 扩展成批量模式,用 glob 遍历所有图像,按模式生成带后缀的输出文件名。

import glob import os import argparse import cv2 import numpy as np def batch_process(input_dir, output_dir): os.makedirs(output_dir, exist_ok=True) image_paths = glob.glob(os.path.join(input_dir, '*.tif')) + \ glob.glob(os.path.join(input_dir, '*.png')) + \ glob.glob(os.path.join(input_dir, '*.tiff')) for path in image_paths: base = os.path.splitext(os.path.basename(path))[0] image = read_image(path) if args.mode == 'gray': cv2.imwrite(os.path.join(output_dir, f'{base}_gray.png'), image) elif args.mode == 'blur': blurred = gaussian_smooth(image) cv2.imwrite(os.path.join(output_dir, f'{base}_blur.png'), blurred) elif args.mode == 'fft': spectrum = frequency_spectrum(image) cv2.imwrite(os.path.join(output_dir, f'{base}_fft.png'), spectrum) print(f'批量处理完成,共 {len(image_paths)} 张图像')

逻辑说明:glob 同时匹配 tif、png、tiff 三种常见格式,splitext 取文件名主体,输出文件名加后缀避免覆盖原图。exist_ok=True 允许重复执行而不报错,多次调试时不用手动删输出目录。

参数说明:这个批量脚本直接把第 6.1 节的三个函数拿过来复用,体现了函数拆分的好处。如果后续要增加新处理模式,只需要新写一个处理函数,并在 if 分支里加一行调用。实际使用时先把输入目录里混放的各类图像挑出来,避免把 README 或 LICENSE 文件也当作图像读进去。

这套工程化脚本我在课程设计答辩前反复用过多次。从那以后,我每次拿到新的图像数据集,都会强制走一遍「先确认 dtype 和通道顺序,再统一读图入口,最后批处理出结果」的流程。把 ImageProcess.py 里的三个函数当作地基,后续加任何新算法都只是往上垒砖,不会再因为读图类型问题推倒重来。希望这套流程和踩坑记录能帮你在同样的路上少走几步。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询