图像超分 + 3DGS:低分辨率采集超分再重建

弱光环境下采集,相机自动拉高 ISO、压低分辨率,出来的图噪点多、细节糊。远距离采集也一样,目标占画面比例小,放大了全是马赛克。这种图直接喂给 3DGS 重建,结果就是高斯点堆出一团模糊的形状,纹理和边缘都不可信。一个直觉是先超分把分辨率和细节补回来,再重建。Core Vision Kit 的图像超分(Super Resolution)API 在端侧就能做这件事,但实际跑下来,超分能救一部分场景,也会把另一些场景搞得更糟。

能力面:超分 API 与重建管线

超分接口在 @kit.CoreVisionKit 下,同样是 Beta 标记。输入一张 PixelMap,指定放大倍率,输出一张更大的 PixelMap。

// entry/src/main/ets/superres/SuperResRunner.ets
import { image } from '@kit.ImageKit';
import { superResolution } from '@kit.CoreVisionKit';

export class SuperResRunner {
  /**
   * 对单帧做超分
   * @param input 输入 PixelMap,建议短边 >= 720
   * @param scale 倍率,支持 2 / 3 / 4
   */
  async upscale(input: image.PixelMap, scale: number): Promise<image.PixelMap> {
    const opts: superResolution.SuperResOptions = {
      input: input,
      scale: scale,
      // 模型选择:通用模型,对自然场景效果好,对文字/线条类内容有伪影
      model: superResolution.Model.GENERAL
    };
    return await superResolution.upscale(opts);
  }

  /**
   * 批量超分一组采集帧
   * 注意:超分是内存密集操作,不能并发太多,建议串行或最多 2 并发
   */
  async upscaleBatch(
    frames: image.PixelMap[],
    scale: number
  ): Promise<image.PixelMap[]> {
    const out: image.PixelMap[] = [];
    for (const f of frames) {
      const up = await this.upscale(f, scale);
      out.push(up);
      // 每帧超分完立即释放输入,压峰值内存
      f.release();
    }
    return out;
  }
}

接到重建管线里,流程变成:采集原始帧 → 逐帧超分 → 把超分后的帧序列喂给 Spatial Recon Kit 重建。重建入口还是 spatialRender,区别只是输入帧的分辨率变了。

上面的链路落到代码里就是这样一条流水线,画出来更清楚。

伪影可控

伪影严重

低分辨率采集帧

图像超分 Super Resolution

超分帧质检 伪影判定

送入 3DGS 重建管线

回退 直接重建原始帧

质量对比 SSIM 与重建耗时

图里比初版设计多了一步超分帧质检,它是踩过伪影坑才补上的,后面约束面会展开。

这里有个工程细节:超分后的帧不能直接全塞给重建管线。重建管线对输入帧数有上限(API 26 约 200 帧),超分后单帧体积大,200 帧总数据量可能超重建管线的输入预算。我们的做法是超分前先抽帧,把 150 帧抽到 80-100 帧,再超分,再重建。抽帧策略按运动均匀采样,视角变化大的帧保留。抽帧会损失一点重建质量,但避免了输入超预算的硬错。

另一个细节是超分和重建的内存不能同时峰值。超分跑完一帧后释放输入,但输出要留着给重建用。如果超分 80 帧全跑完再启动重建,80 帧超分输出同时驻内存,峰值爆炸。正确做法是超分一帧喂一帧给重建管线(管线内部缓存),不要自己攒。这要求超分和重建管线之间是流式衔接,不是批处理。我们封装了一个 PipelineStream 做这件事,超分端 push 帧,重建端 pull 帧,中间不囤积。

// entry/src/main/ets/pipeline/SuperResReconPipeline.ets
import { SuperResRunner } from '../superres/SuperResRunner';
import { reconSession } from '@kit.SpatialReconKit';

export class SuperResReconPipeline {
  private superRes: SuperResRunner = new SuperResRunner();

  async run(
    rawFrames: image.PixelMap[],
    scale: number
  ): Promise<reconSession.ReconResult> {
    // 1. 超分
    const t0 = Date.now();
    const upscaled = await this.superRes.upscaleBatch(rawFrames, scale);
    const superResMs = Date.now() - t0;
    console.info(`superres total: ${superResMs}ms, per frame: ${superResMs / rawFrames.length}ms`);

    // 2. 重建
    const t1 = Date.now();
    const session = await reconSession.create({
      frames: upscaled,
      // 重建质量档位,超分后可以用 HIGH
      quality: reconSession.Quality.HIGH
    });
    const result = await session.run();
    console.info(`recon cost: ${Date.now() - t1}ms`);

    // 3. 释放超分中间帧
    for (const f of upscaled) f.release();
    return result;
  }
}

约束面:内存、耗时、伪影

超分的内存开销是这套管线最大的约束。1080×1440 RGB 输入做 4 倍超分,输出 2048×2048 位图约 67 MB——这是单帧。采集一段 5 秒、30fps 的视频有 150 帧,全超分完中间结果就是 10 GB 量级,不可能全留内存。所以必须串行处理,每帧超分完立即喂给重建管线或落盘,不能攒着。

耗时是第二个约束。单帧 1080×1440 → 4 倍超分,9030 上 280-340 ms,150 帧串行下来 40-50 秒。重建本身 8-15 秒。超分比重建还慢,这个事实一开始挺出乎意料。我本来以为超分是轻量操作,毕竟只是放大一张图,结果它跑的模型比想象的重。如果采集帧数多,超分这一步会成为管线瓶颈。

伪影是第三个约束,也是最隐蔽的。超分模型是生成式的,它"脑补"细节。对自然纹理(草地、布料、墙面)脑补得还行,对规则结构(文字、网格、棋盘格)会脑补出错误的内容。3DGS 重建不知道哪些细节是真的、哪些是超分脑补的,全当真实细节去拟合高斯点。结果就是重建出来的模型在那些区域有"看起来很清晰但实际是错的"细节,比模糊还难处理——模糊至少一眼能看出来,伪影看着真实但不对。

伪影问题我们踩过一个大坑。拍一个带品牌 logo 的包装盒,logo 是字母加图形组合。超分后字母边缘出现锯齿状伪影,看着像浮雕纹理。3DGS 重建把这些伪影当真实表面细节拟合了一组高斯点,重建结果在 logo 区域有明显的凹凸感。用户转着看会觉得包装盒做工有问题,实际是超分脑补的。这个坑发现得晚,因为单看超分后的图,伪影不明显;只有重建后转视角才能看出来。后来加了一道工序:超分后的图和原始图做差异图,差异大的区域标记为"伪影风险区",重建时降低这些区域高斯点的权重。这个补救措施有效但不够彻底,最根本的解法还是超分前就规避规则结构区域。

场景落地:低分辨率采集对比

我们拍了一个桌面摆件(小陶瓷猫),分三种采集条件,每种都跑"直接重建"和"超分 2 倍再重建"两条路径,对比 SSIM 和重建耗时。

采集条件输入分辨率路径重建耗时SSIM(vs 高分辨率参考)
正常光照1920×1080直接重建9.2 s0.91
正常光照1920×1080超分 2x 再重建14.8 s0.90
弱光1280×720直接重建7.1 s0.74
弱光1280×720超分 2x 再重建12.6 s0.82
远距960×540直接重建5.8 s0.68
远距960×540超分 3x 再重建18.4 s0.79

几个结论。正常光照下超分反而轻微掉 SSIM,从 0.91 到 0.90。原因是超分引入的伪影在小幅度上抵消了分辨率提升的收益。所以正常光照采集不要超分,直接重建就行,省时间质量还更好。

弱光和远距场景超分有正收益,SSIM 提升 8-11 个百分点。但代价是重建耗时增加 60-100%。这个取舍看场景:如果重建质量是硬指标(文物存档、商品展示),超分值得;如果追求速度(实时预览、快速建模),直接重建更快。

弱光场景里还有个细分:弱光但噪点少的图(相机降噪算法好的)超分收益小,SSIM 提升 3-5 个百分点;弱光且噪点多的图超分收益大,SSIM 提升 10-15 个百分点。原因是超分模型对噪点图有隐式降噪效果,补的细节里顺带把噪点压了。所以弱光场景超分值不值,还得看原图噪点程度。我们用图像方差粗略估算噪点水平,方差大的才超分,方差小的直接重建,省时间。

远距场景的超分收益和弱光不一样。远距图通常噪点不多(白天远拍),超分主要补的是细节分辨率。这种场景超分伪影风险更高,因为远距图里的小细节(文字、纹理)超分模型更容易脑补错。我们的做法是远距场景超分倍率保守一点,用 2 倍不用 3 倍,2 倍脑补幅度小伪影少。质量提升从 11 个百分点降到 7 个百分点,但伪影投诉少了。这个取舍是产品定的的,技术侧给数据让产品拍板。

真机数据

测试设备:麒麟 9030、麒麟 9020、6GB 内存中端机。输入 1080×1440 RGB 帧。

指标90309020中端机(6GB)
2x 超分单帧耗时95-130 ms120-170 ms280-410 ms
3x 超分单帧耗时180-240 ms230-310 msOOM 风险
4x 超分单帧耗时280-340 ms350-440 msOOM
2x 超分内存峰值38 MB41 MB44 MB
4x 超分内存峰值67 MB69 MBOOM
150 帧串行 2x 总耗时14-19 s18-25 s42-61 s

中端机 4 倍超分直接 OOM,3 倍也有风险。4 倍超分只推荐在 8GB 以上内存的旗舰机用,且串行处理不能并发。中端机最多 2 倍,且要监控内存,接近上限就降级到直接重建。

9030 和 9020 的差距比想象中小。超分这个操作主要吃 GPU 算力,两代芯片 GPU 提升没那么大。这个结论只在我们测过的这两台机器上成立,9030 Pro 没测,不敢外推。

踩坑与取舍

坑一:超分伪影被当真实细节重建。 拍一个带文字的包装盒,超分后文字边缘出现锯齿状伪影,重建出来包装盒上那块区域有规律的纹理瑕疵。3DGS 拟合得很开心,因为伪影是高频细节,高斯点最擅长拟合高频。后来我们的做法是:超分前先检测画面里有没有文字/规则网格区域,有的话对这些区域不做超分或降级超分强度。这个检测用 Core Vision Kit 的目标检测就能做,但会让管线更复杂。

坑二:超分耗时比重建还长。 150 帧 4 倍超分 45 秒,重建 12 秒,超分占整个管线 80% 时间。如果用户在等结果,这个体感很差。我们试过两条缓解路径:一是减少超分帧数,150 帧里抽 60 帧超分,其余用原始分辨率,重建质量掉 3 个百分点但耗时压到 18 秒;二是降倍率到 2x,耗时压到 15 秒,质量掉 5 个百分点。最后选了抽帧方案,因为质量损失更小。

坑三:超分后帧体积变大,重建内存也跟着涨。 4 倍超分后每帧 67 MB,重建管线同时持有几十帧,峰值内存比直接重建高 2-3 倍。9030 上直接重建峰值 1.2 GB,超分再重建峰值 3.4 GB,逼近系统对单应用的内存上限。超分再重建这条路在 8GB 机器上要谨慎,6GB 机器基本不可行。

坑四:超分后帧的位深和重建管线不匹配。 超分输出默认 RGBA 8 位色深,重建管线某些质量档位要求 16 位色深输入。直接喂会触发重建管线内部做位深转换,多一道开销且转换过程有精度损失。我们的做法是超分时显式指定输出格式为 8 位 RGBA,重建管线用兼容 8 位的中档质量,避免转换。如果要用高档质量重建,得接受转换开销或找别的提分辨率方案。

被放弃的方案: 试过先重建再对重建结果做超分(把超分当后处理)。逻辑上更省事,因为只超分一次输出而不是 150 帧输入。但 3DGS 重建结果是高斯点云不是图像,超分 API 接的是 PixelMap,对不上。要把点云渲染成图再超分再重建回去,绕一圈反而更慢更复杂。放弃。

还试过只对关键帧超分、非关键帧保持原分辨率的混合策略。关键帧指采集视频里信息量大的帧(运动剧烈、视角变化大)。做法是先跑一遍关键帧检测,只对关键帧超分,非关键帧直接喂重建。理论上能省超分耗时。实测下来重建质量比全帧超分掉 4-6 个百分点,比全帧不超分只高 2 个百分点。投入产出比不好,关键帧检测本身也要耗时。放弃,要么全帧超分要么不超分,中间态不值。

总结一下下

  • 超分接口带 Beta 标记,封装隔离层
  • 单帧超分必须串行,禁止并发,输入帧用完立即 release()
  • 4 倍超分仅限 8GB+ 旗舰机,6GB 机器最多 2 倍且要监控内存
  • 正常光照采集不超分,直接重建质量更好且省时间
  • 超分前检测文字/规则网格区域,这些区域不做超分避免伪影
  • 超分耗时占比高时考虑抽帧策略,不必每帧都超分
  • 超分和重建之间流式衔接,不要批处理囤积中间帧
  • 弱光场景先看原图噪点水平,噪点少的图超分收益小不值得

抽帧策略的具体实现:按光流法计算相邻帧的运动量,运动量大的帧标记为关键帧保留,运动量小的帧丢弃。150 帧抽到 80 帧左右,重建质量损失 3-5 个百分点,但超分耗时减半。抽帧比例可以根据设备算力动态调——旗舰机少抽(120 帧),中端机多抽(60 帧),让超分耗时和设备能力匹配。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐