HarmonyOS 7 端侧 AI 视觉能力实战 01:把图片里的文字直接提取出来

做笔记应用的时候,经常有用户提需求:能不能拍一张票据或者截图,直接把上面的文字识别出来,不用手动敲?这个需求听起来简单,真做起来才发现,鸿蒙的 OCR 能力虽然开箱即用,但要做到识别准、不卡顿、异常兜底到位,还是有不少细节要抠的。
这篇是端侧 AI 视觉系列的第一篇,就从最基础的文字识别开始讲。我们做的是一个笔记类应用,需要从用户拍的图片里提取文字,自动填充到笔记里。从最开始调 API 的 Demo 能跑,到最后上线前调优,中间踩的坑都记下来了。
一、真实开发中遇到的问题
最开始用官方 OCR API 写了个 Demo,传一张清晰的图片进去,返回识别结果,看起来挺简单的。但真放到项目里用,问题就来了:
第一,识别准确率不够稳定。拍一张发票,有的角度识别出来全是乱码;光线暗一点,数字就认错。Demo 里用的是标准测试图,实际用户拍的图五花八门。
第二,识别速度慢。一张高分辨率的手机截图,识别要 2-3 秒,用户以为应用卡了。
第三,结果解析麻烦。API 返回的是按行、按区域的结构化数据,不是直接给你一段文字。怎么把这些区域数据拼成用户能读的文本,还要保持换行和段落,得自己处理。
第四,识别失败怎么处理。图片模糊、文字太小、没有文字,这些情况 API 返回什么?UI 上怎么给用户反馈?
二、这个能力怎么接入
HarmonyOS 7 的 OCR 能力在 TextRecognition Kit 里,端侧运行,不需要联网。整个接入流程分三步:
1. 初始化识别器:调用 TextRecognitionKit 创建识别器实例,指定识别语言(中文/英文/混合)。
2. 准备图片数据:把用户选的图片转成 PixelMap,预处理一下(降噪、二值化可选)。
3. 执行识别并解析结果:调用异步识别接口,拿到结果后解析成结构化文本。
这里要注意:识别器实例不要每次都创建,创建一次复用就行。识别是异步的,要在子线程做,不要阻塞 UI。

三、关键代码怎么写
下面是我们实际封装的 OCR 工具类,文件位置在 entry/src/main/ets/utils/OcrUtil.ets:
import { textRecognition } from '@kit.CoreVisionKit';
import { image } from '@kit.CoreImageKit';
export class OcrUtil {
private static recognizer: textRecognition.TextRecognition | null = null;
// 初始化识别器(中文+英文混合)
static async initRecognizer(): Promise<void> {
if (!this.recognizer) {
this.recognizer = await textRecognition.TextRecognition.createInstance(
textRecognition.RecognitionMode.FREE_FORM,
'zh-CN'
);
}
}
// 从图片中提取文字
static async extractTextFromImage(
pixelMap: image.PixelMap
): Promise<OcrResult> {
if (!this.recognizer) {
await this.initRecognizer();
}
try {
// 执行识别
const result = await this.recognizer!.recognize(pixelMap);
// 解析识别结果
return this.parseResult(result);
} catch (e) {
console.error('OCR failed: ' + e.message);
return {
success: false,
text: '',
errorMsg: '文字识别失败,请换一张清晰的图片试试'
};
}
}
// 解析识别结果
private static parseResult(
result: textRecognition.RecognitionResult
): OcrResult {
if (!result.blocks || result.blocks.length === 0) {
return {
success: true,
text: '',
errorMsg: ''
};
}
// 按 block 拼接文本,保持段落结构
let fullText = '';
for (const block of result.blocks) {
for (const line of block.lines) {
fullText += line.text + '\n';
}
fullText += '\n'; // 段落间空一行
}
return {
success: true,
text: fullText.trim(),
errorMsg: ''
};
}
}
// 识别结果类型
interface OcrResult {
success: boolean;
text: string;
errorMsg: string;
}
这段代码的核心:recognize 方法是异步的,返回的 RecognitionResult 里有 blocks → lines → words 三层结构。我们按行拼接,段落间加换行,还原原始排版。
调用侧在页面里怎么用?文件位置 pages/OcrPage.ets:
@Entry
@Component
struct OcrPage {
@State extractedText: string = '';
@State isRecognizing: boolean = false;
@State errorMsg: string = '';
// 选择图片并识别
async onImageSelected(pixelMap: image.PixelMap) {
this.isRecognizing = true;
this.errorMsg = '';
this.extractedText = '';
const result = await OcrUtil.extractTextFromImage(pixelMap);
this.isRecognizing = false;
if (result.success) {
this.extractedText = result.text;
if (!result.text) {
this.errorMsg = '这张图片里没有识别到文字';
}
} else {
this.errorMsg = result.errorMsg;
}
}
build() {
Column({ space: 16 }) {
// 图片预览
Image(this.imagePath)
.width('100%')
.height(200)
// 识别结果
if (this.isRecognizing) {
LoadingProgress().width(40).height(40)
Text('正在识别文字...')
} else if (this.errorMsg) {
Text(this.errorMsg).fontColor(Color.Red)
} else {
Text(this.extractedText)
.fontSize(14)
.width('100%')
}
}
.padding(16)
}
}

四、运行过程中怎么处理异常
OCR 的异常场景比想象中多,我们处理了这几种:
1. 图片里没有文字:blocks 为空数组。这时候不算错误,UI 上提示"未识别到文字"就行。
2. 图片模糊/光线暗:识别出来全是乱码或者空。这种情况 API 一般不会报错,结果质量差。我们在 UI 上给个"识别效果不好?"的提示,让用户重新拍。
3. 图片太大:4000×3000 的原图识别很慢。我们在识别前先把图片缩放到合适尺寸(最长边不超过 2000px),识别速度提升了一倍,准确率几乎没影响。
4. 识别器初始化失败:极少数情况下设备不支持 OCR,或者模型加载失败。要 catch 住初始化异常,降级提示用户"当前设备不支持文字识别"。
5. 内存问题:连续识别多张图片,PixelMap 没有及时释放,内存会涨。识别完记得调用 pixelMap.release() 释放资源。
五、实际开发中容易忽略的问题
1. 识别语言要选对:zh-CN 是中英混合,如果只选英文,中文识别率会暴跌。根据用户场景选对语言模型。
2. 结果排版不要简单拼接:直接把所有文字连起来是不行的。要按 blocks 和 lines 的结构来拼,保持原有的段落和换行,用户体验才好。
3. 票据识别有专门模式:发票、营业执照这类有固定版式的图片,用专门的版式识别模式比自由模式准。不过我们暂时用自由模式够了。
4. 识别是端侧的:HarmonyOS 7 的 OCR 完全在本地跑,不上传图片。这个对用户隐私很重要,写在隐私说明里。
5. 预热识别器:第一次创建识别器要加载模型,有几百毫秒延迟。可以在应用启动的时候后台预热,用户点识别的时候就快了。
文字识别是视觉能力的基础。后面几篇会在这个基础上,做图片超分、文搜图、人脸检测这些更复杂的能力。OCR 这个点看起来小,但真要做到用户觉得"好用",细节还是很多的。
更多推荐
所有评论(0)