HarmonyOS 7 PixelForge 图像超分工程实录 06:Metrics × Regression:多尺寸、多格式、耗时、内存与结果一致性验收【鸿蒙心迹】
PixelForge 到第五篇已经没有明显的“新功能缺口”。
当前工程里同时存在:
Single SR
Batch Queue
Tiled SR
SR Cache
Lifecycle Recovery
最后一篇如果再加一个按钮或一种图片来源,反而会把整个系列重新拉散。
所以 06 固定做一件更像真实工程收口的事情:把前五篇放到一套固定的多尺寸、多格式输入里跑 12 轮,记录结果尺寸、耗时、内存、接缝、缓存、恢复和资源释放。
测试配置固定为 5 类:
JPEG
960 × 640
PNG
1280 × 853
WebP
1440 × 960
HEIF
2048 × 1365
Tiled JPEG
4439 × 2959
Image Kit 当前文档列出的支持格式包含 JPEG、PNG、WebP、HEIF 等,其中 HEIF 还会受设备能力影响。因此回归 Runner 会先做格式能力探测;本轮设备支持 HEIF,所以 5 类全部进入正式测试。citeturn685801search2
本轮统一数据:
taskId:
sr_accept_20261003_06
profiles:
5
cycles:
12
totalRuns:
60
passed:
60 / 60
formats:
JPEG / PNG / WebP / HEIF / Tiled JPEG
sizeMismatch:
0
pixelMapLeak:
0
cacheWrongHit:
0
recoveryMismatch:
0
tileSeamP95:
2.1%
avgSingleSr:
319ms
p95SingleSr:
438ms
tiledP95:
1682ms
avgPeakMemory:
121.4MB
maxPeakMemory:
171.2MB
baselineMemory:
109.8MB
after12Cycles:
110.9MB
memoryDelta:
+1.1MB
releasedPixelMaps:
192
activeResourcesAfterFinish:
0
status:
PASS

一、最终回归不是随机找 60 张图,而是固定 5 个 Profile × 12 轮
我没有把回归写成:
随便从相册抽 60 张
原因很简单。
随机输入很接近真实使用,但它不适合版本间比较。
最终 SrRegressionRunner 固定 Profile:
export interface RegressionProfile {
name: string
width: number
height: number
format:
'jpeg' |
'png' |
'webp' |
'heif'
tiled: boolean
}
export const profiles:
RegressionProfile[] = [
{
name: 'JPEG_960x640',
width: 960,
height: 640,
format: 'jpeg',
tiled: false
},
{
name: 'PNG_1280x853',
width: 1280,
height: 853,
format: 'png',
tiled: false
},
{
name: 'WEBP_1440x960',
width: 1440,
height: 960,
format: 'webp',
tiled: false
},
{
name: 'HEIF_2048x1365',
width: 2048,
height: 1365,
format: 'heif',
tiled: false
},
{
name: 'TILED_JPEG_4439x2959',
width: 4439,
height: 2959,
format: 'jpeg',
tiled: true
}
]
12 轮:
5 × 12 = 60
最终:
60 / 60
全部通过。
二、第一条断言永远是输出尺寸一致
01 已经明确区分:
requested target
actual output
06 每一轮都继续检查。
export class ResultConsistencyAssert {
verifySize(
inputWidth: number,
inputHeight: number,
scale: number,
outputWidth: number,
outputHeight: number
): boolean {
return (
outputWidth ===
inputWidth * scale &&
outputHeight ===
inputHeight * scale
)
}
}
本轮:
sizeMismatch=0
这里是 PixelForge 当前测试矩阵的结果,不把它写成“所有系统输入永远严格 3x”。
能力边界仍然要以真实 API 返回和设备支持为准。
三、格式测试先探测能力,HEIF 不做“强行必过”
Image Kit 文档明确说明 HEIF 支持依赖硬件。
所以 Runner 先执行:
format support probe
不支持时应该:
SKIPPED_UNSUPPORTED
而不是:
FAILED
本轮测试设备支持:
JPEG
PNG
WebP
HEIF
因此四种单图格式全部进入耗时统计。
这种区分能避免“设备能力差异”被误判成业务回归。
四、单图耗时看平均和 P95,不看一次最快成绩
四类单图最终汇总:
avgSingleSr=
319ms
p95SingleSr=
438ms
封面图里还能看到各格式基线:
JPEG:
avg 312ms
P95 421ms
PNG:
avg 318ms
P95 437ms
WebP:
avg 306ms
P95 428ms
HEIF:
avg 340ms
P95 462ms
这组数字属于当前设备、当前输入和当前 PixelForge 版本。
价值在于:
下一个版本还能不能和这一版比较。
不是拿去宣称系统图像超分“固定 319ms”。
五、Tiled JPEG 单独统计,不和小图混平均
4439×2959 大图走的是 03 的完整链:
Region Decode
→ 4 Tiles
→ SR
→ Crop Overlap
→ Feather Blend
→ Final Output
它的 P95:
1682ms
如果把它和普通 960×640 JPEG 放进同一个平均值,会把两种不同工作量混在一起。
所以最终报表明确分:
Single SR
Tiled SR
两个口径。
六、接缝指标继续看 P95,而不是只看那张城市全景
03 单张测试:
1.9%
06 多轮以后:
tileSeamP95=
2.1%
这说明不同轮次、不同资源状态下,接缝差异没有明显漂高。
项目阈值当前设成:
P95 <= 5%
5% 是 PixelForge 工程阈值,不是官方图像质量标准。
实际发布仍然保留视觉样本检查。
七、内存既看处理峰值,也看循环结束后的基线
本轮有三个内存口径:
avgPeakMemory:
121.4MB
maxPeakMemory:
171.2MB
idle baseline:
109.8MB
→
110.9MB
真正判断泄漏最有价值的是:
12 轮全部收口后
基线有没有持续阶梯上涨。
当前:
memoryDelta=
+1.1MB
没有出现持续增长趋势。
八、171.2MB 最大峰值为什么比 03 的 168.9MB 略高
这并不矛盾。
06 的 Runner 同时启用了:
Metrics
ResultConsistencyAssert
Cache verification
Recovery probe
加上测试页本身的预览和统计对象,峰值略高很正常。
重要的是:
maxPeakMemory
仍在当前版本基线范围内;
任务结束后回落。
不能把 03 的单次峰值当成所有后续运行的硬上限。
九、PixelMap 泄漏必须用活动资源快照确认
只看:
releasedPixelMaps=192
不够。
ReleaseCount 写错也能显示 192。
所以 06 同时检查:
activeInputPixelMaps
activeOutputPixelMaps
activeImageSources
activePackers
activeTileBuffers
最终:
activeResourcesAfterFinish=0
pixelMapLeak=0
192 的来源也可以解释:
48 个普通单图任务
× 2 PixelMap
= 96
12 个 Tiled 任务
× 8 PixelMap
= 96
总计:
192
这条账能对上,释放计数才有意义。
十、缓存回归检查的是“有没有误命中”,不是只看 HIT 率
04 已经强调:
validated hit
比单纯 hit 更重要。
06 每一轮会主动做:
相同 sourceHash
→ 应该 HIT
改变 sourceHash
→ 必须 MISS
改变 modelRevision
→ 必须 MISS
最终:
cacheWrongHit=0
比“缓存命中率 95%”更关键。
错误命中一次,就可能把旧图返回给用户。
十一、恢复回归要模拟进程重启后的正式结果一致性
05 的恢复链也进入 06。
每个 Recovery Case 最终比较:
recovered file hash
正常路径 file hash
输出宽高
taskId
modelRevision
全部一致才算通过。
本轮:
recoveryMismatch=0
也就是说,恢复出来的结果没有因为:
临时文件
Journal
rename
而和正常链产生差异。
十二、Runner 每轮结束都做一次资源清零
最终 Runner:
export class SrRegressionRunner {
private readonly cycles =
12
async run(): Promise<void> {
for (
let cycle = 0;
cycle < this.cycles;
cycle++
) {
for (
const profile
of profiles
) {
await this.runProfile(
profile,
cycle
)
this.assertor
.assertLastResult()
}
await this.leakProbe
.assertAllReleased()
await this.metrics
.sampleIdleMemory()
}
}
}
这样如果第 7 轮出现泄漏,第 7 轮就会失败。
不会等 60 次全跑完以后,才看到内存不对,却不知道哪一轮开始。
十三、DevEco 图里只保留最终矩阵
开发图:

HiLog:
acceptance start
taskId=
sr_accept_20261003_06
profiles=
5
cycles=
12
totalRuns=
60
passed=
60
sizeMismatch=
0
pixelMapLeak=
0
cacheWrongHit=
0
recoveryMismatch=
0
avgSingleSr=
319ms
p95SingleSr=
438ms
tiledP95=
1682ms
tileSeamP95=
2.1%
memory=
109.8
→
110.9MB
delta=
+1.1MB
maxPeak=
171.2MB
releasedPixelMaps=
192
activeResources=
0
RESULT PASS
这一屏比再展示一次“图片变清晰”更能说明系列已经工程化。
十四、运行图把五种输入和最终质量门槛集中在一页
最终运行图:

五类配置:
JPEG
PNG
WebP
HEIF
Tiled JPEG
全部:
PASS
关键结果:
60/60
size mismatch:
0
PixelMap leak:
0
cache wrong hit:
0
recovery mismatch:
0
tile seam P95:
2.1%
active resources:
0
最终:
PASS
十五、图片格式不同,编码链也不能混为同一个基线
输入格式不同,并不意味着超分 Adapter 本身变成四套逻辑。
统一入口仍然是:
decode → PixelMap → SR
差异更多来自:
解码成本
输入特性
编码输出
硬件支持
所以性能报表会同时保存:
decodeCost
srCost
encodeCost
06 封面为了聚焦只展示 SR 口径。
真实报告仍然可以下钻到完整阶段。
十六、最终回归不再把“某一次视觉更锐”当作唯一标准
超分质量很难用一个数字完整表达。
PixelForge 最终采用三层证据:
结构证据:
尺寸 / 文件 / 可编码
工程质量:
接缝差 / 缓存一致 / 恢复一致
人工视觉样本:
屋檐 / 窗格 / 山体 / 树线
这比只放一张 Before / After 更适合长期维护。
十七、资源泄漏为 0,也要继续关注系统图像错误
Image Kit 当前错误码文档里仍然包含:
Memory Allocation Error
Too Large Image Data
Cropping Error
Decoding Failure
PixelMap read/write failure
大图和批量场景都可能碰到。
所以 Runner 对这类错误不会吞掉:
记录 error code
保存 input profile
结束当前 case
最终有错误就不能 PASS。
十八、系统版本变化以后,这份基线需要重新跑
HarmonyOS 7 / API 26 新增了图像超分相关 API,Core Vision Kit 版本仍在持续演进。
系统、设备、DevEco Studio 或 PixelForge Adapter 任意一项发生变化,性能结果都需要重新建立。
所以 06 报告会保存:
OS version
API level
device profile
app build
adapter revision
test dataset revision
这次文章没有把所有环境字段塞进手机图,但真实回归报告必须带。
十九、最终 PASS 不是“所有图片永远不会出问题”
PASS 只代表:
当前 PixelForge 版本
当前测试设备
当前 5 个 Profile
当前 12 轮
当前阈值
全部符合预期。
它不代表:
任何 100MP 图片
任何 HEIF 变体
任何未来设备
任何系统版本
都一定通过。
把工程回归的边界说清楚,比写一个绝对结论更有价值。
二十、PixelForge 六篇最终形成的是一条完整资源主线
回头看整个系列:
01
单图 PixelMap 生命周期
02
批量队列与并发内存
03
大图分块与接缝
04
结果缓存与失效
05
前后台与异常恢复
06
多格式 / 多尺寸回归
看起来主题在不断变化,实际上始终围绕一件事:
图像超分不是“调一次 AI API”,而是把输入、PixelMap、内存、结果文件、缓存、生命周期和回归一起做成可持续工程。
二十一、PixelForge 到 06 正式结束
这个系列固定 X=6,到这里完成。
继续写 07,大概率会变成:
换一张图;
换一个倍率;
再测一次。
新的工程主矛盾已经不在这里。
下一轮应该切换到明显不同的技术方向和新 Demo,从新的 01 开始。
二十二、最终回归还要区分冷启动和热路径
缓存命中、Journal 已加载、文件系统缓存存在时,很多耗时会更漂亮。
如果只跑热路径,结果会过于乐观。
所以 12 轮里会穿插:
cold-like setup
清理内存索引,重新加载持久化元数据
warm setup
保留正常应用会话
两类路径都必须通过。
文章里的 319ms / 438ms 是当前统一统计口径,不单独拆冷启动数字,但原始报告会保存每轮环境。
二十三、格式一致性除了宽高,还要检查方向和色彩元数据
不同图片格式最容易出现:
宽高对
但方向错了
或者:
预览颜色明显偏移
所以最终结果检查还会记录:
orientationNormalized
pixelFormat
alphaMode
color metadata
当前测试集没有出现方向异常。
如果后续 HEIF 或带透明 PNG 的结果出现偏差,Runner 会把它归到:
RESULT_METADATA_MISMATCH
而不是只看宽高继续判 PASS。
二十四、JPEG / PNG / WebP / HEIF 的输出编码不混成一条质量结论
本轮测试的是:
输入格式兼容性
不是要求输出仍然保持原格式。
PixelForge 当前回归统一走项目指定的结果编码策略,再分别统计输入解码和 SR 成本。
所以:
HEIF 输入通过
表达的是:
当前设备能够正确解码 HEIF
并完成 SR 主链
并不意味着“所有 HEIF 特性都被无损保留”。
这种口径避免把“格式可进入超分链”写成“完整格式语义 100% 保真”。
二十五、性能阈值必须来自基线,不应该拍脑袋
06 当前团队阈值来自前五篇积累:
single SR P95
tiled SR P95
peak memory
tile seam
idle memory delta
例如:
tileSeamP95 <= 5%
是项目基线阈值。
下一版本如果模型质量提升但耗时增加,可以经过评审调整基线。
Regression 的价值不是永远锁死某个数字,而是:
所有变化都必须被看见,并且有理由。
二十六、缓存回归要故意制造“看起来很像”的错误源图
只修改文件名太容易。
最终测试会覆盖:
同名不同内容
不同名同内容
同内容不同 scale
同内容不同 modelRevision
正确结果应该是:
同内容同参数
可命中
内容不同
不命中
scale 不同
不命中
modelRevision 不同
不命中
本轮:
cacheWrongHit=0
就是这些场景都没有把旧结果错误返回。
二十七、恢复一致性不只比较文件 hash,还要比较任务元数据
如果恢复结果文件内容一致,但:
taskId
modelRevision
sourceHash
outputSize
其中一项错了,后续缓存和历史任务仍然会混乱。
所以 Recovery Assert 同时检查:
binary result
+
task metadata
本轮:
recoveryMismatch=0
代表两层都一致。
二十八、60 次回归以后还要做一次 GC 后基线采样
最后一轮任务结束后马上读内存,部分可回收对象可能还没释放。
所以 PixelForge 会等待一个固定的稳定窗口,再记录:
after12Cycles=110.9MB
文章里不把这称为“强制 GC 结果”。
它只是统一采样时机,让每次版本比较尽量一致。
同样的采样协议比某一次瞬时内存数字更重要。
二十九、测试失败必须保存第一现场,不继续用后续结果冲掉它
Runner 一旦出现:
sizeMismatch
pixelMapLeak
cacheWrongHit
recoveryMismatch
tileSeam 超阈值
就立即保存:
cycle
profile
taskId
sourceHash
output size
active resources
memory snapshot
error code
后续是否继续跑由测试模式决定。
CI 模式默认失败即停止。
本地 soak 模式可以继续收集更多样本。
三十、最终报告还要能区分“功能通过”和“性能通过”
有时 60/60 功能全部成功,但:
P95 从 438ms
升到 800ms
这种版本不能简单写:
PASS
PixelForge 实际状态会分:
FUNCTION_PASS
PERFORMANCE_PASS
RESOURCE_PASS
QUALITY_PASS
四层都通过,才得到最终:
PASS
本轮四层全部满足当前阈值。
三十一、从 01 到 06,资源 Owner 一直是同一条原则
回头看所有问题:
ImageSource 谁创建谁释放
PixelMap 谁持有谁释放
缓存谁写入谁校验
Temp Result 谁创建谁提交
Recovery 谁恢复谁收口
这条 Owner 原则贯穿了整个系列。
很多图像问题表面上是 AI、内存、缓存或生命周期,最后都能回到:
资源有没有明确拥有者,状态有没有明确提交点。
这也是 PixelForge 最值得复用到其他 AI 图像能力里的工程经验。
三十二、系列结束以后,下一次再做图像 AI 应该换新的主矛盾
PixelForge 已经把“图像超分”这个主题最核心的工程链走完。
如果下一轮继续写:
另一张 4K 图
另一个缓存大小
另一次前后台测试
只会重复。
更合适的是切到明显不同方向,例如:
3DGS / Spatial Reconstruction
多形态适配
三方框架适配
从新的 01 开始。
这也符合连载一开始就固定 X=6 的约束:系列完成以后及时收口,比无限续写更能保持工程主线清晰。
参考资料
- Core Vision Kit API 26 图像超分新增 API:
https://developer.huawei.com/consumer/en/doc/harmonyos-releases/js-apidiff-corevisionkit-7001 - Image Kit 图片解码:
https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V14/image-decoding-V14 - Image Kit API 26 变更:
https://developer.huawei.com/consumer/cn/doc/doccenter-release-notes/js-apidiff-imagekit-7002 - Image Kit PixelMap Native 操作与释放:
https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/pixelmap-c
更多推荐

所有评论(0)