数字图像处理习题库:滤波、均衡化与边缘检测代码复现
2026/9/18 12:06:10 网站建设 项目流程

简介:这份数字图像处理复习习题库面向高校本科生、考研学生及需要夯实图像处理基础的工程技术人员,用于课程期末复习、考研专业课梳理与知识自测。文档以简答、名词解释和计算三类题型组织,覆盖图像消噪、图像锐化与平滑、均值滤波与中值滤波的适用噪声差异、伪彩色与假彩色增强的区别、几何失真校正的空间变换与灰度插值步骤、梯度法与Laplacian算子边缘检测的一阶与二阶导数特性、同态滤波流程、直方图均衡化与规定化、图像恢复与图像增强的主客观差别、图像分辨率与显示分辨率及采样量化对图像质量的影响等核心考点,并配有典型计算题的变换公式推导练习。资源为1个doc文档,压缩包约1.08MB,打开即可查阅,适合打印后逐题作答与对照答案订正。目前已有787人学习下载,可作为课堂笔记之外的补充题库,帮助读者在有限时间内串联各章节概念、检验掌握程度并查漏补缺。

1. 一份能当"错题本"用的数字图像处理习题库

期末周最怕的不是没资料,而是资料只有答案没有推导。这份"数字图像处理复习习题库.doc"属于后者里比较少见的一类:简答题、名词解释、计算题、实训题四段式结构,每题下面都带着推演过程,比如直方图均衡化的取整映射表、中值滤波前后的逐像素对比、串行通信波特率的单位换算。它不是课件截图拼盘,而是一份可以反推出考点分布的题册。

适合两类人:一是正在备考数字图像处理的学生,想找一份能对着公式逐行核对的题库;二是工作几年后要做图像预处理模块的工程师,需要把遗忘的滤波、锐化、直方图理论捡回来。全文围绕均值滤波与中值滤波的适用边界、直方图均衡化的离散归并、边缘检测的一阶与二阶导数差异这几个核心点展开,每个点都还原成能跑的代码或能手算的步骤。

2. 均值滤波与中值滤波:从噪声模型推导选型

2.1 两种噪声的统计特性决定了滤波器的胜负

题库里第 3 题问得很直白:椒盐噪声和高斯噪声分别适合谁。答案背下来容易,理解为什么才是关键。椒盐噪声的特点是幅值近似相等、位置随机,图像里既有干净点也有被污染的点;中值滤波在一个窗口内排序后取中间值,只要污染点不超过窗口像素的一半,中值就落在干净值上,等于"用干净点替换脏点"。均值滤波则把污染点算进平均,噪声均值不为零,误差被摊到整个窗口。

高斯噪声相反,它作用在每一个像素上,幅值近似正态分布,均值接近 0。此时窗口内找不到"干净点"可挑,中值的抽样优势消失;而均值相当于一次无偏估计,多次取值求平均能把均值为 0 的随机扰动抵消掉。题库第 1 题把这一点抽象成了更一般的结论:参与平均的像素越多,消噪能力越强,所以多帧平均和空间模板平均本质是同一件事,只是样本来自时间维还是空间维。

噪声类型推荐滤波器核心原因代价
椒盐噪声中值滤波排序取中值,剔除孤立极值点计算量随窗口增大而升高
高斯噪声均值滤波零均值随机量可被平均抵消边缘被一并模糊
混合噪声先中值后均值先去脉冲,再压随机扰动两次滤波,细节损失叠加
多帧可获取帧间平均时间维样本数 M 可远大于 n×n需要静止场景

选型不是死规矩。实际工程里如果噪声模型未知,我一般会先对一块平坦区域估方差,再决定走哪条路,而不是直接套中值。

2.2 用 NumPy 复现两种滤波并对比

把题库里的定性结论跑成定量结果,最省事的做法是用 NumPy 手写滤波核,不依赖 OpenCV,这样每一行都能对应到课堂公式。

import numpy as np def add_salt_pepper(img, ratio): """按比例注入椒盐噪声,0 和 255 各占一半""" out = img.copy() num = int(img.size * ratio) coords = np.random.choice(img.size, num, replace=False) out.flat[coords[:num // 2]] = 0 out.flat[coords[num // 2:]] = 255 return out def mean_filter(img, k): """k x k 均值模板,边界用反射填充避免黑边""" pad = k // 2 p = np.pad(img.astype(np.float32), pad, mode='reflect') out = np.zeros_like(img, dtype=np.float32) for i in range(k): for j in range(k): out += p[i:i + img.shape[0], j:j + img.shape[1]] return (out / (k * k)).astype(np.uint8) def median_filter(img, k): """k x k 中值模板,逐像素排序取中位""" pad = k // 2 p = np.pad(img, pad, mode='reflect') out = np.zeros_like(img) for i in range(img.shape[0]): for j in range(img.shape[1]): window = p[i:i + k, j:j + k] out[i, j] = np.median(window) return out

add_salt_peppernp.random.choice无放回抽取像素坐标,保证同一位置不会被重复污染,前半赋值 0、后半赋值 255,模拟明暗两种脉冲。均值滤波采用先累加后统一除的写法,比每步除法少一次浮点运算;np.padreflect模式让边界像素参与滤波时不会引入人为的 0 值黑边,这一点题库里的手算题通常直接规定"边界填 0",但工程实现中反射填充更合理。中值滤波用双重循环加np.median,可读性优先;真实项目里会改成sliding_window_view或形态学加速,百万像素以上的图直接跑这个版本会明显卡顿。

跑一组对比就能复现题库结论:对同一张图分别注入椒盐噪声后做 3×3 均值和中值,中值的峰值信噪比通常高出好几个分贝;换成高斯噪声则反过来。这个实验比背结论有用得多。

2.3 关键参数与踩坑点

窗口大小是唯一需要调的参数,但它不是越大越好。3×3 适合细纹理图像,5×5 以上开始明显吞掉边缘,7×7 在二值或高对比图像上甚至会把细线滤没。题库第 10 题就是拿 7 像素宽的白条做例子,考察 3×3、5×5、7×7 邻域平均后白条会不会消失——当窗口尺寸接近或超过线宽,噪声是没了,目标也没了。

提示:中值滤波的窗口尺寸要保证"污染像素数小于窗口像素数的一半",否则中值本身就可能被噪声占据。椒盐噪声密度 20% 时,3×3 窗口理论余量已经接近极限,建议换 5×5。

另一个容易被忽略的点是数据类型。用uint8做累加会溢出,必须像上面代码那样先转float32。中值滤波不涉及求和,可以直接在整数域操作,这也是它比均值滤波省内存的一个细节。最后,彩色图像不要对 RGB 三通道各自滤波后拼接,色度通道的排序结果可能引入新的杂色,正确做法是转成 YCbCr 或 Lab,只对亮度通道滤波。

3. 直方图均衡化:离散灰度级下为什么得不到平坦直方图

3.1 从累积分布到取整映射的完整推演

题库第 3 题给出了一组 10 灰度级的原始直方图[1, 4, 5, 5, 3, 1, 3, 3, 2, 9],总和 36 个像素。均衡化的标准做法是:先归一化成概率,再求累积分布,最后乘以最大灰度级取整,得到映射关系。这套流程在连续情况下能保证输出直方图完全均匀,但在离散情况下,多个相近的累积值会被取整到同一个灰度级,发生"灰度级归并"。

用代码把这个过程完整走一遍,顺便验证题库里那张映射表。

import numpy as np hist = np.array([1, 4, 5, 5, 3, 1, 3, 3, 2, 9], dtype=np.float64) total = hist.sum() # 总像素数 = 36 pdf = hist / total # 归一化概率 cdf = np.cumsum(pdf) # 累积分布 L = len(hist) - 1 # 最大灰度级 = 9 mapping = np.round(cdf * L).astype(int) # 取整得到映射关系 print("累积分布:", np.round(cdf, 4)) print("映射关系:", mapping) # 统计均衡化后的新直方图 new_hist = np.zeros_like(hist) for old, new in enumerate(mapping): new_hist[new] += hist[old] print("均衡后直方图:", new_hist) print("均衡后概率:", np.round(new_hist / total, 4))

total取 36 是因为直方图各分量之和就是像素总数,这一步错了后面全错。np.cumsum代替手写循环,累积分布的物理含义是"灰度小于等于当前级的像素占比"。mapping里出现的重复值就是归并点,比如原始灰度 3 和 4 可能同时映射到 4,合并后该灰度级的概率变成两者之和,自然就比相邻级高出许多,直方图不再平坦。

映射关系会得到类似0→0, 1→1, 2→3, 3→4, 4→4, 5→5, 6→5, 7→6, 8→7, 9→9的结果,和题库答案一致。可以看到 3、4 归并到 4,5、6 归并到 5,这就是离散均衡化无法完全平坦的直接证据。题库第 10 题问的正是这一点:灰度级的离散性导致归并,结果只是"趋于平坦"。

3.2 均衡化与规定化的差异与选择

题库第 8 题把均衡化和规定化放在一起比较。均衡化不需要人工干预,输出是固定的,适合每张图都不同的批量处理场景;规定化需要指定目标直方图形状,比如"高灰度区域概率更大",适合有明确亮度区间的场景——题库里说规定化在高灰度区域用较大的规定化函数,结果图像比均衡化更亮、细节更清楚,说的就是这个道理。

两者的数学基础相同,都是通过改变直方图形状来拉伸对比度,只是规定化多做了一步"从均衡结果反查目标累积分布"的映射。实现上,规定化等价于两次均衡:先把原图均衡一次,再把目标直方图均衡一次,最后用后者的反函数去查前者。用代码描述就是map2[inv_map1[pixel]],理解了这层嵌套,题库里所有的直方图题都不需要背步骤。

方法输入输出直方图适用场景
直方图均衡化原图趋于均匀,自适应批量增强、对比度未知
直方图规定化原图 + 目标直方图逼近指定形状需要特定亮度分布
多帧平均M 帧同一场景噪声方差降至 1/M静态场景、有连续帧

注意:均衡化处理的是整幅图的全局对比度。如果图像存在明显的光照不均,比如一侧过曝一侧过暗,全局均衡会把亮区拉爆,这时应先做同态滤波或分块均衡。题库第 7 题提到的同态滤波,作用就是压缩照度分量、保留反射分量,和均衡化不是替代关系。

3.3 用 OpenCV 快速验证与参数化

手写一遍是为了理解,实际验证用 OpenCV 一行即可,但要留意它内部做了一次额外的归一化,结果和手算可能略有出入。

import cv2 import numpy as np img = cv2.imread('input.jpg', cv2.IMREAD_GRAYSCALE) # 标准全局均衡化 eq = cv2.equalizeHist(img) # 对比度受限自适应均衡化,抑制平坦区域噪声放大 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) adap = clahe.apply(img) # 统计均衡前后的直方图熵,熵越大说明灰度分布越均匀 def entropy(image): h = cv2.calcHist([image], [0], None, [256], [0, 256]).ravel() p = h / h.sum() p = p[p > 0] return -np.sum(p * np.log2(p)) print("原图熵:", entropy(img)) print("均衡后熵:", entropy(eq)) print("CLAHE 后熵:", entropy(adap))

clipLimit=2.0是 CLAHE 的对比度裁剪阈值,值越大增强越强但噪声也越明显,一般取 2.0 到 4.0;tileGridSize=(8, 8)把图像切成 8×8 的块分别均衡,块越小局部适应性越强,但块边界可能出现明显的接缝。熵值用于定量判断均衡效果,灰度分布越均匀熵越大,把三种方法的熵排一下序,就能看出全局均衡在多数图像上确实提升了分布均匀度。

需要留意的是,cv2.equalizeHist只接受单通道 8 位图,彩色图要先转灰度或转 YCrCb 只处理 Y 通道。另外它内部按 256 级计算,和用 10 级手算的结果在取整点上会有细微差异,核对题库答案时以手算为准。

4. 边缘检测与几道计算题的标准化解法

4.1 梯度算子与 Laplacian 的模板差异

题库第 6 题的答案点到了一阶和二阶导数的本质区别:梯度算子用一阶导数,认为灰度剧烈变化的极大值点是边缘;Laplacian 用二阶导数,认为零交叉点是边缘。这个区别决定了它们对噪声的敏感程度不同——求导阶数越高,对高频噪声的放大越厉害,所以 Laplacian 对噪声更敏感,通常要先做高斯平滑,这正是 LoG 算子的由来。

用代码把两个模板的响应值算出来,比看公式直观。梯度算子常见的简化模板是[-1, -1, 1; 1, 1, 1; -1, -1, 1]这类形式,Laplacian 则用[1, 1, 1; 1, -4, 1; 1, 1, 1]。注意 Laplacian 模板的系数和为 0,意味着平坦区域的响应严格为 0,只在灰度变化处产生正负值,边缘就藏在正负交界的零交叉点上。

import numpy as np from scipy.signal import convolve2d img = np.array([[8, 9, 0, 7, 6], [3, 5, 6, 1, 5], [8, 4, 0, 2, 9], [0, 4, 2, 1, 2], [3, 4, 1, 2, 1]], dtype=np.float64) # 水平方向的梯度模板(简化形式) grad = np.array([[-1, -1, -1], [ 0, 0, 0], [ 1, 1, 1]], dtype=np.float64) lap = np.array([[ 1, 1, 1], [ 1, -4, 1], [ 1, 1, 1]], dtype=np.float64) g_res = convolve2d(img, grad, mode='same', boundary='fill', fillvalue=0) l_res = convolve2d(img, lap, mode='same', boundary='fill', fillvalue=0) print("梯度响应:\n", g_res) print("Laplacian 响应:\n", l_res)

mode='same'保证输出尺寸与原图一致,符合题库第 12 题"给出与图像尺寸一致的结果"的要求;boundary='fill', fillvalue=0对应题库反复出现的"边界像素填 0"约定。梯度响应里的最大值位置就是边缘位置,Laplacian 响应里正负过零点才是边缘,两者在理想阶跃边缘上会给出接近的位置,但在斜边缘和噪声区域分歧很大。这也解释了为什么 Canny 要先把梯度做非极大值抑制,而 LoG 必须先平滑。

4.2 存储容量与传输时间的单位换算套路

题库第 9、10、11 题和串行通信那题都属于"给参数算容量或时间",公式固定,坑全在单位上。

帧大小计算:宽 × 高 × 每像素位数 / 8得到字节数,/ 1024得 KB,再/ 1024得 MB。256 级灰度是 8 位,32 位真彩色是 4 字节,别把 32 位当成 32 个灰度级。题库里 1024×1024、256 级灰度的图,数据量是1024×1024×8 = 8388608位,约 1 MB。

传输时间计算要先算帧长:一帧包含起始位、8 个信息位、结束位,共 10 位。题库的1024×1024×(8+2)/56000就是先把像素位宽补上 2 位开销再除以波特率。这里要分清"波特率"和"比特率",题库定义波特率就是每秒传输的比特数,所以可以直接除。

def frame_bits(width, height, bit_per_pixel, overhead=0): """计算一幅图像的总位数,overhead 为每像素额外开销位""" return width * height * (bit_per_pixel + overhead) def transfer_seconds(bits, baud_bps): return bits / baud_bps # 1024x1024 灰度图,帧开销 2 位,56Kbps 信道 bits = frame_bits(1024, 1024, 8, overhead=2) print("总位数:", bits) print("56K 信道耗时(秒):", transfer_seconds(bits, 56000)) # 视频帧存储:512x512 真彩色,32 位,每秒 30 帧 per_frame_bytes = 512 * 512 * 32 / 8 per_second = per_frame_bytes * 30 print("每秒字节:", per_second) print("40GB 可存秒数:", 40 * 1024**3 / per_second) print("10 倍压缩后可存秒数:", 40 * 1024**3 / (per_second / 10))

frame_bitsoverhead参数就是为串行帧开销准备的,题库里的8+2对应这里传overhead=2。存储容量换算统一用 1024 进制,机械硬盘厂商标注常用 1000 进制,题目没说就按 1024。压缩比 10 直接作用在每秒字节数上,注意是"可存秒数变成 10 倍",而不是"容量变成 10 倍",这两个说法容易在写答案时混。

提示:二值图像的白条宽度那类题,先换算白条宽度占多少个像素,再和滤波窗口尺寸比较。窗口大于线宽时白条会被填掉,这是判断答案能否成立的最快方法,不用逐像素算。

4.3 中值滤波在具体图像上的手动验证

题库第 13 题给了一张 8×8 的灰度矩阵要求做中值滤波。手算时按行滑动窗口,每个窗口取第 5 大(9 个元素的中位数),可以先用代码验证再对照答案。

import numpy as np img = np.array([[1, 1, 1, 8, 7, 4, 2, 7], [2, 3, 2, 5, 5, 3, 3, 3], [3, 2, 5, 5, 3, 3, 3, 3], [2, 5, 5, 6, 4, 6, 3, 2], [5, 5, 4, 5, 5, 8, 2, 3], [4, 6, 7, 8, 2, 3, 4, 5]]) def median_filter_manual(img, k=3): h, w = img.shape pad = k // 2 p = np.pad(img, pad, mode='constant', constant_values=0) out = np.zeros_like(img) for i in range(h): for j in range(w): out[i, j] = np.median(p[i:i + k, j:j + k]) return out print(median_filter_manual(img))

np.padconstant_values=0对应题库"边界像素填 0",np.median自动处理奇数窗口的中位数。中值滤波的非线性特性意味着它不满足叠加原理,所以不能像均值那样靠卷积快速实现,必须逐点排序。这也是它在实时系统里通常被限定在小窗口的原因。

5. 从习题到工程:给滤波和均衡化做参数化封装

习题做完一圈,真正能用起来的是把它封装成带参数、能批量验证的模块。把前面零散的函数组织成一个类,输入输出路径、窗口大小、滤波类型都做成配置项,跑一次就能同时输出滤波结果和量化指标。

import cv2 import numpy as np class PreprocessPipe: def __init__(self, method='median', ksize=3, use_clahe=False): self.method = method self.ksize = ksize self.use_clahe = use_clahe def _filter(self, gray): if self.method == 'median': return cv2.medianBlur(gray, self.ksize) if self.method == 'mean': return cv2.blur(gray, (self.ksize, self.ksize)) if self.method == 'gaussian': return cv2.GaussianBlur(gray, (self.ksize, self.ksize), 0) raise ValueError(f"未知滤波方法: {self.method}") def run(self, path): gray = cv2.imread(path, cv2.IMREAD_GRAYSCALE) out = self._filter(gray) if self.use_clahe: clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) out = clahe.apply(out) return gray, out def psnr(a, b): """计算峰值信噪比,用于比较滤波前后与参考图的差距""" mse = np.mean((a.astype(np.float64) - b.astype(np.float64)) ** 2) if mse == 0: return float('inf') return 10 * np.log10(255 ** 2 / mse) pipe = PreprocessPipe(method='median', ksize=5, use_clahe=True) src, dst = pipe.run('noisy.jpg') print("PSNR:", psnr(src, dst))

_filter用方法名做分发,ksize必须传奇数,OpenCV 的medianBlurblur都要求如此,传偶数会直接报错。use_clahe开关把滤波和对比度增强串成一条流水线,对应题库实训题里"组织成一个处理流程"的要求——先做什么后做什么,就是靠这种开关组合去验证的。psnr用来量化滤波是否过度:信噪比太低说明欠滤波,结构相似度掉太多说明过滤波,两个指标一起看比单看一个可靠。

参数标定可以用网格搜索,把窗口尺寸在 3、5、7 之间遍历,滤波方法在 median、mean、gaussian 之间遍历,每种组合算 PSNR 和运行耗时,最后按"噪声类型 → 最优组合"整理成一张表。椒盐噪声占比超过 15% 时,median 5×5 几乎总是最优;高斯噪声下 mean 或 gaussian 更稳。把这张表存成配置,下次直接查,不用重新试。

注意:PSNR 对亮度偏移不敏感,对模糊敏感,比较不同窗口大小时它偏向小窗口。做选型时建议再补一个边缘保持度指标,比如用 Canny 提取边缘后统计边缘像素量的下降比例,下降越多说明细节丢得越厉害。

最后留一个可复现的验证方法:拿一份自带噪声的样图,固定随机种子生成椒盐和高斯两种污染版本,把三种滤波和各窗口组合的 PSNR、耗时、边缘保持度打包成 CSV,用同一份数据画曲线。这样得到的结论是可以写进项目文档、也可以拿去对答案的,比背题库原话靠谱得多。

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

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

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

立即咨询