OpenToonz 内置的 KISS FFT:基于混合基数算法的高效轻量 FFT 库使用指南
2026/9/16 18:36:49 网站建设 项目流程

OpenToonz 内置的 KISS FFT:基于混合基数算法的高效轻量 FFT 库使用指南

【免费下载链接】opentoonzOpenToonz - An open-source full-featured 2D animation creation software项目地址: https://gitcode.com/GitHub_Trending/op/opentoonz

导读

本文围绕 OpenToonz 仓库第三方面组件 KISS FFT(完整源码位于 thirdparty/kiss_fft)展开,系统讲解这一以"Keep It Simple, Stupid"为设计哲学的快速傅里叶变换库:从 1D 复数 FFT 的十分钟快速接入、Make/CMake 双构建系统的全部配置项,到混合基数算法、定点数缩放、SIMD 扩展等底层原理。读完本文,你将掌握如何把 KISS FFT 以 float / double / int16_t / int32_t 任意数据类型嵌入自己的 C 程序,并能理解 OpenToonz 的景深虚化(Bokeh)与眩光(Glare)特效为何以及如何调用该库完成频域卷积。

一、KISS FFT 是什么:为"够用且简单"而生的 FFT 库

KISS FFT(项目首页文档)是一个基于**混合基数(mixed-radix)**原理的快速傅里叶变换实现。它的自我定位非常克制:

There are many great fft libraries already around. Kiss FFT is not trying to be better than any of them. It only attempts to be a reasonably efficient, moderately useful FFT that can use fixed or floating data types and can be incorporated into someone's C program in a few minutes with trivial licensing.

即:市面上优秀的 FFT 库很多,KISS FFT 并不试图超越它们,它只追求三件事——合理的效率够用的功能、以及数分钟之内即可嵌入任意 C 程序的极简集成体验,并附带宽松的 BSD 许可证。

从仓库目录结构看,核心实现极其精简:

文件作用
kiss_fft.h公共 API 声明与kiss_fft_scalar类型宏
kiss_fft.c1D 复数 FFT 核心实现
kiss_fftnd.c多维 FFT
kiss_fftr.c实数优化 FFT(只输出正半频谱)
kiss_fftndr.c多维实数 FFT
kfc.cFFT 对象缓存工具
kissfft.hhC++ 模板封装

作者 Mark Borgerding 在 BACKGROUND 一节解释了创作动机:当时找不到不用汇编语言的定点 FFT,于是他先用浮点把理论走通,最终得到一段能通过重编译轻松切换shortfloatdouble三种数据类型的精简代码。这与 kiss_fft.h 中的宏设计一一对应——通过FIXED_POINTkiss_fft_scalar即可在编译期切换数据类型。

二、快速上手:1D 复数 FFT 的十分钟接入

KISS FFT 的 1D 复数 FFT 用法被刻意设计得极其直接。原文档给出的最小可运行流程如下:

#include "kiss_fft.h" kiss_fft_cfg cfg = kiss_fft_alloc( nfft ,is_inverse_fft ,0,0 ); while ... ... // put kth sample in cx_in[k].r and cx_in[k].i kiss_fft( cfg , cx_in , cx_out ); ... // transformed. DC is in cx_out[0].r and cx_out[0].i kiss_fft_free(cfg);

核心调用只有三个函数:

  1. kiss_fft_alloc(nfft, inverse_fft, mem, lenmem)—— 创建并初始化 FFT/IFFT 的配置缓冲区(plan)。nfft是变换长度;inverse_fft0表示正变换、非0表示逆变换;后两个参数用于内存复用(见下文)。
  2. kiss_fft(cfg, fin, fout)—— 执行变换。输入输出均为复数数组kiss_fft_cpx,每个元素通过.r(实部)与.i(虚部)访问。
  3. kiss_fft_free(cfg)—— 释放配置缓冲区。

频域数据的布局约定

原文档特别强调频率域数据的排列顺序:从 DC 一路排到 2π

  • cx_out[0]是 FFT 的DC(直流)bin
  • cx_out[nfft/2]Nyquist(奈奎斯特)bin(当该 bin 存在时)。

即输出按DC, 正频率, ..., Nyquist, 负频率的顺序排列,正变换时fin应为f[0], f[1], ..., f[nfft-1],输出fout对应F[0], F[1], ..., F[nfft-1](见 kiss_fft.h)。

alloc 的内存复用机制(源码级细节)

kiss_fft.h 对kiss_fft_alloc的第四个参数mem/lenmem做了完整说明:

  • lenmemNULL:内部用malloc分配 cfg 缓冲区,使用完毕后应free以避免内存泄漏;
  • lenmemNULLmemNULL、且*lenmem足够大:cfg 会放置到用户提供的缓冲区mem中,并把实际占用大小写回*lenmem,返回mem
  • lenmemNULL但缓冲区不足:函数返回NULL,并把所需最小缓冲区大小写入*lenmem(可用作两次调用的探测模式)。

此外,kiss_fft.h 还提供了两个实用 API:

  • kiss_fft_stride(cfg, fin, fout, fin_stride)—— 每fin_stride个样本取一个输入的通用水印版本;
  • kiss_fft_next_fast_size(n)—— 返回大于等于n的最小整数,且该整数只含有"快速"因子(2、3、5)。这一函数在 OpenToonz 中被大量用于将图像尺寸取整为 FFT 友好尺寸,见下文第六节。

超越 1D 的扩展功能

原文档指出,tools/目录(thirdparty/kiss_fft/tools)提供了一系列"很酷"的扩展:

  • 多维 FFTkiss_fftnd/kiss_fftndr);
  • 实数优化 FFTkiss_fftr):只返回正半频谱,即(nfft/2+1)个复数频率 bin;
  • 快速卷积 FIR 滤波kiss_fastfir.c,注意不支持定点数);
  • 频谱图像生成psdpng.c,依赖 libpng);
  • 命令行 FFT 工具fftutil.c)。

核心 FFT 与绝大多数 tools 代码都能以 float、double、Q15 short 或 Q31 样本编译运行,默认数据类型是 float

三、构建系统与全部配置项详解

KISS FFT 同时维护两套功能等价的构建系统(thirdparty/kiss_fft/CMakeLists.txt 与 thirdparty/kiss_fft/Makefile):

  • Make:面向 Unix/Linux 的传统 Makefile;
  • CMake(>= 3.6):Kitware 出品的现代构建系统。

支持的编译器环境包括 GCC、Clang、GNU Make,以及带 CMake 的 MSVC。构建与测试的可选依赖如下:

依赖用途
libpng编译psdpng频谱图工具
libfftw3验证 kissfft 结果正确性
Python 2/3 + Numpy验证 kissfft 结果正确性
OpenMP(GCC/Clang/MSVC 支持)多核 FFT 变换加速

原文档同时说明:面向 Windows 的 Cygwin、MinGW 环境也"很可能"可以构建(虽然尚未实测)。

3.1 核心配置参数一览

两个构建系统提供同名配置项(Make 用KEY=value,CMake 用-DKEY=value),原文档中的完整清单如下:

配置项Make 写法CMake 写法说明
主数据类型KISSFFT_DATATYPE=<dt>-DKISSFFT_DATATYPE=<dt>float(默认)/double/int16_t/int32_t/simd(需目标 CPU 支持 SSE)
OpenMP 加速KISSFFT_OPENMP=1-DKISSFFT_OPENMP=ON默认关闭,需要编译器支持
静态库KISSFFT_STATIC=1-DKISSFFT_STATIC=ON生成.a(Unix/Linux)或.lib(Windows);默认生成共享库(.so/.dll/.dylib
关闭测试-DKISSFFT_TEST=OFFMake 下测试由make testall/make testsingle单独触发
关闭工具KISSFFT_TOOLS=0-DKISSFFT_TOOLS=OFF不构建fastconv等命令行工具,默认构建
使用 allocaKISSFFT_USE_ALLOCA=1-DKISSFFT_USE_ALLOCA=ONalloca替代malloc/free
安装前缀PREFIX=/path-DCMAKE_INSTALL_PREFIX=/path指定安装目录前缀

3.2 配置项的底层实现

这些开关并非黑盒,均可在源码中直接验证:

  • 数据类型:CMake 在 CMakeLists.txt 中把float/double映射为编译宏kiss_fft_scalar=<type>,把int16_t/int32_t映射为FIXED_POINT=16/FIXED_POINT=32,把simd映射为USE_SIMD并自动追加-msse(GCC/Clang)或/arch:SSE(MSVC)。这些宏在 kiss_fft.h 中直接决定kiss_fft_scalar的真实类型。非法取值会被 CMake 直接FATAL_ERROR拒绝。
  • 静态/共享:CMake 在 CMakeLists.txt 中依据KISSFFT_STATIC自动设置BUILD_SHARED_LIBS;共享库构建时还会追加KISS_FFT_SHARED编译定义,配合 kiss_fft.h 的KISS_FFT_API实现 Windows DLL 的dllexport/dllimport符号导出。
  • OpenMP:CMake 在 CMakeLists.txt 中按编译器追加-fopenmp(GCC/Clang)或/openmp(MSVC),并将输出库名改为kissfft-<datatype>-openmp
  • alloca:追加KISS_FFT_USE_ALLOCA编译定义,使内部临时缓冲区走栈上分配而非堆分配。

3.3 典型构建命令

静态库 + int16_t 定点数据类型 + OpenMP为例,Make 构建:

make KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 all

等价 CMake 构建:

mkdir build && cd build cmake -DKISSFFT_DATATYPE=int16_t -DKISSFFT_STATIC=ON -DKISSFFT_OPENMP=ON .. make all

指定安装前缀/tmp/1234的安装命令(Make 版):

make PREFIX=/tmp/1234 KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 install

(CMake 版):

mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/tmp/1234 -DKISSFFT_DATATYPE=int16_t -DKISSFFT_STATIC=ON -DKISSFFT_OPENMP=ON .. make all make install

值得补充的是:仓库中 Makefile 通过语义化版本变量KFVER_MAJOR = 131KFVER_MINOR = 1KFVER_PATCH = 0管理 ABI 版本(当前目录名Lz4旁并列的 thirdparty/kiss_fft 对应 KISS FFT 131.1.0 版本),CMake 构建系统会在配置时自动从 Makefile 中正则提取这组版本号用于生成共享库的VERSION/SOVERSION

四、测试与验证

构建完成后可用以下命令验证:

  • Make 单配置验证(对应上面 int16_t 示例):
make KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 testsingle
  • CMake 验证
make test
  • 全配置扩展测试套件
sh test/kissfft-testsuite.sh

该扩展测试套件覆盖所有可能的构建配置组合,耗时约20~40 分钟(取决于设备性能),适合报告 bug 或验证 pull request 时使用。测试基础设施位于 thirdparty/kiss_fft/test,其中benchkiss.c(性能基准)、benchfftw.c(对照 FFTW)、test_real.c(实数 FFT)、test_simd.c(SIMD 路径)、testcpp.cc(C++ 封装)以及testkiss.py(Numpy 对照)分别负责不同维度的验证;twotonetest.c用于两音叠加的数值正确性检查。

五、典型接入流程总结

综合原文档 USAGE 与 BUILDING 两节,把 KISS FFT 接入自有 C 工程的最短路径是:

  1. 将 kiss_fft.h 与 kiss_fft.c 加入工程(如需多维/实数/卷积再追加对应源文件);
  2. 按需定义kiss_fft_scalarFIXED_POINT等预处理宏(默认 float 则无需定义);
  3. 调用kiss_fft_allockiss_fftkiss_fft_free三步完成一次变换;
  4. 注意所有参与编译的源文件必须使用一致的FIXED_POINTkiss_fft_scalar预处理定义(这正是原文档 FAQ 中"混编环境"错误的根源)。

六、在 OpenToonz 中的实战:Bokeh 与 Glare 特效的频域实现

KISS FFT 并非 OpenToonz 的孤立附件,而是被正式纳入特效渲染管线的第三方依赖。toonz/sources/stdfx/CMakeLists.txt 将kiss_fft.ckiss_fftnd.c编译进 stdfx 库,并在第 332 行将thirdparty/kiss_fft加入头文件搜索路径。

6.1 基于 FFT 的快速卷积方案

光学模糊类特效(景深虚化、眩光)在空间域是逐像素的卷积运算,计算量与核尺寸成正比;KISS FFT 通过"空间域 → FFT 频域相乘 → IFFT"的卷积定理将其转化为逐元素乘法,复杂度与核尺寸解耦。这一思路在 iwa_bokeh_util.cpp 中体现得十分完整:

  1. 维度取整:在 iwa_bokehfx.cpp 中,用kiss_fft_next_fast_size()将图像尺寸放大到"只含快速因子(2、3、5)"的 FFT 友好尺寸,避免混合基数退化到慢速因子。
  2. plan 分配:在 iwa_bokeh_util.cpp 中分别创建正向与反向两个 2D plan:kiss_fftnd_alloc(dims, ndims, false, 0, 0)(正变换)与kiss_fftnd_alloc(dims, ndims, true, 0, 0)(逆变换)。
  3. 执行与复用:正向kiss_fftnd(...)将图像与光瞳掩模(iris)变换到频域,频域相乘后(见multiplyFilter)再做逆向kiss_fftnd(...)还原空域;plan 用完立即kiss_fft_free释放。
  4. 资源管理:多处用QList<kiss_fftnd_cfg>登记所有 plan,统一在特效结束时循环kiss_fft_free,防止泄漏(iwa_bokeh_util.cpp)。

6.2 眩光特效的同类实现

iwa_glarefx.cpp 采用完全相同的模式:先kiss_fft_next_fast_size取整光瞳尺寸,再kiss_fftnd_alloc建立正变换 plan,把光瞳(iris)变换到频域供后续多次复用。RGB 与 Alpha 通道各自维护独立的 fwd/bkwd plan(iwa_bokeh_util.cpp),通过"一次 FFT、频域逐元素相乘、一次 IFFT"完成整张图像与光瞳核的快速卷积。

从源码结构看,KISS FFT 之所以被选中,正是因为它"混合基数、可缩放、无依赖、可直接编译进工程"的特性与 OpenToonz 的插件化渲染框架高度契合。

七、底层原理(UNDER THE HOOD)

原文档对内部实现给出了精确描述,结合源码可梳理出以下要点:

7.1 算法形态:时域抽取的混合基数、异地(out-of-place)FFT

KISS FFT 使用时域抽取(time decimation)的混合基数算法,且默认异地执行——输入与输出使用不同缓冲区。若传入相同缓冲区,内部会自动创建一个临时缓冲区来承接数据。这一设计使调用方无需关心 in-place 与 out-of-place 的差异。

7.2 线程安全与无静态数据

核心例程不使用任何静态数据,因此是线程安全的(原文档特别注明 tools 目录下并非全部线程安全)。这使其天然适合 OpenToonz 这类多线程渲染环境:不同渲染线程可各自持有独立的 plan 与缓冲区并行执行 FFT。

7.3 缩放策略:浮点不缩放,定点双向缩放

  • 浮点版本不做任何缩放(纯粹为了速度);
  • 定点版本正反两个方向都做缩放(为了防止溢出)。

这一点也解释了原文档 FAQ 中常见的"输出与预期不符"问题——请先检查是否存在一个常量倍率差异(scaling factor)。

7.4 优化的蝶形单元

对因子2、3、4、5使用专门优化的蝶形(butterfly)实现,这也是kiss_fft_next_fast_size只认可 2、3、5 因子、以及偶数长度才能用实数优化的原因。

7.5 实数优化:偶数长度专用

实数(非复数)优化只对偶数长度有效:它并行执行两个半长度 FFT(打包进实部与虚部),再通过旋转因子(twiddling)合并,最终输出DC 到 Nyquist 的nfft/2+1个复数频率 bin(kiss_fft.h 中kiss_fftr_next_fast_size_real宏强制把尺寸取为偶数,正是为此)。

7.6 快速卷积:overlap-scrap 法

kiss_fastfir.c的快速卷积滤波采用overlap-scrap(重叠丢弃)方法,并做了轻微改动:把 scrap(废弃段)放在尾部而非头部。

八、SIMD 扩展:一次并行计算 4 路 FFT

仓库中的 README.simd 对KISSFFT_DATATYPE=simd模式给出专门说明。其基本思想是把 4 个 float 打包的__m128当作一个"标量元素"使用,从而一次 FFT 调用同时完成 A、B、C、D 四路独立信号的 FFT,在支持 SSE 的 Intel x86 机器上可换取2~3 倍加速

  • 复数数据交错布局rA0,rB0,rC0,rD0, iA0,iB0,iC0,iD0, rA1,rB1,rC1,rD1, iA1,iB1,iC1,iD1 ...rA0为信号 A 第 0 个样本的实部);
  • 纯实数数据布局rA0,rB0,rC0,rD0, rA1,rB1,rC1,rD1, ...
  • 推荐编译选项-O3 -mpreferred-stack-boundary=4 -DUSE_SIMD=1 -msse
  • 对齐是最大的坑:SIMD 需要 16 字节对齐,栈上临时变量的地址必须落在 16 字节边界上,这是 SIMD 模式最常见的段错误来源。为此 kiss_fft.h 在USE_SIMD下自动将KISS_FFT_MALLOC切换为_mm_malloc(nbytes, 16)KISS_FFT_FREE切换为_mm_free,并在尺寸上做 16 字节向上取整。

README.simd 也坦诚地警告:该 API 不好用、文档不全,并且违背了 KISS 原则("Beyond here there be dragons!")。它附带的pack128/unpack128示例代码(来源为 Divide Concept 的 Robin,未经作者实测,风险自负)演示了如何在 4×N 与 N×4 转置之间格式化 SIMD 数据。

九、常见问题(FAQ)

原文档 FAQ 的权威回答如下:

Q:我能在使用 ___ 许可证的项目里用 kissfft 吗?A:可以,见下文的许可证说明。Revised BSD 许可证兼容面极广,从 GPL 到闭源商业软件都在其覆盖范围之内。

Q:为什么我得不到预期的输出?A:最常见原因有两个:

  1. 缩放:你得到的结果与预期之间是否只差一个常量倍率?
  2. 混编环境:所有代码必须用相同的FIXED_POINTkiss_fft_scalar预处理定义编译。

Q:你能帮我写/调试代码吗?A:作者表示除非付费否则大概率不会,但乐于回答具体而切题的问题。

十、性能画像与使用边界

原文档给出了一组朴素但直观的性能数据(Athlon XP 2100+,gcc 2.96,float 类型):

  • 完成 10000 次 1024 点复数 FFT 耗时约0.63 秒CPU 时间;
  • 处理同样数据量,md5sum耗时约为其两倍;
  • 变换 5 分钟 CD 音质音频(nfft=1024)耗时不足 1 秒。

对照实验中,作者将 KISS FFT 与某个"备受尊敬且高度优化"的库(文中匿称为 FFT_BRANDX)对比,得到四个关键结论:

  1. FFT_BRANDX 有超过 10 万行代码,而 kiss_fft 的核心(1D 复数)约500 行
  2. 作者花了很长时间才让 FFT_BRANDX 跑起来;
  3. 使用 FFT_BRANDX 的简单程序体积 522KB,而类似功能的 kiss_fft 程序仅18KB(且未做体积优化);
  4. FFT_BRANDX 在默认模式下大约比 KISS FFT快一倍

因此作者给出的边界十分清醒——"DO NOT: 如果需要全世界最快的 FFT 就别用 KISS FFT;也别要求添加会让代码膨胀的功能",并留下一句耐人寻味的设计哲学:"Sometimes simpler is better, even if it's not better.(有时更简单更好,即使它并非更好。)"

十一、许可证与后续 TODO

  • 许可证:Revised BSD License,详见 COPYING。概括为"可自由使用与修改,注明出处即可,不提供任何担保"。该许可证对 GPL 开源软件与闭源商业软件都兼容。kiss_fft.h 头部的版权声明(2003-2010, Mark Borgerding)与 SPDX 标识BSD-3-Clause与此一致。
  • TODO 清单(原文档记录,供贡献者参考):
    • 为奇数长度 FFT 增加实数优化;
    • 文档化/重新审视输入输出的缩放策略;
    • 撰写kiss_fastfir.c中 overlap(尾部 scrap)快速卷积滤波的说明文档;
    • 用定点数全面测试tools/下代码(已知kiss_fastfir.c不工作,其余待测)。

结语

KISS FFT 以约 500 行核心代码同时交付了"混合基数 FFT、四类数据类型、线程安全、宽松许可证、分钟级集成"的组合,是嵌入式与桌面软件中"够用就好"路线的典型代表。在 OpenToonz 中,它被 stdfx 模块用于 Bokeh 景深虚化与 Glare 眩光特效的频域卷积,实现了核尺寸无关的高效模糊;如果你正在为自己的 C/C++ 工程寻找一个零依赖、可定点、可快速集成的 FFT 方案,thirdparty/kiss_fft 的这套源码与文档就是现成的参考答案。

【免费下载链接】opentoonzOpenToonz - An open-source full-featured 2D animation creation software项目地址: https://gitcode.com/GitHub_Trending/op/opentoonz

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询