
简介这是一份基于全局法实现图像垂直条纹去除的MATLAB程序包专攻高光谱图像与RGB图像中的规则条纹噪声问题适合遥感影像预处理、计算机视觉等方向的科研人员和工程师使用。程序注释详尽代码结构清晰针对倾斜状态的条纹图像需先进行旋转校正再调用算法即可获得较好效果。压缩包共含2个文件均为.m脚本大小仅2KB分别提供循环低速版与无循环高速版两种实现。作者通过处理高光谱数据对比测试高速版执行效率比低速版提升近20倍能够显著缩短批量影像处理时间同时为读者展示了循环语句与向量化运算在实际算法中的性能差异可作为图像去噪教学或自学的典型范例。该资源目前已有886人学习下载对于希望快速去除条纹、提升处理效率或研究算法优化机制的读者是一份轻量而实用的参考资料。1. 全局法图像垂直条纹去除为什么局部滤波压不住竖条噪声低剂量CT图像、遥感影像或工业线阵相机里垂直条纹是一类典型系统噪声画面中每隔几列就叠一条或亮或暗的竖线病灶边缘、道路纹理还没看清条纹先糊了一脸。它的本质是探测器像元响应不一致、读出偏置漂移特征是列相关、与行内容无关。留给工程师的问题是局部空域滤波比如中值滤波和均值滤波压这些竖条噪声时总在翻车——压干净了细节也没了细节保住了条纹还在。全局法不逐像素修补而是把整幅图当作一个整体从傅里叶频谱定位条纹周期或在全局优化框架里把条纹分量从图像内容中分离出来才能真正把竖条剥离。这篇笔记讲清全局法原理、可复现的Python流程、参数设定和常见踩坑点适合做遥感图像预处理、低剂量CT去噪和工业视觉的工程师。2. 垂直条纹的全局建模从傅里叶谱亮线到列偏置矩阵2.1 垂直条纹的数学模型加性列偏置把一张含条纹的图像写成矩阵行方向是图像的高度y方向列方向是宽度x方向。垂直条纹的特点是同一列里所有像素的偏差完全相同不同列的偏差不同。因此观测图像可以建模为I(x, y) J(x, y) s(x) n(x, y)其中J是理想图像s(x)是每条列上的加性偏置n是随机噪声。这个模型虽然简单却把垂直条纹从图像内容里剥离出来——条纹是只依赖列坐标的向量不是随空间变化的二维结构。用线性代数的话说条纹矩阵S [s(1), s(2), ..., s(W)]沿每一行重复展开是一个秩为1矩阵。这就是全局法的理论基础。实际工程中条纹不总是纯加性。线阵CCD的像元响应不一致会产生乘性条纹g(x)·J(x, y)此时可以先用对数变换ln(I) ln(J) ln(g)把乘性变成加性再用同一套全局分离方法。遥感影像处理里经常先做辐射定标再做条纹校正也是这个原因。这个模型的价值在于它告诉你竖条噪声不是二维噪声而是一维列偏置在二维平面上的复制粘贴。局部滤波的问题在于它们把s(x)当成图像内的结构来做邻域平均。全局法的思路是直接估计整条列的偏置曲线s(x)或者在频域把该曲线对应的能量剔除。模型正确后面的算法才有意义。2.2 局部空域滤波为什么翻车中值、均值与维纳滤波的局限很多工程师第一次处理竖条噪声会习惯性上中值滤波或均值滤波。以3x3中值滤波为例它对每个像素取邻域中值竖条纹的宽度通常是1到2列邻域混合后条纹确实会被压淡但代价是图像分辨率整体下降。低剂量CT里的肺纹理、血管断面都是亚毫米级结构卷积窗口一过纹理被拉成各向同性模糊定量分析直接失准。更隐蔽的问题是维纳滤波。维纳滤波需要噪声的功率谱先验而竖条噪声是非平稳的固定模式噪声实际功率谱估算不准时逆滤波会在条纹边缘产生大量振铃图像看起来像蒙了一层水波。这类方法把条纹当成普通高频噪声处理本质上没有利用条纹是列相关的这个关键信息。还有一个容易翻车的场景遥感图像中的农田、城市道路本身就有规则的垂直纹理。局部滤波会把真实纹理和条纹一起平滑你无法告诉算法这里有一条真正的道路边界要保留。这是局部空域方法的死结——它没有一个全局的列偏置模型就无法区分内容造成的列间变化和噪声造成的列间变化。真正的解决路径只有两条要么把图像变换到频域让条纹周期能量聚成一条线后切除要么建立一个全局优化模型让算法在整个图像范围内自动分离条纹与内容。这两条都是全局法的范畴。2.3 全局法两条路线频域陷波与全局优化频域陷波的基本观察来自图像傅里叶变换。垂直条纹沿列方向恒定只在水平方向变化所以它的频谱能量完全集中在二维频谱图的水平中轴v0上并且只出现在若干离散频率峰值处。换句话说竖条噪声在频谱里不是一片弥漫的能量而是中央横轴上插着的几根针。陷波滤波要做的事情就是找到这几根针的位置用窄带掩膜把它拔掉再做逆变换。频域陷波的优点非常明显只要条纹周期稳定一次FFT加掩膜加IFFT就完成计算开销小适合线阵扫描仪这类条带噪声严格周期化的场景。缺点也很直接如果图像本身在水平方向存在同频率的周期纹理掩膜会把纹理能量一并切除。另外硬掩膜截断频谱会造成空间域振铃需要设计软过渡带。这些问题在参数章节会展开。全局优化路线不依赖周期性假设。它把去条纹变成一个能量函数求极小值的问题将图像拆成干净图像U 列偏置条纹S两部分U用全变分约束保持平滑和边缘S用列恒定结构约束然后通过交替方向乘子法求解。这种方法的优势是能处理随机性的列偏置比如探测器坏像元引起的非周期竖条劣势是计算量高需要调正则化权重处理大图时要小心内存。低剂量CT图像中的条纹往往来自探测器通道响应不一致通常不是严格周期我一般优先用全局优化法光学遥感影像的推扫式条纹则周期性强频域法更高效。3. 用全局法去除垂直条纹三个可复现的Python流程3.1 先做频谱体检确认条纹周期和能量分布任何去条纹操作前先别急着跑算法花两分钟看一下条纹在频谱里的样子。以一张灰度图像img为例用numpy计算二维FFT的幅度谱并提取水平中轴的剖面。import numpy as np from numpy.fft import fft2, fftshift def stripe_frequency_profile(img): # 将图像标准化避免亮度整体偏移干扰频谱分析 gray (img - img.mean()) / (img.std() 1e-8) gray gray.astype(np.float64) rows, cols gray.shape F fftshift(fft2(gray)) spec np.log(np.abs(F) 1e-8) # log 压缩动态范围 # 垂直条纹能量集中在 v0 水平中轴上 profile spec[rows // 2, :].copy() # 去掉直流分量中心点否则会掩盖周围峰 profile[cols // 2] -np.inf return profile, spec逻辑说明fftshift把零频移到中心幅度谱取log是防止直流峰值淹没低频细节。垂直条纹沿行方向恒定所以能量落在v0这条水平线上profile就是这条线上的能量分布。如果在profile上看到对称的峰且峰间距对应某个周期P那么条纹就是周期性的适合用陷波如果profile是杂乱的一片平缓抬升说明条纹偏置是随机列噪声应该走列偏置校正或全局优化。参数说明这里只做定性检查不需要调参。关注的是峰的位置和宽度。峰的横坐标u0换算成条纹周期P cols / (u0 - cols/2)注意u0是相对中心的位置。做完体检后再去选择具体方法。3.2 频域陷波滤波定位并切除垂直条纹谱线当确认条纹是周期性后可以用陷波滤波切除谱峰。下面的函数接受上一步检测到的峰位置列表在幅度谱水平中轴上构造软掩膜。def notch_filter_column_stripes(img, peak_positions, half_width2): rows, cols img.shape F fftshift(fft2(img.astype(np.float64))) mask np.ones_like(F) c0 cols // 2 for u0 in peak_positions: # 频谱关于中心共轭对称需同时处理两个对称峰 u_sym 2 * c0 - u0 for du in range(-half_width, half_width 1): u u0 du us u_sym du if 0 u cols and 0 us cols: # 用梯形过渡带代替硬截断降低振铃 blend np.cos(np.pi * abs(du) / (half_width 1)) * 0.5 0.5 mask[rows // 2, u] * blend mask[rows // 2, us] * blend F_filtered F * mask out np.real(np.fft.ifft2(np.fft.ifftshift(F_filtered))) return out, mask逻辑说明对每个检测到的峰值连同对称位置一起衰减。mask值是0到1之间的系数而不是0/1硬切换blend在远离峰值中心时平滑过渡回1这样频谱不会出现突变。处理完后逆变换回空间域。参数说明half_width是抑制半径单位是频谱像素。条纹周期越短峰离直流越远half_width可以用1到3。如果half_width取太大会切除周围真实图像的低频能量导致图像模糊。如果只是切了峰但条纹没干净多半是peak_positions没找全要检查频谱里的高次谐波。3.3 列均值全局校正应对无固定周期的列偏置周期性不明显的条纹比如坏像元引起的单列竖线频域陷波效果很差。此时直接用列均值的全局校正更合适计算每一列的平均灰度它由真实内容的列方向低频变化和列偏置噪声叠加而成用中值滤波把真实内容基线估计出来剩下的偏差就是噪声。from scipy.ndimage import median_filter def column_bias_correction(img, baseline_sigma31, thresh1.5): img img.astype(np.float64) col_mean img.mean(axis0) # 每列均值 # 中值滤波估计“真实内容”的列方向基线 baseline median_filter(col_mean, sizebaseline_sigma) bias col_mean - baseline # 列偏置估计值 std_bias np.std(bias) mask np.abs(bias) thresh * std_bias # 只修正明显偏离的列 corrected img.copy() corrected[:, mask] - bias[mask] return corrected逻辑说明计算col_mean是沿行方向平均把所有行的信息汇总到一列上。真实图像内容在列方向通常有缓变的亮度梯度农田、水体、天空过渡都比较平滑条纹噪声则是快速地上下跳动所以用较大窗口的中值滤波可以得到没有条纹的内容基线相减后剩下的就是偏置。这里它仍然是全局法——每次修正都影响整列所有像素而不是在局部邻域内操作。参数说明baseline_sigma控制基线平滑程度图像宽度几百像素时取21到51比较稳如果取太小基线会跟着条纹走偏置估计不足。thresh用标准差倍数控制灵敏度取1.0到1.5能保留真实内容造成的列间差异取2.0以上只修最明显的坏列。这个阈值是整个方法中真正需要根据噪声强度调的参数。3.4 全局优化法低秩条纹分离的ADMM实现针对非周期但全图一致的列偏置更严谨的做法是建立全局优化模型将图像分解为干净图与条纹图。设X为观测图像U为干净图像S为条纹分量。S被约束为每一行都相同的列恒定矩阵匹配垂直条纹的物理模型。import numpy as np from skimage.restoration import denoise_tv_chambolle def remove_stripes_global_admm(img, lam0.08, rho1.0, iters40): X img.astype(np.float64) X X - X.mean() # 去除整体直流方便优化 rows, cols X.shape U X.copy() S np.zeros_like(X) P np.zeros_like(X) # 对偶变量 / 拉格朗日乘子 for it in range(iters): # 1. 更新 S把残差投影到列恒定空间取每列均值 R X - U P col_bias R.mean(axis0) # 每列均值 S np.tile(col_bias, (rows, 1)) # 每一行相同 # 2. 更新 U对 X - S P 做全变分去噪保持边缘 U denoise_tv_chambolle(X - S P, weightlam, eps1e-4) # 3. 更新对偶变量 P P rho * (X - U - S) # 4. 检查残差范数提前收敛 residual np.linalg.norm(X - U - S) / np.sqrt(rows * cols) if residual 1e-4: break return U, S逻辑说明ADMM每一轮分三步。更新S时X - U P是在当前估计下待解释的条纹部分把它投影到列恒定空间就是取每列均值对应垂直条纹在单列上等值叠加的性质。更新U时把剩余部分交给全变分去噪全变分约束允许强边缘但惩罚平坦区域的小幅抖动这样真实结构的边界能保住。对偶变量P不断累积模型误差保证收敛。参数说明lam是全变分权重灰度范围0到255的图像推荐0.05到0.15值越大U越平滑条纹分离得越彻底但细节损失越多。rho是对偶步长一般取1.0没用。iters取30到50轮观察residual不再下降即可提前停止。这个方法的优点是随机列偏置也能分离缺点是需要scikit-image且全图迭代在超大遥感图上会慢实际工程中我会先降采样试参定好lam后再全分辨率跑。4. 去条纹参数怎么调陷波半径、校正阈值与低秩正则化的取舍4.1 陷波带宽与振铃的关联频域陷波最常踩的坑就是把掩膜宽度设得太大。垂直条纹在频谱里的峰值宽度通常只有1到3个像素如果用半径为5的圆形或矩形掩膜强制清零等于是把该频率附近的真实图像频谱成分一起挖掉。逆变换后画面会蒙上一层细密波纹边缘处尤其明显这就是频谱截断引起的Gibbs振铃。判断带宽是否合适的标准很简单去条后图像整体没有模糊感但在条纹频率附近也不存在残留亮线。我一般先用profile的半峰全宽来决定half_width如果条纹峰很尖取half_width1就够如果峰有展宽说明条纹周期轻微漂移取3到5。要注意软掩膜比硬掩膜稳得多上一章代码里的cos过渡带就属于软掩膜工程上不要为了省事改成if判断的直接置零。4.2 列均值校正的阈值与平滑窗口列均值校正里有两个互相牵制的参数baseline_sigma和thresh。baseline_sigma太小中值滤波的窗口只能容纳几列数据条纹造成的跳动会被当成真实内容的一部分偏差估计不足baseline_sigma太大真实图像的亮度变化也会被滤平导致图像整体灰阶被篡改。我的调参顺序是先用一个较大的baseline_sigma比如图像宽度的1/10看偏差序列的长相再逐步减小。thresh则直接从1.5倍标准差开始观察校正图。如果真实结构的列向边缘被压出明暗差说明thresh太小如果还有几条亮暗竖线残留说明thresh太大。这个参数存在一定的玄学成分不同图像内容差异很大建议在整幅图里随机取几行剖面来辅助判断。4.3 全变分正则化lambda与迭代次数全局优化法的核心参数就是全变分的权重lam。从信号处理角度看lam决定模型把多少能量分配给噪声而不是内容。lam太小条纹没有被充分分离U里还残留列偏置lam太大U被过度平滑图像变成水彩画。一个经验公式对0到255灰度图像lam从0.05开始若条纹去除不理想每次加0.02对归一化到0到1的图像lam从0.1开始。判断收敛时不要只看迭代次数要打印残差曲线通常前10次下降快后面进入平台期。rhop和迭代次数并不敏感rho1.0、iters30是通用起点。若想加快收敛可以在每轮更新S后给S乘一个阻尼系数0.9可以防止震荡代价是收敛速度变慢。下面是三个方法的关键参数速查表。方法关键参数推荐起点现象判断频域陷波half_width2有振铃则缩小有条纹残留则先检查峰定位列均值校正baseline_sigma31条纹未除则调小图像亮度异常则调大列均值校正thresh1.5真实结构变暗则调大条纹残留则调小全局优化lam0.08图像过于平滑则调小条纹残留则调大全局优化iters40残差曲线进入平台即可停止不必跑满5. 条纹去除常见翻车与排查从振铃到纹理误删5.1 现象条纹变淡了但整幅图变模糊这是频域陷波最典型的翻车现场。去完以后竖线确实没了但图像像蒙了一层毛玻璃边缘不再锐利。原因half_width设置太宽把真实图像在水平方向的细节频谱也切掉了。垂直条纹的谱峰虽然集中在v0轴上但真实图像在这个频率附近同样有能量。掩膜宽度一旦超过条纹峰的自然宽度细节就跟着陪葬。解决先回看频谱profile确认峰宽度把half_width调到1到3像素范围并用软过渡带替换硬置零。如果仍然模糊考虑改用全局优化法它的全变分约束能更好地保留边缘。5.2 现象去完竖条后图像边缘出现新的横纹这种横纹通常出现在灰度剧烈变化的边界附近比如天空与地面的接缝、CT图像里骨骼与软组织交界处。原因频谱陷波只处理了v0水平轴上的点但没有处理该峰值在空间中沿垂直方向的旁瓣。当条纹周期不稳定或图像内容在频域有各向异性能量时硬截断会造成谐波泄漏逆变换后振铃沿垂直方向展开。解决一是把陷波区域从水平中轴扩展到上下若干行形成一个窄横带二是改用高斯或余弦过渡的软掩膜。检查掩膜矩阵时确认mask不是只有一行是0而是中心行附近有个平滑过渡的谷地。5.3 现象真实纹理被当成条纹删掉了这类坑在遥感图像里特别常见。农田的整齐田垄、建筑物立面规则排列的窗户、机场跑道本身就具有强烈的周期性垂直结构。垂直条纹去除算法无法区分传感器噪声和真实场景周期纹理处理完以后田垄没了地块变成一片平坦色块。原因全局法天然没有语义理解。频域陷波只认频谱峰值不认峰值来源全局优化里的全变分约束会倾向于把周期性纹理当成可压缩的高频噪声来平滑。解决先用列均值偏差的统计特征做筛查。真实纹理引起的列均值偏差通常与图像内容强相关在频谱里会有谐波序列且强度随距离衰减更慢系统条纹引起的偏差则相对独立。另一个保底手段是只对条纹能量最大的列做部分校正保留一部分残余条纹换取真实纹理不丢。或者把方法局限在条纹影响明显的子区域而不是全图统一处理。5.4 现象大图处理内存溢出或速度慢到无法接受低剂量CT图像动辄512x512到1024x1024还能扛住但遥感大图几万乘几万直接做全图FFT或ADMM迭代numpy矩阵乘法和中间变量会轻易吃掉几十GB内存。原因全局法确实需要全局信息但实现时所有中间矩阵都保持原图分辨率。频域法两个复数矩阵加掩膜还好ADMM里U、S、P三个同样大小的float64数组加上skimage内部临时矩阵内存很容易失控。解决工程上先降采样确定参数再在原图分块处理。分块时块与块之间重叠16到32个像素重叠区域用余弦权重融合避免跨块亮度跳变。如果条纹是全图一致的系统噪声更稳的做法是先在降采样图上估计列偏置曲线s(x)然后上采样回原图做一次性全局校正中间不涉及全分辨率迭代。这个技巧能同时保住全局法的收益和工程可行性。6. 用合成条纹验证去条效果从定指标到守住边界验证一个去条算法不能只靠肉眼说看着干净了需要用合成数据搭一套量化流程。做法是找一张干净的灰度图作为Ground Truth给它加上已知周期和幅度的垂直条纹再跑去条算法。import numpy as np def add_synthetic_stripes(clean, period32, amplitude12, phase0.0): rows, cols clean.shape x np.arange(cols) # 用正弦构造周期性列偏置再叠加少量随机列偏置 bias amplitude * np.sin(2 * np.pi * x / period phase) bias np.random.normal(0, amplitude * 0.2, sizecols) noisy clean.astype(np.float64) bias[np.newaxis, :] return noisy, bias def stripe_reduction_ratio(noisy, corrected, bias): # 先估计处理前后的列偏置能量 before np.linalg.norm(noisy.mean(axis0) - bias) after np.linalg.norm(corrected.mean(axis0) - bias) return 20 * np.log10(before / (after 1e-8) 1e-8)逻辑说明noisy是合成含条纹图corrected是去条结果。stripe_reduction_ratio计算的是经过去条后列均值序列与真实偏置的能量差相比降低了多少dB这个指标比PSNR更直接它只关注条纹方向的噪声能量。在合成图上调好参数后还要检查细节保真。我会计算残差图noisy - corrected看残差是否近似等于bias而不是包含边缘结构。如果残差里有明显的血管、道路或建筑边缘说明算法把内容也抹进了条纹分量。最后在真实数据上看频谱中v0轴的残余能量峰值峰值下降一个数量级才算过关。这个流程是我的固定习惯先合成调参再真实数据验证否则在真实数据上出了问题根本分不清是参数问题还是噪声模型不匹配。希望帮到你。本文还有配套的精品资源点击获取