找回密码
 立即注册
首页 业界区 业界 【拥抱鸿蒙】HarmonyOS NEXT实现双路预览并识别文字 ...

【拥抱鸿蒙】HarmonyOS NEXT实现双路预览并识别文字

韶侪 3 天前
我们在许多其他平台看到过OCR功能的应用,那么HarmonyOS在这方面的支持如何呢?我们如何能快速使用这一能力呢?使用这一能力需要注意的点有哪些呢?就让我们一起来探究吧~
【开发环境】

  • 版本规则号:HarmonyOS NEXT
  • 版本类型:Developer Preview2
  • OpenHarmony API Version:11 Release
  • compileSdkVersion:4.1.0(11)
  • IDE:DevEco Studio 4.1.3.700(Mac)
实现目标

通过对Core Vision Kit的基础功能的实现,完成相册图片获取、OCR、相机预览,图片格式转换等功能,熟悉ArkTS的开发流程和细节,加深对HarmonyOS中各类基础库的理解。
名词解释


  • Core Vision Kit:基础视觉服务
  • Camera Kit:相机服务
  • Core File Kit:文件基础服务
  • OCR:Optical Character Recognition,通用文字识别或光学字符识别
  • URI: Uniform Resource Identifier,资源标识符,本文中URI指图片资源的访问路径
核心功能

本篇所涉及的核心功能就是通用文字识别(OCR)。
OCR是通过拍照、扫描等光学输入方式,把各种票据、卡证、表格、报刊、书籍等印刷品文字转化为图像信息,再利用文字识别技术将图像信息转化为计算机等设备可以使用的字符信息的技术。
首先,我们实现从相册选取一张图片,并识别图片上的文字的功能。这一功能的实现基于系统提供的Core Vision Kit中的OCR能力。

  • 创建一个ImageOCRUtil类,用于封装OCR相关功能。
    从CoreVisionKit中导入textRecognition模块,声明一个名为ImageOCRUtil的类,并创建其new()方法。
  1. import { textRecognition } from '@kit.CoreVisionKit';
  2. export class ImageOCRUtil {}
  3. export default new ImageOCRUtil();
复制代码

  • 在ImageOCRUtil中实现图片中文字识别功能。
    构建一个异步方法:async recognizeText(image:  PixelMap | undefined, resultCallback: Function),其中PixelMap为图像像素类,用于读取或写入图像数据以及获取图像信息。目前pixelmap序列化大小最大128MB,超过会送显失败。大小计算方式为:宽 x 高 x 每像素占用字节数。
  1. export class ImageOCRUtil {
  2.   /**
  3.    * 文字识别
  4.    *
  5.    * @param image 图片源数据
  6.    * @param resultCallback 结果返回
  7.    * @returns
  8.    */
  9.   static async recognizeText(image:  PixelMap | undefined, resultCallback: Function) {
  10.     // 非空判断
  11.     if (!image || image === undefined) {
  12.       hilog.error(0x0000, 'OCR', 'the image is not existed');
  13.       return;
  14.     }
  15.     let visionInfo: textRecognition.VisionInfo = {
  16.       pixelMap: image
  17.     };
  18.     let textConfiguration: textRecognition.TextRecognitionConfiguration = {
  19.       isDirectionDetectionSupported: false
  20.     };
  21.     textRecognition.recognizeText(visionInfo, textConfiguration, (error: BusinessError, data: textRecognition.TextRecognitionResult) => {
  22.       // 识别成功,获取结果
  23.       if (error.code == 0) {
  24.         let recognitionRes = data.value.toString();
  25.         // 将识别结果返回
  26.         resultCallback(recognitionRes);
  27.       }
  28.     });
  29.   }
  30. }
复制代码

  • 在ImageOCRUtil中实现从相册获取图片URI功能。
    这里需用到Core File Kit,可借助图片选择器获取图片的存储路径。
  1. import { picker } from '@kit.CoreFileKit';
  2. /**
  3.   * 打开相册选择图片
  4.   * @returns 异步返回图片URI
  5.   */
  6. static openAlbum(): Promise<string> {
  7.     return new Promise<string>((resolve, reject) => {
  8.       let photoPicker = new picker.PhotoViewPicker;
  9.       photoPicker.select({
  10.         MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE,
  11.         maxSelectNumber: 1
  12.       }).then((res: picker.PhotoSelectResult) => {
  13.         resolve(res.photoUris[0]);
  14.       }).catch((err: BusinessError) => {
  15.         hilog.error(0x0000, "OCR", `Failed to get photo uri, code: ${err.code}, message: ${err.message}`)
  16.         resolve('')
  17.       })
  18.     })
  19. }
复制代码
UI与调用

为了验证图片识别的效果,我们可以搭建简单的UI,提供从相册获取图片 -> 文字识别 -> 显示识别结果这一流程的UI与交互。
在Index页面中,UI相关的代码如下:
  1. import { image } from '@kit.ImageKit'
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. import { ImageOCRUtil } from '../common/utils/ImageOCRUtil';
  4. import { CommonUtils } from '../common/utils/CommonUtils';
  5. import { fileIo } from '@kit.CoreFileKit';
  6. @Entry
  7. @Component
  8. struct Index {
  9.   private imageSource: image.ImageSource | undefined = undefined;
  10.   @State selectedImage: PixelMap | undefined = undefined;
  11.   @State dataValues: string = '';
  12.   build() {
  13.     Column() {
  14.       // 选中的图片
  15.       Image(this.selectedImage)
  16.         .objectFit(ImageFit.Fill)
  17.         .height('60%')
  18.       // 识别的内容
  19.       Text(this.dataValues)
  20.         .copyOption(CopyOptions.LocalDevice)
  21.         .height('15%')
  22.         .width('60%')
  23.         .margin(10)
  24.       // 选择图片按钮
  25.       Button('选择图片')
  26.         .type(ButtonType.Capsule)
  27.         .fontColor(Color.White)
  28.         .width('80%')
  29.         .margin(10)
  30.         .onClick(() => {
  31.           this.selectImage();
  32.         })
  33.       Button('开始识别')
  34.         .type(ButtonType.Capsule)
  35.         .fontColor(Color.White)
  36.         .alignSelf(ItemAlign.Center)
  37.         .width('80%')
  38.         .margin(10)
  39.         .onClick(() => {
  40.             // 点击“开始识别”
  41.           });
  42.         })
  43.     }
  44.     .width('100%')
  45.     .height('100%')
  46.     .justifyContent(FlexAlign.Center)
  47.   }
  48.   private async selectImage() {
  49.     let uri = await ImageOCRUtil.openAlbum();
  50.     if (uri === undefined) {
  51.       hilog.error(0x0000, 'OCR', 'Failed to get the uri of photo.')
  52.       return;
  53.     }
  54.     this.loadImage(uri);
  55.   }
  56.   loadImage(path: string) {
  57.     setTimeout(async () => {
  58.       let fileSource = await fileIo.open(path, fileIo.OpenMode.READ_ONLY);
  59.       this.imageSource = image.createImageSource(fileSource.fd);
  60.       this.selectedImage = await this.imageSource.createPixelMap();
  61.     })
  62.   }
  63. }
复制代码
在“开始识别”的按钮的点击事件中,我们调用ImageOCRUtil的recognizeText,并在其回调中显示识别结果。
并对imageSource和selectedImage进行release()释放内存空间。
  1. ImageOCRUtil.recognizeText(this.selectedImage, (content: string) => {
  2.   if (!CommonUtils.isEmpty(content)) {
  3.     this.dataValues = content;
  4.   }
  5.   
  6.   // 释放内存空间
  7.   this.imageSource?.release();
  8.   this.selectedImage?.release();
  9. });
复制代码
其实现效果如下所示:
1.webp

双路预览

为了对文字识别这一功能进行扩展,我们可以结合相机的双路预览功能实时获取图片帧,并对图片帧进行文字识别。
我们创建一个XComponentPage的页面,添加一个相机预览视图。

  • 获取ImageReceiver组件的SurfaceId。
  1. async getImageReceiverSurfaceId(receiver: image.ImageReceiver): Promise<string | undefined> {
  2.     let ImageReceiverSurfaceId: string | undefined = undefined;
  3.     if (receiver !== undefined) {
  4.       console.info('receiver is not undefined');
  5.       let ImageReceiverSurfaceId: string = await receiver.getReceivingSurfaceId();
  6.       console.info(`ImageReceived id: ${ImageReceiverSurfaceId}`);
  7.     } else {
  8.       console.error('createImageReceiver failed');
  9.     }
  10.     return ImageReceiverSurfaceId;
  11.   }
复制代码

  • 创建XComponent组件Surface。
  1. XComponent({
  2.         // 组件的唯一标识
  3.         id: 'LOXComponent',
  4.         // surface:EGL/OpenGLES和媒体数据写入  component:开发者定制绘制内容
  5.         type: XComponentType.SURFACE,
  6.         // 应用Native层编译输出动态库名称,仅XComponent类型为"surface"时有效
  7.         libraryname: 'SingleXComponent',
  8.         // 给组件绑定一个控制器,通过控制器调用组件方法,仅XComponent类型为"surface"时有效
  9.         controller: this.mXComponentController
  10.       })// 插件加载完成时回调事件
  11.         .onLoad(() => {
  12.           // 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置
  13.           // 预览流与录像输出流的分辨率的宽高比要保持一致
  14.           this.mXComponentController.setXComponentSurfaceSize({ surfaceWidth: 1920, surfaceHeight: 1080 });
  15.           // 获取Surface ID
  16.           this.xComponentSurfaceId = this.mXComponentController.getXComponentSurfaceId();
  17.         })// 插件卸载完成时回调事件
  18.         .onDestroy(() => {
  19.         })
  20.         .width("100%")
  21.         .height(display.getDefaultDisplaySync().width * 9 / 16)
复制代码

  • 实现双路预览。
  1. import camera from '@ohos.multimedia.camera';
  2. async createDualChannelPreview(cameraManager: camera.CameraManager, XComponentSurfaceId: string, receiver: image.ImageReceiver): Promise<void> {
  3.     // 获取支持的相机设备对象
  4.     let camerasDevices: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
  5.     // 获取支持的模式类型
  6.     let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(camerasDevices[0]);
  7.     let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;
  8.     if (!isSupportPhotoMode) {
  9.       console.error('photo mode not support');
  10.       return;
  11.     }
  12.     // 获取profile对象
  13.     let profiles: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(camerasDevices[0], camera.SceneMode.NORMAL_PHOTO); // 获取对应相机设备profiles
  14.     let previewProfiles: Array<camera.Profile> = profiles.previewProfiles;
  15.     // 预览流1
  16.     let previewProfilesObj: camera.Profile = previewProfiles[0];
  17.     // 预览流2
  18.     let previewProfilesObj2: camera.Profile = previewProfiles[0];
  19.     // 创建 预览流1 输出对象
  20.     let previewOutput: camera.PreviewOutput = cameraManager.createPreviewOutput(previewProfilesObj, XComponentSurfaceId);
  21.     // 创建 预览流2 输出对象
  22.     let imageReceiverSurfaceId: string = await receiver.getReceivingSurfaceId();
  23.     let previewOutput2: camera.PreviewOutput = cameraManager.createPreviewOutput(previewProfilesObj2, imageReceiverSurfaceId);
  24.     // 创建cameraInput对象
  25.     let cameraInput: camera.CameraInput = cameraManager.createCameraInput(camerasDevices[0]);
  26.     // 打开相机
  27.     await cameraInput.open();
  28.     // 会话流程
  29.     let photoSession: camera.PhotoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
  30.     // 开始配置会话
  31.     photoSession.beginConfig();
  32.     // 把CameraInput加入到会话
  33.     photoSession.addInput(cameraInput);
  34.     // 把 预览流1 加入到会话
  35.     photoSession.addOutput(previewOutput);
  36.     // 把 预览流2 加入到会话
  37.     photoSession.addOutput(previewOutput2);
  38.     // 提交配置信息
  39.     await photoSession.commitConfig();
  40.     // 会话开始
  41.     await photoSession.start();
  42.   }
复制代码

  • 通过ImageReceiver实时获取预览图像。
  1. onImageArrival(receiver: image.ImageReceiver): void {
  2.   receiver.on('imageArrival', () => {
  3.     receiver.readNextImage((err: BusinessError, nextImage: image.Image) => {
  4.       if (err || nextImage === undefined) {
  5.         console.error('readNextImage failed');
  6.         return;
  7.       }
  8.       nextImage.getComponent(image.ComponentType.JPEG, async (err: BusinessError, imgComponent: image.Component) => {
  9.         if (err || imgComponent === undefined) {
  10.           console.error('getComponent failed');
  11.         }
  12.         if (imgComponent && imgComponent.byteBuffer as ArrayBuffer) {
  13.           let imageArrayBuffer = imgComponent.byteBuffer as ArrayBuffer;
  14.           console.log("得到图片数据:" + JSON.stringify(imageArrayBuffer))
  15.           console.log("图片数据长度:" + imageArrayBuffer.byteLength)
  16.          
  17.           //TODO:OCR识别
  18.         } else {
  19.           console.error('byteBuffer is null');
  20.         }
  21.         nextImage.release();
  22.       })
  23.     })
  24.   })
  25. }
复制代码
最后,我们对预览返回进行文字识别。预览返回的结果imageArrayBuffer的类型为ArrayBuffer,我们需要将其转换为PixelMap类,然后再调用recognizeText()识别。
  1. // 转换图片格式为PixelMap,并识别其中的文字
  2. let opts: image.InitializationOptions = {
  3.   editable: true,
  4.   pixelFormat: 3,
  5.   size: { height: 320, width: 320 }
  6. }
  7. image.createPixelMap(imageArrayBuffer, opts).then((pixelMap: image.PixelMap) => {
  8.   console.info('Succeeded in creating pixelmap.');
  9.   ImageOCRUtil.recognizeText(pixelMap, (res: string) => {
  10.     console.info("识别结果:" + res);
  11.   });
  12.   }).catch((error: BusinessError) => {
  13.     console.error('Failed to create pixelmap.');
  14. })
复制代码
这样,运行XComponentPage时,打开预览对准包含文字的物体,就可从Log中看到识别的文字信息。
完整代码见 -> hosgo-vision
拥抱鸿蒙,拥抱未来,选择远方,风雨兼程。
参考

  • 机器学习-基础视觉服务(ArkTS)
  • 指南-Core Vision Kit
  • 通用文字识别
  • 双路预览(ArkTS)

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册