HarmonyOS 7 端侧 AI 视觉能力实战 07:完成一个可实际使用的智能截图整理工具

前面六篇分别讲了 OCR、超分、文搜图、人脸检测、截图分类和处理链。这篇是系列最后一篇,把所有能力整合起来,做一个完整可用的智能截图整理工具。
这个工具的最终形态很简单:用户导入截图,App 自动识别内容、分类、打标签、建索引,用户可以按分类浏览、用文字搜索、查看详情。所有处理都在本地完成,不上传图片。
这篇不讲单个 API 怎么调,而是讲怎么把前面六篇的能力组装成一个完整的产品。从项目结构到页面设计,再到数据持久化和异常兜底,把整个项目从头到尾捋一遍。
一、项目整体结构
我们的项目叫 ScreenshotOrganizer,工程结构如下:
entry/src/main/ets/
├── utils/ # 工具类
│ ├── OcrUtil.ets # OCR 文字识别
│ ├── ImageEnhanceUtil.ets # 图像超分
│ ├── FaceDetectUtil.ets # 人脸检测
│ ├── ScreenshotClassifier.ets # 截图分类
│ └── ImageSearchUtil.ets # 文搜图
├── pipeline/ # 处理链
│ └── PipelineRunner.ets # 流水线调度
├── database/ # 数据层
│ ├── DbUtil.ets # 数据库操作
│ └── ScreenshotDao.ets # 截图数据访问
├── pages/ # 页面
│ ├── HomePage.ets # 首页(分类浏览)
│ ├── SearchPage.ets # 搜索页
│ └── DetailPage.ets # 详情页
└── models/ # 数据模型
└── ScreenshotItem.ets # 截图实体
分层很清晰:utils 是单个能力封装,pipeline 把它们串起来,database 负责持久化,pages 是 UI 层。每一层职责单一,方便维护和测试。
二、核心页面怎么设计
整个应用三个核心页面:
首页:按分类展示截图。顶部是分类标签(全部/聊天/文档/票据/人像),下面是网格布局的截图缩略图。点击某个分类就筛选对应类别的截图。
搜索页:顶部一个搜索框,用户输入自然语言描述,下面显示匹配的截图结果。支持关键词和语义搜索两种模式。
详情页:点击某张截图进入,显示大图、OCR 识别出的文字、分类标签、人脸检测结果。用户可以手动修改分类。

三、关键代码怎么写
下面是首页的核心代码,文件位置在 pages/HomePage.ets:
import { ScreenshotDao } from '../database/ScreenshotDao';
import { PipelineRunner } from '../pipeline/PipelineRunner';
@Entry
@Component
struct HomePage {
@State screenshots: ScreenshotItem[] = [];
@State filteredScreenshots: ScreenshotItem[] = [];
@State currentCategory: string = 'all';
@State isImporting: boolean = false;
@State importProgress: number = 0;
// 分类标签
private categories = [
{ key: 'all', label: '全部' },
{ key: 'chat', label: '聊天' },
{ key: 'document', label: '文档' },
{ key: 'invoice', label: '票据' },
{ key: 'portrait', label: '人像' }
];
aboutToAppear() {
this.loadScreenshots();
}
// 从数据库加载截图列表
async loadScreenshots() {
this.screenshots = await ScreenshotDao.getAll();
this.filterByCategory(this.currentCategory);
}
// 按分类筛选
filterByCategory(category: string) {
this.currentCategory = category;
if (category === 'all') {
this.filteredScreenshots = this.screenshots;
} else {
this.filteredScreenshots = this.screenshots.filter(
item => item.category === category
);
}
}
// 导入截图并处理
async onImportImages(images: image.PixelMap[]) {
this.isImporting = true;
this.importProgress = 0;
// 调用处理链
const results = await PipelineRunner.processImages(
images.map((p, i) => ({ id: 'img_' + Date.now() + '_' + i, pixelMap: p })),
(done, total) => {
this.importProgress = Math.round((done / total) * 100);
}
);
// 保存到数据库
for (const [id, result] of results) {
await ScreenshotDao.insert({
id: id,
category: result.category,
ocrText: result.ocrText,
hasFace: result.hasFace,
tags: result.tags.join(','),
vector: result.vector ? Array.from(result.vector) : []
});
}
// 刷新列表
await this.loadScreenshots();
this.isImporting = false;
}
build() {
Column() {
// 分类标签栏
Row({ space: 12 }) {
ForEach(this.categories, (cat) => {
Text(cat.label)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.currentCategory === cat.key
? '#007aff' : '#f0f0f0')
.fontColor(this.currentCategory === cat.key
? Color.White : Color.Black)
.borderRadius(16)
.onClick(() => this.filterByCategory(cat.key))
})
}
.padding(8)
// 截图网格
if (this.isImporting) {
Column() {
Text('正在导入处理... ' + this.importProgress + '%')
Progress({ value: this.importProgress, total: 100 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
Grid() {
ForEach(this.filteredScreenshots, (item: ScreenshotItem) => {
GridItem() {
Column() {
Image(item.thumbnailPath)
.width('100%')
.height(120)
Text(item.category)
.fontSize(12)
.fontColor('#666')
}
}
}, (item: ScreenshotItem) => item.id)
}
.columnsTemplate('1fr 1fr 1fr')
.layoutWeight(1)
}
}
}
}
这段代码是整个应用的主页面。导入截图的时候调 PipelineRunner 跑处理链,处理完存数据库,然后刷新列表。分类标签点击就筛选对应的截图。
数据库操作怎么写?文件位置 database/ScreenshotDao.ets:
import { relationalStore } from '@kit.ArkData';
export class ScreenshotDao {
private static store: relationalStore.RdbStore | null = null;
// 初始化数据库
static async init(): Promise<void> {
if (!this.store) {
this.store = await relationalStore.getRdbStore(
getContext(this),
{
name: 'screenshots.db',
securityLevel: relationalStore.SecurityLevel.S1
}
);
// 建表
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS screenshots (
id TEXT PRIMARY KEY,
category TEXT,
ocr_text TEXT,
has_face INTEGER,
tags TEXT,
vector BLOB,
created_at INTEGER
)
`);
}
}
// 插入截图记录
static async insert(item: ScreenshotItem): Promise<void> {
if (!this.store) await this.init();
await this.store!.insert('screenshots', {
id: item.id,
category: item.category,
ocr_text: item.ocrText,
has_face: item.hasFace ? 1 : 0,
tags: item.tags,
vector: item.vector ? new Uint8Array(item.vector.buffer) : null,
created_at: Date.now()
});
}
// 查询全部
static async getAll(): Promise<ScreenshotItem[]> {
if (!this.store) await this.init();
const result = await this.store!.querySql(
'SELECT * FROM screenshots ORDER BY created_at DESC'
);
const items: ScreenshotItem[] = [];
while (result.goToNextRow()) {
items.push({
id: result.getValue(result.getColumnIndex('id')),
category: result.getValue(result.getColumnIndex('category')),
ocrText: result.getValue(result.getColumnIndex('ocr_text')),
hasFace: result.getValue(result.getColumnIndex('has_face')) === 1,
tags: result.getValue(result.getColumnIndex('tags')),
vector: []
});
}
result.close();
return items;
}
}

四、异常兜底怎么设计
一个能上线的工具,异常兜底比主流程更重要。我们做了这几层防护:
1. 处理失败降级:单张截图处理失败了,存成"未分类",不影响其他截图。用户可以手动重新处理。
2. 数据库损坏恢复:如果数据库文件损坏,启动的时候检测到就重建一个空库,不崩溃。老数据丢了总比应用打不开好。
3. 内存不足保护:处理大量图片的时候,监控内存占用。如果内存快满了,暂停处理,提示用户分批导入。
4. 模型加载失败降级:端侧 AI 模型加载失败的情况虽然少,但要有兜底。检测到模型不可用,提示用户"当前设备不支持 AI 识别功能",但基础的图片浏览功能还能用。
五、上线前要做的检查
最后上线前,我们跑了一轮完整的检查清单:
性能检查:单张截图处理时间控制在 3 秒以内,批量 20 张不超过 30 秒。用 DevEco Studio 的 Profiler 看内存和 CPU 占用。
稳定性检查:连续导入 100 张截图不崩溃,内存不泄漏。反复进出页面 50 次,内存稳定不涨。
兼容性检查:在不同尺寸的设备上测试——手机、折叠屏、平板。布局要自适应,不能在大屏上就一块小图。
隐私合规:所有处理都在本地,不上传图片。隐私说明里写清楚数据怎么存、怎么用、怎么删。
做完这些,这个智能截图整理工具才算真正能用了。从第一篇讲 OCR 开始,到这里做一个完整的项目,整个系列就算闭环了。端侧 AI 视觉能力看起来都是单点的小功能,但组合起来就能做出很实用的产品。HarmonyOS 7 把这些能力都做成了系统级 API,开发者不用自己训练模型,直接调接口就行——这才是端侧 AI 真正的价值。
更多推荐



所有评论(0)