在日常的微信小程序开发中,我们经常会遇到需要提取图片中文字的场景,比如身份证识别、银行卡识别、名片提取或普通的文本扫描。过去,我们通常需要将图片上传到服务器,调用云端的 OCR API 来实现,这不仅增加了服务器的带宽和计算成本,还可能引发用户的隐私担忧。

好消息是,微信小程序原生提供了 VisionKit(视觉能力),让我们可以直接在 手机端(端侧) 离线运行计算机视觉算法!今天,我们就来结合官方文档和实际代码,详细盘点如何使用 VisionKit 的 OCR 模块实现高效的端侧文字识别。

为什么选择 VisionKit OCR?

  1. 零服务器成本:纯端侧计算,不需要将图片上传到云端,大幅降低后台 API 调用成本。
  2. 保护隐私:数据不离开用户设备,对于包含敏感信息(如身份证号、手机号)的图片尤为重要。
  3. 极速响应:省去了网络传输的耗时,识别速度极快,用户体验极佳。

核心实现思路

要在小程序中使用 VisionKit 进行 OCR,核心流程可以总结为三步:

  • 获取图片:让用户从相册选择或拍摄一张图片。
  • 提取像素数据:VisionKit 底层引擎需要的是原始的像素内存数据(frameBuffer),而不是简单的图片路径。我们需要借助离屏画布(OffscreenCanvas)将图片转换为 ArrayBuffer。
  • 调用引擎识别:将准备好的 frameBuffer 喂给 VisionKit Session 进行文字识别。

代码实战解析

下面我们结合一段实际的 Component 组件代码,一步步拆解如何实现。

1. 基础配置与图片选择

首先,我们需要提供一个触发器,让用户选择图片,并获取图片的原始宽高信息。这对于后续的 Canvas 绘制至关重要。

Component({
  behaviors: [getBehavior(), yuvBehavior],
  data: {
    faceImgUrl: '',
    faceImgOriginWidth: 0,
    faceImgOriginHeight: 0,
    theme: 'light',
  },
  
  methods: {
    // 步骤一:选择图片
    chooseMedia() {
      wx.chooseMedia({
        count: 1,
        mediaType: ['image'],
        success: res => {
          const imgUrl = res.tempFiles[0].tempFilePath
          
          // 获取图片真实宽高
          wx.getImageInfo({
            src: imgUrl,
            success: res => {
              const { width, height } = res
              this.setData({
                faceImgUrl: imgUrl,
                faceImgOriginWidth: width,
                faceImgOriginHeight: height
              })
            },
            fail: err => console.error('获取图片信息失败', err)
          })
        }
      })
    },
    // ... 其他方法
  }
})
避坑指南:一定要使用 wx.getImageInfo 获取图片的真实物理像素大小(widthheight)。很多开发者直接使用屏幕展示的逻辑像素大小,会导致后续喂给 OCR 引擎的图片被严重拉伸或模糊,从而导致识别率暴跌。

2. 将图片转换为 FrameBuffer (核心步骤)

这是很多新手最容易卡住的地方。VisionKit 的 runOCR 方法不接收图片 URL,它需要的是图片的二进制像素数据。这里我们使用了小程序的 离屏画布(OffscreenCanvas) 来完成这项硬核工作。

  methods: {
    // ... 前置代码
    
    async runOCR() {
      if (!this.data.faceImgUrl) return;
      
      // 1. 创建离屏 Canvas
      const canvas = wx.createOffscreenCanvas({
        type: '2d',
        width: this.data.faceImgOriginWidth,
        height: this.data.faceImgOriginHeight,
      })
      const context = canvas.getContext('2d')
      
      // 2. 加载图片对象
      const img = canvas.createImage()
      await new Promise(resolve => {
        img.onload = resolve
        img.src = this.data.faceImgUrl
      })

      // 3. 将图片绘制到 Canvas 上
      context.clearRect(0, 0, this.data.faceImgOriginWidth, this.data.faceImgOriginHeight)
      context.drawImage(img, 0, 0, this.data.faceImgOriginWidth, this.data.faceImgOriginHeight)

      // 4. 获取图片的像素数据 (ImageData)
      this.imgData = context.getImageData(0, 0, this.data.faceImgOriginWidth, this.data.faceImgOriginHeight)

      console.log('[frameBuffer] 准备完毕 --> ', this.imgData.data.buffer)
      
      // 5. 调用 VisionKit 进行 OCR 识别
      if (this.session && this.session.runOCR) {
        this.session.runOCR({
          frameBuffer: this.imgData.data.buffer, // 传入 ArrayBuffer
          width: this.data.faceImgOriginWidth,
          height: this.data.faceImgOriginHeight,
        })
      }
    }
  }

原理解析:

  • 我们先创建了一个与图片实际宽高 1:1 等大的离屏 Canvas。
  • 利用 canvas.createImage() 加载本地图片路径,并在图片 onload 之后将其画到 Canvas 上。
  • 通过 context.getImageData() 提取画面上的所有像素点,其返回值的 .data.buffer 就是一个包含 RGBA 排列的 ArrayBuffer。这就是 VisionKit 梦寐以求的 frameBuffer

3. 处理识别结果

在上面的代码中,我们调用了 this.session.runOCR()。注意,这里的 this.session 通常是在组件初始化(如 init 生命周期或绑定的 behavior)时,通过 wx.createVKSession 创建的 VisionKit 实例。

调用 runOCR 后,引擎会在后台进行计算。你需要在 session 的回调函数中监听并获取包含文本内容、包围盒(Bounding Box)等坐标信息的识别结果。

性能与优化建议

  • 图片尺寸控制:如今手机拍摄的图片动辄几千万像素,直接转换成 ArrayBuffer 会占用极大的运行内存。建议在 wx.getImageInfo 之后,按比例对图片进行降采样(缩放),比如将宽高限制在 720p 级别,既能保证识别率,又能极大提升处理速度并节省内存。
  • 异步与 Loading 状态:图片转码和 OCR 运算都需要消耗一定 CPU 资源。处理大图时务必在 UI 层面上加上 wx.showLoading 提示,避免用户觉得应用卡死。
  • 资源释放:使用完毕后,记得调用 this.session.destroy() 释放底层的视觉引擎资源,防止内存泄漏。

总结

利用微信小程序原生 VisionKit 提供的 OCR 能力,我们可以非常优雅地在前端闭环完成复杂的文本识别需求。结合 OffscreenCanvas 进行像素提取,不仅拓宽了端侧 AI 算法的输入方式,也为更高级的 AR 和视觉处理打下了基础。希望这篇文章能帮你快速跑通小程序端的 OCR 功能!

参考资料与致谢