#HarmonyOS NEXT 体验官#打破文字边界:探索通用文字识别技术的魅力与应用 原创 精华

因为活着就一定行
发布于 2024-8-14 17:29
浏览
1收藏

能力介绍与使用场景

通用文字识别技术,简单来说,就是通过拍摄或扫描的方式,将票据、证件、表格、报纸、书籍等印刷品上的文字转化为图像信息。然后,这项技术会利用先进的文字识别算法,将这些图像信息进一步转化为计算机和其他设备能够理解和处理的字符信息。
这项技术的应用范围非常广泛。例如,你可以对文档、街景等进行翻拍,然后利用它来检测和识别图片中的文字。此外,这项技术还可以集成到其他应用程序中,提供文字检测和识别功能。根据识别结果,它还能提供翻译、搜索等相关服务。
无论是来自相机、图库还是其他来源的图像数据,这项技术都能处理。它具备自动检测文本、识别图像中文本位置以及文本内容的功能,这是一种开放的能力。
值得一提的是,这项技术在处理文本时具有很强的适应性。无论是文本倾斜、拍摄角度倾斜,还是复杂的光照条件和文本背景,它都能在这些特定场景下实现精准的文字识别。

图识文字-库读

开发步骤

要顺利使用通用文字识别技术,请遵循以下步骤进行设置和配置:

  1. 模块JSON中注册能力标识信息:在 src/main/module.json5 配置requestPermissions节点中,标识当前应用需要的能力集合
"requestPermissions": [
  {
    "name": "ohos.permission.CAMERA",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "inuse"
    },
    "reason": "CAMERA"
  },
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  1. 添加相关类至工程文件:首先,将负责文字识别的相关类导入到你的工程项目中。
// 导入文本识别工具类
import { textRecognition } from '@kit.CoreVisionKit'
  • 1.
  • 2.
  1. 配置用户界面布局:简单地设计一个用户界面,包括为按钮组件添加点击事件,以便用户可以选择图片。
Button('选择图片')
  .type(ButtonType.Capsule)
  .fontColor(Color.White)
  .alignSelf(ItemAlign.Center)
  .width('80%')
  .margin(10)
  .onClick(() => {
    // 拉起图库,获取图片资源
    this.selectImage();
  })
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  1. 获取并转换图片资源:当用户从图库中选取一张图片后,获取该图片并将其转换成PixelMap格式。
private async selectImage() {
  let uri = await this.openPhoto();
  if (uri === undefined) {
    hilog.error(0x0000, 'StoreRead', "Failed to get uri.");
    return;
  }
  this.loadImage(uri);
}

private openPhoto(): Promise<string> {
  return new Promise<string>((resolve) => {
    let photoPicker = new picker.PhotoViewPicker();
    photoPicker.select({
      MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE,
      maxSelectNumber: 1
    }).then((res: picker.PhotoSelectResult) => {
      resolve(res.photoUris[0]);
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, 'StoreRead', `Failed to get photo image uri. code:${err.code},message:${err.message}`);
      resolve('');
    })
  })
}

private loadImage(name: string) {
  setTimeout(async () => {
    let imageSource: image.ImageSource | undefined = undefined;
    let fileSource = await fileIo.open(name, fileIo.OpenMode.READ_ONLY);
    imageSource = image.createImageSource(fileSource.fd);
    this.chooseImage = await imageSource.createPixelMap();
  }, 100)
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  1. 创建VisionInfo实例:实例化一个VisionInfo对象,并将包含待识别文字的PixelMap传入此对象。请注意,VisionInfo仅支持PixelMap类型的视觉信息。
let visionInfo: textRecognition.VisionInfo = {
  pixelMap: this.chooseImage
};
  • 1.
  • 2.
  • 3.
  1. 设置文本识别配置:配置TextRecognitionConfiguration,这影响是否启用图片的朝向检测等选项。
let textConfiguration: textRecognition.TextRecognitionConfiguration = {
  isDirectionDetectionSupported: false
};
  • 1.
  • 2.
  • 3.
  1. 执行文字识别处理:调用textRecognition.recognizeText方法,并根据返回的结果码进行处理—成功时返回0,失败则返回相应的错误码。该方法提供几种不同的调用方式,这里展示其中一种作为示例,其他方式参考官网API文档。
    确保按照这些步骤操作,可以有效地实现通用文字识别功能。
textRecognition.recognizeText(visionInfo, textConfiguration, (error: BusinessError, data: textRecognition.TextRecognitionResult) => {
  if (error.code !== 0) {
    hilog.error(0x0000, 'StoreRead', `Failed to recognize text. Code: ${error.code}, message: ${error.message}`);
    return;
  }
  // 识别成功,获取对象的结果
  let recognitionString = JSON.stringify(data);
  hilog.info(0x0000, 'StoreRead', `Succeeded in recognizing text:${recognitionString}`);

  // 将结果更新到Text中显示
  this.dataValues = data.value;

  if(this.chooseImage && this.imageSource) {
    this.chooseImage.release();
    this.imageSource.release();
  }
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

PixelMap 对象

为了方便处理图像数据,我们引入了一个名为图像像素的类别。这个类别主要用于读取或写入图像数据,以及获取图像的相关信息。在开始使用图像像素类别的功能之前,我们需要通过createPixelMap的方法来创建一个新的PixelMap实例。
目前,PixelMap的序列化大小是有限制的,最大不能超过128MB。如果超过这个大小,程序会显示失败。计算大小的公式是:图像的宽度乘以高度再乘以每个像素点所占用的字节数。
从API版本11开始,PixelMap支持跨线程调用。也就是说,你可以在不同的线程中调用PixelMap的方法,这大大提高了程序的效率。但是,一旦你通过Worker将PixelMap跨线程后,原来线程中的PixelMap的所有接口都不能调用,否则会出现501错误,意味着服务器暂时不具备完成请求的功能。
在使用图像像素类别的方法前,我们需要通过image.createPixelMap的方法来构建一个PixelMap对象。这个对象就是我们进行图像处理的关键。

VisionInfo 对象

在处理视觉信息进行识别时,目前只接受颜色数据格式为rgba_8888的pixelmap类型。这种格式能够提供丰富的颜色信息,确保识别过程的准确性和效率。
recognizaText 对象的三种调用形式

  1. recognizeText(visionInfo: VisionInfo, callback: AsyncCallback<TextRecognitionResult>): void
  • 识别视觉信息内包含的文本。使用Callback方式异步返回结果。
  • 参数情况
    • visionInfo:待识别的视觉信息;
    • 回调函数,返回文字识别的对象;
  1. recognizeText(visionInfo: VisionInfo, configuration?: TextRecognitionConfiguration): Promise<TextRecognitionResult>
  • 识别视觉信息内包含的文本。使用Promise方式异步返回结果。
  • 参数情况
    • visionInfo:待识别的视觉信息;
    • configuration:识别的配置项;
    • 返回值:Promise对象,返回文字识别的结果对象;
  1. recognizeText(visionInfo: VisionInfo, configuration: TextRecognitionConfiguration, callback: AsyncCallback<TextRecognitionResult>): void
  • 识别视觉信息内包含的文本。使用Callback方式异步返回结果。
  • 参数情况
    • visionInfo:待识别的视觉信息;
    • configuration:识别的配置项;
    • callback:识别结果的回调,可以用于界面显示或交互;

完整实例代码

// Main UI and logic code 

// 导入文本识别工具类
import { textRecognition } from '@kit.CoreVisionKit'
// 导入图片处理工具类
import { image } from '@kit.ImageKit';
// 导入性能分析工具类
import { hilog } from '@kit.PerformanceAnalysisKit';
// 导入业务错误处理类
import { BusinessError } from '@kit.BasicServicesKit';
// 导入文件选择器和文件输入输出工具类
import { picker, fileIo } from '@kit.CoreFileKit';


@Entry
@Component
struct Index {
  // 定义一个私有变量imageSource,联合类型为 image.ImageSource或undefined
  private imageSource: image.ImageSource | undefined = undefined;

  // 使用@State装饰器声明一个名为chooseImage的状态变量,联合类型为 PixelMap 或 undefined
  @State chooseImage: PixelMap | undefined = undefined;

  // 使用@State装饰器声明一个名为 dataValues 的状态变量,类型为字符串
  @State dataValues: string = '';

  build() {
    Column() {
      Image(this.chooseImage)
        .objectFit(ImageFit.Fill)
        .height('60%')

      Text(this.dataValues.length > 0 ? `识别的文字:${this.dataValues}` : '')
        .copyOption(CopyOptions.LocalDevice)
        .height('15%')
        .margin(10)
        .width('60%')
        .copyOption(CopyOptions.LocalDevice)
        .height('15%')
        .margin(10)
        .width('60%')

      Button('选择图片')
        .type(ButtonType.Capsule)
        .fontColor(Color.White)
        .alignSelf(ItemAlign.Center)
        .width('80%')
        .margin(10)
        .onClick(() => {
          // 拉起图库,获取图片资源
          this.selectImage();
        })

      Button('开始识别')
        .type(ButtonType.Capsule)
        .fontColor(Color.White)
        .alignSelf(ItemAlign.Center)
        .width('80%')
        .margin(10)
        .onClick(async () => {
          this.textRecognitionTest();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  private textRecognitionTest() {
    if (!this.chooseImage) {
      return;
    }
    // 调用文本识别接口
    let visionInfo: textRecognition.VisionInfo = {
      pixelMap: this.chooseImage
    };
    let textConfiguration: textRecognition.TextRecognitionConfiguration = {
      isDirectionDetectionSupported: false
    };
    textRecognition.recognizeText(visionInfo, textConfiguration)
      .then((data: textRecognition.TextRecognitionResult) => {
        // 识别成功,获取对象的结果
        let recognitionString = JSON.stringify(data);
        hilog.info(0x0000, 'OCRDemo', `Succeeded in recognizing text:${recognitionString}`);
        // 将结果更新到Text中显示
        this.dataValues = data.value;
        if(this.chooseImage && this.imageSource) {
          this.chooseImage.release();
          this.imageSource.release();
        }
      })
      .catch((error: BusinessError) => {
        hilog.error(0x0000, 'OCRDemo', `Failed to recognize text. Code: ${error.code}, message: ${error.message}`);
        this.dataValues = `Error: ${error.message}`;
      });
  }
  
  // 异步选择图片方法
  private async selectImage() {
    let uri = await this.openPhoto(); // 调用打开图片的方法,获取图片的URI
    if (uri === undefined) { // 如果获取不到图片的URI,则输出错误日志并返回
      hilog.error(0x0000, 'StoreRead', "Failed to get uri.");
      return;
    }
    this.loadImage(uri); // 加载图片
  }

  // 打开图片的方法,返回Promise对象
  private openPhoto(): Promise<string> {
    return new Promise<string>((resolve) => {
      let photoPicker = new picker.PhotoViewPicker(); // 创建图片选择器对象
      photoPicker.select({ // 设置选择图片的条件
        MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE, // 只允许选择图片类型
        maxSelectNumber: 1 // 最多选择一个图片
      }).then((res: picker.PhotoSelectResult) => { // 成功选择图片后的回调函数
        resolve(res.photoUris[0]); // 返回选中的图片的URI
      }).catch((err: BusinessError) => { // 选择图片失败后的回调函数
        hilog.error(0x0000, 'OCRDemo', `Failed to get photo image uri. Code:${err.code},message:${err.message}`); // 输出错误日志
        resolve(''); // 返回空字符串
      })
    })
  }

  // 加载图片的方法
  private loadImage(name: string) {
    setTimeout(async () => { // 使用setTimeout延迟执行,避免阻塞UI线程
      let fileSource = await fileIo.open(name, fileIo.OpenMode.READ_ONLY); // 打开文件
      this.imageSource = image.createImageSource(fileSource.fd); // 创建图片源
      this.chooseImage = await this.imageSource.createPixelMap(); // 创建像素映射
    }, 100)
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.

实现效果

#HarmonyOS NEXT 体验官#打破文字边界:探索通用文字识别技术的魅力与应用-鸿蒙开发者社区
#HarmonyOS NEXT 体验官#打破文字边界:探索通用文字识别技术的魅力与应用-鸿蒙开发者社区

图识文字-扫读

开发步骤

要顺利使用通用文字识别技术,请遵循以下步骤进行设置和配置:

  1. 模块JSON中注册能力标识信息:在 src/main/module.json5 配置requestPermissions节点中,标识当前应用需要的能力集合
"requestPermissions": [
  {
    "name": "ohos.permission.CAMERA",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "inuse"
    },
    "reason": "CAMERA"
  },
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  1. 添加相关类至工程文件:首先,将负责图像识别、文字识别和支撑能力的相关类导入到你的工程项目中。
// 引入相机处理库
import { camera } from '@kit.CameraKit';
// 引入文本识别工具类
import { textRecognition } from '@kit.CoreVisionKit';
  • 1.
  • 2.
  • 3.
  • 4.
  1. 配置用户界面布局:简单地设计一个用户界面,包括扫描显示区域和为按钮组件添加点击事件,以便设备识别场景图片。
Row() {
      Column() {
        Row() {
          XComponent({
            id: 'xcomponent1',
            type: 'surface',
            controller: this.xcomponentController
          })
            .onLoad(async () => {
              await this.camera.releaseCamera();
              this.XComponentinit()
            })
            .width('100%')
            .height(this.xcomponentHeight)
        }
        .width('100%')
        .margin({ top: 30 })
        .flexGrow(1)

        Column() {
          Text('识别文字')
            .fontSize('14fp')
            .fontColor(Color.White)
            .margin({ top: 16 })
          Row()
            .backgroundColor("#0A59F7")
            .width(6)
            .height(6)
            .border({
              radius: 3
            })
            .margin({
              top: 3,
              bottom: 20
            })
          Row() {
            Row()
              .backgroundColor(Color.White)
              .width(60)
              .height(60)
              .border({
                radius:37
              })
          }
          .onClick(async () => {
            await this.camera.takePicture()
          })
          .backgroundColor(Color.Black)
          .width(76)
          .height(76)
          .border({
            color: Color.White,
            width: 1,
            radius: 37
          })
          .justifyContent(FlexAlign.Center)
          .alignItems(VerticalAlign.Center)
        }
        .width('100%')
        .flexShrink(0)
        .height($r('app.float.camera_lower_height'))
        .backgroundColor(Color.Black)
        .alignItems(228)
      }
      .width('100%')
      .height('100%')
      .backgroundColor(Color.Black)
    }
    .height('100%')
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  1. 调用摄像头能力,实现图像场景扫描:当用户打开应用后,自动调用摄像头,将读取到的场景展示到应用显示的区域。
// 释放场景化能力相关数据
async releaseCamera(): Promise<void> {
  // 如果存在相机输入(this.cameraInput),则关闭它并记录日志。
  if (this.cameraInput) {
    await this.cameraInput.close();
    Logger.info(TAG, 'cameraInput release');
  }
  // 如果存在预览输出(this.previewOutput),则释放它并记录日志。
  if (this.previewOutput) {
    await this.previewOutput.release();
    Logger.info(TAG, 'previewOutput release');
  }
  // 如果存在接收器(this.receiver),则释放它并记录日志。
  if (this.receiver) {
    await this.receiver.release();
    Logger.info(TAG, 'receiver release');
  }
  // 如果存在照片输出(this.photoOutput),则释放它并记录日志。
  if (this.photoOutput) {
    await this.photoOutput.release();
    Logger.info(TAG, 'photoOutput release');
  }
  // 如果存在捕获会话(this.captureSession),则释放它并记录日志,然后将捕获会话设置为未定义。
  if (this.captureSession) {
    await this.captureSession.release();
    Logger.info(TAG, 'captureSession release');
    this.captureSession = undefined;
  }
  // 将图像接收器(this.imgReceive)设置为未定义。
  this.imgReceive = undefined;
}

// 图形绘制和媒体数据的初始化
 async XComponentinit() {
    // 设置XComponent持有Surface的显示区域,仅XComponent类型为SURFACE("surface")或TEXTURE时有效。
    this.xcomponentController.setXComponentSurfaceRect({
      surfaceWidth: 1200,
      surfaceHeight: 2000
    });
    // 获取XComponent对应Surface的ID,供@ohos接口使用,用于摄像头进程的管理
    this.surfaceId = this.xcomponentController.getXComponentSurfaceId();
    // 初始化场景化相关数据
    await this.initCamera(this.surfaceId);
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.

完整实例代码

// Main UI and logic code 
// 引入设备屏幕工具类
import { DeviceScreen } from '../common/utils/DeviceScreen'
// 引入相机工具类
import Camera from '../common/utils/Camera'
// 引入自定义对话框视图组件
import { CustomDialogExample } from '../view/CustomDialogView'
// 引入图片处理库
import { image } from '@kit.ImageKit';
// 引入相机处理库
import { camera } from '@kit.CameraKit';
// 引入通用功能库
import { common } from '@kit.AbilityKit';
// 引入业务错误处理类
import { BusinessError } from '@kit.BasicServicesKit';
// 引入日志工具类
import Logger from '../common/utils/Logger';
// 引入文本识别工具类
import { textRecognition } from '@kit.CoreVisionKit';


const TAG: string = '[IndexPage]';

@Entry
  @Component
  struct Index {
    // 定义一个私有状态变量,用于存储识别结果
    @State private recognitionResult: string = '';
    // 监听watchedCamera属性的变化,并在变化时更新camera状态
    @Watch('watchedCamera') @State private camera: Camera = new Camera();
    // 定义surfaceId变量,用于存储表面ID
    private surfaceId: string = '';
    // 创建一个XComponentController实例
    private xcomponentController = new XComponentController();
    // 获取设备屏幕高度
    private screenHeight = DeviceScreen.getDeviceHeight();
    // 计算xcomponent的高度
    private xcomponentHeight = this.screenHeight - 264;
    // 定义相机管理器、相机设备、相机输出能力、相机输入、预览输出、图像接收器和照片输出的可选类型变量
    private cameraMgr?: camera.CameraManager;
    private cameraDevice?: camera.CameraDevice;
    private capability?: camera.CameraOutputCapability;
    private cameraInput?: camera.CameraInput;
    public previewOutput?: camera.PreviewOutput;
    private receiver?: image.ImageReceiver;
    private photoOutput?: camera.PhotoOutput;
    // 定义捕获会话的可选类型变量
    public captureSession?: camera.PhotoSession;
    // 监听watchedCamera属性的变化,并在变化时更新result状态
    @Watch('watchedCamera') @State result: string = '';
    // 定义imgReceive函数类型的可选变量
    private imgReceive?: Function;

    // 定义一个异步函数initCamera,接收一个字符串类型的surfaceId参数
    async initCamera(surfaceId: string) {
      // 获取相机管理器实例
      this.cameraMgr = camera.getCameraManager(getContext(this) as common.UIAbilityContext);
      // 获取所有相机设备列表
      let cameraArray = this.getCameraDevices(this.cameraMgr);
      // 选择第一个相机设备作为输入设备
      this.cameraDevice = cameraArray[0];
      // 获取相机输入实例
      this.cameraInput = this.getCameraInput(this.cameraDevice, this.cameraMgr) as camera.CameraInput;
      // 打开相机输入
      await this.cameraInput.open();
      // 获取相机支持的输出能力
      this.capability = this.cameraMgr.getSupportedOutputCapability(this.cameraDevice, camera.SceneMode.NORMAL_PHOTO);
      // 获取预览输出实例
      this.previewOutput = this.getPreviewOutput(this.cameraMgr, this.capability, surfaceId) as camera.PreviewOutput;
      // 获取照片输出实例
      this.photoOutput = this.getPhotoOutput(this.cameraMgr, this.capability) as camera.PhotoOutput;
      // 监听照片输出的'photoAvailable'事件
      this.photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => {
        // 获取全质量图Image,支持基本的图像操作,包括获取图像信息、读写图像数据。
        let imageObj = photo.main;
        // 根据图像的组件类型从图像中获取组件缓存并使用callback返回结果。
        imageObj.getComponent(image.ComponentType.JPEG, async (errCode: BusinessError, component: image.Component) => {
          // 如果发生错误或组件未定义,则返回
          if (errCode || component === undefined) {
            return;
          }
          // 定义一个ArrayBuffer类型的变量buffer
          let buffer: ArrayBuffer;
          // 将组件的字节缓冲区赋值给buffer
          buffer = component.byteBuffer
          // 调用recognizeImage方法处理buffer,并将结果赋值给this.result
          this.result = await this.recognizeImage(buffer);
        })
      })

      // Session Init
      this.captureSession = this.getCaptureSession(this.cameraMgr) as camera.PhotoSession;
      this.beginConfig(this.captureSession);
      this.startSession(this.captureSession, this.cameraInput, this.previewOutput, this.photoOutput);
    }

    // 定义一个名为watchedCamera的方法
    watchedCamera() {
      // 检查this.result是否与this.recognitionResult不同
      if (this.result !== this.recognitionResult) {
        // 如果不同,则将this.recognitionResult更新为this.result的值
        this.recognitionResult = this.result;
        // 检查this.recognitionResult是否有值(即不为空或未定义)
        if (this.recognitionResult) {
          // 如果有值,则调用this.dialogController的open方法打开对话框
          this.dialogController.open();
        }
      }
    }

    // 当页面即将消失时调用此方法
    async aboutToDisappear() {
      // 释放相机资源
      await this.releaseCamera();
      // 关闭对话框
      this.dialogController.close();
    }

    // 当页面显示时调用此方法
    onPageShow() {
      // 初始化XComponent组件
      this.XComponentinit();
    }

    // 当页面隐藏时调用此方法
    onPageHide() {
      // 释放相机资源
      this.releaseCamera();
      // 关闭对话框
      this.dialogController.close();
    }

    // XComponent 组件控制器初始化
    async XComponentinit() {
      // 设置XComponent持有Surface的显示区域,仅XComponent类型为SURFACE("surface")或TEXTURE时有效。
      this.xcomponentController.setXComponentSurfaceRect({
        surfaceWidth: 1200,
        surfaceHeight: 2000
      });
      // 获取XComponent持有的Surface的ID
      this.surfaceId = this.xcomponentController.getXComponentSurfaceId();
      // 初始化相机并传入surfaceId
      await this.initCamera(this.surfaceId);
    }

    // 刷新方法,用于启动捕获会话
    async refresh() {
      this.captureSession!.start();
    }

    // 创建一个自定义组件,实现对话框控制器实例,传入一个配置对象
    dialogController: CustomDialogController = new CustomDialogController({
      // 设置对话框的内容构建器,使用CustomDialogExample组件,并传入一个对象,该对象包含text属性,值为recognitionResult
      builder: CustomDialogExample({
        text: this.recognitionResult,
      }),
      // 设置对话框的取消回调函数为refresh方法
      cancel: this.refresh
    })

    build() {
      Row() {
        Column() {
          Row() {
            XComponent({
              id: 'xcomponent1',
              type: 'surface',
              controller: this.xcomponentController
            })
              .onLoad(async () => {
                await this.releaseCamera();
                this.XComponentinit()
              })
              .width('100%')
              .height(this.xcomponentHeight)
          }
          .width('100%')
            .margin({ top: 30 })
            .flexGrow(1)

          Column() {
            Text('识别文字')
              .fontSize('14fp')
              .fontColor(Color.White)
              .margin({ top: 16 })
            Row()
              .backgroundColor("#0A59F7")
              .width(6)
              .height(6)
              .border({
                radius: 3
              })
              .margin({
                top: 3,
                bottom: 20
              })
            Row() {
              Row()
                .backgroundColor(Color.White)
                .width(60)
                .height(60)
                .border({
                  radius: 37
                })
            }
            .onClick(async () => {
              await this.takePicture()
            })
              .backgroundColor(Color.Black)
              .width(76)
              .height(76)
              .border({
                color: Color.White,
                width: 1,
                radius: 37
              })
              .justifyContent(FlexAlign.Center)
              .alignItems(VerticalAlign.Center)
          }
          .width('100%')
            .flexShrink(0)
            .height($r('app.float.camera_lower_height'))
            .backgroundColor(Color.Black)
            .alignItems(228)
        }
        .width('100%')
          .height('100%')
          .backgroundColor(Color.Black)
      }
      .height('100%')
    }

    // 定义一个函数,用于获取支持的相机设备列表
    getCameraDevices(cameraManager: camera.CameraManager): Array<camera.CameraDevice> {
      // 使用cameraManager的getSupportedCameras方法获取支持的相机设备数组
      let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
      // 判断获取到的相机设备数组是否有效(不为undefined且长度大于0)
      if (cameraArray != undefined && cameraArray.length > 0) {
        // 如果有效,则返回相机设备数组
        return cameraArray;
      } else {
        // 如果无效,则记录错误日志并返回空数组
        Logger.error(TAG, `getSupportedCameras faild`);
        return [];
      }
    }


    // 定义一个函数,用于获取相机输入
    getCameraInput(cameraDevice: camera.CameraDevice,
                   cameraManager: camera.CameraManager): camera.CameraInput | undefined {
      // 初始化相机输入为undefined
      let cameraInput: camera.CameraInput | undefined = undefined;
      // 使用相机管理器创建相机输入
      cameraInput = cameraManager.createCameraInput(cameraDevice);
      // 返回相机输入
      return cameraInput;
    }

    // 定义一个函数,用于获取预览输出

    getPreviewOutput(cameraManager: camera.CameraManager, cameraOutputCapability: camera.CameraOutputCapability,
                     surfaceId: string): camera.PreviewOutput | undefined {
      // 获取预览配置文件数组
      let previewProfilesArray: Array<camera.Profile> = cameraOutputCapability.previewProfiles;
      // 初始化预览输出为undefined
      let previewOutput: camera.PreviewOutput | undefined = undefined;
      // 使用相机管理器创建预览输出
      previewOutput =
        cameraManager.createPreviewOutput(previewProfilesArray[5], surfaceId);
      // 返回预览输出
      return previewOutput;
    }

    // 定义一个函数,用于获取照片输出
    getPhotoOutput(cameraManager: camera.CameraManager,
                   cameraOutputCapability: camera.CameraOutputCapability): camera.PhotoOutput | undefined {
      // 获取照片配置文件数组
      let photoProfilesArray: Array<camera.Profile> = cameraOutputCapability.photoProfiles;
      // 打印日志信息
      Logger.info(TAG, JSON.stringify(photoProfilesArray));
      // 如果照片配置文件数组为空或未定义,则打印日志信息
      if (!photoProfilesArray) {
        Logger.info(TAG, `createOutput photoProfilesArray == null || undefined`);
      }
      // 初始化照片输出为undefined
      let photoOutput: camera.PhotoOutput | undefined = undefined;
      try {
        // 使用相机管理器创建照片输出
        photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[5]);
      } catch (error) {
        // 如果创建照片输出失败,则打印错误日志信息
        Logger.error(TAG, `Failed to createPhotoOutput. error: ${JSON.stringify(error as BusinessError)}`);
      }
      // 返回照片输出
      return photoOutput;
    }

    // 定义一个异步函数,用于识别图像中的文本
    async recognizeImage(buffer: ArrayBuffer): Promise<string> {
      // 使用传入的ArrayBuffer创建一个图像资源对象
      let imageResource = image.createImageSource(buffer);
      // 创建一个像素映射实例
      let pixelMapInstance = await imageResource.createPixelMap();
      // 设置视觉信息对象,包含像素映射实例
      let visionInfo: textRecognition.VisionInfo = {
        pixelMap: pixelMapInstance
      };
      // 设置文本识别配置对象,支持方向检测
      let textConfiguration: textRecognition.TextRecognitionConfiguration = {
        isDirectionDetectionSupported: true
      };
      // 初始化识别结果字符串
      let recognitionString: string = '';
      // 检查设备是否支持文本识别功能
      if (canIUse("SystemCapability.AI.OCR.TextRecognition")) {
        // 如果支持,调用文本识别方法并处理结果
        await textRecognition.recognizeText(visionInfo, textConfiguration).then((TextRecognitionResult) => {
          // 如果识别结果为空,则获取无法识别的提示信息
          if (TextRecognitionResult.value === '') {
            let context = getContext(this) as common.UIAbilityContext
            recognitionString = context.resourceManager.getStringSync($r('app.string.unrecognizable').id);
          } else {
            // 否则,将识别结果赋值给识别字符串
            recognitionString = TextRecognitionResult.value;
          }
        })
        // 释放像素映射实例和图像资源对象
        pixelMapInstance.release();
        imageResource.release();
      } else {
        // 如果设备不支持文本识别功能,获取不支持设备的提示信息
        let context = getContext(this) as common.UIAbilityContext
        recognitionString = context.resourceManager.getStringSync($r('app.string.Device_not_support').id);
        Logger.error(TAG, `device not support`);
      }
      // 返回识别结果字符串
      return recognitionString;
    }

    // 定义一个函数getCaptureSession,用于获取相机捕获会话实例
    getCaptureSession(cameraManager: camera.CameraManager): camera.PhotoSession | undefined {
      let captureSession: camera.PhotoSession | undefined = undefined;
      try {
        // 尝试创建一个捕获会话实例
        captureSession = cameraManager.createSession(1) as camera.PhotoSession;
      } catch (error) {
        // 如果创建失败,记录错误日志
        Logger.error(TAG,
                     `Failed to create the CaptureSession instance. error: ${JSON.stringify(error as BusinessError)}`);
      }
      return captureSession;
    }

    // 定义一个函数beginConfig,用于开始配置捕获会话
    beginConfig(captureSession: camera.PhotoSession): void {
      try {
        // 尝试开始配置捕获会话
        captureSession.beginConfig();
        Logger.info(TAG, 'captureSession beginConfig')
      } catch (error) {
        // 如果配置失败,记录错误日志
        Logger.error(TAG, `Failed to beginConfig. error: ${JSON.stringify(error as BusinessError)}`);
      }
    }

    // 定义一个异步函数startSession,用于启动相机捕获会话
    async startSession(captureSession: camera.PhotoSession, cameraInput: camera.CameraInput, previewOutput:
                       camera.PreviewOutput, photoOutput: camera.PhotoOutput): Promise<void> {
      // 将cameraInput添加到captureSession中
      captureSession.addInput(cameraInput);
      // 将previewOutput添加到captureSession中
      captureSession.addOutput(previewOutput);
      // 将photoOutput添加到captureSession中
      captureSession.addOutput(photoOutput);
      // 提交配置并处理成功和失败的情况
      await captureSession.commitConfig().then(() => {
        Logger.info(TAG, 'Promise returned to captureSession the session start success.')
      }).catch((err: BusinessError) => {
        Logger.info(TAG, 'captureSession error')
        Logger.info(TAG, JSON.stringify(err))
      });
      // 启动捕获会话并处理成功和失败的情况
      await captureSession.start().then(() => {
        Logger.info(TAG, 'Promise returned to indicate the session start success.')
      }).catch((err: BusinessError) => {
        Logger.info(TAG, JSON.stringify(err))
      })
    }

    async releaseCamera(): Promise<void> {
      // 如果存在相机输入(this.cameraInput),则关闭它并记录日志。
      if (this.cameraInput) {
        await this.cameraInput.close();
        Logger.info(TAG, 'cameraInput release');
      }
      // 如果存在预览输出(this.previewOutput),则释放它并记录日志。
      if (this.previewOutput) {
        await this.previewOutput.release();
        Logger.info(TAG, 'previewOutput release');
      }
      // 如果存在接收器(this.receiver),则释放它并记录日志。
      if (this.receiver) {
        await this.receiver.release();
        Logger.info(TAG, 'receiver release');
      }
      // 如果存在照片输出(this.photoOutput),则释放它并记录日志。
      if (this.photoOutput) {
        await this.photoOutput.release();
        Logger.info(TAG, 'photoOutput release');
      }
      // 如果存在捕获会话(this.captureSession),则释放它并记录日志,然后将捕获会话设置为未定义。
      if (this.captureSession) {
        await this.captureSession.release();
        Logger.info(TAG, 'captureSession release');
        this.captureSession = undefined;
      }
      // 将图像接收器(this.imgReceive)设置为未定义。
      this.imgReceive = undefined;
    }

    /**
 * 异步拍照方法
 */
    async takePicture() {
      this.result = ''; // 初始化结果为空字符串
      this.photoOutput!.capture(); // 调用拍照功能
    }
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.
  • 421.
  • 422.
  • 423.
  • 424.
  • 425.
  • 426.
  • 427.
  • 428.
  • 429.
// Component Dialog code
@CustomDialog
export struct CustomDialogExample {
  text: string = "";
  cancel: () => void = () => {
  };
  private scroller: Scroller = new Scroller();
  controller: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample({
      text: this.text
    }),
    maskColor: "rgba(0, 0, 0, 0.2)",
    cancel: this.cancel
  })

  build() {
    Column() {
      Text('识别结果')
        .height(56)
        .width('100%')
        .fontSize('20fp')
        .fontColor('#182431')
        .padding({
          top: 14,
          bottom: 14,
          left: 24
        })
        .font({
          weight: 500
        })
      Column() {
        Scroll(this.scroller) {
          Text(this.text)
            .width('100%')
            .fontSize('16fp')
            .lineHeight(33)
            .textAlign(TextAlign.Start)
            .padding({
              left: 24,
              right:24,
              bottom: 33
            })
            .copyOption(CopyOptions.LocalDevice)
        }
        .width('100%')
      }
      .width('100%')
      .height(407)
    }
    .width('100%')
    .height(463)
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
// DeviceScreen model code
import { display } from '@kit.ArkUI';

export class DeviceScreen {
  public static getDeviceHeight(): number {
    let displayObject = display.getDefaultDisplaySync();
    let screenPixelHeight = displayObject.height;
    let screenDensityDPI = displayObject.densityDPI;
    return screenPixelHeight * (160 / screenDensityDPI);
  }

  public static getDeviceWidth(): number {
    let displayObject = display.getDefaultDisplaySync();
    let screenPixelWidth = displayObject.width;
    let screenDensityDPI = displayObject.densityDPI;
    return screenPixelWidth * (160 / screenDensityDPI);
  }
}
{

  "string":[
    {
      "name": "unrecognizable",
      "value": "暂未识别到文字信息,请重新拍摄"
    },
    {
      "name": "Device_not_support",
      "value": "Sorry, your current device does not support OCR"
    }
  ]
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
// element/string
{
  "string":[
    {
      "name": "unrecognizable",
      "value": "暂未识别到文字信息,请重新拍摄"
    },
    {
      "name": "Device_not_support",
      "value": "Sorry, your current device does not support OCR"
    }
  ]
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

实现

#HarmonyOS NEXT 体验官#打破文字边界:探索通用文字识别技术的魅力与应用-鸿蒙开发者社区

#HarmonyOS NEXT 体验官#打破文字边界:探索通用文字识别技术的魅力与应用-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-8-15 00:29:59修改
3
收藏 1
回复
举报
3
1
1
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

这篇的技术很先进

回复
2024-8-14 18:57:50


回复
    相关推荐