HarmonyOS 7 端侧 AI 视觉能力实战 03:在相册里实现“用一句话找图片”

做了 OCR 和超分之后,产品又提了个需求:用户能不能用一句话找图片?比如输入"去年在海边拍的那张",相册就能把海边的照片筛出来。这个功能在 iOS 和安卓上已经有了,鸿蒙端侧能不能做?
研究了一下,HarmonyOS 7 的端侧多模态能力支持文搜图——把文字描述和图片做向量匹配,返回相似度最高的几张。这个能力完全在本地跑,不用上传图片到云端,隐私安全有保障。这篇就讲讲我们怎么把它集成到相册应用里。
一、真实开发中遇到的问题
最开始的想法很简单:用户输入一句话,系统返回匹配的图片。但真做起来才发现问题不少:
第一,相册有几千张图,每张都要算向量,一次性全算完要好久。用户打开搜索页不能等半分钟吧?
第二,自然语言搜索不是精确匹配。用户说"海边的照片",图片本身没有"海边"这个标签,系统怎么知道哪张是海边的?这就需要多模态模型理解图片内容。
第三,搜索结果排序怎么搞?返回 20 张相似的图,哪张排前面?用户最想看的应该是相似度最高的,但也要考虑时间、地点这些辅助因素。
第四,增量更新怎么办?用户拍了新照片,要不要重新算向量?总不能每次打开都全量索引一遍。
二、这个能力怎么接入
HarmonyOS 7 的文搜图能力在 VisionKit 里,核心是两个步骤:给图片生成向量,给文字生成向量,然后算余弦相似度。
接入思路:
1. 离线建索引:应用首次启动时,后台遍历相册所有图片,每张图生成一个向量(float 数组),存到本地数据库。这个过程很慢,但只做一次。
2. 增量更新:监听相册变化,新增的图片单独算向量,追加到索引里。
3. 在线搜索:用户输入文字,先把文字转成向量,然后和所有图片向量算相似度,按相似度排序返回前 N 张。
这里的关键是:图片向量是离线算好的,在线搜索只需要算文字向量 + 向量比对,速度很快。

三、关键代码怎么写
下面是封装的文搜图工具类,文件位置在 entry/src/main/ets/utils/ImageSearchUtil.ets:
import { vision } from '@kit.CoreVisionKit';
import { image } from '@kit.CoreImageKit';
export class ImageSearchUtil {
private static encoder: vision.ImageTextEncoder | null = null;
// 初始化编码器
static async init(): Promise<void> {
if (!this.encoder) {
this.encoder = await vision.ImageTextEncoder.create();
}
}
// 给图片生成向量
static async encodeImage(
pixelMap: image.PixelMap
): Promise<Float32Array> {
if (!this.encoder) await this.init();
const result = await this.encoder!.encodeImage(pixelMap);
return result.embedding; // 图片向量
}
// 给文字生成向量
static async encodeText(text: string): Promise<Float32Array> {
if (!this.encoder) await this.init();
const result = await this.encoder!.encodeText(text);
return result.embedding; // 文字向量
}
// 余弦相似度计算
private static cosineSimilarity(
vecA: Float32Array,
vecB: Float32Array
): number {
let dot = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < vecA.length; i++) {
dot += vecA[i] * vecB[i];
normA += vecA[i] * vecA[i];
normB += vecB[i] * vecB[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
// 搜索图片
static async searchImages(
query: string,
imageVectors: Map<string, Float32Array>,
topK: number = 20
): Promise<SearchResult[]> {
// 1. 文字转向量
const queryVec = await this.encodeText(query);
// 2. 和所有图片向量算相似度
const results: SearchResult[] = [];
for (const [imageId, imageVec] of imageVectors) {
const score = this.cosineSimilarity(queryVec, imageVec);
results.push({ imageId: imageId, score: score });
}
// 3. 按相似度排序,取前 topK
results.sort((a, b) => b.score - a.score);
return results.slice(0, topK);
}
}
// 搜索结果类型
interface SearchResult {
imageId: string;
score: number;
}
这段代码的核心:encodeImage 和 encodeText 分别给图片和文字生成向量,cosineSimilarity 算相似度,searchImages 遍历所有图片向量排序返回。
页面里怎么用?文件位置 pages/SearchPage.ets:
@Entry
@Component
struct SearchPage {
@State searchQuery: string = '';
@State results: SearchResult[] = [];
@State isSearching: boolean = false;
// 执行搜索
async onSearch() {
if (!this.searchQuery.trim()) return;
this.isSearching = true;
// 从本地数据库读取所有图片向量
const imageVectors = await DbUtil.getAllImageVectors();
// 执行搜索
const results = await ImageSearchUtil.searchImages(
this.searchQuery,
imageVectors,
20
);
this.results = results;
this.isSearching = false;
}
build() {
Column() {
// 搜索框
Search({ placeholder: '用一句话找图片...' })
.onSubmit(() => this.onSearch())
// 搜索结果
if (this.isSearching) {
LoadingProgress().width(40).height(40)
} else {
List({ space: 8 }) {
ForEach(this.results, (result: SearchResult) => {
ListItem() {
Image(result.imageId)
.width('100%')
.height(200)
}
}, (result: SearchResult) => result.imageId)
}
}
}
.padding(16)
}
}

四、运行过程中怎么处理异常
文搜图的异常场景:
1. 索引为空:第一次打开应用,索引还没建完,搜索的时候没有数据。要提示用户"正在建立索引,请稍后再试"。
2. 图片太多:几千张图的向量存在本地,数据库查询要快。我们用了 SQLite 存向量,每个向量存成 blob,查询的时候遍历算相似度。几千条数据毫秒级就能返回。
3. 索引建一半失败了:遍历相册的时候某张图读取失败,跳过这张继续,不要因为一张图失败就中断整个索引过程。
4. 搜索词太短:用户只输入一个词,搜索结果可能不准。UI 上给个提示,建议输入更详细的描述。
五、实际开发中容易忽略的问题
1. 索引要后台建:首次建索引很慢,绝对不能在启动的时候同步做。放后台线程,分批处理,建完了通知用户。
2. 向量维度:端侧模型输出的向量一般是 512 维或者 768 维,存在数据库里要注意格式。float32Array 存成 blob,查询的时候转回来。
3. 增量索引:新拍的照片要及时更新到索引里。监听相册的变化事件,新图单独算向量追加进去。
4. 相似度阈值:不是所有搜索都有好结果。相似度太低的结果(比如低于 0.3)就不要展示了,不然用户觉得搜的不准。
5. 隐私问题:文搜图完全在本地跑,不上传图片。这个是端侧 AI 的优势,要在隐私说明里强调。
文搜图是视觉能力里用户感知最强的功能——一句话就能找到照片,体验很直观。但背后的索引构建、向量存储、增量更新这些工程细节,才是决定好不好用的关键。
更多推荐



所有评论(0)