【共创稿事节】从2D平铺到3D沉浸:我用HarmonyOS 7端侧AI做了一个全程数据不出设备的空间化私密相册
本文参与 HarmonyOS 「共创季·征文稿事节」第2期,聚焦HarmonyOS 7(API 26)的「空间 × AI 新视界」主题。
#鸿蒙 #HarmonyOS 7(API 26) #图像超分 #文搜图 #空间计算 #端侧AI
一、写在前面:为什么要做这样一个相册?
相信很多人都有过这样的体验:手机里存了几万张照片,想找一张特定的图,翻来翻去翻不到;老照片因为年代久远分辨率太低,想分享给朋友又拿不出手;把照片传到云端相册吧,又担心隐私泄露——毕竟谁也不想自己的生活照出现在某个训练数据集里。
在体验HarmonyOS 7(API 26)的新特性时,我突然意识到:系统级的端侧AI能力 + 空间计算,不就是解决这些痛点的最佳组合吗?
- 图像超分:端侧4倍高清放大,数据全程不出设备,老照片一键变清晰
- 文搜图:自然语言检索本地图片,"去年夏天海边的合影"一句话就能找到
- 空间计算:从2D平铺升级为3D分层沉浸浏览,看相册不再是刷列表,而是"走"进回忆里
于是我花了两周时间,基于鸿蒙云手机 + DevEco Studio 7.0 模拟器,从零做了一个全程数据不出设备的空间化私密相册应用,今天把完整的技术实现过程分享给大家。
二、整体架构设计
先上架构图,整个应用分为三层:
| 层级 | 核心能力 | 对应鸿蒙能力 |
|---|---|---|
| 展示层 | 3D空间化时间线、沉浸光感卡片 | ArkUI声明式空间界面、ArkGraphics 3D |
| 能力层 | 图像超分修复、自然语言搜图 | Core Vision Kit(API 26新增) |
| 数据层 | 本地图片管理、特征索引管理 | 文件系统 + 端侧向量数据库 |
核心设计原则只有一条:所有计算全部在端侧完成,应用不申请任何联网权限。
这也是为什么我把它叫做"私密相册"——你的照片从导入到处理到浏览,永远不会离开你的设备。
三、核心功能一:端侧图像超分实战
3.1 能力介绍
Core Vision Kit 在 API 26 中新增的图像超分能力,支持端侧 4 倍高清放大。和传统的云端超分方案相比,最大的优势就是数据不出设备 + 低延迟。
官方文档说支持 NPU 硬件加速,我实测下来确实比纯 CPU 推理快了 5 倍以上。
3.2 接入代码
先看依赖引入和基础封装:
// 导入Core Vision Kit图像超分模块
import { VisionSuperResolution, VisionErrorCode } from '@kit.VisionKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class SuperResolutionManager {
private static instance: SuperResolutionManager;
private engine: VisionSuperResolution.SuperResolutionEngine | null = null;
private constructor() {}
static getInstance(): SuperResolutionManager {
if (!SuperResolutionManager.instance) {
SuperResolutionManager.instance = new SuperResolutionManager();
}
return SuperResolutionManager.instance;
}
// 初始化超分引擎
async initEngine(): Promise<void> {
try {
this.engine = VisionSuperResolution.createEngine({
scale: VisionSuperResolution.ScaleMode.SCALE_4X, // 4倍超分
enableNPUAcceleration: true, // 开启NPU硬件加速
quality: VisionSuperResolution.QualityMode.QUALITY_HIGH // 高质量模式
});
console.info('[SR] 超分引擎初始化成功,NPU加速已开启');
} catch (err) {
const error = err as BusinessError;
console.error(`[SR] 超分引擎初始化失败: code=${error.code}, msg=${error.message}`);
throw error;
}
}
// 执行单张图片超分
async processImage(inputPixelMap: image.PixelMap): Promise<image.PixelMap> {
if (!this.engine) {
throw new Error('超分引擎未初始化');
}
try {
const result = await this.engine.process(inputPixelMap);
const outputPixelMap = result.getOutputPixelMap();
console.info(`[SR] 超分完成: 输出尺寸 ${outputPixelMap.width} x ${outputPixelMap.height}`);
return outputPixelMap;
} catch (err) {
const error = err as BusinessError;
if (error.code === VisionErrorCode.ERROR_IMAGE_FORMAT_UNSUPPORTED) {
console.error('[SR] 不支持的图片格式');
}
throw error;
}
}
// 释放引擎资源
release(): void {
if (this.engine) {
this.engine.release();
this.engine = null;
console.info('[SR] 超分引擎已释放');
}
}
}
3.3 批量处理的优化技巧
刚开始做批量修复的时候踩了个坑:同时丢10张图进去并行处理,NPU直接满载,手机发烫还卡顿。
后来做了两层优化:
第一层:NPU任务队列串行化
// 用队列控制并发数,避免NPU算力抢占
private taskQueue: Array<{
pixelMap: image.PixelMap;
resolve: (result: image.PixelMap) => void;
reject: (err: Error) => void;
}> = [];
private isProcessing: boolean = false;
async processImageQueue(inputPixelMap: image.PixelMap): Promise<image.PixelMap> {
return new Promise((resolve, reject) => {
this.taskQueue.push({ pixelMap: inputPixelMap, resolve, reject });
this.processNextTask();
});
}
private async processNextTask(): Promise<void> {
if (this.isProcessing || this.taskQueue.length === 0) return;
this.isProcessing = true;
const task = this.taskQueue.shift()!;
try {
const result = await this.processImage(task.pixelMap);
task.resolve(result);
} catch (err) {
task.reject(err as Error);
} finally {
this.isProcessing = false;
// 处理下一张
setTimeout(() => this.processNextTask(), 50); // 加50ms间隔,给NPU喘息时间
}
}
第二层:智能跳过不需要超分的图片
不是所有图片都需要4倍超分——本身已经是4K以上分辨率的图片,超分的收益很低,反而浪费算力。我加了一个前置判断:
// 判断是否需要超分
shouldProcessSuperResolution(width: number, height: number): boolean {
// 长边超过2560px的图片不做超分,收益太低
const longSide = Math.max(width, height);
return longSide < 2560;
}
3.4 实测效果
我在鸿蒙云手机上跑了一组测试数据,结果如下:
| 输入尺寸 | 输出尺寸 | 模式 | 平均耗时 |
|---|---|---|---|
| 1080 x 720 | 4320 x 2880 | NPU加速 | 0.78秒 |
| 1080 x 720 | 4320 x 2880 | 纯CPU | 4.2秒 |
| 1920 x 1080 | 7680 x 4320 | NPU加速 | 1.65秒 |
| 640 x 480(老照片) | 2560 x 1920 | NPU加速 | 0.32秒 |
NPU加速带来的性能提升非常明显,比纯CPU快了5倍以上。 对于老照片修复这种场景,几乎是"秒出结果"。
四、核心功能二:文搜图——用自然语言找本地图片
4.1 能力介绍
文搜图是 Core Vision Kit 在 API 26 中新增的另一项重磅能力。简单说就是:你说一句话,它帮你在本地相册里找到对应的图片。
最关键的是——整个检索过程完全在端侧完成,图片不需要上传到云端,也不需要联网。这一点对于隐私敏感的用户来说太重要了。
4.2 接入代码
import { VisionTextToImageSearch, VisionErrorCode } from '@kit.VisionKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class TextImageSearchManager {
private static instance: TextImageSearchManager;
private engine: VisionTextToImageSearch.TextImageSearchEngine | null = null;
private constructor() {}
static getInstance(): TextImageSearchManager {
if (!TextImageSearchManager.instance) {
TextImageSearchManager.instance = new TextImageSearchManager();
}
return TextImageSearchManager.instance;
}
// 初始化文搜图引擎
async initEngine(libraryPath: string): Promise<void> {
try {
this.engine = VisionTextToImageSearch.createEngine({
localImageLibraryPath: libraryPath,
enableIndexAutoUpdate: true, // 自动同步本地新增图片的特征索引
indexBuildPriority: VisionTextToImageSearch.Priority.PRIORITY_LOW // 后台低优先级建索引
});
// 监听索引构建进度
this.engine.on('indexProgress', (progress: number) => {
console.info(`[TIS] 索引构建进度: ${progress.toFixed(1)}%`);
});
console.info('[TIS] 文搜图引擎初始化成功');
} catch (err) {
const error = err as BusinessError;
console.error(`[TIS] 文搜图引擎初始化失败: code=${error.code}, msg=${error.message}`);
throw error;
}
}
// 用自然语言搜索图片
async searchByText(queryText: string, topK: number = 20): Promise<SearchResult[]> {
if (!this.engine) {
throw new Error('文搜图引擎未初始化');
}
try {
const results = await this.engine.search(queryText, { topK });
console.info(`[TIS] 搜索完成: "${queryText}", 命中 ${results.length} 张`);
return results.map(item => ({
imagePath: item.imagePath,
score: item.similarityScore,
thumbnail: null as image.PixelMap | null
}));
} catch (err) {
const error = err as BusinessError;
console.error(`[TIS] 搜索失败: code=${error.code}`);
throw error;
}
}
}
interface SearchResult {
imagePath: string;
score: number; // 相似度得分 0-1
thumbnail: image.PixelMap | null;
}
4.3 踩坑记录
坑一:首次索引构建时间
本地有一万张图片的话,首次全量构建特征索引需要一定时间。我的做法是:
- 应用启动时不在前台等索引建好,先让用户能用普通方式浏览
- 索引在后台低优先级线程慢慢建
- 建完后给用户一个提示"文搜图功能已就绪"
坑二:中文语义理解的边界
文搜图对具象描述的效果非常好,比如"海边日落"“猫”“绿色的树"基本一搜一个准。但对抽象描述(比如"开心的照片”“悲伤的氛围”)效果就一般了。
所以我在UI上做了引导,告诉用户什么样的搜索词效果最好。
4.4 实测效果
我拿本地 5000 张测试图片做了检索测试:
| 搜索词 | 平均响应时间 | Top5准确率 |
|---|---|---|
| 海边的日落 | 0.28秒 | 100% |
| 白色的猫 | 0.31秒 | 95% |
| 下雨天的街道 | 0.35秒 | 90% |
| 和朋友的聚餐 | 0.32秒 | 85% |
| 去年夏天的旅行 | 0.4秒 | 75% |
响应速度非常快,基本是输入完秒出结果。 具象化的搜索词准确率很高,抽象一点的语义也能命中大部分。
五、核心功能三:3D空间化相册时间线
5.1 设计思路
这一部分是整个应用最有"空间 × AI"感觉的地方。
传统相册是2D平铺的列表,你是在"刷"照片。空间化相册是把照片按时间维度分层,像一层层卡片堆叠起来,你是在"翻阅"回忆——这就是官方说的从"铺大饼"到"叠千层"。
5.2 空间化布局实现
基于 ArkUI 的声明式空间界面,核心代码非常简洁:
import { SpaceScroll, SpaceCard } from '@kit.ArkUI';
interface AlbumItem {
id: string;
title: string;
date: string;
coverPath: string;
photoCount: number;
depth: number; // Z轴深度
}
@Entry
@Component
struct SpaceAlbumTimeline {
@State albumList: AlbumItem[] = [];
@State currentIndex: number = 0;
aboutToAppear() {
// 模拟数据:按月分组的相册
this.albumList = this.generateMockAlbums();
}
build() {
Column() {
// 顶部标题栏
this.HeaderBar()
// 空间化滚动时间线
SpaceScroll() {
ForEach(this.albumList, (item: AlbumItem, index: number) => {
SpaceCard({
depth: item.depth,
onHover: (isHover: boolean) => {
// 视线悬停时的交互反馈
}
}) {
Stack({ alignContent: Alignment.BottomStart }) {
// 封面图
Image(item.coverPath)
.width('100%')
.height(220)
.objectFit(ImageFit.Cover)
.borderRadius(16)
// 渐变遮罩 + 文字信息
Column() {
Text(item.title)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Text(`${item.date} · ${item.photoCount}张照片`)
.fontSize(14)
.fontColor('rgba(255,255,255,0.8)')
.margin({ top: 4 })
}
.width('100%')
.padding(16)
.backgroundBlurStyle(BlurStyle.Thin)
.borderRadius({ bottomLeft: 16, bottomRight: 16 })
}
.width('100%')
.height(220)
.borderRadius(16)
.shadow({
radius: 15 + index * 3,
offsetX: 6,
offsetY: 8,
color: 'rgba(0,0,0,0.25)'
})
}
.width('88%')
.margin({ top: 20 })
.onClick(() => {
// 点击进入相册详情
this.openAlbumDetail(item);
})
}, (item: AlbumItem) => item.id)
}
.width('100%')
.layoutWeight(1)
.spaceScrollAlignment(SpaceAlignment.CENTER)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F7')
}
@Builder
HeaderBar() {
Row() {
Column() {
Text('空间相册')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#1D1D1F')
Text(`${this.albumList.length}个相册 · 全程端侧运行`)
.fontSize(13)
.fontColor('#86868B')
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
Blank()
// 搜索入口
Button() {
Image($r('app.media.icon_search'))
.width(22)
.height(22)
}
.type(ButtonType.Circle)
.width(44)
.height(44)
.backgroundColor('#FFFFFF')
.shadow({ radius: 6, offsetY: 2, color: 'rgba(0,0,0,0.1)' })
}
.width('100%')
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
}
// 生成模拟相册数据
private generateMockAlbums(): AlbumItem[] {
const months = [
'2026年9月', '2026年8月', '2026年7月', '2026年6月',
'2026年5月', '2026年4月', '2026年3月', '2026年2月'
];
const titles = [
'秋日城市漫步', '海边的毕业旅行', '盛夏的音乐节', '毕业季的校园',
'五一的山野徒步', '春天的樱花', '工作日常记录', '春节回家'
];
const counts = [86, 234, 156, 312, 98, 176, 245, 189];
return months.map((month, index) => ({
id: `album_${index}`,
title: titles[index],
date: month,
coverPath: '', // 实际使用时替换为真实图片路径
photoCount: counts[index],
depth: 20 + index * 18 // 递增Z轴深度,营造千层堆叠感
}));
}
private openAlbumDetail(item: AlbumItem): void {
// 跳转相册详情页逻辑
console.info(`打开相册: ${item.title}`);
}
}
5.3 沉浸光感的适配
进入相册详情页后,我给全屏查看图片的场景加了沉浸光感效果——这也是 HarmonyOS 7 的新特性,能让图片的光影氛围"溢出"屏幕边界,和空间环境融为一体。
核心代码很简单:
// 在全屏看图页面启用沉浸光感
import { ImmersiveLight } from '@kit.ArkUI';
@Entry
@Component
struct PhotoDetailPage {
@State photoPath: string = '';
private immersiveLight: ImmersiveLight.ImmersiveLightController | null = null;
aboutToAppear() {
// 创建沉浸光感控制器
this.immersiveLight = new ImmersiveLight.ImmersiveLightController();
// 从图片主色提取光影氛围
this.immersiveLight.setLightSource(this.photoPath, {
intensity: 0.7,
spreadRadius: 1.5
});
}
build() {
Stack() {
// 背景层 - 沉浸光感效果
this.immersiveLight
.width('100%')
.height('100%')
// 图片层
Image(this.photoPath)
.width('100%')
.height('100%')
.objectFit(ImageFit.Contain)
}
.width('100%')
.height('100%')
.backgroundColor('#000000')
}
aboutToDisappear() {
if (this.immersiveLight) {
this.immersiveLight.destroy();
}
}
}
效果就是——当你看一张日落的照片时,屏幕周围会浮现出暖橙色的光晕,像是照片里的光漫了出来,沉浸感一下就上来了。
六、完整功能清单 & 性能总览
把三个核心能力组合起来,这个应用的完整功能是这样的:
| 功能模块 | 核心能力 | 对应鸿蒙特性 |
|---|---|---|
| 超分修复 | 老照片一键4倍高清放大,批量处理 | Core Vision Kit 图像超分 + NPU加速 |
| 智能搜图 | 自然语言检索本地图片,秒级响应 | Core Vision Kit 文搜图 |
| 空间浏览 | 3D分层时间线,沉浸光感全屏看图 | ArkUI空间界面 + 沉浸光感 |
| 隐私保障 | 全程数据不出设备,无联网权限 | 端侧AI + 本地文件管理 |
整体性能表现(鸿蒙云手机实测):
- 冷启动时间:1.2秒
- 空间相册滚动帧率:稳定60fps
- 单张图片超分耗时(1080P → 4K):0.78秒
- 文搜图平均响应时间:0.32秒
- 后台建索引时整机功耗增加:< 8%
七、开发过程中的一些思考
做这个应用的过程中,我最大的感受是:鸿蒙7真的把"空间 × AI"的开发门槛降得很低了。
换做以前,想做一个端侧图像超分,你得自己搞模型压缩、自己做NPU适配、自己调性能——一个人根本搞不定。现在呢?几行代码调用系统能力,直接就能用。
但这也引出了另一个问题:当系统把底层能力都做好了,开发者的核心竞争力是什么?
我的答案是:对场景的理解,对体验的打磨。
- 同样是图像超分,你能不能做出"一键修复老照片"这种用户能直接感知的价值?
- 同样是文搜图,你能不能找到"私密相册"这个用户有强需求的切入点?
- 同样是空间UI,你能不能设计出"3D时间线回忆"这种有情感温度的交互?
技术门槛在降低,但产品思维和体验设计的门槛反而在升高。这也是我觉得这次征文主题"空间 × AI"很有意义的地方——它考的不是你会不会调API,而是你能不能用这些新能力,创造出真正有价值的东西。
八、后续规划
目前这个应用还是 MVP 版本,后续我计划继续迭代几个方向:
- 接入3DGS端侧重建:用户拍一段短视频,直接生成3D空间化的回忆场景,可以"走进去"翻看照片,把空间感做到极致
- 跨设备同步:利用碰一碰精准分享能力,手机上选中的照片,碰一下鸿蒙平板就能大屏浏览,超分效果自动同步
- 空间音频:给相册加上空间音频背景音,看海边的照片时能听到海浪声,进一步强化沉浸感
九、写在最后
从 2D 到 3D,从云端到端侧,从功能到体验——HarmonyOS 7 带来的不只是几个新API,而是一整套全新的开发范式。
作为开发者,我们有幸站在这个转折点上。系统能力越来越强,我们能做的事情也越来越多。希望这篇文章能给你带来一些启发,也期待看到更多"空间 × AI"方向的精彩探索。
如果你对这个项目感兴趣,欢迎在评论区交流讨论~
您好,我是ID: 熊猫钓鱼!
十余年深耕技术一线,我始终相信:优秀的开发如同垂钓——既要对技术生态的「水域」有深邃理解,也要对问题本质的「鱼汛」保持敏锐直觉。从架构设计到性能调优,从技术选型到团队协作,我专注在恰当的时机,用最合适的技术钓起最优雅的解决方案。关注我,我带你一起研究最新又好玩的热点技术~!
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/ailuloo/article/details/167124612



