[旧日谈] 再考 IIR 与 FIR 滤波器对相位影响的定量分析
IIR 与 FIR 濾波器对音频相位的影响 先前我有写过一个简单的文章分析过两种滤波器对音频相位的影响,但是我只是知其然不知其所以然。对于音频,我虽然知道相位是一个很重要的概念,但是我始终不知道相位对实际音频的印象是什么水平的。这个问题在这些年的开发过程中始终萦绕在心头。虽然不做音频了,但是我仍然对这个问题保持好奇,综上,这也是为什么有了这个文章。 一、从一个问题开始 假设我们有一个 1kHz 的正弦信号,经过一个低通滤波器之后,输出还是 1kHz 的正弦信号,幅度变小了——这很好理解,滤波器嘛,该衰减的衰减。 但仔细看输出波形,会发现它相对于输入信号产生了一个时间上的延迟。这个延迟不是简单的"整体往后挪了 N 个采样点",而是不同频率的信号延迟不一样。 1kHz 的信号延迟了 0.5ms,500Hz 的信号延迟了 0.8ms,2kHz 的信号延迟了 0.3ms——每个频率成分的延迟都不一样。 这就是相位失真。 对于音频处理来说,这个问题比听起来严重得多。人耳对相位差的感知不如幅度那么直接,但当不同频率成分的延迟差异大到一定程度时,会导致: 瞬态信号(比如鼓点、齿音)的波形被"模糊化" 立体声声像偏移 某些频段的"堆叠"或"空洞" 所以,理解滤波器的相位特性,是做音频处理的基本功。 先说结论,IIR 的相位响应受幅度响应约束(最小相位特性),无法独立控制;FIR 可以独立控制幅度和相位,因此能实现线性相位或任意指定相位。 但是至于为什么音频行业常用IIR滤波器,这个问题我将在补充后说明。 二、先回顾一下:FIR 和 IIR 是什么 FIR(有限脉冲响应) FIR 滤波器的差分方程: $$ y[n] = \sum_{k=0}^{M} b_k \, x[n-k] $$输出只依赖于当前和过去的输入,没有反馈。脉冲响应是有限长的(长度 M+1)。 IIR(无限脉冲响应) IIR 滤波器的差分方程: $$ y[n] = \sum_{k=0}^{M} b_k \, x[n-k] - \sum_{k=1}^{N} a_k \, y[n-k] $$输出同时依赖于输入和过去的输出(反馈)。脉冲响应理论上是无限长的。 两者的核心区别在于有没有反馈。这个结构上的差异,直接决定了它们的相位特性。 三、相位响应的推导 从频率响应说起 一个 LTI(线性时不变)系统的频率响应可以写成: ...
柔性触觉阵列显示层在线双态蠕变观测器补偿算法
摘要 柔性触觉阵列在反复增减同一负载时,前一代显示层补偿器暴露出一个结构性缺陷:整片卸载后重新加载,239 s 受载历史累积的蠕变补偿被状态机清零,同一负载的前后显示相差 2516 ADC(满载族极差),用户看到的就是"丢基线"。此后用事件式"蠕变记忆"修补,虽修好了目标工况,却在随机切换工况上把显示整体下移约 1200 ADC——账本式记忆伤害了它没见过的工况,该方案已废弃。本文给出第三条路线:把传感器与夹具当作线性粘弹性体,逐通道在线维护两个 Kelvin-Voigt 非弹性状态(快态 + 慢态),叠加一个近零带内慢速再校准的零点跟踪,显示 = 零点 + 纯弹性响应。整条算法没有事件检测、没有状态清零、没有跨事件账本,受载门由弹性估计的自然下限形成;慢态改为跟随实测慢漂移速率,因为辨识表明蠕变幅度因工况差 3 倍、固定幅度必在一边过扣一边欠扣。在真实数据回放上:反复增减工况一致性 10/10 段合格(用户判据 ±10%·台阶 ≈ ±1500 ADC);346 s 恒载保压显示漂移 −108 ADC(输入自身 +1010);首次加载稳定时间 2.0~2.6 s,已接近输入斜坡的物理地板。代价如实:受载态小台阶的稳定时间 7.5~10.5 s(缺快相前馈);频繁切换工况的保压段 std 相对固定幅度版上升(如 R1 从 33 升到 81);卸载后到零点再校准完成前显示有一段低于原始零点的过渡(τ₀=8 s 量级)。在线实现与独立复算实现逐帧对拍,最大逐帧差 5×10⁻⁵ ADC。 关键词 触觉阵列;粘弹性蠕变;在线观测器;Kelvin-Voigt 模型;零点跟踪;无事件补偿;一致性 1 引言 1.1 丢失的 2000 ADC:上一代方案的失效方式 前代显示层补偿(事件状态机方案)建立在事件状态机上:加载沿、卸载沿、变载各自触发状态迁移,补偿量挂在事件血统上逐代继承。它在"加载—保持—再加载"的家族路径上工作正常,但有一条它没有覆盖的路径:整片卸载。 现场录制 R1(31306 帧,21 通道)里的时间线是:239~240 s 输入经约 1.5 s 四级台阶塌到基线,检测器 240.08 s 确认卸载、240.86 s 状态机转入空载并清空全部慢相补偿状态;244.3 s 重新加载被当作全新事件,幅度参考直接锚在快相终点电平上。于是第一次加载段(t16~239)显示 = 输入 − 累积补偿(补偿从 +1570 涨到 +2289,这是正确行为:load1 从 2.4 s 一直压到 239 s,其蠕变一直在长),而重载段(t246 起)的扣除是 −54 与 +13——239 s 受载历史被整段遗忘。同一满载的前后显示相差 2516 ADC(族极差),这就是用户报障的"丢基线"。 ...
受力平衡分析:质心与扭转
这是一篇面向开发和调试的算法文档。读完后你应该能回答三件事: 算法在算什么、为什么这么算、界面上每个元素对应公式里的哪个量。 全文用一个贯穿例子:一块 5×5 的受力区域,压力沿主对角线(左上—右下)分布,左上端更重。 1. 算法要做什么 阵列式压力传感器的数据就是一张二维表:每个格子一个力值。画成三维曲面(坐标做底面、力值做高度),哪里轻哪里重一目了然。 图 1:起点。 左:压力曲面沿对角线隆起、左上端更高;右:同一组数据的 2D 视图和每格数值。人眼能看出"不均匀",算法要把这件事变成数字结论。 用户拿压头按传感器时,“按得不规范"有两种情况,物理上完全不同: 问题类型 表现 用户该怎么修 位置偏移 压头放歪了位置,但贴得是平的。压力分布形状正常,只是整体挪了位 照着箭头把压头挪过去 姿态歪斜 位置可能很正,但压头翘了边。翘起的一侧轻、压下的一侧重,分布形状被扭曲 停下来,检查贴合,重新放平 修法完全不同,所以算法输出两个相互独立的结论,各管一类: 位置通道:受力中心偏在哪 → 画面上画一个箭头; 扭转通道:压力沿哪条线失衡、哪侧重 → 画面上画一条双向轴线 + 偏重侧铺色。 后面所有设计都是围绕这两个通道展开的。第 2~3 节分别讲两个通道怎么算,第 4 节给出完整流程汇总,第 5 节讲界面怎么读,第 6 节讲能力边界和参数。 2. 位置通道:算受力中心(质心) 2.1 符号约定 后面所有公式共用这套记号: 受力区域(下称"斑块”)内的格子编号为 $i = 1, 2, \dots, n$; $V_i$:第 $i$ 格的力值(热力图上显示的那个数); $p_i = (r_i, c_i)$:第 $i$ 格中心的坐标,$r$ 是行号(向下),$c$ 是列号(向右); $\sum_i$:只对斑块内的 $n$ 个格子求和。 2.2 公式 把每格的力值当权重,对格子位置做加权平均,得到的点就是受力中心(质心): ...
一个基本问题:当我们谈论移植的时候,我们聊的是 ONNX 还是自定义算子?
这篇笔记不讨论哪条路线“更先进”,只记录一个实际选型问题:模型离开训练框架以后,究竟应该交给通用推理引擎,还是把它改写成项目自己的 C++ 实现。 面试时我常问这个问题。我关心的是,对方能不能把“能跑”和“适合长期维护”分开看。很多项目一开始只是照着现成方案接进去,等到包体积、启动时间或某个算子性能出了问题,才发现当初其实已经做了取舍。 所以这里的“移植”,不只是换一个文件格式。它更像是在两套成本之间做选择:一套成本由运行时和通用性承担,另一套成本由自己的代码、测试和维护承担。 一、ONNX 到底做了一件什么事 先从模型离开训练框架后的处境说起。 我们在 PyTorch 里训练模型,享受的是动态图、Python 生态、随时 print 随时调试。但部署环境——一个可能没有 Python、没有 torch、甚至没有文件系统的进程——什么都没有。训练与部署之间需要一次"交接"。交接最朴素的方式是序列化:把权重存下来,把结构描述出来。问题在于,每家框架的序列化格式都是私有方言:PyTorch 有 TorchScript 和 .pt,TensorFlow 有 SavedModel 和 .pb,互不相认。而部署端的推理引擎又层出不穷:ONNX Runtime、TensorRT、OpenVINO、CoreML、DirectML、各家 NPU 的私有 SDK……如果每个框架都要为每种引擎各写一条导出通道,对接复杂度是 N × M 的;在中间垫一层公共格式,它就变成 N + M。 ONNX(Open Neural Network Exchange)就是这层公共格式。具体来说,它负责三件事: 定义了一套标准算子集(operator set,简称 opset):Conv、Gemm、BatchNormalization、Relu……每个算子的语义、输入输出、数值行为都有书面规范,并按版本演进(opset 13、17、18……)。 定义了一张计算图格式:用 protobuf 描述节点(NodeProto)、张量(ValueInfoProto)、权重(Initializer),以及它们之间的连接关系。一个 .onnx 文件本质上就是一段序列化后的 protobuf。 定义了类型与张量规范:数据类型、布局约定、广播规则。 可以把它看成模型领域的交换文件:训练框架负责把图和权重写出来,推理引擎负责把它读进去。双方不必直接适配彼此,只要共同遵守这份格式即可。它能普及,主要也是因为这个生态位置,而不是因为它在每一种设备上都能做到最好。 这里有一个几乎所有初学者都会混淆、但一问就露馅的点:ONNX 只是一个格式,它自己不会执行任何东西。真正让模型跑起来的是推理引擎,最常见的就是 ONNX Runtime(下文简称 ORT)。前者是协议,后者是实现,就像 JSON 和某个 JSON 解析器的关系。我们日常说"用 onnx 跑",省略掉的主语几乎都是 ONNX Runtime。 那么这条路线的优势和代价分别是什么? 先看它为什么常常是默认选项。实际项目里,通常看重的是三点:格式标准,工具链成熟,以及性能能够满足要求。 标准:一次导出,处处可跑(至少在纸面上)。跨框架、跨引擎、跨操作系统。 成熟:ORT 背后是大量工程师十几年打磨的图优化器、算子融合 Pass,以及接驳成熟的 kernel 库(CPU 上有 MLAS 一系,移动端常用 XNNPACK)。你几乎不可能在同等工时内手写出比它更稳、覆盖面更广的实现。 快得足够:对绝大多数 PC 端、服务端场景,ORT 的默认性能已经在业务容忍范围之内;而且它提供 Execution Provider(EP)机制——CUDA、TensorRT、DirectML、CoreML、QNN——同一个 .onnx 几乎零改动就能下发到 GPU 甚至 NPU。 再说代价。代价不在"能不能跑",而在你为这份通用性交出的东西: ...
3D 曲面力场重建架构与点云显示算法研究
3D 曲面力场重建与点云显示算法研究 背景 三维曲面力场重建要解决的,是如何根据少量离散传感器的空间位置、表面方向和测量值,估计模型表面上没有传感器位置的力值,并把结果显示成连续、稳定且符合物体形状的分布。 这件事看起来像普通插值,实际包含两个性质不同的问题。第一个问题是力值应该传播到哪里。它由距离定义、曲面连通关系和传感器朝向决定。第二个问题是重建结果应该怎样显示。它由表面采样密度、颜色映射和几何位移决定。早期方案多次调整,根本原因就是把传播算法和显示载体混在一起:传播关系不正确时,仅改变颜色无法消除串色;传播关系正确时,直接在粗三角面上着色又可能产生大片色块。 最终采用的思路是先把三角模型整理成可传播的曲面图,再把每个传感器附着到模型表面,沿曲面执行有限半径的最短路径搜索,用 Wendland C² 核计算距离权重。多个传感器按连续场采样点进行归一化插值,再通过 coverage 控制支撑区域边缘的衰减。显示层不再改变模型本身,而是在灰色模型表面覆盖彩色点云,用颜色和沿法线的隆起共同表达力值。 图 Fig001:力场算法的主要演进。距离定义、边缘衰减和显示载体分别经历调整,最终形成曲面传播与点云显示的组合。 本文以左手手套三维模型作为演示对象,使用确定性的 Wave 合成力值观察动态效果。由于没有与该模型对应的真实加载实验数据,文中的结果只能说明算法行为和显示效果,不能被解释为真实设备精度验证。 从欧氏插值到曲面传播 最直接的重建方法,是计算传感器与模型顶点之间的三维直线距离,然后让距离近的传感器具有更大权重。这种方法实现简单,在平面或结构单一的缓曲面上也可能得到合理结果,但它隐含了一个不适用于复杂模型的假设:空间上接近的位置,也可以沿物体表面直接传播。 手套模型能清楚暴露这个问题。两根手指之间的空气间隙很小,手掌正反面之间也可能只有很薄的模型厚度。欧氏距离会把这些位置视为近邻,使一个传感器的影响直接到达另一根手指或模型背面。折角附近也有同样的问题。两个点的直线距离可能很短,但真实的表面路径需要绕过较长距离,甚至根本不存在连续路径。 早期的视觉修正曾把低值区域改成灰色,重新定义平滑显示与离散点显示的切换,并调整颜色过渡。这些改动提高了画面可读性,却没有改变传播距离,因此不能从根本上修复薄壁、折角和相邻部件之间的串色。 像素化重建是另一条尝试路线。它先把模型投影到高分辨率平面,在规则像素网格上插值和后处理。规则像素能够减轻原始三角面大小不均造成的色块,Gaussian 平滑也能让边缘更柔和,但单一投影难以表达任意方向、多层和自遮挡曲面。模型前后两层还可能落到相同像素位置。高分辨率重建带来的计算量也比较大。因此,这条路线适合作为视觉实验,却不适合作为一般三维模型的传播基础。 曲面传播的核心变化,是把"距离"从三维空间中的直线长度,改成模型表面的路径长度。模型顶点作为图节点,共享三角形边作为图边,边权是相邻顶点之间的几何长度。传感器先找到附近且朝向兼容的表面点,再从该点沿曲面图执行截断 Dijkstra。一个位置只有在曲面上存在合法路径,并且路径总长不超过传播半径时,才会受到该传感器影响。 这并不意味着算法完全不再使用欧氏距离。传感器的导出位置可能略微离开模型表面,因此仍要用直线距离找到附近表面作为附着点。区别在于,这个距离只用于完成传感器到表面的初始附着。附着之后的扩散范围由曲面路径决定,不能继续穿过模型内部走捷径。 曲面如何变成可计算的传播域 三角网格不能直接拿来做曲面搜索。STL 模型通常以彼此独立的三角形保存,同一个几何点可能在相邻三角形中重复出现。如果不先合并这些重复顶点,算法看到的会是大量互不连接的小三角形,最短路径也无法跨越三角形边界。 因此,曲面准备的第一步是顶点焊接。算法按照一个较小的位置容差,将坐标相同或足够接近的顶点合并为共享顶点,同时删除顶点重复或面积接近零的退化三角形。焊接容差需要与模型单位和导出精度匹配。容差过小会留下不应存在的裂缝,容差过大则可能把距离很近但实际分离的两层表面错误连接起来。 焊接之后可以根据三角形共享边建立邻接图。图边的长度取两个顶点之间的几何距离。为了避免传播穿过尖锐折角,还要比较相邻顶点法线。设相邻顶点为 $u$ 和 $v$,只有满足 $$ \mathbf{n}_u\cdot\mathbf{n}_v\ge\tau_e $$时,二者之间的边才允许参与传播。本文采用的邻接法线阈值为 $\tau_e=0.25$。这是一种硬门控:达到阈值的边可以通过,低于阈值的边完全禁止通过。它的优点是折角隔离清楚,缺点是高曲率但连续的表面也可能被切断。更平滑的替代方案是根据夹角增加路径代价,但这会引入新的衰减参数,必须通过真实数据才能判断是否更合理。 网格密度同样会影响曲面距离和点云连续性。如果最大三角形边长远大于传播半径,支撑区域内可能只有少量顶点,最短路径会变得粗糙,显示点也会过于稀疏。可以把目标边长定义为 $$ E_{target}=\max\left(\frac{R}{4},1\text{ mm}\right) $$当最大边长明显超过目标边长时,将一个三角形沿三条边的中点拆成四个子三角形。共享边的中点必须复用,避免细分后重新产生裂缝。细分还要设置顶点数上限,否则高面数模型可能快速膨胀。 细分后需要重新计算顶点法线。顶点法线既控制传播能否经过某条边,也决定点云隆起方向,因此不能把它当成单纯的光照数据。通常将相邻三角形的面积加权法线累加到顶点,再进行归一化。无条件执行 Laplacian 平滑并不稳妥,因为平滑会收缩几何并改变传感器到表面的物理距离。视觉上更圆润不代表几何关系更准确。 传感器附着也使用法线约束。设曲面点法线为 $\mathbf{n}_v$,传感器法线为 $\mathbf{n}_i$,只有满足 $$ \mathbf{n}_v\cdot\mathbf{n}_i\ge\tau_c $$的顶点才参与附着点选择。本文采用 $\tau_c=0.1$。在所有兼容顶点中选择与传感器中心最近的点作为种子。如果传感器到种子的初始距离已经大于传播半径,该传感器不产生曲面影响。 从种子开始执行 Dijkstra 搜索,路径距离由曲面边长累计: $$ d_i(v)=\min_{\pi:i\rightarrow v}\sum_{(u,w)\in\pi}\|\mathbf{x}_u-\mathbf{x}_w\| $$路径只能经过法线兼容的邻接边,并在距离超过半径 $R$ 时停止。这样得到的 $d_i(v)$ 是离散网格上的测地距离近似。它不完美依赖模型质量,但比直接使用穿过空间的直线距离更符合复杂表面的传播关系。 图 Fig004:传感器附着、曲面路径和紧支撑权重。欧氏距离只用于选择附近表面,实际传播距离由曲面路径决定。 离散测量如何形成连续场 得到曲面距离之后,仍然需要决定距离如何转化为权重。本文采用 Wendland C² 紧支撑核。对曲面距离 $d$ 和传播半径 $R$,令 $r=d/R$,权重定义为 ...
频域维纳滤波降噪算法——设计思路与工程手记
频域维纳滤波降噪算法——设计思路与工程手记 写在前头 这份笔记记录的是一个面向实时语音通信的频域降噪算法。它的设计目标很明确:在 48kHz 采样率下,以 10ms 帧长实时处理单通道语音,把稳态和准稳态背景噪声压下去,同时尽量不伤语音。算法脱胎于 WebRTC 的 Noise Suppression 模块,但经过了面向嵌入式的重构——所有动态分配被消除,所有 C++ 模板和虚函数被拍平成纯 C 结构体。 我按照信号流经的顺序来谈。先说为什么要这么做,再说具体怎么做。 1. 信号模型 一切降噪算法的出发点都是同一个假设:麦克风采集到的信号 $x(n)$ 是干净语音 $s(n)$ 和加性噪声 $d(n)$ 的叠加: $$ x(n) = s(n) + d(n) $$这个模型简单到近乎天真,但它成立的前提条件其实很苛刻:噪声和语音不相关,噪声在短时间尺度上是准平稳的。实际工程中,空调嗡嗡声、风扇声、马路上的低频轰鸣,在几十毫秒内确实变化不大——这就够了。 我们的目标是从 $x(n)$ 中恢复 $\hat{s}(n)$。在频域里,这意味着对每个频率 bin 施加一个增益 $G(k)$: $$ \hat{S}(k) = G(k) \cdot X(k) $$问题归结为:怎么求这个 $G(k)$? 2. 为什么选频域 时域降噪(比如 LMS 自适应滤波)需要参考信号,这在单麦克风场景下不现实。频域方法的好处是: 频率分辨率。噪声和语音在不同频段的能量分布差异很大。白噪声全频段平坦,语音能量集中在 300Hz–4kHz。频域处理可以逐 bin 调节衰减深度。 短时平稳假设更容易满足。一帧 10ms 内,噪声功率谱几乎不变,这给估计器提供了稳定的观测窗口。 计算量可控。256 点 FFT 在嵌入式平台上是成熟操作,远比时域长 FIR 滤波器经济。 代价是引入了帧延迟和频谱泄漏,这些后面会谈。 3. 总体处理流程 一帧 480 个采样点(48kHz × 10ms)进来后,经过以下环节: ...
琴谱 - Duvet
Duvet ← Previous Page 0 / 0 Next → 下载 PDF 正在加载 PDF 预览...
琴谱 - 蓬莱伝説 (Forest306)
蓬莱伝説 Forest306 编曲。 ← Previous Page 0 / 0 Next → 下载 PDF 正在加载 PDF 预览...
线性代数不难
线性代数不难 《线性代数不难》是生姜 DrGinger 编写的线性代数入门读物。本页面收录带书签的 PDF 版本,方便在线阅读与章节跳转。 ← Previous Page 0 / 0 Next → 下载 PDF 正在加载 PDF 预览...
散点到 3D 曲面:力场重建工程算法
散点到 3D 曲面:力场重建工程算法 一、目标与边界 本文说明如何将三角网格表面的离散 Cell 采样值重建为连续、稳定且可实时渲染的 3D 曲面力场,覆盖算法输入、几何预处理、曲面距离、插值、颜色映射、缓存、渲染和验证。 算法解决以下工程问题: STL 三角面重复顶点导致表面拓扑断裂。 三维欧氏距离会穿过薄壁、折角或独立零件传播数值。 离散 Cell 数值需要连续插值,但不能因邻近 Cell 数量增加而产生虚假峰值。 覆盖边缘需要自然回落到模型基底颜色,不能出现硬截断。 几何计算与实时帧更新必须分离,以满足连续数据刷新需求。 算法输入包括三角网格、Cell 位置与法线、当前帧采样值、显示范围和重建参数;输出为与渲染顶点一一对应的颜色数组。 1.1 算法全景流程 二、曲面采样模型 2.1 曲面距离问题 目标曲面具有曲率、折角、正反面和多个独立零件,不能把它视为平面或无拓扑关系的三维点集。 核心问题是:两个点在三维空间里看起来很近,是否意味着它们沿模型表面也很近? 2.2 Cell 语义:离散采样,不是独立力源 Cell 表示连续真实力场的离散采样点。 重建值表达附近采样值的加权平均,不把多个 Cell 的数值简单相加。 若四个相邻 Cell 都是 0.6,中间位置也应接近 0.6,不能仅因为附近有四个点就变成 2.4。 以下 12 个采样点用于说明算法在圆柱曲面上的输入形式: 点 z / mm θ / ° x / mm y / mm 归一化压力 S1 -6 -70 -5.638 37.948 0.18 S2 0 -70 -5.638 37.948 0.32 S3 6 -70 -5.638 37.948 0.46 S4 -6 -25 -2.536 34.562 0.42 S5 0 -25 -2.536 34.562 0.82 S6 6 -25 -2.536 34.562 0.68 S7 -6 25 2.536 34.562 0.55 S8 0 25 2.536 34.562 1.00 S9 6 25 2.536 34.562 0.76 S10 -6 70 5.638 37.948 0.28 S11 0 70 5.638 37.948 0.48 S12 6 70 5.638 37.948 0.36 三、Wendland C2 与归一化插值 3.1 紧支撑核 算法使用 Wendland C2 作为紧支撑权重函数: ...