简介:文档围绕微透镜阵列光学实现卷积运算展开,面向光子计算、光学神经网络与图像处理方向的研究者和工程师,聚焦如何以微透镜阵列结合透镜构建光学系统,在光域模拟二维卷积操作,以突破电子计算在速度与能耗上的瓶颈。内容从二维离散卷积的same、valid、full类型入手,逐步讲解系统结构设计与矩阵解释:微透镜阵列将输入图像划分为窗口并并行匀光,透镜L1/L2完成乘加运算,P2处可放置强度调制元件作为卷积核,并通过理论推导说明该结构能实现任意正值的传输矩阵。全文给出清晰的公式、结构示意图和可行性分析,可帮助读者理解光学卷积层的原理,也可为光学卷积神经网络中的卷积与池化层硬件实现提供参考。压缩包内为1个docx格式文档,约207KB,篇幅精炼但知识点集中,适合科研入门或课程设计参考。截至当前,已有120人学习。
1. 光学卷积做一个乘法为什么值得兴师动众
做过图像处理的人都知道,一张 512×512 的图过一遍 3×3 卷积,理论上要跑 26 万次乘加。到了 CNN 里,这个量还要再乘上通道数和层数,训练一次 ResNet 的浮点运算量是以亿为单位的。传统电子芯片靠晶体管吞吐这些计算,功耗和延迟都压在这个乘法器上;摩尔定律放缓之后,大家开始琢磨能不能换个介质来做矩阵乘法。光有一个天然优势:光子不带电荷,互相穿过不干扰,透镜一照就是二维并行,一次传播完成的乘加数量取决于空间分辨率而不是时钟频率。这篇文章拆的项目,就是把「输入图像和卷积核做卷积」这件事,用微透镜阵列配合两片透镜在光路上直接算出来。输出端放个 CMOS 就能读结果,电学部分只负责加载数据和接收光强。适合对光学计算、光电混合加速、以及想把成像系统做成计算单元的人读。
2. 把滑动窗口看成共享权重:卷积的矩阵乘法本质
2.1 从二维离散卷积到矩阵乘法
要理解光学怎么做卷积,得先把卷积的运算形式换一种写法。假设输入图像为
x = [[6, 3, 5], [2, 7, 1], [3, 1, 2]]卷积核为 k =[[4, 2], [1, 5]],步长为 1,做 valid 卷积,输出中的第一个元素是左上角 2×2 窗口与核逐元素相乘再求和,也就是 6×4 + 3×2 + 2×1 + 7×5。这个逐窗口的「乘加」在数学上可以重排成一次矩阵乘法:把每个窗口内的像素按列堆叠成一个列向量,把所有窗口对应的列向量拼在一起,再把卷积核拉成一行。这样整个卷积就变成了一行向量乘以一个矩阵。
y' = [4, 2, 1, 5] × [[6, 3, 2, 7], [3, 5, 7, 3], [2, 7, 1, 1], [7, 1, 1, 2]]可以验证,这个结果按列展开,恰好就是滑动窗口卷积的输出。这个变换的意义在于:它让「卷积」与「矩阵乘法」之间画了等号。矩阵乘法在光路里是可以直接实现的——用一个强度调制面给每个光线乘一个系数,再用透镜把同一组的乘积光强叠加到一个点上。下面要讲的光学系统,本质上就是在做这件事。
2.2 三种卷积类型的光学实现差异
卷积按边界处理方式分为 full、same、valid 三种,区别只在于输入图像边缘是否补零、输出尺寸如何裁剪。在光学实现里,这对应的是输入面加载图像时是否在边缘补黑色像素,也就是让对应位置的像素透射率为 0。补零本身不需要额外器件,只需要在空间光调制器加载图案时把边缘像素设成不透光状态。
| 卷积类型 | 输出尺寸(仅限奇数核) | 光学实现方式 | 注意事项 |
|---|---|---|---|
| valid | (H-kh+1) × (W-kw+1) | 输入直接投影,不做边缘处理 | 输出面尺寸最小,光能利用率低 |
| same | H × W | 输入边缘补 (kh-1)/2 圈零 | 需保证补零像素透射率严格为 0 |
| full | (H+kh-1) × (W+kw-1) | 输入边缘补 kh-1 圈零 | 输出面范围最大,需要更大的探测器 |
2.3 光学实现卷积的三个前提条件
从上面的矩阵乘法表示可以看出,光学系统要做的事情实际上只有两件:乘法——每个像素乘上对应的核系数;加法——同一个窗口内乘完的结果在输出面叠加到一起。要完成这两件事,需要满足三个前提:
第一,能把输入图像按窗口划分成互不重叠的小块,并且每个小块能独立走一条光路。第二,每个小块内相同位置的像素需要被乘上相同的系数,这要求来自不同窗口、但窗口内位置相同的光线在调制面上落在同一个位置。第三,调制之后,同一个窗口内的光线需要被重新汇聚到一起,这样才能完成求和。微透镜阵列恰好同时满足前两个前提:每一个微透镜单元对应一个窗口,而微透镜单元的周期性排列,让不同窗口中位置相同的光线具有相同的出射方向,从而在透镜焦平面上汇聚到同一个点。这个「方向相同即聚焦同点」的原理,是整个系统的核心机制。
3. 微透镜阵列卷积系统的结构与核心参数
3.1 系统光路布局
整个光学系统的结构沿光轴方向依次是:输入面 P0 → 微透镜阵列 MLA → 透镜 L1 → 强度调制面 P2 → 透镜 L2 → 输出面。其中微透镜阵列上每个透镜单元的焦距为 f1,L1 的焦距为 f2,L2 的焦距为 f3。输入图像放置在微透镜阵列的前焦面附近,光经过 MLA 后被分割成与窗口数量一致的子光束;每一束子光束经过 L1 后,在 P2 面上形成一个与窗口内像素排列对应、但来自不同窗口相同位置的信息被叠合在一起的强度分布。
3.2 微透镜尺寸与窗口划分
微透镜单元的尺寸直接决定了卷积核的窗口大小。假设输入是 4×4 的图像,卷积核是 2×2、步长为 2,那么图像被划分为 4 个不重叠的 2×2 窗口,每个窗口对应一个微透镜单元。窗口划分的关键约束是:步长必须等于窗口尺寸,否则相邻窗口之间会有间隙或重叠,光学上就无法用固定的微透镜阵列一次完成划分。这一点在后面的应用部分会再讨论如何用预处理绕开限制。
需要特别强调的是,微透镜阵列在这里的作用并不是成像,而是做「方向编码」。每个微透镜把窗口内的四条光线变成四束具有不同出射角的光线;不同窗口内位于相同相对位置的光线,由于在各自微透镜上的入射位置相同,出射角也相同。L1 的作用就是把方向相同的光线汇聚到 P2 面上同一个点——同方向的光线经过薄透镜后汇聚于焦平面同一点,这是几何光学的基本性质。于是 P2 面上接收到的分布,就是按窗口内相对位置排列的、来自所有窗口对应位置的叠加。
3.3 关键器件与参数表
实际搭建时,器件的选型比公式推导要更敏感。下面是这类系统的典型参数及选择理由,参考自类似的「光学卷积 / 光学 4f 系统」工程实现:
| 器件 | 典型参数 | 选型理由与注意事项 |
|---|---|---|
| 微透镜阵列 MLA | 单元节距 0.5~2 mm,f1 = 5~20 mm | 单元尺寸必须与图像像素尺寸匹配;节距过小时 MLA 加工公差会直接导致窗口间串扰 |
| 透镜 L1 | f2 = 100~200 mm | 焦距越长,P2 面的光斑越大,越容易对准强度调制元件;但系统总长也会增加 |
| 强度调制面 P2 | 超表面或 SLM,透过率 0~1 | 超表面无源、响应快,但一旦制作完成不可编程;SLM 可编程但刷新率受限 |
| 透镜 L2 | f3 = 80~150 mm | 与 L1 配合组成成像系统,需要满足输出实像条件,见下文公式 |
| 探测器 | CMOS 或 CCD | 放置在 L2 的像方焦平面附近,需要与 P2 的像面共轭 |
3.4 实像输出条件
P2 面经过强度调制以后,光线方向没有被改变,因此只是让每个像素的振幅乘上了一个系数。要完成窗口内求和,需要让这些光线经过 L2 成像到输出面。这里有一个需要留意的约束:由于 L1 已经将同方向光线汇聚到 P2,而 P2 处相当于次级光源,L2 加上 L1 的组合等效焦距与物距之间的关系,决定了输出面能否得到实像。设计时需要满足
L < f2 × f3 / Δ其中 L 是 P1 物面到等效系统主面的距离,Δ 是 L1 后焦面到 L2 的距离。这个条件如果不满足,输出面得到的是虚像,CMOS 无法直接接收。实际工程中我一般会先按这个公式估算 L 的范围,再用 Zemax 或 CODE V 做一遍真实光线追迹验证,因为公式是基于理想薄透镜的,实际厚透镜的主面位置会有偏移。
4. 只能做 0~1 的正核:酉矩阵与光学系统的算力边界
4.1 线性无损系统的传输矩阵为什么是酉矩阵
把光路看成一个线性系统:输入是一个向量 [m, n]^T,经过一个传输矩阵 T = [[a, b], [c, d]] 后输出 [h, k]^T。如果系统是无损的,能量守恒要求输入输出模长相等,即
m² + n² = h² + k² = (a² + b²)m² + (c² + d²)n² + 2(ac + bd)mn由于该等式对任意 m、n 都要成立,所以必须有
a² + b² = 1 c² + d² = 1 ac + bd = 0这正是酉矩阵的定义条件。也就是说,任何只由透镜、反射镜、棱镜构成的无源光学系统,其传输矩阵必然是酉矩阵。换句更直白的话说:透镜只能重新分配光能量,不能让总能量变多。
4.2 振幅调制与奇异值分解的对应关系
在当时设计的系统里,P2 面上的强度调制元件(超表面)是一个关键器件。它只改变光线的振幅,不改变传播方向,数学上对应一个对角矩阵——每个元素是某个空间位置上的透过率,值域为 [0, 1]。从奇异值分解的角度看,任意一个矩阵 M 都可以写成
M = U Σ V†其中 U 和 V 都是酉矩阵,Σ 是对角矩阵。在光路中,L1 与 L2 组成的成像系统分别充当 U 和 V 的角色,而 P2 处的强度调制面充当 Σ。表面上看,似乎任意矩阵的奇异值分解都能在光路上找到对应元件,从而实现任意的传输矩阵。但实际上这里有一个微妙的限制:成像系统的传输矩阵虽然是酉矩阵,但它是固定的——L1 和 L2 的相对位置一旦确定,U 和 V 就被冻结了,只有 Σ 是可调的,而且只能取主对角线为非负实数、且每个元素在 0 到 1 之间的对角矩阵。
4.3 这意味着什么
这带来两个层面的结论。第一,这个光学系统可以实现任意非负卷积核——因为任何非负矩阵都可以通过调节 P2 面的透过率分布来直接编码。高斯平滑、均值滤波这些正算子可以直接做。第二,它无法直接实现含负系数的卷积核,比如 Prewitt、Sobel、拉普拉斯算子。这倒不是说光学卷积就此失效,而是需要在算法层面做等价变换:把负系数拆成两个正核的差分,或者给整个核加一个正偏置、再做一次光强相减。具体做法在后面的应用部分展开。
| 能力项 | 电学卷积 | 该光学系统 | 说明 |
|---|---|---|---|
| 核的取值 | 任意实数/复数 | 仅 0~1 正实数 | 负核需拆分成多次正核运算 |
| 核的更新速度 | 纳秒级 | 超表面不可变 / SLM 毫秒级 | 训练阶段用 SLM,推理阶段用固定超表面 |
| 并行度 | 取决于 GPU 核心数 | 空间并行,一行光路处理整幅图像 | 对高分辨率图像优势明显 |
| 精确度 | 浮点精度 | 受光学噪声与元件公差影响,约 8-bit 等效 | 适合推理,不适合高精度训练 |
由于这种结构天然只能做正值卷积核,想把它嵌入到 CNN 里当作卷积层,需要在网络设计时就把核的取值约束到 [0,1],或者在网络输出前加一个激活/裁剪操作。这在一些量化神经网络(如 BinaryNet、DoReFa-Net)里反而是一种自然的匹配。如果目标算子含负值,我一般会采用「双通道差分」的做法,即将负核分解为两个正核运算后再相减,作为后续在实验中验证的方案。
5. 用光线追迹验证系统可行性:传输矩阵仿真与参数调试
5.1 光线传输矩阵建模
在搭建真实光路之前,可以用几何光学的传输矩阵方法做一次数值仿真,验证上述「窗口划分 → 聚焦 → 调制 → 叠加」的各个环节是否成立。把一条光线表示为一个三维向量 [r, θ, e]^T,r 是光线距主光轴的高度,θ 是与光轴的夹角,e 是光强。自由空间传播距离 L、薄透镜、强度调制面的传输矩阵分别写为:
import numpy as np def propagate(L): # 自由空间传播距离 L return np.array([[1, L, 0], [0, 1, 0], [0, 0, 1]]) def thin_lens(f): # 薄透镜,焦距 f,只改变方向不改变高度和强度 return np.array([[1, 0, 0], [-1 / f, 1, 0], [0, 0, 1]]) def amplitude_mask(tau): # 强度调制面,tau 为透射率(0~1) return np.array([[1, 0, 0], [0, 1, 0], [0, 0, tau]])三个矩阵对应三种物理过程:光在均匀介质中直线传播,经过薄透镜发生偏折,经过调制面只改变振幅。特别要注意的是,这种三维向量表示法把光强放在向量第三维,因此调制面矩阵第三行乘上透射率 τ,而前两行(空间与角度)不变,对应「不改变传播方向」。
5.2 微透镜阵列的光轴偏移处理
微透镜阵列与单片透镜的关键区别在于:每个微透镜单元有自己的光轴,而矩阵追迹默认以主光轴为参考。因此在追迹到 MLA 前表面时,需要为每条光线加上一个光轴偏移量 offset = 单元序号 × 单元节距,使其以自己所属微透镜的光轴为参考;完成该微透镜的折射计算后,再减去同一个偏移量,将参考轴恢复为主光轴。这一步如果漏掉,光线的角度会完全错误,后续所有聚焦位置都不对。代码段中体现为:
# 假设一个一维 MLA,节距 p,单元数为 n offsets = np.arange(n) * p # 入射光线追迹到 MLA 前表面 rays = np.vstack([r, theta, intensity]) # 切换到各微透镜自己的光轴坐标系 rays_local = rays.copy() rays_local[0] -= offsets[:, None] # 应用微透镜折射 rays_local = thin_lens(f1) @ rays_local # 回到主光轴坐标系 rays_global = rays_local.copy() rays_global[0] += offsets[:, None]这段代码展示了一个一维 MLA 的光轴切换逻辑,二维情况只是把 offsets 变成二维网格、对行和列分别处理。参数说明:p 是微透镜节距,对应一个窗口的空间尺寸;f1 是微透镜焦距,决定了光线经过 MLA 后出射角的离散度;offsets 的维度必须与窗口数量对齐,否则会出现窗口间串扰。仿真中常见的错误是把 MLA 当作单透镜一次求完,导致 P2 面上的汇聚点位置出现周期性偏差。
5.3 在 P2 面做哈达玛积调制
到达 P2 面后,需要根据光线所处的位置 r 对强度做调制,而不是直接乘一个全局矩阵。原因在于 P2 面上不同空间位置对应卷积核的不同系数。实现上可以先按 r 的值分组,将同一 r 的光线索引找出来,再给这些光线赋予同一个透射率 τ_i,与原强度做哈达玛积:
def apply_kernel_at_p2(rays, r_positions, kernel_values): """ 在 P2 面按空间位置提取透射率 :param rays: 当前追迹状态,shape (3, n_rays) :param r_positions: 光线对应的空间位置数组 :param kernel_values: 卷积核展开后按位置排列的透射率数组 :return: 调制后的光线 """ modulated = rays.copy() for i, tau in enumerate(kernel_values): idx = np.where(np.isclose(r_positions, r_positions[i]))[0] modulated[2, idx] *= tau return modulated这里的 idx 是「位置相同的光线」索引。因为来自不同窗口、但窗口内相同位置的光线在 P2 面会汇聚到同一个 r,它们需要乘上同一个核系数。用哈达玛积而不是矩阵乘法,是为了保持光线维度的独立性,也符合调制面「逐点透过率」的物理意义。核系数数组 kernel_values 的长度等于窗口内像素数,即微透镜单元内的采样点数。这里实际仿真时的采样间隔,决定了 r 的离散精度;不论是否仿真,最终产物都是强度的二维空间分布。
5.4 输出面与仿真参数参考
在输出面处,把所有 r 相同的光线强度相加,再按 r 重排,就得到实际的卷积输出分布。这就完成了从「光线独立传播」到「光强叠加」的转换。仿真时沿光轴每隔 0.5 mm 取一个采样面,把所有截面的光强记录下来,可以得到类似图 5 所示的光路扩散过程。一维卷积核 [0, 1, 0] 的仿真结果表现为:只在中间窗口对应的输出位置出现亮斑,两侧位置光强接近零。更改 P2 面的透射率分布,例如换成 [1, 0, 0],输出亮斑就会转移到对应位置。仿真参数参考如下:
| 参数 | 典型取值 | 说明 |
|---|---|---|
| 微透镜节距 p | 1 mm | 对应一个窗口宽度 |
| 微透镜焦距 f1 | 10 mm | 决定 P2 面上光斑间距 |
| L1 焦距 f2 | 100 mm | 决定 P2 面位置 |
| 采样间隔 | 0.5 mm | 越小越能看到光路局部变化,但计算量增大 |
| 光线数 | 4096(64×64) | 足够呈现光强分布且仿真耗时可控 |
在这个仿真中容易踩的坑有两个。第一个是光轴偏移量在二维情况下的方向:如果只处理了 x 方向而忘了 y 方向,P2 面的光斑会在 y 方向错位,输出图像会呈现「纵向模糊」;第二个是 P2 面调制时用数值相等而非位置索引相等来判断 r 相同,导致部分光线没被调制,输出出现半透明条纹。正确做法是先在仿真里打印出 r_positions 的取值集合,核对去重后的数量是否等于窗口内像素数,再做调制。
6. 从固定算子到可学习卷积核:光学卷积层的工程落地
6.1 锐化算子的光学变形:把负核拆成差分
由于 P2 面的透过率只能取 0~1,Prewitt、Sobel 这类带负系数的算子无法直接编码。常见的做法是把算子拆成两个正核。以 Prewitt 的 x 方向梯度核为例,原始核为[-1, 0, 1; -1, 0, 1; -1, 0, 1],可以拆成[0, 0, 1; 0, 0, 1; 0, 0, 1]减去[1, 0, 0; 1, 0, 0; 1, 0, 0]。在光路上做两次卷积,第一次用正核 A 得到光强分布 I_A,第二次用正核 B 得到 I_B,最终梯度为 I_A - I_B(在电学域做减法)。代价是需要两倍的光学曝光时间,但好处是所有核的系数仍然在 [0,1] 范围内,超表面不用做任何特殊处理。这个方案在实验中的效果较原版算子略差一些——原因是两次透射率分布的动态范围被压缩,但用于边缘检测任务时输出灰度级仍然够用。
6.2 通用光学卷积层架构
把整个系统接到 CNN 里当卷积层,推荐用两个 SLM 加一个 CMOS 的架构。SLM1 加载输入图像,SLM2 替代超表面加载当前层的卷积核,CMOS 接收输出。流程是:先把当前层的输入图像投到 SLM1,把卷积核权重编码到 SLM2,CMOS 曝光并读取输出;然后由外部处理器把输出重排后投回 SLM1,更新 SLM2 为下一层卷积核,进入下一层计算。因为 SLM2 是可编程的,这就规避了超表面一次性制造、不可修改的问题。这里的 SLM1 需要是相位型或振幅型均可,关键是像素间的填充率要高,避免窗口间串扰。
6.3 用预重排破解步长限制
前文提到,光学系统默认步长等于窗口尺寸,也就是卷积和池化同时发生。如果要在光学卷积层中实现 stride=1,最简单的方式是把输入图像在加载到 SLM1 之前先做一次重排。具体做法是:把原图中卷积核每滑动一次所覆盖的窗口,从原图里裁剪出来、按列堆叠、重排成一个更大的图像,再投到 SLM1 上。这样光学系统仍然按「窗口大小 = 核大小」的方式工作,但屏幕上每个窗口实际对应的是原图滑动位置的一个裁剪块,等效步长就变成了 1。实现 same 或 full 卷积时,只需要在重排前对原图进行补零操作。由于 SLM 上对应补零位置的像素透射率设为 0,这些像素不会对输出光强产生贡献,等效于在输入端完成了边缘填充。重排的像素映射关系可以用以下逻辑生成:
# 假设输入为 img,核大小为 k,步长为 s # 生成滑动窗口坐标后做列堆叠重排 windows = [] for i in range(0, img.shape[0] - k + 1, s): for j in range(0, img.shape[1] - k + 1, s): patch = img[i:i+k, j:j+k] windows.append(patch.reshape(-1)) # 每个窗口列堆叠后,按行排成 SLM 上的输入图案 slm_pattern = np.vstack(windows).T.flatten()这里的核心逻辑是:把每个窗口内的像素按行展开,所有窗口再按列方向依次排列,最后展平成一个一维序列,再 reshape 回二维图案加载到 SLM。这样投影到微透镜阵列上时,每个微透镜单元对应一个窗口,窗口与窗口之间在空间上互不重叠。注意np.vstack(windows).T的作用是保证同一窗口内的像素位于 SLM 图案的同一列区域,从而被同一个微透镜单元覆盖。这个重排过程在实验里我用 Python 实现不到 50 行代码,耗时主要花在校验窗口坐标与原图索引的一一对应关系上。调试时先拿一个 4×4 的随机矩阵验证映射关系:把重排结果再逆变换回来,能还原出原图像即说明映射正确,再放到光路上做真实实验。
本文还有配套的精品资源,点击获取