TensorFlow 分布式 FFT 实战:数据切分、跨设备转置与性能优化

做大规模谱分析的人,很多都会在某个阶段撞上同一堵墙:数据越算越大,FFT 开始成为整条流水线里最慢的环节。TensorFlow 里的分布式快速傅里叶变换,乍一看就是把tf.signal.fft2d放到多张卡上跑,实际做起来才会发现,真正的工作量全在数据切分、跨设备转置、显存峰值和算子语义上。这篇文章就围绕这个场景,把从环境准备到性能踩坑的完整过程捋一遍,适合已经会用 TensorFlow、但对分布式策略接触不深的读者,也适合那些正准备把大矩阵 DFT 从单机搬到多卡、又不清楚通信代价的人。

1. 分布式 FFT 到底在解决什么问题

1.1 大多数人的 FFT 需求,其实用不到“分布式”

先泼一盆冷水:很多场景下你并不需要真正的分布式快速傅里叶变换。

TensorFlow 里一个很常见的误操作,是为了“显得高级”给一段本来就放得下的数据套一层 MirroredStrategy。比如输入是一批[batch, height, width]的图像,单张 4096×4096 的灰度图转成 complex64 也就 128MB,显存 16GB 的卡轻松装下。这时候把整个 batch 复制到每张卡上,每张卡独立跑tf.signal.fft2d,速度反而可能比单卡还慢,因为:

  • 数据从 CPU 到多卡显存要做一次广播;
  • 每个设备计算相同的数据,算力等于被除以了卡数;
  • 如果后续还有跨卡同步,通信开销会淹没计算收益。

我见过不少团队在这上面折腾好几天,最后发现把 batch 按卡切一半,每张卡处理自己那份,不做任何数据冗余,加速比反而接近卡数。

所以第一步要想清楚:你的“分布式”到底是哪种分布式。是数据放不下需要分块,还是数据放得下但想通过并行加速?两者方案完全不同。

1.2 什么时候才需要真的行-列分解

真正需要跨设备协作做 FFT 的场景,通常是单样本维度大到一块卡装不下。比如:

  • 高分辨遥感影像的频谱分析,单张图可能到 16384×16384 甚至更大;
  • 大规模声场模拟、波束形成里的平面阵响应计算;
  • 气候或流体模拟里对二维剖面的空间谱估计;
  • 某些医学成像重建里对一整幅大矩阵做频域处理。

以 16384×16384 的实矩阵为例,float32 原始数据约 1GB,转成 complex64 做频域处理直接翻到 2GB。如果还要做窗函数、频域滤波、逆变换,中间还可能出现多个临时复数张量,24GB 显存未必够用。这时候就必须把矩阵切开,分到多张卡上,各自算完一部分再协作完成全局变换。

这才是真正意义上的“分布式快速傅里叶变换”,也是这篇文章重点讲的部分。

1.3 分布式 FFT 的技术切分方式:Batch、行并行、两步法

面对一个二维 FFT 任务,实际工程里通常有三种切分思路,复杂度从低到高:

  1. Batch 维切分:数据是[batch, H, W]或[batch, channel, H, W],直接把 batch 维度分配给多张卡,每张卡独立做fft2d,全程无通信。这是最理想的情况,加速比接近线性,实现最简单。
  2. 行并行 + 两阶段转置:单张图太大,按行切成多个[H/P, W]的块,每张卡先对行做 FFT,然后通过跨卡通信把列方向的数据凑齐,再对列做 FFT。
  3. 单卡能装下但想加速:可以直接每张卡保留完整数据,用tf.signal.fft2d独立算,然后用框架把结果收集回来。注意这种方案只适合小规模矩阵,卡数一多带宽就吃紧。

大多数人在标题里看到的“分布式快速傅里叶变换”,指的其实是第 2 种。因为它才真正涉及数据分布、负载均衡和通信重排,也才是所有坑的集中地。

2. 把二维 FFT 拆开看:为什么需要通信

2.1 二维 FFT 的可分离性

二维离散傅里叶变换的数学定义是:

F(u, v) = sum over y sum over x f(x, y) * exp(-2πi * (ux/W + vy/H))

这个表达式可以拆成两次一维变换:先对每一行做一维 FFT,得到中间结果;再对每一列做一维 FFT,得到最终结果。这就是“可分离性”,也是分布式 FFT 的理论基础。

这个性质给分布式实现带来的最大好处是:行变换天然可以并行,因为每一行的计算独立于其他行。但列变换不行,因为某一列的数据在按行切分后分散到了不同的设备上。

换句话说,第一步计算不需要任何通信,第二步计算前必须先做一次全局数据重排,把相同列的散块集中到同一台设备上。这个重排本质上就是矩阵转置。

我经常用一句话概括:行 FFT 是幸福的,列 FFT 是痛苦的,痛苦从转置开始。

2.2 两个 FFT 方向与数据连续性

TensorFlow 的张量默认是行主序存储,也就是[row0, row1, ...]依次连续排列。tf.signal.fft默认沿最后一个轴做变换,所以它读数据时是连续读取,缓存友好,GPU 上的计算单元也能把带宽用满。

但如果你要对列方向做 FFT,把同一列的元素提出来,它们在内存里每 N 个元素才出现一次。这种跨步访问会让缓存命中率暴跌,GPU 实际吞吐量可能只有连续访问的几分之一。

更麻烦的是分布式场景:不同设备上各自有若干行,你想对列做 FFT,必须先把这些列数据搬到同一个设备上。这就引出了通信量问题。

工程上常见的规避手段是先转置再做第二次一维 FFT。转置之后的“列”变成了行,存储连续,第二次 FFT 又能用满带宽。但转置本身也花钱,所以需要精确计算这笔账。

2.3 通信量估算:一张 1 万阶矩阵要搬多少数据

假设输入矩阵是 N×N 的 complex64,每个元素占 8 字节(实部虚部各 4 字节)。总数据量:

总数据量 = N * N * 8 字节

如果把它按行等分到 P 台设备上,每台设备持有 N/P 行。行 FFT 完成后,为了做列 FFT,理论上需要把每台设备上的数据交换给其他所有设备,也就是一次 all-to-all 通信。通信量接近一个完整矩阵的大小,约:

通信量 ≈ N * N * 8 字节

按 N=16384、P=4 来算,原始 complex64 矩阵约 2GB,通信量也接近 2GB。如果设备之间走的是万兆以太网,理论带宽 1.25GB/s,光传输就要 1.6 秒;而单卡做一次 16384×16384 的二维 FFT,GPU 上通常只需要几十毫秒到几百毫秒。通信直接成为绝对瓶颈。

所以真正的大矩阵分布式 FFT,不会傻到把整块数据一次性 gather 到某个节点,而是用分块转置(block transposition)+ 流水线重叠,边传边算,让通信和计算尽量并行。

这一节看完,你大概已经明白为什么网上很多教程里的“分布式 FFT”只是玩具:一步all_gather就把所有性能优势全送回去了。

3. TensorFlow 里的最小可行实现

3.1 环境准备与验证脚本

我用的环境是 TensorFlow 2.x 的 GPU 版本,多卡通过tf.distribute.MirroredStrategy管理。先跑一个最小的验证脚本,确认环境没问题:

import tensorflow as tf import numpy as np print(tf.__version__) print(tf.config.list_physical_devices("GPU"))

然后做一次 64×64 的二维 FFT,专门用来和 NumPy 对照:

x = np.random.randn(2, 64, 64).astype(np.float32) t = tf.signal.rfft2d(x).numpy() ref = np.fft.rfft2(x) print(np.max(np.abs(t - ref)))

正常情况下误差在 1e-5 量级。这一步非常重要,因为后续所有分布式实现的正确性,都要回到这个基准上验证。

TensorFlow 的 FFT 算子基于 GPU 上的高性能 FFT 库,通常对 2 的幂次长度支持最好。如果你的矩阵边长不是 2、3、5 的倍数,实测速度会明显下降,工程上一般先 padding 到合适的长度,算完再把多余部分裁掉。注意 padding 之后,傅里叶变换的归一化因子和频率间隔都会变,必须统一处理。

3.2 方案 A:Batch 维分布式(推荐首选)

如果你的数据有 batch 维,一定要优先用这个方案。代码非常短:

strategy = tf.distribute.MirroredStrategy() dataset = tf.data.Dataset.from_tensor_slices(images) dataset = dataset.batch(global_batch_size, drop_remainder=True) dist_dataset = strategy.experimental_distribute_dataset(dataset) def compute_spectrum(batch): # batch 形状 [global_batch_size, H, W] return tf.signal.fft2d(tf.cast(batch, tf.complex64)) for batch in dist_dataset: spectrum = strategy.run(compute_spectrum, args=(batch,))

关键点在于experimental_distribute_dataset会自动把 batch 切成多个 replica,每张卡拿到的只是global_batch_size / num_replicas一份。因为每一份的 FFT 完全独立,设备之间没有任何通信需求,加速比可以做到接近线性。

我用 4 张卡测试过一批 64 张 512×512 的复数矩阵,单卡耗时约 60ms,4 卡切分后单批次耗时可压到 20ms 左右,效果立竿见影。

3.3 方案 B:行-列分解的两步法

当单张矩阵大到一块卡装不下时,必须按行切分。我先写一个演示原理的简化版本:

def distributed_fft2d_by_rows(matrix, rank, world_size): # matrix 是 [N, N] 复数矩阵 local_rows = N // world_size local_block = tf.slice(matrix, [rank * local_rows, 0], [local_rows, N]) # 第一步:本地行 FFT,无通信 local_mid = tf.signal.fft(local_block) # [local_rows, N] # 第二步:转置,让第二根轴变成原来的列坐标 local_mid_t = tf.transpose(local_mid) # [N, local_rows] # 第三步:把所有设备上的转置块拼接成完整矩阵 all_mid_t = all_gather(local_mid_t) # 伪代码,切勿直接在生产中使用 # all_mid_t 形状约为 [N, N],此时最后一维恰好对应原来矩阵的列索引 # 第四步:沿最后一维做第二次 FFT,等价于对原矩阵的每一列做 FFT result = tf.signal.fft(all_mid_t) # [N, N] return result

先说清楚,上面的all_gather只是用来演示原理。真实工程里如果这么做,等于把整块矩阵又汇聚到一张卡上,显存和带宽都会瞬间爆炸。正确做法是分块转置:把本地[N, local_rows]再细分成更小的块,每个小块单独发送给对应的目标设备,目标设备收到后马上开始做局部列 FFT,形成流水线。

我用一个简化的调度逻辑描述核心:

for step in range(world_size): src = (rank + step) % world_size # 从 src 设备的连接上接收一块 [block_cols, local_rows] 数据 # 拼到本地的列方向缓冲区 # 当缓冲区的列数足够时,立刻对该块做 fft

这个模式和通信库里的 all-to-all 很像,只不过每个方向传输的是分片而不是整块。分片越细,通信和计算的重叠程度越高,但调度开销也会变大。我实际测试下来,块大小取在 1 万到 10 万个元素之间比较合适,太小了启动开销压不住,太大了通信延迟暴露明显。

3.4 方案 C:MirroredStrategy 下直接 fft2d 的适用边界

还有一类常见误区是把MirroredStrategy当作神药,直接在每张卡上算完整矩阵:

def compute_full_fft(matrix): return tf.signal.fft2d(tf.cast(matrix, tf.complex64)) result = strategy.run(compute_full_fft, args=(matrix,))

这本质上不是分布式 FFT,而是“每张卡都算一份完整 FFT”,属于冗余计算。只有当以下两个条件同时满足时才值得考虑:

  • 矩阵单卡能放下,且复用变量或后续 ops 需要同步;
  • 矩阵很小,多卡间的低延迟通信可以忽略不计。

否则就是拿显存换时间,卡越多浪费越大。我见过有人把 2048×2048 的矩阵塞进 8 卡MirroredStrategy,最后端到端耗时比单卡还慢 30%,原因就是框架同步和传输开销。

4. 性能、显存与数值精度:几个必须提前知道的事

4.1 实数输入请第一时间换 rfft

很多人的输入明明是一张灰度图、一个实值场,却直接用tf.signal.fft2d,白白浪费一半频谱空间。实数信号的傅里叶变换具有共轭对称性,只需要保存一半频谱就能完整还原所有信息。

TensorFlow 提供tf.signal.rfft和tf.signal.rfft2d,输入是 float32,输出是 complex64,最后一维长度是W//2+1:

spectrum = tf.signal.rfft2d(image) # [H, W//2+1]

这一点在分布式 FFT 里影响巨大。复数矩阵如果是[N, N]complex64,占 8N² 字节;换 rfft 后变成[N, N/2+1]complex64,约 4N² 字节,数据量直接减半。通信量也减半,列变换阶段的压力小很多。如果后续还需要逆变换得到原实值数据,用tf.signal.irfft2d即可。

4.2 显存峰值的隐藏来源:gather 与转置

分布式 FFT 最常见的 OOM 不是发生在计算阶段,而是发生在数据重排阶段。两个典型场景:

一是用strategy.gather把所有副本结果收集到单卡。假设每卡处理[16384/4, 16384]的复数块,约 512MB,四卡 gather 后主卡显存瞬间多出 2GB;如果中间还有临时变量,峰值可能就是完整矩阵的 2~3 倍。

二是把矩阵整体转置。TensorFlow 的tf.transpose在高阶张量上经常需要创建一份新拷贝,转置的同时显存翻倍。如果直接在分布式程序里对完整大矩阵调用tf.transpose,大概率会看到"Resource exhausted"。

我的经验是,所有跨设备操作都按“分块、收发、计算、丢弃”四个阶段处理,不要保留任何完整中间态。比如本地转置后立刻切片发送,发送完成就释放本地大块缓存,让峰值停留在单块大小而不是整矩阵大小。

4.3 精度对比:float32 下的复数误差

TensorFlow GPU 上的 FFT 默认基于 float32/complex64 计算。对于 N=8192 的随机数据,实测tf.signal.rfft2d与 NumPy 的误差通常在 1e-4 到 1e-3 之间,这在大多数谱分析场景够用。但如果你要做的是高动态范围信号处理,比如只关心某个极弱频点,float32 可能不够。

TensorFlow 对 complex128 的 FFT 支持一直不完整,很多算子在 GPU 上直接不支持 double 精度。如果你需要双精度结果,目前最靠谱的路径是先切数据块,用单精度检查整体误差是否可接受;如果不可接受,再考虑用 CPU 上的双精度算子,或者把数据拆成实部虚部各自处理再合并。

另外要记住tf.signal.fft的归一化约定。很多人在做“正变换-逆变换”还原时会发现幅值不对,多半是忘了归一化因子。我的习惯是每次写新代码都先用一个 8×8 的小矩阵验证正逆变换,确认还原因子后再放大规模。

5. 常见问题与排查实录

5.1 结果转置 / 轴顺序错乱

这是手动做行-列分解时最容易出的问题。二维 FFT 如果先对行做 FFT,再对列做 FFT,中间转置的次数与方向必须一致。最简单的验证办法:用 4×4 小矩阵分别跑tf.signal.fft2d和你的分布式实现,对比输出。如果结果正好是转置后的形式,说明最后少了一次tf.transpose或多了两次。

5.2 设备间通信比计算还慢

这是分布式 FFT 最现实的问题。我曾经做过一组对比:N=4096 时,单卡fft2d大约 5ms,而 4 卡行切分+all-to-all 重排需要 50ms 以上;N=32768 时,通信量变大,但单卡计算时间长,分布式方案才逐渐体现出优势。

我的结论是:分布式 FFT 的收益存在明显临界点。如果你处理的是几千阶矩阵,老老实实用单卡;如果上到两三万阶且网络带宽高,再考虑分布式。

5.3 gather 后 OOM 的教训

我之前做 32768×32768 复数矩阵时,图省事在最后用strategy.gather汇总频谱,结果主卡直接 OOM。后来把所有 gather 改成异步分块接收,同时把列方向 FFT 改成边收边算,显存峰值从原来的 16GB 降到 6GB,端到端时间也缩短了近一半。

记住:分布式 FFT 的核心不是把计算结果汇聚到一张卡上,而是让每张卡只持有自己需要的那部分结果。如果后续必须用到完整频谱,也要考虑用文件系统分片保存或流式消费,而不是一次性塞进显存。

5.4 tf.function 与 per-replica 值的坑

在strategy.run里写普通 Python 控制流很容易触发重试或抽象张量错误。举个常见错误:在tf.function内部依赖rank做if rank == 0:判断,但rank不是张量,在自动图模式下会被当作符号值处理,导致ValueError: Tensor-typed variable ... cannot be used as Python boolean。

解决方法是把设备编号作为普通 Python 参数传入,或在进入tf.function之前完成分支逻辑。如果你要在strategy.run里调用tf.distribute.all_gather,还要注意 API 在不同版本里的行为差异:有的版本沿第一维拼接,之后再默认形状变成了[num_replicas, ...],需要手动 reshape。

5.5 快速验证脚本:先跑小规模再放大

大规模分布式 FFT 最容易翻车,但只放大不缩小测。我现在的工作习惯是固定一套验证矩阵:

sizes = [64, 256, 1024, 4096, 8192, 16384]

每个尺寸都跑一遍,先测单卡基线,再测分布式实现,同时记录三样东西:峰值显存、单步耗时、与tf.signal.fft2d结果的误差。一旦某个尺寸误差突然变大,就回过去检查是不是 padding、归一化或转置逻辑写错了。

这套流程看起来笨,实际能省大量排查时间。

6. 我踩过这些坑之后的一点经验总结

最后说一个我自己的检查习惯:任何分布式 FFT 实现,我都会先在 64×64 的小矩阵上跑通,再逐步放成 512、2048、8192……放大过程中盯两个指标,一是设备间通信占比,二是聚合后单卡显存峰值。只要通信占比接近或者超过计算,基本可以认为这个规模不该上多卡,回到 batch 并行或直接单卡更划算。

另外,不要迷信“越多的卡越快”。分布式 FFT 和分布式训练不一样,它的通信模式是全局交换而不是梯度同步,网络延迟和带宽的制约非常明显。对于单张超大矩阵,多卡真正能帮助的是把行 FFT 和列 FFT 分阶段流水起来,而不是简单地堆算力。

如果你正在规划一个大矩阵的频谱分析项目,我建议的顺序是:先确认有没有 batch 维可并行,再看能不能用 rfft 省一半数据,最后才考虑行-列跨卡转置。大多数项目走到第一步就够用了;走到第二步已经不算多;真走到第三步,那你大概率已经把显存和带宽规划得很清楚了。

相关新闻

石鱼的世界模拟:基于ROS与Python的水下机器人仿真平台解析

石鱼的世界模拟:基于ROS与Python的水下机器人仿真平台解析

简介:面向机器人、生物学仿真与Python开发者的石鱼生态环境模拟项目,基于ROS Noetic分支构建,可在虚拟三维场景中还原石鱼行为、环境互动及多物种交互,涉及环境建模、物理模拟、行为算法、事件驱动与多线程等技术点,适…

2026/10/19 19:31:48 阅读更多 →
从形式主义到基础设施:开放式Code Review机制设计与落地

从形式主义到基础设施:开放式Code Review机制设计与落地

1. 先聊聊为什么很多团队的Code Review形同虚设我参与过大大小小不下十个项目的评审流程改造,见过最典型的一个场景是:团队嘴上喊着"我们要做Code Review",GitHub仓库的PR也确实挂了一堆,但点进去一看,评论全…

2026/10/19 19:31:48 阅读更多 →
自建代码审查基础设施:open-code-review设计部署与优化

自建代码审查基础设施:open-code-review设计部署与优化

去年我在主导一个中台服务重构,那阵子代码评审基本处于失控状态:PR平均合并时间从3小时拖到近3天,核心代码的评审意见大量集中在缩进和命名上,而真正的接口设计问题、事务边界问题反而经常放过去,等上了灰度环境才炸出…

2026/10/19 19:30:47 阅读更多 →

最新新闻

命令模式实战:从撤销重做到宏命令的行为型设计模式详解

命令模式实战:从撤销重做到宏命令的行为型设计模式详解

我先说个结论:命令模式是我个人最常用的行为型设计模式,没有之一。很多人觉得它不就是一个“把方法调用包一层对象”的小技巧嘛,但等你真正遇到需要撤销、排队、做宏、写操作日志的场景时,你会感谢这个“小技巧”的。我在一个模拟…

2026/10/19 20:14:57 阅读更多 →
存储型XSS实战:从靶场原理到Cookie窃取与防御

存储型XSS实战:从靶场原理到Cookie窃取与防御

没错,今天要聊的就是那台几乎每个Web安全学习者都装过的开源靶场——一个把常见漏洞都塞进同一个PHP项目里的测试环境。存储型XSS,是其中我最推荐新手反复练的模块:它需要你同时理解输入点、存储点、输出点这三件事,才能把漏洞真正…

2026/10/19 20:14:57 阅读更多 →
ArtCraft:工程化思维驱动的可控创意生产方法论

ArtCraft:工程化思维驱动的可控创意生产方法论

1. 项目概述:ArtCraft 不是“艺术手作”的简单拼贴,而是一套可落地的创意生产方法论ArtCraft 这个词乍看像英文直译——“艺术工艺”,但如果你最近刷过设计类社区、创意工作坊预告或高校跨学科课程表,会发现它频繁出现在项目命名、…

2026/10/19 20:13:53 阅读更多 →
BCGControlBar合规替代方案:Qt与wxWidgets工程实践指南

BCGControlBar合规替代方案:Qt与wxWidgets工程实践指南

简介:本资源是面向C桌面开发工程师与MFC高级学习者的BCGControlBar Pro 22.1专业版完整破解方案,专为解决VC6至VC2010多版本兼容性开发需求而设计,支持ANSI/Unicode双编码及Debug/Release双模式编译,可直接集成到各类MFC项目中快速…

2026/10/19 20:11:50 阅读更多 →
C++ GUI组件注册机制:用函数表映射替代if-else分支

C++ GUI组件注册机制:用函数表映射替代if-else分支

1. 为什么非要搞一套组件注册机制1.1 一次重构把我逼到墙角去年这时候,我在做一个面向内部使用的可视化配置工具。需求不复杂:左边是组件列表,右边是画布,点一下组件就能添加,选中之后右侧属性面板可以调参数。听起来平…

2026/10/19 20:11:50 阅读更多 →
Artcraft:数字时代的手艺逻辑与工艺知识体系

Artcraft:数字时代的手艺逻辑与工艺知识体系

1. “Artcraft”不是新词,而是被低估的创作方法论原点“Artcraft”这个词最近在设计、手作、数字内容社区里频繁冒头,但几乎没人说清楚它到底指什么。它既不是某个新出的AI绘画工具,也不是某家公司的产品代号,更不是某种加密艺术协…

2026/10/19 20:10:47 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →