4. 信号采集与数据预处理:原始干涉信号采集、背景噪声去除、去直流项、波长到波数(k域)重采样
好,咱们进入正题。这一章讲的是OCT系统里最“脏”的活——信号采集与预处理。
很多人觉得算法才是核心,采集嘛,接上探测器就行。我刚开始也这么想。直到有一次,我花了两周调一个去噪算法,效果始终不理想。最后发现,问题出在采集卡的地线没接好。嗯,从那以后,我再也不敢小看预处理这一步了。
说白了,原始干涉信号就像一块璞玉。你得先把它表面的泥巴洗干净,才能看出它是不是块好料。这一章,我们就来聊聊怎么“洗”这块玉。
4.1 原始干涉信号采集
先说说采集。OCT系统的核心是迈克尔逊干涉仪。光源发出的光,被分束器一分为二。一束去样品,一束去参考镜。两束光反射回来,在探测器上相遇,形成干涉。
这个干涉信号,就是我们的原始数据。它长什么样?
我习惯用一个公式来描述:
I(k) = I_r + I_s + 2 * sqrt(I_r * I_s) * cos(2 * k * Δz)
其中:
- I(k):探测器接收到的总光强,随波数k变化
- I_r:参考臂的光强(直流项的一部分)
- I_s:样品臂返回的光强(也是直流项)
- Δz:样品与参考镜的光程差
- k:波数,k = 2π/λ
你想想看,这个信号里,真正有用的信息是那个余弦项。它包含了样品的深度信息。但直流项(I_r + I_s)和噪声,把有用的信号淹没了。
核心要点:原始干涉信号 = 直流项 + 互相关项(有用信号)+ 噪声。我们的目标,就是把互相关项干净地提取出来。
4.2 背景噪声去除
背景噪声从哪来?
我遇到过最头疼的情况,是系统里的固定模式噪声。它来自光学元件的表面反射、探测器暗电流、甚至电源纹波。这些噪声不随样品变化,但会叠加在信号上,形成固定的“背景”。
怎么去除?最直接的方法——减背景。
具体做法:
- 采集背景信号:挡住样品臂,只采集参考臂和系统本身的噪声。这时的信号就是背景。
- 采集样品信号:正常采集样品干涉信号。
- 相减:样品信号减去背景信号。
代码实现很简单:
# 假设 data 是采集到的原始干涉信号(二维数组,行数=光谱数,列数=像素数)
# background 是采集到的背景信号
def remove_background(data, background):
"""
去除背景噪声
"""
# 确保背景和数据的维度一致
if data.shape != background.shape:
# 如果背景是单帧,则扩展到与数据相同维度
background = np.tile(background, (data.shape[0], 1))
# 相减
cleaned_data = data - background
return cleaned_data
我的经验:背景信号不是一成不变的。系统温度变化、光源功率漂移,都会改变背景。我建议每采集10-20帧,重新采集一次背景。或者,用滑动平均的方式动态更新背景。
4.3 去直流项
减完背景,信号里还有直流项。直流项就是公式里的 I_r + I_s。它不随光程差变化,是一个常数偏移。
为什么要去直流?
因为直流项会严重影响后续的傅里叶变换。你想想看,傅里叶变换会把直流分量变成零频处的尖峰。这个尖峰会淹没低频信号,还会产生伪影。
去直流的方法,我常用两种:
| 方法 | 原理 | 优缺点 |
|---|---|---|
| 均值法 | 计算信号的平均值,然后减去 | 简单快速,但无法去除缓慢变化的直流漂移 |
| 高通滤波法 | 用高通滤波器滤除低频直流分量 | 可以去除漂移,但可能影响低频信号 |
| 多项式拟合 | 用多项式拟合直流基线,然后减去 | 灵活,适合非线性漂移,但计算量大 |
我个人习惯用均值法。简单,有效。前提是信号里没有大的直流漂移。
def remove_dc(data):
"""
去直流项 - 均值法
"""
# 计算每个光谱的平均值
dc = np.mean(data, axis=1, keepdims=True)
# 减去直流
data_ac = data - dc
return data_ac
注意:去直流和去背景是两个步骤,不要搞混。去背景是去除系统固定噪声,去直流是去除信号本身的常数偏移。顺序上,我建议先做去背景,再做去直流。
4.4 波长到波数(k域)重采样
这一步,是预处理里最关键的。也是很多人容易忽略的。
为什么需要重采样?
因为我们的探测器采集到的信号,是在波长域(λ域)上均匀采样的。但傅里叶变换要求信号在波数域(k域)上均匀采样。k和λ的关系是:k = 2π/λ。这不是线性关系。
说白了,你在λ域上均匀采样,到了k域就变成不均匀了。直接做傅里叶变换,结果会失真,分辨率下降。
我见过有人直接拿λ域数据做FFT,结果图像模糊得一塌糊涂。他还以为是系统分辨率不够。其实,只是没做重采样。
重采样的核心步骤:
- 计算k向量:根据波长范围,计算出对应的k值。
- 插值:在k域上建立均匀的网格,然后用插值方法(线性插值、样条插值等)从原始λ域数据中获取k域均匀网格上的值。
代码示例:
def resample_k_domain(wavelength, signal, num_points=None):
"""
波长到波数域重采样
参数:
wavelength: 原始波长数组 (1D)
signal: 原始干涉信号 (2D, 行数=光谱数, 列数=波长点数)
num_points: 重采样后的点数,默认与原始点数相同
返回:
k_uniform: 均匀的k域坐标
signal_resampled: 重采样后的信号
"""
# 1. 计算原始k域
k = 2 * np.pi / wavelength
# 2. 建立均匀的k域网格
if num_points is None:
num_points = len(wavelength)
k_uniform = np.linspace(k.min(), k.max(), num_points)
# 3. 插值(对每个光谱进行插值)
signal_resampled = np.zeros((signal.shape[0], num_points))
for i in range(signal.shape[0]):
# 使用线性插值
signal_resampled[i, :] = np.interp(k_uniform, k, signal[i, :])
return k_uniform, signal_resampled
避坑指南:我曾经犯过一个错误——直接用波长数据做插值,然后做FFT。结果图像出现了严重的“鬼影”。后来发现,插值必须在k域做,而不是在λ域。因为FFT要求的是k域均匀采样,不是λ域均匀采样。这个顺序不能搞反。
另外,插值方法的选择也有讲究。线性插值速度快,但精度一般。样条插值精度高,但计算量大。我一般用线性插值,够用了。如果追求极致分辨率,可以用三次样条。
4.5 本章小结
好,我们来捋一捋。信号采集与预处理,一共四步:
- 采集:拿到原始干涉信号,理解它的组成。
- 去背景:减去系统固定噪声。
- 去直流:去掉常数偏移。
- k域重采样:把信号从λ域映射到k域,为FFT做准备。
这四步做完,你的信号才算“干净”了。可以进入下一步——傅里叶变换和图像重建。
嗯,预处理这一步,看似简单,但细节很多。我见过太多人在这上面栽跟头。希望你能重视起来。
下面这张图,是我自己画的预处理流程图。你可以对照着看,每一步的位置和作用。
好了,这一章就到这里。预处理是OCT信号处理的地基。地基打不牢,后面盖再高的楼也是白搭。希望你能把每一步都吃透。
公众号:蓝海资料掘金营,微信deep3321