HarmonyOS 7 端侧 AI 视觉能力实战 06:把识别、增强与搜索串成完整处理链

前面五篇分别讲了 OCR、超分、文搜图、人脸检测、截图分类。每篇单独都能跑,但真要用到产品里,得把它们串成一条完整的处理链。用户导入一张截图,从图片到最终可搜索、可分类的结构化数据,中间要经过好几个步骤,每一步都可能失败。
这篇就讲怎么设计这条处理链:怎么安排步骤顺序,怎么做状态管理,失败了怎么重试,怎么让整个流程既高效又稳定。
一、真实开发中遇到的问题
最开始我们把所有步骤按顺序串起来:导入图片 → 超分增强 → OCR 识别 → 人脸检测 → 分类 → 建索引。看起来很顺,但实际跑起来问题一堆:
第一,串行太慢。一张图跑完全部步骤要 5-6 秒,用户导入 10 张图就要等一分钟,体验很差。
第二,一步失败全挂。OCR 失败了,后面的人脸检测和分类就不跑了?还是继续跑?整个流程的状态怎么管?
第三,有的步骤不需要跑。一张高清截图不需要超分,一张没有文字的图不需要 OCR。但串行流程不管这些,每一步都跑,浪费性能。
第四,进度怎么反馈?用户导入了 20 张图,现在跑到哪一步了?哪张图处理完了?UI 上怎么展示?
二、这个能力怎么接入
我们最后设计的处理链是一个有向无环图(DAG),不是简单的串行。每个节点是一个处理步骤,节点之间有依赖关系:
节点 1:图片导入与预处理。读取图片、压缩到合适尺寸、生成缩略图。所有后续步骤的基础。
节点 2:条件判断。根据图片尺寸判断要不要做超分;根据图片内容判断要不要做 OCR。不是所有步骤都必跑。
节点 3:并行处理。超分、人脸检测、图像分类这三个互相独立,可以并行跑。OCR 依赖超分的结果,所以 OCR 要等超分跑完。
节点 4:内容分析。把 OCR 结果、人脸结果、分类结果汇总,做关键词提取和标签生成。
节点 5:建搜索索引。生成图片向量,存到本地数据库,供文搜图用。
整个流程用状态机管理,每个节点有 pending / running / success / failed 四种状态。

三、关键代码怎么写
下面是处理链的核心调度器,文件位置在 entry/src/main/ets/pipeline/PipelineRunner.ets:
import { image } from '@kit.CoreImageKit';
import { ImageEnhanceUtil } from '../utils/ImageSuperResolution';
import { OcrUtil } from '../utils/OcrUtil';
import { FaceDetectUtil } from '../utils/FaceDetectUtil';
import { ScreenshotClassifier } from '../utils/ScreenshotClassifier';
import { ImageSearchUtil } from '../utils/ImageSearchUtil';
// 处理状态
enum PipelineState {
PENDING = 'pending',
RUNNING = 'running',
SUCCESS = 'success',
FAILED = 'failed'
}
// 单张图片的处理任务
interface PipelineTask {
imageId: string;
pixelMap: image.PixelMap;
state: PipelineState;
currentStep: string;
result: ProcessResult | null;
}
// 处理结果
interface ProcessResult {
ocrText: string;
hasFace: boolean;
category: string;
tags: string[];
vector: Float32Array | null;
}
export class PipelineRunner {
private static tasks: Map<string, PipelineTask> = new Map();
// 批量处理入口
static async processImages(
images: { id: string; pixelMap: image.PixelMap }[],
onProgress: (done: number, total: number) => void
): Promise<Map<string, ProcessResult>> {
const results = new Map<string, ProcessResult>();
let done = 0;
// 串行处理每张图(避免内存压力)
for (const img of images) {
try {
const result = await this.processSingle(img.pixelMap);
results.set(img.id, result);
} catch (e) {
console.error('Process failed for ' + img.id + ': ' + e.message);
// 单张失败不影响其他图
results.set(img.id, this.emptyResult());
} finally {
img.pixelMap.release();
done++;
onProgress(done, images.length);
}
}
return results;
}
// 单张图片处理链
private static async processSingle(
pixelMap: image.PixelMap
): Promise<ProcessResult> {
// Step 1: 条件判断 - 要不要超分
let workingMap = pixelMap;
const size = pixelMap.getImageInfoSync();
const needEnhance = Math.max(size.width, size.height) < 1000;
if (needEnhance) {
try {
workingMap = await ImageEnhanceUtil.enhanceImage(pixelMap);
} catch (e) {
console.warn('Enhance skipped: ' + e.message);
}
}
// Step 2: 并行做 OCR 和人脸检测
const [ocrResult, hasFace] = await Promise.all([
OcrUtil.extractTextFromImage(workingMap),
FaceDetectUtil.hasFace(workingMap)
]);
// Step 3: 分类
const category = await ScreenshotClassifier.classify(workingMap);
// Step 4: 生成搜索向量
let vector: Float32Array | null = null;
try {
vector = await ImageSearchUtil.encodeImage(workingMap);
} catch (e) {
console.warn('Vector encode failed: ' + e.message);
}
// 释放增强后的图
if (workingMap !== pixelMap) {
workingMap.release();
}
return {
ocrText: ocrResult.text,
hasFace: hasFace,
category: category.type,
tags: this.generateTags(ocrResult.text, hasFace, category.type),
vector: vector
};
}
// 生成标签
private static generateTags(
text: string,
hasFace: boolean,
category: string
): string[] {
const tags: string[] = [category];
if (hasFace) tags.push('有人脸');
if (text.includes('发票')) tags.push('票据');
if (text.includes('聊天')) tags.push('聊天');
return tags;
}
private static emptyResult(): ProcessResult {
return {
ocrText: '',
hasFace: false,
category: 'unknown',
tags: [],
vector: null
};
}
}
这段代码的核心:processSingle 方法按条件跑步骤,OCR 和人脸检测用 Promise.all 并行,单步失败不影响整体流程。processImages 串行处理多张图,避免内存压力。

四、运行过程中怎么处理异常
处理链的异常处理原则:单步失败降级,单图失败跳过。
1. 超分失败:用原图继续做 OCR 和检测,不中断流程。
2. OCR 失败:ocrText 为空,后面的关键词提取和标签生成就基于空文本处理,不影响分类。
3. 人脸检测失败:hasFace 设为 false,分类按无脸处理。
4. 向量生成失败:vector 为 null,这张图就不能参与文搜图,但其他功能正常。
5. 重试机制:瞬时失败(比如模型加载超时)可以重试一次。但不要无限重试,重试一次还失败就放弃。
6. 断点续传:处理到一半用户退出去了,下次进来从上次中断的地方继续,不要从头再来。已完成的图直接读缓存结果。
五、实际开发中容易忽略的问题
1. 并行不是越多越好:OCR 和人脸检测并行没问题,但如果三个都并行,CPU 和 GPU 都打满了,反而更慢。要根据设备性能调整并行度。
2. 内存是最大瓶颈:每张图的 PixelMap 占用很大。处理链里每一步用完就释放,不要一直持着。中间结果(比如超分后的图)用完就 release。
3. 进度反馈要准确:UI 上显示"正在处理第 5/20 张",比"正在处理..."更有安全感。但不要每处理完一张就刷新 UI,攒一批再更新。
4. 结果要持久化:处理完的结果存到本地数据库。下次打开应用直接读缓存,不要重新跑一遍处理链。
5. 性能监控:每张图每一步花了多少时间,要打日志。上线后如果发现用户反馈慢,看日志就知道哪一步是瓶颈。
处理链设计的核心思路:条件执行 + 并行优化 + 异常降级 + 结果缓存。把这些做好,用户感知到的就是"导入截图之后,App 自动帮我整理好了",而不是"我等了半天不知道它在干嘛"。下一篇是系列最后一篇,把这些能力全部整合起来,做一个完整可用的工具。
更多推荐



所有评论(0)