头图
作者:狼哥
团队:坚果派
团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原生应用,三方库60+,欢迎交流。

介绍

​ 本案例旨在介绍一种创新的图像识别与语音合成技术,专注于将图片中的文字内容精准识别并转化为可听的语音输出。通过集成先进的OCR(光学字符识别)技术和TTS(文本到语音)转换技术,本方案能够迅速捕捉图片中的文字信息,无论是文档扫描、书籍页面还是路标指示,都能实现高效准确的识别。随后,利用智能语音合成技术,将识别出的文字流畅地朗读出来,为视觉障碍人士、阅读不便者以及需要高效信息获取的用户提供极大便利。该技术不仅拓宽了信息获取的渠道,还极大地提升了信息处理的效率和用户体验,是现代智能科技助力生活品质提升的典型应用。

效果预览

知识点

1. Picker(选择器)
2. textRecognition(文字识别)
3. textToSpeech (文本转语音)

工程目录

├──entry/src/main/ets                         // 代码区
│  ├──entryability
│  │  └──EntryAbility.ets 
│  ├──pages
│  │  └──Index.ets                            // 首页
│  └──utils
│     ├──ImageUtils.ets                       // 图片操作
│     └──Speaker.ets                            // 文字朗读操作
└──entry/src/main/resources                   // 应用资源目录

具体实现

下面介绍一下如何实现识别图片文字,并朗读识别出来的文字,我习惯把逻辑功能放到单独的文件里,在界面上调用,这样UI和逻辑分开,从工程目录可以看出,Index.ets文件是负责UI的,ImageUtils.ets文件里包含两个funtion,一个是getChooseImage从图库选择图片并返回,一个是readImage2Text把参数图片里的文字识别出来并返回,Speaker.ets文件是文字转语音的逻辑,包含创建对象,调用播放、暂停接口。

1. 图片选择

选择器(Picker)是一个封装PhotoViewPicker、DocumentViewPicker、AudioViewPicker等API模块,具有选择与保存的能力。应用可以自行选择使用哪种API实现文件选择和文件保存的功能。该类接口,需要应用在界面UIAbility中调用,否则无法拉起photoPicker应用或FilePicker应用。

export function getChooseImage():Promise<PixelMap> {
  return new Promise((resolve, reject) => {
    let imageSource: image.ImageSource | undefined = undefined;
    let chooseImage: PixelMap | undefined = undefined;
    // 将图片转换为PixelMap,可以通过图库获取
    let photoPicker = new picker.PhotoViewPicker();
    photoPicker.select({
      MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE, maxSelectNumber: 1
    }).then((res: picker.PhotoSelectResult) => {
      let uri = res.photoUris[0];
      if (uri === undefined) {
        console.error('OCRDemo', "Failed to get uri.");
        return;
      }
      setTimeout(async () => {
        let fileSource = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
        imageSource = image.createImageSource(fileSource.fd);
        chooseImage = await imageSource.createPixelMap();
        console.info('OCRDemo', `chooseImage Success`);
        resolve(chooseImage);

      }, 100)
    }).catch((err: BusinessError) => {
      console.error('OCRDemo', `Failed to get photo image uri. Code:${err.code},message:${err.message}`);
    }).finally(() => {
      if(chooseImage && imageSource) {
        chooseImage.release();
        imageSource.release();
      }
    })
  });
}
2. 识图文字

通用文字识别服务提供图像信息转换为字符信息的能力。通过拍照、扫描等光学输入方式,把各种票据、卡证、表格、报刊、书籍等印刷品文字转化为图像信息,再利用文字识别技术将图像信息转化为计算机等设备可以使用的字符信息,便于用户提取字符内容、屏幕坐标及外框。目前本服务支持识别的语言有:简体中文、英文、日文、韩文、繁体中文五种语言。

export function readImage2Text(img: PixelMap):Promise<string> {
  return new Promise((resolve, reject) => {
    if (!img) {
      promptAction.showToast({message: '请先选择图片,才能识别图片哦!'})
      return;
    }

    // 调用文本识别接口
    let visionInfo: textRecognition.VisionInfo = {
      pixelMap: img
    };
    setTimeout(async () => {
      let recognitionResult = await textRecognition.recognizeText(visionInfo);

      // 识别成功,获取对应的结果
      let recognitionString = recognitionResult.value;
      console.info('OCRDemo', `Succeeded in recognizing text:${recognitionString}`);

      resolve(recognitionString);
    },100);
  });
}
3. 朗读文字

文本转语音服务提供将文本信息转换为语音并进行播报的能力,便于用户与设备进行互动,实现实时语音交互,文本播报。

export class Speaker {
  ttsEngine?: textToSpeech.TextToSpeechEngine;
  speakListener?: textToSpeech.SpeakListener;

  extraParam: Record<string, Object> = {
    "queueMode": 0,   // 播报模式,不传参时默认为0。0为排队模式播报,1为抢占模式播报
    "speed": 1,       // 合成音频播报时的语速,支持范围[0.5-2],不传参时默认为1
    "volume": 2,      // 合成音频播报时的音量,支持范围[0-2],不传参时默认为1
    "pitch": 1,       // 合成音频播报时的音调,支持范围[0.5-2],不传参时默认为1
    "languageContext": 'zh-CN',
    "audioType": "pcm",
    "soundChannel": 3,  // 通道,参数范围0-16,整数类型,可参考音频流使用类型介绍来选择适合自己的音频场景。不传参时默认为3,语音助手通道
    "playType": 1       // 合成类型,不传参时默认为1。当该参数为0时表示仅合成不播报,返回音频流,为1时表示合成与播报不返回音频流
  };
  initParamsInfo: textToSpeech.CreateEngineParams = {
    language: 'zh-CN',    // 语种 (如zh-CN为中文)
    person: 0,            // 音色(如0为聆小珊女声音色)
    online: 1,            // 模式(在线、离线等)
    extraParams: {"style": 'interaction-broadcast', "locate": 'CN', "name": 'EngineName'} // 额外参数
  };
  speakParams: textToSpeech.SpeakParams = {
    requestId: Date.now().toString(),
    extraParams: this.extraParam
  };
  constructor() {
    this.initListener()
    this.createEngine()
  }
  /**
   * 初始化监听
   */
  initListener() {
    this.speakListener = {
      // 开始播报回调
      onStart(requestId: string, response: textToSpeech.StartResponse) {
      },
      // 合成完成及播报完成回调
      onComplete(requestId: string, response: textToSpeech.CompleteResponse) {
        if (response.type === 1) {
          emitter.emit("eventId");
        }
      },
      // 停止播报回调
      onStop(requestId: string, response: textToSpeech.StopResponse) {
        if (response.type === 1) {
          emitter.emit("eventId");
        }
      },
      // 返回音频流
      onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {
      },
      // 错误回调
      onError(requestId: string, errorCode: number, errorMessage: string) {
      }
    };
  }
  /**
   * 创建Engine
   */
  createEngine() {
    try {
      textToSpeech.createEngine(this.initParamsInfo,
        (err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {
          if (!err) {
            this.ttsEngine = textToSpeechEngine;
            this.ttsEngine.setListener(this.speakListener);
          } else {
          }
        });
    } catch (error) {
      let message = (error as BusinessError).message;
      let code = (error as BusinessError).code;
    }
  }
  /**
   * 播报参数内容
   * @param content
   */
  startSpeak(content: string) {
    this.createEngine();
    this.ttsEngine?.speak(content, this.speakParams);
  }
  /**
   * 停止播报
   */
  stopSpeak() {
    this.shutdownEngine();
    this.ttsEngine?.stop();
  }
  /**
   * 销毁引擎实例
   */
  shutdownEngine() {
    this.ttsEngine?.shutdown();
  }
}
4. 界面布局

界面布局为垂直布局,最上面是两个按钮,水平布局,下来是选择图片后,预览图片,再下来是从图片识别出来的文字。

@Entry
@Component
struct Index {
  @State chooseImage: PixelMap | undefined = undefined;
  @State recognitionString: string = '';
  private speaker: Speaker = new Speaker();
  /**
   * 选择图片
   */
  async onChooseImage() {
    this.chooseImage = await getChooseImage();
  }
  /**
   * 识别图片
   */
  async onRecognition() {
    if (this.chooseImage) {
      this.recognitionString = await readImage2Text(this.chooseImage);
      // 朗读识别出来的文字
      this.speaker.startSpeak(this.recognitionString)
    }
  }
  build() {
    Column({ space: 10 }) {
      Row() {
        Button('选择图片')
          .width(100)
          .onClick(() => {
            this.onChooseImage()
          })
        Button('识字朗读')
          .width(100)
          .onClick(() => {
            this.onRecognition()
          })
      }
      .width('100%')
      .height(50)
      .justifyContent(FlexAlign.SpaceEvenly)
      Divider()
      Image(this.chooseImage)
        .width('100%')
        .objectFit(ImageFit.Contain)
      Divider()
      Text(this.recognitionString)
        .lineHeight(25)
        .width('100%')
        .fontSize(18)
        .backgroundColor('#ffd6d3d3')
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Center)
  }
}

约束与限制

1.本示例仅支持标准系统上运行,支持设备:华为手机。

2.HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上。

3.DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上。

4.HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上。


狼哥
1 声望0 粉丝