熊猫钓鱼>_>头像
关注

从2D平铺到3D沉浸:我用HarmonyOS 7端侧AI做了一个全程数据不出设备的空间化私密相册

【共创稿事节】从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 7204320 x 2880NPU加速0.78秒
1080 x 7204320 x 2880纯CPU4.2秒
1920 x 10807680 x 4320NPU加速1.65秒
640 x 480(老照片)2560 x 1920NPU加速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 踩坑记录

坑一:首次索引构建时间

本地有一万张图片的话,首次全量构建特征索引需要一定时间。我的做法是:

  1. 应用启动时不在前台等索引建好,先让用户能用普通方式浏览
  2. 索引在后台低优先级线程慢慢建
  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 版本,后续我计划继续迭代几个方向:

  1. 接入3DGS端侧重建:用户拍一段短视频,直接生成3D空间化的回忆场景,可以"走进去"翻看照片,把空间感做到极致
  2. 跨设备同步:利用碰一碰精准分享能力,手机上选中的照片,碰一下鸿蒙平板就能大屏浏览,超分效果自动同步
  3. 空间音频:给相册加上空间音频背景音,看海边的照片时能听到海浪声,进一步强化沉浸感

九、写在最后

从 2D 到 3D,从云端到端侧,从功能到体验——HarmonyOS 7 带来的不只是几个新API,而是一整套全新的开发范式。

作为开发者,我们有幸站在这个转折点上。系统能力越来越强,我们能做的事情也越来越多。希望这篇文章能给你带来一些启发,也期待看到更多"空间 × AI"方向的精彩探索。

如果你对这个项目感兴趣,欢迎在评论区交流讨论~

您好,我是ID: 熊猫钓鱼!
十余年深耕技术一线,我始终相信:优秀的开发如同垂钓——既要对技术生态的「水域」有深邃理解,也要对问题本质的「鱼汛」保持敏锐直觉。从架构设计到性能调优,从技术选型到团队协作,我专注在恰当的时机,用最合适的技术钓起最优雅的解决方案。关注我,我带你一起研究最新又好玩的热点技术~!

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/ailuloo/article/details/167124612

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--