完整代码:SmartRecognitionDemo
在寄件、收件或填写订单信息时,手动输入姓名、电话、地址往往繁琐易错。本文将实现一个快递地址图片识别工具:用户上传一张包含地址信息的图片(如名片、快递单截图),系统自动识别文字,并从中智能提取姓名、电话号码、地址,自动填充到对应表单中。
一、最终效果
- 选择本地图片或拍照,调用 Core Vision Kit 的文字识别(OCR)提取图中所有文字。
- 利用 Natural Language Kit 的实体抽取 API,从文字中自动分离出姓名、电话号码、地址。
- 将抽取结果自动填入姓名、电话、地址输入框,支持手动修正。
- 所有图片获取均通过安全控件实现,无需申请存储或相机权限。
二、实现思路
- 安全控件选图/拍照 :使用
PhotoViewPicker选择相册图片,或CameraPicker拍照,返回PixelMap。 - 文字识别 :初始化
textRecognition,调用recognizeText获取识别文本。 - 实体抽取 :调用
textProcessing.getEntity,指定EntityType.NAME、PHONE_NO、LOCATION,获取结构化信息。 - 表单填充 :将抽取结果更新到
@State变量,界面自动刷新。
运行效果
不支持模拟器,图片中的收货信息是虚拟生成的,相册选图的视频删除只保留结果。

三、完整代码
1. 权限声明 (module.json5)
使用安全控件,不需要 申请 ohos.permission.READ_MEDIA 或 ohos.permission.CAMERA。
2. 工具类:AddressRecognizer.ets
封装文字识别与实体抽取逻辑
javascript
import { textRecognition } from '@kit.CoreVisionKit';
import { textProcessing, EntityType } from '@kit.NaturalLanguageKit';
import { image } from '@kit.ImageKit';
import { AddressInfo } from '../model/AddressInfo';
export class AddressRecognizer {
private static isInit = false;
static async init(): Promise<void> {
if (AddressRecognizer.isInit) return;
try {
await textRecognition.init();
AddressRecognizer.isInit = true;
console.info('Text recognition init success');
} catch (err) {
console.error('Init failed', err);
throw new Error(err);
}
}
static async release(): Promise<void> {
if (!AddressRecognizer.isInit) return;
try {
await textRecognition.release();
AddressRecognizer.isInit = false;
} catch (err) {
console.error('Release failed', err);
}
}
static async recognizeAndExtract(pixelMap: image.PixelMap): Promise<AddressInfo | null> {
try {
// 1. 文字识别
let visionInfo: textRecognition.VisionInfo = {
pixelMap: pixelMap
};
const config: textRecognition.TextRecognitionConfiguration = {
isDirectionDetectionSupported: false
};
const result = await new Promise<textRecognition.TextRecognitionResult>((resolve, reject) => {
textRecognition.recognizeText(visionInfo, config, (error: BusinessError, data: textRecognition.TextRecognitionResult) => {
if (data.value) {
resolve(data);
}else {
reject(error)
}
});
});
const fullText = result.value;
console.info('识别文本:', fullText);
if (!fullText) return null;
// 2. 实体抽取
const entities = await textProcessing.getEntity(fullText, {
entityTypes: [EntityType.NAME, EntityType.PHONE_NO, EntityType.LOCATION]
});
return AddressRecognizer.parseEntities(entities);
} catch (err) {
console.error('识别或抽取失败', err);
return null;
}
}
private static parseEntities(entities: textProcessing.Entity[]): AddressInfo {
let name = '';
let phone = '';
let address = '';
let detailAddress = '';
for (const entity of entities) {
if (entity.type === EntityType.NAME) {
name = entity.text;
} else if (entity.type === EntityType.PHONE_NO) {
phone = entity.text;
} else if (entity.type === EntityType.LOCATION) {
// 简单处理:以"区"或"县"为界拆分为主地址和详细地址
const parts = entity.text.split(/[区县]/);
if (parts.length >= 2) {
address = parts[0] + '区';
detailAddress = parts.slice(1).join('');
} else {
address = entity.text;
detailAddress = '';
}
}
}
return { name, phone, address, detailAddress };
}
}
3. 图片选择工具:ImagePicker.ets
使用安全控件无需申请权限
javascript
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { camera, cameraPicker } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { fileIo } from '@kit.CoreFileKit';
export class ImagePicker {
static async pickFromGallery(): Promise<image.PixelMap | null> {
try {
const picker = new photoAccessHelper.PhotoViewPicker();
const result = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
});
if (result.photoUris.length === 0) return null;
const uri = result.photoUris[0];
const file = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
const pixelMap = await imageSource.createPixelMap();
await fileIo.close(file.fd);
return pixelMap;
} catch (err) {
console.error('选择图片失败', err);
return null;
}
}
// 使用 CameraPicker 拍照(安全控件,无需权限)
static async takePhoto(context: Context): Promise<image.PixelMap | null> {
try {
const mediaTypes: Array<cameraPicker.PickerMediaType> = [cameraPicker.PickerMediaType.PHOTO];
const pickerProfile: cameraPicker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
const pickerResult = await cameraPicker.pick(context, mediaTypes, pickerProfile);
if (!pickerResult || pickerResult.resultCode !== 0) return null;
const photoUri = pickerResult.resultUri;
if (!photoUri) return null;
const file = await fileIo.open(photoUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
const pixelMap = await imageSource.createPixelMap();
await fileIo.close(file.fd);
return pixelMap;
} catch (err) {
console.error('拍照失败', err);
return null;
}
}
}
4. 主页面:Index.ets
javascript
import { textRecognition } from '@kit.CoreVisionKit';
import { textProcessing, EntityType } from '@kit.NaturalLanguageKit';
import { image } from '@kit.ImageKit';
import { AddressInfo } from '../model/AddressInfo';
export class AddressRecognizer {
private static isInit = false;
static async init(): Promise<void> {
if (AddressRecognizer.isInit) return;
try {
await textRecognition.init();
AddressRecognizer.isInit = true;
console.info('Text recognition init success');
} catch (err) {
console.error('Init failed', err);
throw new Error(err);
}
}
static async release(): Promise<void> {
if (!AddressRecognizer.isInit) return;
try {
await textRecognition.release();
AddressRecognizer.isInit = false;
} catch (err) {
console.error('Release failed', err);
}
}
static async recognizeAndExtract(pixelMap: image.PixelMap): Promise<AddressInfo | null> {
try {
// 1. 文字识别
let visionInfo: textRecognition.VisionInfo = {
pixelMap: pixelMap
};
const config: textRecognition.TextRecognitionConfiguration = {
isDirectionDetectionSupported: false
};
const result = await new Promise<textRecognition.TextRecognitionResult>((resolve, reject) => {
textRecognition.recognizeText(visionInfo, config, (error: BusinessError, data: textRecognition.TextRecognitionResult) => {
if (data.value) {
resolve(data);
}else {
reject(error)
}
});
});
const fullText = result.value;
console.info('识别文本:', fullText);
if (!fullText) return null;
// 2. 实体抽取
const entities = await textProcessing.getEntity(fullText, {
entityTypes: [EntityType.NAME, EntityType.PHONE_NO, EntityType.LOCATION]
});
return AddressRecognizer.parseEntities(entities);
} catch (err) {
console.error('识别或抽取失败', err);
return null;
}
}
private static parseEntities(entities: textProcessing.Entity[]): AddressInfo {
let name = '';
let phone = '';
let address = '';
let detailAddress = '';
for (const entity of entities) {
if (entity.type === EntityType.NAME) {
name = entity.text;
} else if (entity.type === EntityType.PHONE_NO) {
phone = entity.text;
} else if (entity.type === EntityType.LOCATION) {
// 简单处理:以"区"或"县"为界拆分为主地址和详细地址
const parts = entity.text.split(/[区县]/);
if (parts.length >= 2) {
address = parts[0] + '区';
detailAddress = parts.slice(1).join('');
} else {
address = entity.text;
detailAddress = '';
}
}
}
return { name, phone, address, detailAddress };
}
}
四、关键 API 说明
| API | 用途 | 安全性 |
|---|---|---|
PhotoViewPicker.select() |
安全选择相册图片 | 无需权限,系统弹出选择界面 |
CameraPicker.pick() |
安全拍照 | 无需相机权限,系统提供拍照界面 |
textRecognition.recognizeText() |
识别图片中的文字 | - |
textProcessing.getEntity() |
从文本中抽取实体 | - |
五、常见问题与优化建议
| 问题 | 解决方案 |
|---|---|
| 识别结果不准确 | 确保图片清晰、文字可读;可增加图片预处理 |
| 地址解析粒度不够细 | 在 parseEntities 中利用正则进一步拆分 |
| 首次调用耗时较长 | 在应用启动时提前初始化 textRecognition |
| 多次识别后内存增大 | 确保 PixelMap 及时释放(pixelMap.release()) |
六、总结
本文利用鸿蒙的安全控件和 Core Vision Kit、Natural Language Kit,实现了一个端侧快递地址图片识别与自动填充工具。整个过程无需申请敏感权限,保护用户隐私的同时大幅提升输入效率。可在此基础上扩展至名片识别、发票识别等更多场景。
如果觉得本文对你有帮助,请点赞、收藏、转发支持!