单像素成像原理与Matlab实现:傅里叶频谱采集与四步相移重建

📅 发布时间:2026/10/16 6:37:18
单像素成像原理与Matlab实现:傅里叶频谱采集与四步相移重建
把镜头盖拧下来换上一只光敏二极管然后跟我说“拍张照片”——大多数人会觉得这是玩笑。单像素成像干的就是这件事探测器没有像素、没有空间分辨能力只输出一个代表总光强的数值却照样能还原出空间图像。我最初接触这个方向时也绕了很久直到把“傅里叶频谱采集”这条链路完整跑通才真正理解它为什么能工作以及为什么标题里敢写“高质量高效”。这篇文章想把这套方案从原理到Matlab实现讲透包括四步相移的公式推导、频谱重排的细节、低频截断带来的重建质量问题以及仿真转硬件时容易踩的坑。无论你是刚入门的计算成像方向研究生还是单纯想做图像频域实验的开发者照着下面的代码和步骤走一遍基本就能复现出可用的单像素成像流程。1. 一只光电管拍照片单像素成像的底层逻辑1.1 桶测量只记总量不记分布传统相机一次曝光传感器上的几百万个像素同时读出得到的是场景在空间上的强度分布。单像素探测器只有一个感光面光线进来以后全部混在一起最终只输出一个“总和”。这种测量方式在计算成像里叫桶测量bucket measurement。单靠一个总和你分不清物体是左边亮还是右边亮更不用说看出轮廓和细节。但是换个角度想如果我每次不直接拍而是先在光路上加一层可控的“模板”让不同位置的贡献被不同地加权再收集总和那么这个总和里就包含了空间分布的信息。多换几组模板就能把空间信息一点一点“挤”出来。这就是单像素成像的非直觉之处——探测器不需要分辨“哪里亮”它只需要忠实地测“有多亮”空间分辨能力全部由外部调制和后续计算承担。1.2 用图案做编码每次测量都是一次加权求和把场景离散成 N×N 的像素网格强度分布记为 I(x,y)。投影仪或者空间光调制器投射一个图案 P(x,y)探测器的读数就是两者的逐像素乘积之和D ΣΣ I(x,y) · P(x,y)这个式子说明一次测量本质上是在求图像与一个权重模板的内积。换个角度理解未知数是 N² 个每次测量得到一个线性方程理论上至少需要 N² 次独立的测量才能解出完整图像。实际方案里因为要消除背景项、提取复频谱系数测次数还会乘一个系数但思路是一样的——用足够多的“加权求和”反推出空间分布。这个过程有点像用一套已知砝码去称一堆未知重量的物体单次称重只能得到总和但你精心设计多组砝码组合就能联立方程解出每个物体各自的重量。单像素成像的“砝码”就是投影图案图案设计得好不好直接决定测量效率和重建质量。1.3 为什么选傅里叶基而不是随机图案图案可以随便选但选什么基决定后面的路好不好走。如果选随机噪声图案测量值之间关联复杂重建需要解大规模线性方程组或者走压缩感知的迭代优化慢且对噪声和参数选择都很敏感。而选傅里叶基——也就是各种频率、方向、相位的正弦条纹——有几点天然优势。第一傅里叶基是正交完备的每测量一个频率成分就能得到图像在那一点上的独立频谱系数不需要迭代求解。第二自然图像的频谱能量高度集中低频分量占绝对主导这意味着采集一小部分低频系数就能重建出可辨认的图像“高效”两个字直接体现在这里。第三重建只需要一次逆傅里叶变换数值上稳定、速度快、不需要调迭代参数这对工程落地非常友好。所以标题里的“通过傅里叶频谱采集”并不是随便选的方案而是单像素成像里最经典、最稳健的路线之一。接下来的四步相移法就是用来从四次强度测量中准确提取单个傅里叶系数的关键手段。2. 四步相移的数学为什么一次频率采样要测四次2.1 从测量模型到复数频谱假设要测频率 (kx,ky)投射的正弦条纹可以写成Pφ(x,y) a b·cos(θ φ)其中 θ 2π(kx·x ky·y)/N这里的 a 是背景偏置b 是条纹对比度。由于光强不能为负必须有 a ≥ b实际代码里常用 ab0.5让条纹动态范围正好落在 0 到 1 之间。把 Pφ 代入测量模型展开余弦项可以得到Dφ ΣΣ I·Pφ a·ΣI b·cosφ·ΣI·cosθ − b·sinφ·ΣI·sinθ为了表述方便定义 C ΣΣ I·cosθS ΣΣ I·sinθ。这两个量分别是该频率余弦分量和正弦分量的加权和。如果相位 φ 取 0、π/2、π、3π/2 四个值就得到四个测量方程相位 φ探测器读数 Dφ差分组合0a·ΣI b·CD0 − D2 2b·Cπ/2a·ΣI − b·SD3 − D1 2b·Sπa·ΣI − b·C—3π/2a·ΣI b·S—这里我用 D0、D1、D2、D3 分别代表相位 0、π/2、π、3π/2 的读数。四个读数两两相减背景项 a·ΣI 被完全消掉这正是四步相移的第一个好处不需要额外标定环境光、暗电流等直流偏置。而图像在该频率处的二维傅里叶系数定义为F(kx,ky) ΣΣ I·exp(−jθ) C − j·S所以只要按下面两式分别求出 C 和 S就能组装出频谱点C (D0 − D2) / (2b)S (D3 − D1) / (2b)然后写成复数形式 F C − j·S。对代码来说这个公式直接用就行但理解背后“为什么要测四次”特别关键。2.2 三次测还是四次测有同学会问既然 C 和 S 两个未知数是不是测两个相位就够了理论上是但实际操作中你并不知道 a·ΣI 这个背景项有多大而且探测器存在饱和风险。如果测两个相位你必须额外估计背景项或者在暗场条件下单独测量非常麻烦。三步相移0°、120°、240°也能解出 C 和 S但公式里会带着 √3 的系数对相位步长误差的敏感度分配不直观。四步相移的好处在于每个正交分量都由两次对称测量做差得到物理意义明确公式简单而且对探测器的非线性响应有一定互补抑制。我在实测里为了省时间试过两步方案结果重建图总是蒙着一层方向性条纹后来老老实实换回四步问题才消失。2.3 相位不准会发生什么四步相移的基本前提是四个实际相移严格等于 0、π/2、π、3π/2。如果投影设备或者空间光调制器的响应有延迟实测相位会偏离理想值比如变成 π/2δ。这时恢复出来的 C 和 S 会互相串扰重建图像出现明显的斜条纹或网状伪影。从数学上看很好理解非正交投影下差分组合里会残留另一分量的部分贡献相当于在频谱上引入了虚假的高频能量。硬件层面常见原因有两个一是投影图案刷新和探测器曝光没有严格同步二是因为空间光调制器本身有像素填充率和上升沿延迟。解决手段通常是加外部触发信号做帧同步测量前先用一组成像标定条纹估计实际相位偏移量再在算法里补偿。这部分细节做到位重建质量才能称得上“高质量”。3. Matlab实现与频谱坐标约定从条纹生成到ifft2重建3.1 初始化与频率坐标的选择写代码前最关键的一个决定是频率坐标怎么排。Matlab 的 fft2 和 ifft2 约定零频在矩阵左上角也就是 F(1,1) 对应 DC 分量这点很多人一开始不注意后面重建出来永远是错位的。我的建议是频率索引直接用 0:N-1和 DFT 的定义严格对应。比如 (kx,ky) 表示图像在水平方向有 kx 个周期、垂直方向有 ky 个周期投影条纹的相位就是phase 2π·(kx·X ky·Y) / N这样把所有测量系数填进 F(kx1,ky1)最后直接调用 real(ifft2(F)) 就能重建图像中间不需要 fftshift/ifftshift 来回折腾。如果你偏要用 -N/2:N/2-1 这类“中心对齐”频率坐标则需要在重建之前做 ifftshift多做一步就多一个出错机会。3.2 核心循环四步相移采集下面是完整可跑的 Matlab 代码我把注释写在关键行上。仿真图像直接用 Matlab 内置的 phantom也可以换成你自己手头的灰度图。clear; close all; clc; N 64; % 图像分辨率 64x64 I phantom(N); % 内置仿真断层图可换成自己的灰度图 [X, Y] meshgrid(0:N-1, 0:N-1); % 像素坐标 a 0.5; % 条纹偏置 b 0.5; % 条纹对比度 F zeros(N, N); % 存储频谱DC 在 (1,1) for kx 0:N-1 for ky 0:N-1 phase 2*pi*(kx*X ky*Y) / N; P0 a b*cos(phase); D0 sum(sum(I .* P0)); P1 a b*cos(phase pi/2); D1 sum(sum(I .* P1)); P2 a b*cos(phase pi); D2 sum(sum(I .* P2)); P3 a b*cos(phase 3*pi/2); D3 sum(sum(I .* P3)); C (D0 - D2) / (2*b); % 频谱实部 S (D3 - D1) / (2*b); % 频谱虚部 F(kx1, ky1) C - 1j*S; % 组装复数频谱 end end I_recon real(ifft2(F)); % 逆傅里叶变换重建 MSE mean((I_recon(:) - I(:)).^2); PSNR 10*log10(1 / MSE); % 图像范围 0~1 时的 PSNR fprintf(PSNR %.2f dB\n, PSNR); figure; subplot(1,3,1); imshow(I, []); title(原图); subplot(1,3,2); imshow(log10(abs(fftshift(F)) 1), []); title(频谱(对数显示)); subplot(1,3,3); imshow(I_recon, []); title(重建图);这段代码在 64×64 分辨率下会循环 4096 次每次生成 4 幅条纹并做乘积求和普通笔记本大概跑几十秒属于完全可接受的范围。如果你用的是 N128 甚至更高建议先用这个版本确认逻辑再考虑向量化或并行加速。3.3 重建、评估和显示代码里重建只用了 real(ifft2(F))没有取 abs也没有做额外滤波。这是因为我们存储的是复数频谱理论上逆变换后虚部应该全是零只是浮点运算会引入极小的虚部数值取实部即可。如果你换用 abs会把不该出现的虚部误差也当成信号反而降低精度。PSNR 的计算要注意图像范围。phantom 输出的数据在 0~1 之间所以分母直接用 1。如果你自己的图像是 uint8记得先 im2double 再算否则 PSNR 会虚高。显示频谱那行我特意加了 log10因为 DC 分量和其它系数的数量级差得太多不压缩动态范围的话你只能看到左上角一个亮斑其余全是黑的。fftshift 只是为了把零频挪到显示中心并不影响存进 F 的数值。4. 截断频谱的代价测量次数、Gibbs振铃和补零错觉4.1 能量都堆在低频全频谱采集需要 4×N² 次测量N64 时是 16384 次。如果只是验证原理这个测量数没问题但真到硬件上每一次测量都对应一次投影和曝光时间成本是很贵的。所以“高效”往往要靠截断频谱来实现只采集低频区域的系数跳过中高频。自然图像和 phantom 这类仿真图的频谱能量普遍向低频集中。我实测下来N64 时只保留半径 r16 的圆域系数大约包含 804 个频率点四步相移后测量次数约 3216 次是全采样的五分之一重建图像依然能清晰分辨主要结构PSNR 大概在 20dB 上下。对很多识别和检测任务来说这个质量已经够用了。4.2 硬截断与平滑窗直接截断会在频谱上形成一个“硬圆窗”重建图边缘会出现一圈一圈的振荡这就是 Gibbs 振铃。想减轻振铃最简单的办法是把硬边界换成一个平滑滚降的窗函数比如余弦平方窗r 16; kaxis -N/2 : N/2-1; [kx_c, ky_c] meshgrid(kaxis, kaxis); [~, rho] cart2pol(kx_c, ky_c); % 硬截断掩模 mask_hard double(sqrt(kx_c.^2 ky_c.^2) r); mask_hard fftshift(mask_hard); % 平滑窗掩模 smooth cos(pi * rho / (2*r)).^2; smooth(rho r) 0; mask_smooth fftshift(smooth); I_hard real(ifft2(F .* mask_hard)); I_smooth real(ifft2(F .* mask_smooth));对比之后你会发现硬截断的轮廓更锐利但振铃明显平滑窗的图干净很多代价是高频信息进一步被压制边缘看起来有点“肉”。工程上具体用哪种取决于你是要视觉上干净还是要边缘定位准。4.3 补零插值不是“提升分辨率”很多初学者拿到截断频谱后想当然地把 F 补零成一个更大的矩阵再 ifft以为这样能提高重建分辨率。先给结论不能。补零只是对现有频谱做 sinc 插值让离散图像在显示上更平滑但缺失的高频分量不会凭空出现。换句话说分辨率的上限由你实际采集的频谱范围决定而不是由补零后的矩阵尺寸决定。如果只是想放大观看直接 imresize 比补零更省事如果想让重建结果在数学上更接近连续图像补零是一种标准操作但别指望它能“恢复细节”。这是我最早踩过的坑之一现在写出来给后来人避开。5. 仿真转实测的三个大坑非线性、同步抖动和动态场景5.1 噪声如何进入频谱系数仿真里不谈噪声就是耍流氓。真实探测器一定有加性电子噪声假设每次测量的噪声独立且方差为 σ²那么四步相移差分之后C 的噪声方差是Var(C) ≈ 2σ² / (2b)² σ² / (2b²)也就是说条纹对比度 b 越大噪声对系数的污染越小。b 从 0.2 提高到 0.5系数噪声功率能降 6 倍多换算成 dB 大约是 8dB。这一点和直觉相反很多人以为条纹亮点越亮越好其实关键是调制幅度足够大让信号和噪声拉开距离。仿真里可以这样验证sigma 5; % 根据D0数量级调整 D0 D0 sigma * randn; D1 D1 sigma * randn; D2 D2 sigma * randn; D3 D3 randn * sigma;分别试 sigma1、5、10看重建 PSNR 的下降趋势。你会发现高频部分先崩因为高频系数本身的幅度就小被噪声一盖就没了反映在图上就是细节糊掉轮廓还在。5.2 条纹对比度为什么取0.5前面说过光强不能为负所以投影图案必须满足 a ≥ b。如果 a0.5、b0.5条纹最亮处接近 1最暗处接近 0理论上利用了整个动态范围调制效率最高对应频谱估计的信噪比最好。实际系统里我建议先保守一点取 b0.4 左右。因为投影仪和空间光调制器在接近 0 和接近 1 的灰度区往往有非线性b 太大容易在暗端削顶、亮端饱和反而引入高次谐波。等你有办法标定非线性并做 Gamma 校正之后再调回 b0.5 也不迟。5.3 实测最容易翻车的三件事第一件是 Gamma 非线性。大多数投影设备的输入灰度和输出光强不是线性关系正弦条纹显示出来会变成变形的正弦产生 2 倍、3 倍频率的谐波这些谐波在频谱里形成假信号重建图上就是方向性伪影。解决方式是用光度计或相机标定灰度-光强曲线做逆 Gamma 校正。第二件是同步抖动。单像素成像的每一次测量都假设“投影图案不变、曝光刚好覆盖完整图案”如果投影刷新和曝光触发不同步相位步长就不是准确的 90°。之前讲相位误差时提过这里再强调一遍优先用硬件触发线连接投影源和采集卡别靠软件延时凑。第三件是场景运动。单像素成像本质是用时间换空间采集一组图往往需要几秒甚至更久。物体一旦移动不同频率系数来自不同时刻、不同位置重建图会模糊甚至完全错乱。这个短板是方案本身决定的所以在硬件实验里尽量用静态目标或者走快速调制加压缩感知的路子去缩短采集时间。6. 什么时候该用单像素成像与Hadamard、压缩感知的选型对比6.1 不同编码基的横向对比傅里叶频谱采集并不是单像素成像唯一的选择工程上常遇到方案选型我把主流路线放在一起对比方案典型测量数重建方式核心优点核心缺点傅里叶频谱4 × 频率点数逆FFT重建快、能量集中可截断、物理意义清晰需要模拟灰度条纹对非线性较敏感Hadamard基N²逆Hadamard二值图案适配数字微镜、乘法简单系数没有天然的视觉重要性排序随机投影压缩感知远小于N²迭代优化测量数最少适合动态场景重建慢对测量矩阵和噪声敏感如果你手里是数字微镜这类二值调制器件Hadamard 基可能更顺手因为图案只有 0/1不需要灰度正弦的精确幅度如果你追求重建速度和截断能力傅里叶频谱是更好的选择。压缩感知适合测量次数被压得很低、且目标足够稀疏的场景但代价是重建时间变成秒级甚至分钟级。6.2 真正值得用单像素成像的场景很多人把单像素成像当成“新奇技术”但在可见光常规拍照上它其实打不过成熟的 CMOS 阵列相机。这个方案真正的价值是在焦平面阵列很难做、很贵或者性能不佳的波段比如短波红外、太赫兹、单光子计数场景。这时候一个高灵敏度的单元探测器配合空间光调制器成本远比一块同等分辨率的焦平面阵列低而且探测器本身可以做到极低的暗噪声。所以我的建议是先别急着否定“成像还要扫描”这个看起来很蠢的设定先想清楚你所在的波段有没有便宜好用的像素化探测器。如果没有单像素成像就不是玩具而是刚需。6.3 这版代码还能往哪些方向改如果你想把这套仿真扩展成自己的研究项目有几个顺手的方向。首先是加速把双层 for 循环改成向量化或者 parforN128 也可以跑得动。其次是换基把正弦条纹换成 Hadamard 图案代码改动量不大但能适配数字微镜硬件。再者是做后处理对截断重建的振铃图用一个小型卷积网络去噪效果通常比频域加窗更自然。最后是改成像模型从“投影条纹到物体再收集”改成“均匀照明加探测端调制”仿真模型完全等价但硬件实现会不同。这部分取决于你手头的实验条件没有银弹。我的个人建议是一切从仿真开始先把四步相移这套链路摸熟再判断要不要转硬件、转哪条硬件路线。我自己的体会是单像素成像这个方向原理听起来不算复杂但细节多到能让人怀疑人生。最开始时为了省那一点测量时间我用两步相移替代四步重建图一直有斜纹查了快一周才发现是相位步长不准。后来老实换回四步加上硬件触发同步PSNR 一下子正常了。如果你现在正准备复现这篇代码我的建议就三条四步相移别省频谱坐标别乱换仿真阶段把截断、噪声、窗函数这些环节全过一遍再上设备。做到这三条你会发现“高质量高效”这几个字并不是标题党的修辞而是这套方案实实在在能摸到的性能边界。