HarmonyOS 7图像超分·调音台后级:四级信号链的级联数学与音色设计法
HarmonyOS 7图像超分·调音台后级:四级信号链的级联数学与音色设计法
SR CONSOLE 是一台把超分做成「演奏」的合成器:四根竖置推子(KERNEL / DENOISE / SHARPEN / GLOW)、三级后级效果器、一枚 HOLD A/B 干湿监听键,拨动推子全图重渲染。器材评测会告诉你「手感如何」,本篇做的是另一件事——把这台机器当电路拆:每一级对信号到底做了什么、级联顺序为什么不可交换、增益怎么预算,以及如何不靠玄学、用一套可复现的流程设计出一个新音色。全文配图为 Mate 90 Pro 模拟器实机运行截图,所有结论均可复算。
一、机架与监听:先把这台机器当电路看
1. 机架全景

工程 Image-Super-Resolution/sr-console(HarmonyOS 7 / API 26)。它在本篇里的身份不是「应用截图」,而是被测机架:Native C++ 引擎约 260 行、ArkTS 面板约 300 行,render() 单入口、完全无状态——同一个推子位置永远得到同一帧输出。无状态是后面一切「设计方法可复现」的前提:第五节的参数搜索之所以成立,靠的就是「渲染是纯函数」这一条。
2. 面板与出厂配法

出厂配法 KERNEL 0.50 / DENOISE 0.00 / SHARPEN 0.50 / GLOW 0.00,右上角 LED 稳定回报 41MS OK——2× 输出、模拟器单线程。这张图同时是一段「标准录音」:第六章的耗时账目就从 41ms 这个数开始算。
3. HOLD A/B:干湿监听总线

按住 A/B,荧幕切回原始音源并盖上 A / SOURCE 标签;松手回到合成信号。调音台语言里这就是 dry/wet 监听:A 是干声(未经处理),B 是湿声(全链处理)。它在音色设计里的角色不只是「给用户对比」——它是设计闭环里的人耳终审:第五节的参数搜索能给出候选答案,但最终签字画押的必须是这一颗键。
4. 一台机器的电路图
把面板抽象成电路,就是一条四级串联的后级信号链:
SRC(180×180 RGBA)
├─ V 前级插值 KERNEL t ∈ [0,1] 变形核:双线性 ↔ Catmull-Rom
├─ D 中值混音 DENOISE d ∈ [0,1] 原信号 ↔ 3×3 中值
├─ U 高频提升 SHARPEN a USM:out = I + a·(I − Blur3)
├─ G 低频注入 GLOW g ∈ [0,1] out = (1−0.7g)·I + 0.7g·Blur3
└─ OUT(dst×factor)
四级顺序是刻意的:先变形、再去噪、再锐化、最后柔焦。这不是美学偏好——第三节会用级联数学证明:把 D 与 U 交换,锐化就白做了。每一级还有一个关键性质:推子为 0 时该级被整段跳过(不是「乘以 0 地执行」),41ms 的 2× 录音能这么快,一半功劳在这里。
二、四级各自的电路角色
1. 前级 V:会变形的插值核
三角波(双线性)与三次曲线(Catmull-Rom)按推子位置线性混音——锐度不是档位,是无级旋钮:
double Kernel(double x, double blend) {
double ax = std::fabs(x);
double tri = std::max(0.0, 1.0 - ax); // 双线性成分:宽过渡
double cr = 0.0; // Catmull-Rom 成分:带肩峰
if (ax < 1.0) {
cr = 1.5 * ax * ax * ax - 2.5 * ax * ax + 1.0;
} else if (ax < 2.0) {
cr = -0.5 * ax * ax * ax + 2.5 * ax * ax - 4.0 * ax + 2.0;
}
return (1.0 - blend) * tri + blend * cr;
}
void BuildTaps(int srcLen, int dstLen, double blend, Taps* taps) {
for (int d = 0; d < dstLen; ++d) {
double center = (d + 0.5) * srcLen / dstLen - 0.5; // 半像素对齐
int s0 = (int)std::floor(center) - 1;
double sum = 0.0;
for (int t = 0; t < 4; ++t) {
taps[d].idx[t] = ClampInt(s0 + t, 0, srcLen - 1);
taps[d].w[t] = Kernel(center - (s0 + t), blend);
sum += taps[d].w[t];
}
for (int t = 0; t < 4; ++t) taps[d].w[t] /= sum; // 权重归一,亮度不漂
}
}
从频域直觉看:blend → 0 时核的通带更宽、过渡更软;blend → 1 时通带更窄、过渡带更陡,且在截止频率附近有轻微肩峰(overshoot)——这就是「锐」的来源。而 blend = 0.5 的混合核在教材里查不到,它同时保留双线性过渡与三次核的锋利,是这台机器独有的音色位。
两个不能省的细节:半像素对齐(角点对齐会让整图偏移半个源像素)与权重归一化(混合核在边界处权重和偏离 1,不归一就亮度漂移)。第四节的标准探针里专门为此留了一个斜坡象限。
2. 中值混音 D:一台非线性的「噪声闸」
每通道取 3×3 邻域中值,9 个元素插入排序,推子控制原信号与中值信号的线性混合:
if (denoise > 0.0) {
std::vector<uint8_t> med(out);
Median3(med, dw, dh);
for (size_t i = 0; i < out.size(); i += 4)
for (int c = 0; c < 3; ++c)
out[i + c] = ClampByte(
out[i + c] * (1.0 - denoise) + med[i + c] * denoise);
}
注意它和前级不同:中值是非线性算子,没有闭式的「增益」。对孤立噪点,中值直接把它挤出窗口(衰减接近 d);对宽结构,中值几乎不动(衰减接近 0)。所以 D 的准确角色是「按结构选择性衰减的闸」,不是均衡器。这一点在第三节很重要——它决定了 D 和 U 为什么不能交换。
3. USM U:高频提升器,增益可以算出来
Box3(out, dw, dh, blur);
for (size_t i = 0; i < out.size(); i += 4)
for (int c = 0; c < 3; ++c) {
size_t bi = i / 4 * 3 + c; // 模糊缓冲按像素×3 通道布局!
out[i + c] = ClampByte(out[i + c] + sharpen * (out[i + c] - blur[bi]));
}
把它写成传递形式就能看清:out = (1+a)·I − a·Blur3。对低频成分 Blur3 ≈ I,增益是 (1+a) − a = 1——低频纹丝不动;对高频成分 Blur3 ≈ 0,增益是 (1+a)——高频被抬了整整 a 倍。所以 SHARPEN 推子本质上是一台「高频增益 = 1+a、低频增益 = 1」的倾斜均衡器:
[
\text{out} = (1+a),I - a,\mathrm{Blur}3
\qquad\Rightarrow\qquad
G{hf}=1+a,;; G_{lf}=1
]
DOC 预设把 a 推到 1.10,等于高频 ×2.1——这也是它有振铃风险的原因(第四节方波探针可见)。
4. GLOW G:与 U 互为镜像的低频注入器
if (glow > 0.0) {
Box3(out, dw, dh, blur);
double mix = glow * 0.7;
for (size_t i = 0; i < out.size(); i += 4)
for (int c = 0; c < 3; ++c) {
size_t bi = i / 4 * 3 + c;
out[i + c] = ClampByte(
out[i + c] * (1.0 - mix) + blur[bi] * mix);
}
}
同一套代数反着读:out = (1−0.7g)·I + 0.7g·Blur3,低频增益 (1−0.7g)+0.7g = 1,高频增益 (1−0.7g)。G 是高频衰减器、低频保持器——高光晕开、结构不动,这正是「柔焦」与「糊」的分界。
5. 四级角色一览
| 级 | 推子 | 数学角色 | 频域角色 | 增益(可解析部分) |
|---|---|---|---|---|
| V 插值 | KERNEL | 重采样 + 核混合 | 通带宽度 / 肩峰 | 非增益级 |
| D 中值 | DENOISE | 非线性混合 | 结构选择性衰减 | 无闭式,湿量 d |
| U 锐化 | SHARPEN | (1+a)I − a·Blur3 | 高频提升 | G_hf = 1+a |
| G 柔焦 | GLOW | (1−0.7g)I + 0.7g·Blur3 | 高频衰减 | G_hf = 1−0.7g |
后两级是精确的线性算子,增益可以直接乘;前两级要靠第四节的标准探针实测。
三、级联数学:顺序为什么不可交换
整条链是四个算子的复合:
[
F = G \circ U \circ D \circ V
\qquad
\text{out} = F(\text{src};, t, d, a, g)
]
线性算子之间交换顺序不影响结果,但这条链里有两个非线性算子(V 的钳制截断、D 的中值),所以顺序就是电路拓扑,动了就换一台机器。
1. 关键反例:D 与 U 交换会发生什么
设想把链改成 V → U → D → G(先锐化再去噪)。USM 抬起的高频里混着被同一个 1+a 放大的噪声,紧接着中值闸按湿量 d 把高频整体往下压——锐化刚抬上去的东西,又被下一级吃掉一块。两个效应叠加:
- 净高频增益缩水:近似记中值对高频的等效衰减为 (\eta_d \in (0,1)),两种顺序的高频增益分别为
[
\text{现行 } (D \to U):; (1+a)\cdot\eta_d’
\qquad\text{vs}\qquad
\text{交换 } (U \to D):; (1+a)\cdot\eta_d \cdot\eta_d’
]
其中 (\eta_d’) 是 D 作用于「已锐化信号」时的衰减——已锐化信号的高频更丰富,被吃掉的绝对量更大; - 噪声被永久烙进结构:先锐化会把噪点震出更深的局部极值,中值对「已成形的大噪点」衰减有限,而现行顺序里小噪点在进入 U 之前就被压平了。
现行顺序(先 D 后 U)让锐化增益全额落在已经被清洗过的信号上——这是「先净化、后整形」的经典效果器排布,与音频链上「降噪在前、激励在后」是同一个道理。
2. 用代码把顺序实验固化下来
争论没意义,跑一遍就知道。把链路做成可换序的,在同一张探针图上对比:
struct Tone { double kernel, denoise, sharpen, glow; };
// order=false: V→D→U→G(现行);order=true: V→U→D→G(反例)
std::vector<uint8_t> RenderChain(const std::vector<uint8_t>& src,
int sw, int sh, int factor,
const Tone& p, bool swapped);
struct Metrics { double sharp, hf, range; }; // 定义见第四节
void SwapExperiment(const std::vector<uint8_t>& src, int sw, int sh) {
Tone stock{0.5, 0.3, 0.8, 0.0}; // 同一组参数,只换顺序
for (int factor : {2, 4}) {
Metrics a = Measure(RenderChain(src, sw, sh, factor, stock, false));
Metrics b = Measure(RenderChain(src, sw, sh, factor, stock, true));
std::printf("f=%d stock: sharp=%.1f hf=%.2f | swapped: sharp=%.1f hf=%.2f\n",
factor, a.sharp, a.hf, b.sharp, b.hf);
}
}
在合成探针上的复现方向是稳定的:交换后 sharp 与 hf 双双下降,且 d 越大掉得越多——湿量 0.3 时掉一档,湿量 0.55(NIGHT 配法)时锐化几乎归零。这就把「顺序不可交换」从设计直觉变成了可断言的实验事实,也顺手成为一条回归测试:如果哪次重构后两种顺序的指标差消失,说明某一级的实现写错了。
3. GLOW 为什么必须站在链尾
同样的分析适用于 G 与 U:若把柔焦放在锐化之前,锐化新增的高频将不会被 1−0.7g 收敛——出图会带着明显的数码味(高频过冲 + 振铃边)。链尾的 GLOW 承担的是终级音染:把 U 刚抬起来的高频「收」一点。三种排布的预期仪表特征:
| 排布 | 锐度 | 高频能量 | 听感(观感)类比 |
|---|---|---|---|
| V→D→U→G(现行) | 高 | 中(被柔焦收过) | 干净、有骨架、不扎眼 |
| V→U→D→G | 低 | 低 | 锐化白做,噪点被烙深 |
| V→D→G→U | 最高 | 最高 | 数码味:过冲与振铃裸奔 |
4. 增益预算:把四个推子换算成一个数
对可解析的两级,链路高频总增益可以直接预算:
[
G_{hf} \approx (1+a)\cdot(1-0.7g)\cdot\eta_d
]
代入面板上的四组典型配置((\eta_d) 按湿量粗估,标注为经验值):
| 配置 | KERNEL | DENOISE | SHARPEN | GLOW | (G_{hf}) 近似 |
|---|---|---|---|---|---|
| 出厂位 | 0.50 | 0.00 | 0.50 | 0.00 | (1.5\times) |
| PORTRAIT | 0.35 | 0.25 | 0.55 | 0.40 | (1.55\times0.72\times\eta_{0.25}\approx 0.8!\sim!1.1\times) |
| DOC | 0.90 | 0.00 | 1.10 | 0.00 | (2.1\times) |
| NIGHT | 0.60 | 0.55 | 0.30 | 0.15 | (1.3\times0.90\times\eta_{0.55}\approx 0.5!\sim!0.6\times) |
这张表直接解释了三颗预设芯片的性格:PORTRAIT 是「高频微降」、DOC 是「高频翻倍」、NIGHT 是「高频腰斩」。用户拨推子时感受到的「音色变化」,就是这条预算线在滑动。工程意义在于:给定一个目标高频增益(比如「要 PORTRAIT 两倍的锐」),可以反解推子而不必逐个试——(a = G_{hf}/((1-0.7g)\eta_d) - 1),先定 G 和 D,再解出 a。
四、仪表:给「音色」装上可复算的刻度
音色设计的前提是能量出音色。主观词(“锐”“肉”“雾感”)必须翻译成三个可从输出帧直接测出的量:
| 仪表 | 定义 | 对应的主观词 |
|---|---|---|
sharp | 四邻域拉普拉斯的方差 | 锐 / 肉 |
hf | |L| 的均值(高频能量) | 颗粒感 / 干净 |
range | 灰度 5%~95% 分位差 | 反差 / 灰蒙 |
三者全部在一次像素遍历里算完,量纲分别是灰度²、灰度、灰度级。
struct Metrics { double sharp, hf, range; };
Metrics ScoreFrame(const std::vector<uint8_t>& img, int w, int h) {
Metrics m{0, 0, 0};
if (w < 3 || h < 3 || img.size() != (size_t)w * h * 4) return m;
int hist[256] = {0};
double sumAbs = 0.0, sumSq = 0.0;
int n = 0;
for (int y = 1; y < h - 1; ++y) {
for (int x = 1; x < w - 1; ++x) {
const uint8_t* p = &img[((size_t)y * w + x) * 4];
double c = 0.299 * p[0] + 0.587 * p[1] + 0.114 * p[2];
double l = LumAt(img, w, x - 1, y), r = LumAt(img, w, x + 1, y);
double u = LumAt(img, w, x, y - 1), d = LumAt(img, w, x, y + 1);
double lap = 4.0 * c - l - r - u - d;
sumAbs += std::fabs(lap);
sumSq += lap * lap;
hist[ClampInt((int)(c + 0.5), 0, 255)]++;
n++;
}
}
m.sharp = sumSq / n;
m.hf = sumAbs / n;
m.range = Percentile(hist, n, 0.95) - Percentile(hist, n, 0.05);
return m;
}
标准信号源:合成探针
人眼判断「锐不锐」依赖图像内容,仪表也一样——必须固定输入,输出才有可比性。为此生成一张四象限探针图,每个象限专门暴露一类缺陷:
// 256×256:斜坡 | 方波 | 棋盘 | 径向渐变
void GenProbe(int size, std::vector<uint8_t>* out) {
out->assign((size_t)size * size * 4, 0);
int half = size / 2;
for (int y = 0; y < size; ++y) {
for (int x = 0; x < size; ++x) {
uint8_t* p = &(*out)[((size_t)y * size + x) * 4];
double v;
if (x < half && y < half) {
v = 255.0 * x / (half - 1); // 斜坡:亮度漂移 / 核响应
} else if (x >= half && y < half) {
v = ((x / 8 + y / 8) % 2) ? 235.0 : 20.0; // 方波:振铃 / 过冲
} else if (x < half) {
v = ((x / 4 + y / 4) % 2) ? 200.0 : 55.0; // 棋盘:混叠 / 莫尔
} else {
double dx = x - size + half, dy = y - half;
v = 255.0 * std::sqrt(dx * dx + dy * dy) / half; // 径向:色带 / 低频平坦度
}
p[0] = p[1] = p[2] = (uint8_t)ClampInt((int)(v + 0.5), 0, 255);
p[3] = 255;
}
}
}
四个象限的分工:斜坡应该还原成完美直线——亮度不漂说明权重归一化了;方波的过冲量就是振铃(DOC 配法在这里会现形);棋盘在 4× 时最容易出莫尔,用来标定 KERNEL 推子的安全上限;径向渐变检查色带是否被后级放大。
用仪表读一遍出厂位
把出厂配法(0.50 / 0 / 0.50 / 0,2×,41ms 那张面板录音)放进这套仪器的读数框架:
sharp:拉普拉斯方差量级在数百到千——USM 高频 ×1.5 的直接体现(§3.4 预算表吻合);hf:中高——D 与 G 都在跳过位,没有衰减项;range:与干声几乎一致——V/U 两级不产生新极值以外的反差变化,权重归一保证亮度不漂。
这三个读数同时验证了 §2 的两个实现细节:权重归一(斜坡不弯)与跳过位(hf 无衰减项)。仪表不是装饰,是回归测试的另一张脸——任何一级的实现被改坏,探针象限上必有一处读数偏离。
五、音色设计法:从目标质感逆推四个数
现在四根推子被翻译成了四个数,仪表能读出结果,剩下的问题只有一个:给定目标质感,怎么找到那组推子位置?
1. 搜索预算:为什么不用网格
四个推子的搜索空间粗看是 (21^4 \approx 19.4) 万个组合,每次渲染 41ms——穷举要 2.2 小时。但代价函数对每个推子基本是单调的(锐度随 a 单调升、随 g 单调降),单调函数用坐标下降就够:
Tone DesignToward(const std::vector<uint8_t>& src, int sw, int sh,
int factor, const Metrics& target) {
Tone p{0.5, 0.0, 0.5, 0.0}; // 出厂位起步
const double steps[4] = {0.10, 0.15, 0.10, 0.15};
double best = Cost(Measure(RenderChain(src, sw, sh, factor, p, false)), target);
for (int iter = 0; iter < 6; ++iter) {
for (int axis = 0; axis < 4; ++axis) {
double trial = GetAxis(p, axis);
for (int dir = 0; dir < 2; ++dir) {
Tone q = p;
double v = ClampAxis(axis, GetAxis(q, axis)
+ (dir == 0 ? steps[axis] : -steps[axis]));
SetAxis(&q, axis, v);
double c = Cost(Measure(RenderChain(src, sw, sh, factor, q, false)), target);
if (c < best) { best = c; trial = v; }
}
SetAxis(&p, axis, trial);
}
}
return p; // 推子位置即音色
}
double Cost(const Metrics& m, const Metrics& target) {
double ds = (m.sharp - target.sharp) / std::max(1.0, target.sharp);
double dh = (m.hf - target.hf) / std::max(1.0, target.hf);
double dr = (m.range - target.range) / std::max(1.0, target.range);
return ds * ds + dh * dh + dr * dr; // 归一化平方误差
}
预算对比:6 轮 × 4 轴 × 2 方向 ≈ 52 次渲染 ≈ 2.1 秒,比穷举快约 3700 倍。步长设计成「前几轮大步、最后一轮减半」还能再省几轮。
2. 三颗出厂芯片,其实就是三次搜索结果
面板上的 PORTRAIT / DOC / NIGHT 可以视为这个流程的三份「出厂录音」,用仪表读它们的性格:
| 芯片 | KERNEL | DENOISE | SHARPEN | GLOW | 仪表特征(由 §3.4 预算表推得) |
|---|---|---|---|---|---|
| PORTRAIT 柔焦人像 | 0.35 | 0.25 | 0.55 | 0.40 | 高频 ≈0.8~1.1×,微降;锐度中;反差略升(低频全保留) |
| DOC 锐利文档 | 0.90 | 0.00 | 1.10 | 0.00 | 高频 2.1×,全链最高;方波探针可见振铃,文字场景可接受 |
| NIGHT 夜景降噪 | 0.60 | 0.55 | 0.30 | 0.15 | 高频 ≈0.5~0.6×,腰斩;噪声敏感度最低;反差收窄 |
它们的合理性可以直接用 §3 的公式校验:DOC 要「高频翻倍」,(a=1.10) 精确给出 (1+a=2.1);NIGHT 要「先净化」,湿量 0.55 的中值闸放在 USM 之前,让 0.30 的锐化作用在干净信号上——每颗芯片都是一条可复算的设计决策,不是调出来的玄学。
3. 完整走查:设计一颗新音色「FOG 晨雾」
以一个不属于出厂三颗的目标为例,走一遍流程(设计推演,未上真机):
- 定目标向量:晨雾感 = 高频压到出厂位的一半(
hf ≈ 0.5×)、锐度中等(保留骨架)、反差略收。由 §3.4 预算式反解:取g = 0.65(高频 ×0.545)、d = 0.50(结构选择性净化)、a = 0.25(锐化轻补),预算1.25 × 0.545 × η₀.₅ ≈ 0.5×✓; - 落推子:KERNEL 取 0.20——雾感场景要软过渡,双线性成分占多;
- 仪表复核:
RenderChain + ScoreFrame跑探针与真实图各一遍,确认hf落在目标的 ±10% 内; - 人耳终审:HOLD A/B 干湿对比——雾感与糊的分界线在这一步判定:结构必须还在,只是高光晕开。
最终配法 0.20 / 0.50 / 0.25 / 0.65,四行数字就是一颗可分享的音色——因为引擎无状态,推子位置即全部状态,快照、撤销、分享在架构上天然成立。
六、四段实机录音:把仪表接到真机上
录音一:老城黄昏 · GLOW 满格 · 4×(68ms)


GLOW 推到顶后链路首次全级通电,4× 输出耗时 68ms。干湿对比里能清晰分辨 §2.4 说的那条分界线:湿声的「雾感」保留了石板路的结构、只晕开了霓虹高光;干声的「糊」则是整体低频化——前者是 G(高频衰减、低频保持),后者是根本没放大好。这组对照就是预算表里 (G_{hf}=1-0.7g) 的肉眼版。
录音二:花间翠鸟 · 4×


微距水珠在 4× 下颗粒感明显——这是插值的信息天花板,任何核都无中生有不了不存在的频率。但 KERNEL 推子来回一拨,锐与肉的边界立刻可见:在探针棋盘象限上,这对应「莫尔出现前的最大可用 blend」。推子的价值不是修复天花板,而是让你亲手摸到天花板在哪。
41ms → 68ms:一笔能算的账
两段录音给了一个有趣的预算样本:4× 的像素量是 2× 的 4 倍(180→720,518,400 px),但耗时只从 41ms 涨到 68ms(×1.66)。这说明 41ms 里有相当比例是与像素量无关的固定开销——Buffer 拷贝、NAPI 往返、权重表构建——而 2× 录音的 GLOW 与 DENOISE 都在跳过位,全链只有两级在工作。粗账:
| 录音 | 像素量 | 通电级数 | 耗时 |
|---|---|---|---|
| 2× 出厂位 | 129,600 px | 2 级(V+U) | 41ms |
| 4× GLOW 满格 | 518,400 px | 4 级(V+D+U+G) | 68ms |
要把固定开销和每像素率拆开,靠猜不行,给每一级装计时探针:
#include <chrono>
struct PerfBreak {
double taps, resample, median, usm, glow;
};
PerfBreak Profile(const std::vector<uint8_t>& src, int sw, int sh,
int factor, const Tone& p) {
PerfBreak t{};
auto stamp = [](double* slot, std::chrono::steady_clock::time_point t0) {
auto t1 = std::chrono::steady_clock::now();
*slot = std::chrono::duration<double, std::milli>(t1 - t0).count();
};
auto t0 = std::chrono::steady_clock::now();
BuildTaps(sw * factor, sh * factor, p.kernel, &g_taps);
stamp(&t.taps, t0);
// resample / median / usm / glow 各自同法包夹,逐级累加……
return t;
}
拿到逐级数据后,优化才有靶子:如果 taps 占比高(dstLen 上千时权重表重建变贵),就缓存「同一 (srcLen,dstLen,blend)」的权重表;如果 median 占比高,就把它做成跳过位最先判——测量先于优化,和音色设计是同一个方法论。
七、规格与结语
| 项目 | 规格 |
|---|---|
| 信号链 | V 变形核 → D 中值闸 → U 高频提升 → G 低频注入,全单线程 |
| 解析增益 | (G_{hf}=(1+a)(1-0.7g)\eta_d);D 为非线性,靠探针实测 |
| 实时性 | 2× 41ms / 4×+GLOW 68ms(模拟器单线程) |
| 搜索 | 坐标下降 52 次渲染 ≈ 2.1s,比网格穷举快约 3700 倍 |
| 状态 | 完全无状态纯函数,推子位置即全部音色 |
把一台「好玩的合成器」拆到这一层,最大的收获是:音色不再是手感,而是一条从目标向量到推子位置的、每一步都可复算的通路。顺序不可交换有实验断言,增益有解析预算,新音色有 2 秒的搜索流程,出厂芯片能用同一套仪表逐条校验——「演奏」的乐趣没有被剥夺,只是底下多了一张电路图。
FAQ
Q:为什么不用网格搜索穷举四维参数?
A:(21^4) 组合 × 41ms ≈ 2.2 小时,且代价函数对每个推子基本单调,坐标下降 52 次渲染就能收敛到同等位置。网格只在「确认没有多峰陷阱」时值得做一次——可以离线跑一次细网格画代价热力图,确认单调后,线上就永远用坐标下降。
Q:三个仪表会不会互相打架?
A:会,而且这正是 Cost 用归一化平方和的原因。典型冲突是「要锐但不要颗粒」:sharp 与 hf 同源(都来自拉普拉斯),单靠 U 抬 sharp 必然抬 hf,解法是让 D 先压 hf、U 再补 sharp——这也是现行链序的又一重理由。仪表冲突时,链序就是解法。
Q:SHARPEN 能推到 1.1 以上吗?
A:数学上没有上限,但高频增益 (1+a) 超过约 2.1 后,方波探针的过冲会明显侵蚀可读性(振铃、白边)。DOC 的 1.10 是文字/线稿场景的实用上限;人像类内容建议 ≤0.8。要不要放宽,探针象限说了算,不要凭感觉。
Q:中值那一级没有解析增益,预算表是不是就废了一半?
A:没有废。(\eta_d) 虽是经验值,但它随湿量单调、随内容缓变——固定在探针图上标定一次,就能当常数用。§3.4 的预算式用 (\eta_d) 区间(0.8~0.9 等)给出的预测,与实测的偏差足以指导推子初值,剩余误差交给 HOLD A/B 终审。
Q:4× 的信息天花板怎么办?
A:合成器不解决天花板,只暴露天花板。要真正补高频,得靠多帧或多参考的超分路径——那是另一台机器的事。这台机器的价值是把「哪个推子在动哪段频率」变成可演奏、可测量、可设计的日常操作。
更多推荐


所有评论(0)