1. 为什么要给时间搞编码?先看传统快门的死穴
1.1 传统快门的困局:不是曝光不足,就是全糊掉
做计算机视觉或者图像处理的人,一定都纠结过同一个问题:拍运动物体,快门速度到底怎么选。快门快了,进光量不够,画面噪点像下雪;快门慢了,运动物体拖出一条长长的尾巴,细节全部丢失。手机夜景模式解决了一部分问题,但那靠的是多帧堆叠和算力,本质上还是在“讨价还价”。
问题的根源在于,传统快门对时间的响应是一段矩形函数。曝光期间,光线均匀地在传感器上积分,运动物体在曝光窗口内走过的位移,会被平均成一个模糊核。理想情况下,这个模糊核就是一条沿着运动方向的拖尾。只要模糊核已知,理论上可以用反卷积恢复出清晰图像。麻烦在于,矩形函数的傅里叶频谱在特定频率上存在零点,这些频率处的信息在曝光瞬间被完全丢失,怎么算都算不回来。也就是说,传统快门的模糊本质上是一个“信息不完整”的问题,而不仅仅是“处理器不够强”的问题。
这里要引出时间编码摄影的思路了。核心想法非常简单粗暴:既然连续的曝光会把信息抹掉,那我把曝光窗口切碎,按照一条精心设计的二进制序列开开关关,让模糊核不再是单一的矩形拖尾,而是一串类似于“指纹”的编码图形。信息不再被均匀抹除,而是以编码的形式散落在模糊图像中,通过后处理就可以还原出清晰时刻的影像。这个方法在文献里叫扑动快门(flutter shutter),也是计算成像领域时间编码方向里最经典的一课。
1.2 扑动快门:把快门的开关动作变成一串伪随机码
扑动快门做的事情,是让相机的快门在一个曝光周期内,按预设的0/1序列快速开合。0代表遮挡光线,1代表让光线通过。这个序列可以看作一个时域上的强度调制函数 c(t)。假设场景里有物体在运动,它的有效曝光就变成了:
g(x, y) = ∫ c(t) * f(x - v_x * t, y - v_y * t) dt
公式看起来复杂,用人话说就是:传统快门是“全程开着等积分”,而扑动快门是“按照口诀节奏性地开与关”。成像结果不再是一条均匀的拖尾,而是被调制过的、有时间结构的拖尾。在频域上,这个调制函数的频谱不再像矩形函数那样有整段的零点,而是只存在少量离散的陷波点,这意味着大部分高频信息都被保留下来了,为后续恢复提供了充足素材。
实际做这个实验,工程上会遇到的一个直接问题:快门物理开合的极限频率不够高怎么办?早期方案是改造传统机械快门,但机械结构的响应速度有限。后来大家用编码孔径或数字微镜阵列(DMD)来做时域调制,本质上就是用反射镜片阵列以微秒级速度切换光路。还有一些方案直接对光源做调制,比如用主动结构光照明配合多帧采集,也能达到类似效果。不管哪种实现路径,核心都在于生成一条随机性足够强的二进制序列。
序列设计有点讲究。理论上,序列越长、随机性越好,频谱越平坦,恢复效果越稳。但序列太长意味着快门切换次数太多,机械或光电系统跟不上。我实测下来的常用选择是长度在 16 到 64 之间的伪随机序列,用线性反馈移位寄存器生成,或者直接从均匀分布中采二进制数。这里有个反直觉的细节:完全随机的序列,反而比精心设计的人眼可见规律序列效果差。因为在频域里过于理想的白噪声特性会让某些子带出现连续的低能量,恢复算法会把噪声也一起放大。更好的做法是用带约束的随机序列,比如限制最短连续保持时间、平衡0和1的比例,这样既能保持频域平坦度,又不过分压低某些频段的信噪比。
1.3 恢复过程:信号空域混叠,算法把它拆开
拍到了模糊加编码的图像,恢复过程就是一个典型的线性逆问题。如果曝光序列已知,模糊核可以预先计算出来,恢复流程就是一次非盲去卷积。这里面有几个关键操作步骤:
第一步,构造退化模型。设模糊图像为 y,清晰图像为 x,模糊核为 h,则 y = h * x + n。这里的 h 就是由扑动快门序列和运动矢量共同决定的点扩散函数。
第二步,选择求解策略。最老派但有效的是维纳滤波,直接在设计频域里做维纳逆滤波,速度快但容易出现振铃伪影。更好用的是带正则化的迭代求解,比如全变分(TV)正则化、稀疏字典先验,或者直接上可学习的深度展开网络。我以前在模拟项目X里跑过一组对比,同样的编码曝光图像,TV正则化能把峰值信噪比做到28dB左右,而展开式网络能到31dB以上,而且边缘明显更锐利。
第三步,处理运动方向未知的情况。如果场景里是多个不同方向、不同速度的物体,模糊核就不是单一的,恢复难度会陡增。经验做法是把图像分块估计局部运动,或者对运动做参数化建模,再做分区域解卷积。这属于进阶玩法,初学建议先从单方向匀速运动开始,跑通之后再扩展到二维旋转运动。
实操中容易踩的坑,集中在互相关的同步问题上。扑动快门要求曝光序列与传感器帧率严格同步,如果序列的开始时刻、切换时刻和实际曝光有偏移,恢复用的模糊核就不是真实现状,结果出来会出现类似“重影叠加”的现象。我遇到过好多次,现象是图像边缘有双重轮廓但中间不模糊,一查都是同步延迟了几个微秒导致的。解决方案很简单:在做系统标定时,先用一条已知的阶跃亮度变化信号触发快门序列,用高速示波器记录曝光曲线,反推出实际编码序列,再拿这个实测序列去做解卷积。别偷懒直接用理想序列,现实中的光电响应总有上升沿和下降沿,二者积累起来足以毁掉恢复效果。
2. SCI时空压缩成像:一次曝光重建一整段“视频”
2.1 从一张打码的“视频封底”里挤出时间维度
先说明一下,计算成像圈里提到SCI,一般指的是时空压缩成像(Spatiotemporal Compressive Imaging),它跟学术文献检索那个SCI不是一个东西。这个技术解决的是另一个经典难题:高速摄影往往受限于传感器帧率、存储带宽和数据量。想要看到高速运动,比如子弹穿过苹果、飞鸟扇动翅膀,常规相机要么连拍速度跟不上,要么数据量大到无法传输。
SCI的思路是把“空间”和“时间”两个维度联合起来做编码采集。假设我们要拍摄一段 16 帧的高速序列,每帧是 256×256 像素。按照传统拍法,需要连续记录 16 帧图像,然后再存下来。SCI只拍一张图:用一组随时间变化的随机掩膜(二维二进制pattern)逐帧调制视频帧,然后在同一传感器上积分累加,成为一个混合了全部时间信息的单张快照。这个快照看起来像一张充满条纹和噪点的“花屏”,但里面压缩了整段高速视频的信息。
数学表达是这样的:设三维数据立方体 F(x, y, t) 代表目标高速场景,掩膜 C(x, y, t) 是时空编码函数,传感器记录到的二维测量值为:
Y(x, y) = Σ_t C(x, y, t) * F(x, y, t) + N
这一张二维图像,就是对一个三维场景的投影测量。在压缩感知的框架下,只要编码矩阵满足一定的条件(比如随机性足够强、限制等距性),重建算法就能从这张二维图像中恢复出完整的三维视频体。原理听起来很神奇,但本质上和拍一张模糊照片再用算法“无中生有”地补高频信息,是同一类逆问题。
这个思路对硬件的要求比扑动快门更复杂,因为它要在传感器前同时做空间和时间调制。最常见的实现方式是:用一块编码掩膜(静态pattern)配合全局快门相机做空间调制,然后通过相机的卷帘或外部可编程时序来制造时间调制。另一种方案是用数字微镜阵列配合单像素探测器,做到真正的时空联合编码,这种结构在近红外和中波红外波段特别实用,因为那些波段很难做成高帧率大阵列传感器。
2.2 掩膜设计、重建算法与参数设置的细节
先讲掩膜设计。理想掩膜是每个像素、每个帧时刻都独立随机取值,也就是一个完整的三维随机矩阵。问题是硬件做不到那么“随机”地快速翻转,实际中通常把掩膜设计成一系列随时间变化的二维编码,每个时刻的编码pattern可以是互不相关的随机块,或者从一组预生成的哈达玛基中选择。工程上,我做过实验,pattern是 32×32 像素的随机块,灰度量化到二值(0和1),然后对每个帧时刻使用不同的随机 pattern。只要连续几帧的pattern之间互相独立,重建结果就不会太差。
重建算法又是老熟人:解一个大规模稀疏优化问题。基础形态是:
min_F ||Y - ΦF||² + λ · R(F)
其中 Φ 是编码矩阵,R 是正则项。如果 F 是小波或字典域稀疏的,R 就选 L1 范数;如果希望保持边缘,R 就选 TV(全变分)。这里有一个很值得注意的经验:正则项的选择对时间分辨率的提升影响极大。如果只做 L2 平滑,重建出来的视频会像被“揉”过一样,时间维度上完全没有细节,因为算法倾向于找一个中间态来同时满足所有约束。加上 TV 或者更高级的稀疏先验之后,重建出的高速帧才会有清晰的边缘和瞬时状态。
现在更主流的是用深度展开网络。把上文那个优化问题按照迭代求解步骤展开成多层神经网络,每一层对应一次近端梯度下降,网络参数用端到端方式学习。这样做的优势是重建速度比传统优化快两个数量级,而且质量更好。我做模拟实验,30 帧测试数据时,传统FISTA算法单次要跑几十秒,展开网络只需几十毫秒,PSNR还高出2dB以上。不过深度方案需要大量训练数据,建议先用传统算法对少量样本求精确解,用这些解做训练标签,再做蒸馏。
实际操作时还有几个容易忽略的细节。传感器响应如果不标定,像素间的暗电平差异会混进测量值里,重建时输出奇怪的条纹底纹。所以每次实验前要采集暗场图像,逐像素扣除。编码掩膜的对齐精度也很关键,一个像素的偏移都会导致重建结果出现严重的空间错位,解决方法是先用一台高倍率相机拍摄掩膜的静态图案,求得掩膜在成像面上的精确坐标映射,再固化到算法里。
2.3 SCI的硬件搭建与跑通一条数据的经验
自己动手搭SCI系统的话,最稳的组合是:一块数字微镜阵列(或者可编程液晶空间光调制器)做空间编码,一台全局快门CMOS相机做时间积分,再加一台同步控制单元。别用卷帘快门相机,因为卷帘快门会让每一行像素的曝光时间不同,整个调制时序就乱了。
我跑通过一次实时SCI视频系统(模拟项目X),过程有点曲折。第一次测试,重建结果里出现大量横向条纹。排查后发现问题出在DMD的pattern刷新率与相机的全局曝光窗口没对齐,DMD在一帧曝光期间更新了好几次pattern,导致每个像素实际看到的其实是多个pattern的叠加。排查方法很简单:把DMD的输出用光电二极管探针引到示波器,同时触发相机,记录两者实际的时间关系。发现DMD响应有微秒级的延迟和上升沿拖尾,于是把每次pattern切换安排在相机曝光结束后、下一帧曝光开始前的那段空隙里。调整后条纹立刻消失。
另一条经验是关于数据量处理的。SCI重建一次要处理的数据量不小,尤其是分辨率和帧数上去之后,一个256×256×16的三维体,展开成一维向量长度超过一百万。矩阵求逆是不可能的,需要迭代求解。建议先用GPU做算子加速,把编码矩阵的乘法用预计算的稀疏矩阵或者FFT实现,能省掉大量等待时间。我第一次用CPU加scipy跑,一次重建要四十分钟,换到GPU上把前向算子和转置算子写成自定义层之后,压缩到不到两分钟。
粘连在SCI上的最后一个坑是评价指标。重建出的视频帧本身是估计值,峰值信噪比和结构相似性两个指标都要看,不要只看PSNR,因为PSNR对模糊不敏感,SSIM反而能反映出时间轴上的结构真实性。另外,记得和原始高帧率视频做帧对齐再算指标,否则光时序上的偏差就足够让分数看起来很惨。
3. 事件相机与事件视觉:常规相机看不懂的“变化”
3.1 事件相机在“拍”什么:只记录光强变化,不记录绝对亮度
事件相机是另一个极端思路的产物。传统相机以固定帧率输出完整图像,每一帧都包含场景的完整亮度分布。而事件相机(动态视觉传感器,DVS)的每个像素完全独立工作,它不采样绝对亮度,只在亮度变化超过一个阈值时输出一个“事件”。
一次事件包含四个信息:像素坐标 x、y、时间戳 t(微秒级精度)、极性 p(变亮为1,变暗为-1)。没有事件的时候,传感器不输出任何数据。这意味着事件相机的数据是异步的、稀疏的、时间连续的,它的时间分辨率可以做到极高,动态范围也能覆盖从暗光到强光的巨大跨度。普通相机在光线变化大的场景常常过曝或欠曝,事件相机却天然地适应这种场景,因为它的输出本身就是变化量,跟绝对亮度脱钩了。
我这里想到一个很生活化的类比。普通相机像一个录像机,每秒钟录下25张照片,然后按顺序播放,信息冗余很大。事件相机像一台变频拾音器,只在有人说话的时候才记录声音片段,没人说话就休息。在环境安静(亮度不变)时,事件相机可以完全“沉睡”,功耗和数据量都极低;一旦有动作发生(亮度变化),它立刻以上百万次的频率输出对应的变化信息。
这个特性让事件相机在高速运动追踪、极端光照场景、低功耗边缘设备上有天然优势。举个例子,你想捕捉一只快速挥动的手臂,传统相机要跑到每秒1000帧才能记录清楚运动轨迹,数据量爆炸。事件相机会输出一串对应手臂边缘亮度变化的事件流,这些事件本身就编码了位置和运动速度,根本不需要高帧率图像序列。
3.2 事件数据的“麻烦”恰恰是价值所在
事件流跟图像帧有本质区别。图像是规则网格上的空间信号,可以直接送进卷积神经网络。事件流是非均匀的、异步的点集,没有天然形成的二维矩阵排列。很多刚接触事件视觉的同学第一反应是先把事件叠加到一张图里,转成类帧形式再走传统视觉算法。这个办法能work,但会丢失掉事件相机最宝贵的时间分辨率。
好一点的中间表示是事件体素网格。把时间轴切成若干时间分块,每个分块内把事件按双线性投票方式分配到三维网格(x, y, t)中,得到一个形状为 channel×H×W 的张量。这种表示既能保留时间上的分布信息,又适合用3D卷积或者2D卷积+时间注意力模块来处理。我用Python实现过一段极简的体素化代码:
import numpy as np def events_to_voxel(events, shape, num_bins=5): # events: 每行为 [t, x, y, p] 的数组 H, W = shape voxel = np.zeros((num_bins, H, W), dtype=np.float32) t_min, t_max = events[:, 0].min(), events[:, 0].max() if t_max == t_min: t_norm = np.zeros_like(events[:, 0]) else: t_norm = (events[:, 0] - t_min) / (t_max - t_min) bin_idx = np.floor(t_norm * num_bins).astype(int) bin_idx = np.clip(bin_idx, 0, num_bins - 1) for i, e in enumerate(events): b = bin_idx[i] x, y, p = int(e[1]), int(e[2]), 1 if e[3] > 0 else -1 if 0 <= x < W and 0 <= y < H: voxel[b, y, x] += p return voxel这里有一个很关键的工程细节:极性 p 不能只作为正负符号累加,最好把正事件和负事件分到两个通道,因为它们的物理含义不同。变亮和变暗触发的电路机制略有差异,混在一起会让模型产生混淆。我跑过的多个事件流分类实验,把极性分开做成双通道后,准确率稳定提升了2到3个百分点。
另一个麻烦是噪声事件。真实事件相机因为有热噪声和噪声像素,即便亮度没变化也会不断触发事件。这些噪声事件在视频里表现为一片闪烁的“雪花点”,会严重干扰算法。常规去噪做法包括滤波(根据时间邻域一致性)、邻域事件密度判断(孤立事件多半是噪声)和硬件校准(对坏像素映射做掩膜)。我实测最有效的是邻域密度法:对每个事件,在时间和空间邻域内统计同类极性的事件数量,如果少于阈值就判定为噪声丢弃。这个方法几乎不消耗算力,但对降低误检率帮助很大。
3.3 事件视觉的典型算法与落地场景分析
事件视觉现在在黑科技领域非常活跃。最经典的方向是高速运动估计和光流计算。因为事件本身就是光强变化引起的,而光强变化往往伴随着边缘的运动,所以从事件流里直接估计光流是可行的,而且延迟极低,适合无人机高速避障、机械臂动态抓取。
动作识别是另一个热门方向。事件流天然适合捕捉动作的时序结构,比如人做手势时手部边缘会在事件流中形成一组有序的轨迹。现在主流做法是把事件体素网格喂给3D卷积网络或者时空Transformer,预训练可以采用自监督方式,让模型从事件流预测未来的事件片段。我参与过一个手势识别demo,采集了6种手势,用体素化和轻量3D卷积,准确率能做到96%以上,而且整个流程在嵌入式平台上跑,延迟只有十几毫秒。
还有一个很有意思的应用是极致动态范围场景下的辅助成像。把事件相机和普通RGB相机做跨界融合,用事件流来估计帧间运动和状态变化,再用插值或者图像生成模型补全出清晰的高帧率图像。这个方向叫“帧-事件融合”,做得好可以每秒重建几百帧的清晰图像,比单纯用帧相机或事件相机效果都强。
不过要注意,事件视觉的算法开发和传统图像算法有很大差别。调试事件数据的时候,不能像看图片一样直接可视化,建议写个简单的事件流可视化工具,把一段事件流按时间窗口累积成灰度图播放,用灰度图的动态变化来判断事件数据是否准确反映场景运动。没有这一步就直接上算法,出问题很难定位。我见过好几个人拿着模型精度低的结论来请教,结果一查,训练数据的事件时间戳根本没有同步对齐,整个时间轴都是乱的,再多调参都是白费。
4. 三种技术路线怎么选、怎么搭、怎么避坑
4.1 选型对照:时间编码方法各有各的主场
扑动快门、SCI、事件相机容易被放在同一个语境里讨论,因为都是围绕“时间”做文章。但它们解决的问题维度完全不同,盲选容易水土不服。我按自己的理解整理了一张选型对照表,做系统设计的时候可以先看这张表:
| 技术路线 | 输出形态 | 时间分辨率 | 硬件成本 | 适用场景 |
|---|---|---|---|---|
| 扑动快门 | 单张模糊编码图像,解码后得清晰帧 | 取决于曝光序列频率,常用于毫秒级运动模糊恢复 | 较低,改造快门或投影设备即可 | 拍照防抖、水下成像、低光运动场景 |
| SCI | 单张二维压缩快照,重建出高速视频帧序列 | 取决于掩膜切换速度,可到微秒级积分窗口 | 较高,需要编码掩膜+全局曝光相机 | 高速摄影、多光谱快照成像、压缩视频采集 |
| 事件相机 | 异步稀疏事件流,无帧概念 | 微秒级连续时间分辨率 | 中等,专用传感器模块 | 高速追踪、低时延控制、极致动态范围、低功耗感知 |
需要特别指出的是,这三个技术也可以组合。比如用扑动快门的思想设计SCI的时域调制序列,或者用事件相机的事件流作为SCI重建时的时间先验信息。这种组合不是加法的关系,而是互相补短板的关系:扑动快门擅长让单帧模糊问题变得可解,SCI擅长把一帧压缩出时间维度,事件相机擅长给出连续时间变化的极低延迟观测。我最早看到有人把三者放在一个大系统里做“全时间信息采集”的时候还觉得是花架子,后来自己搭了一次测试,发现它们之间的互补性非常强。
4.2 踩坑实录:时钟同步、掩膜对齐、噪声和那些“看起来正常”的异常
做时间编码系统,最大的敌人永远是“时间不同步”。扑动快门的调制序列要和曝光窗口严格对齐,SCI的掩膜刷新要和帧积分严格对齐,事件相机的时间戳要和外部姿态估计系统严格对齐。只要时间轴错位,哪怕只差零点几毫秒,输出结果都能差得离谱。有一次我做事件光流评估,运动物体明明在向左移动,算法输出却是向右,找了半天原因,发现是我的数据录制软件里事件时间戳和图像时间戳之间有一个固定的偏移量,而且这个偏移量在某个版本更新后变了。解决方法是每次录制前后,都用一个LED闪光同步信号進行一次时间偏移标定,把偏移量的变化曲线记录在数据包里。
掩膜对齐是SCI系统里最折腾人的环节。因为编码掩膜是物理器件,安装时必然有旋转和平移误差,这些误差在重建时会被放大成条纹伪影。虽然前面提到了用高倍率相机拍摄pattern来算映射,但要注意的是,这个映射在长时间运行后会因为热膨胀或机械松动而漂移。有效做法是每次开拍前先校准一次,并且把几组已知pattern在系统中的响应记录下来,用于验证映射是否有效。
噪声处理的优先级也很高。事件相机有坏像素噪声和热噪声,扑动快门有机械振动噪声,SCI有编码掩膜非理想二值化的灰度泄漏。这些噪声单独看都不大,组合在一起却会让重建结果崩坏。我的习惯是先做数据预处理的“三步走”:先做传感器级标定(暗场扣除、坏像素掩膜),再做时间同步验证(用脉冲光源扫一遍),最后做模拟仿真对比(从仿真数据跑一遍完整链路,确定噪声底限)。三步走完再跑正式实验,能省下90%的排查时间。
4.3 快速上手的技术栈与复现路径
如果是学生或者工程师想快速跑通一遍完整链路,我的建议是不要一开始就上专业硬件。先做仿真,把整个算法链路验证通顺,再碰真机。仿真的思路是:用现有的高帧率视频(模拟项目X用的是一段高速拍摄的旋转风扇视频)当作真实场景,人为地按照时间编码模型做投影测量,再用重建算法恢复。这样做的优点是你手头有“标准答案”,可以精确评估每一步的损失和瓶颈。
软件栈方面,我习惯的组合是Python加NumPy、SciPy、OpenCV做基础图像处理和逆问题求解,PyTorch做深度展开网络和事件序列建模。很多刚入门的朋友喜欢一上来就装很重的框架,其实纯调包跑算法,最基础的NumPy就已经能覆盖大部分流程。我的建议是先在你熟悉的框架里跑通扑动快门盲去卷积,再做SCI重建,最后处理事件流数据。这三个任务难度递增,而且共享大量的数组操作和优化求解经验。
关于公开数据集,科研领域现在有不少标准数据集可以拿来练手,包括高速动态场景的事件数据、压缩感知编码快照数据集、以及各种风格的模糊图像对。用这些数据先在本地把自己的算法跑通,再开始自采数据会稳妥很多。自采数据时,记得记录所有标定信息和硬件设置,特别是时间同步参数和掩膜映射文件,别等做到一半发现没存,回头重拍太痛苦了。
最后讲一个我踩过最深的坑:拿到新硬件,做出来的结果很差,第一反应是调算法参数,结果调了一个月也没起色。后来把整个硬件链路重新做了一次端到端测试,发现是在数据采集时相机的位深设成了8bit,导致编码曝光中的很多微弱灰度差都被量化掉了。换了16bit采集之后,算法几乎没改,重建效果直接上了一个台阶。这也算是对“先查数据链路,再动算法”这句话的一次深刻理解。
还有一个个人经验:如果你准备把事件相机和普通相机做时间对齐,别依赖后处理软件去做帧级对齐,强烈建议在硬件设计阶段就用外部时钟源给两个传感器提供同源的触发信号。后处理对齐能修漂移,但补不了随机的触发延迟差,这个差值在高速场景下足以让融合结果出现明显的重影。我见过太多项目栽在这上面了。
整套内容看下来,时间编码摄影和事件视觉的本质,其实都是想尽办法让“时间”这个维度不再被传感器结构限制住,有人说这是成像系统在从“拍照片”走向“感知信息”。我对这句话比较认同,但我的建议是:不管概念多新奇,拿到手先跑通一条最简的链路,比看十篇综述都有用。