Intégration de la Reconnaissance d'Écriture Manuscrite via Baidu OCR sur HarmonyOS PC

Justification du choix de Baidu OCR

Bien que HarmonyOS propose l'API textRecognition via @kit.CoreVisionKit pour une reconnaissance optique de caractères (OCR) locale, son utilisation en production révèle certaines limites. Les problèmes fréquents incluent des erreurs d'initialisation du service et des dépassements de délai (timeout) lors du traitement d'images de grande taille (PixelMap), même après redimensionnement.

À l'inverse, l'API de reconnaissance manuscrite de Baidu offre une stabilité supérieure, une optimisation spécifique pour l'écriture mnauelle et un quota gratuit quotidien suffisant pour les phases de développement. Bien qu'elle nécessite une connexion réseau, son temps de réponse est généralement constant (1 à 2 secondes).

Configuration des autorisations réseau

L'appel aux services cloud de Baidu exige l'autorisation Internte. Celle-ci doit être déclarée dans le fichier module.json5 :

{
  "module": {
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ]
  }
}

Sous HarmonyOS, cette permission est accordée au niveau du système dès sa déclaration, sans nécessiter de demande dynamique à l'exécution.

Implémentation du service d'OCR

Initialisation et gestion du jeton d'authentification

L'authentification auprès de l'API Baidu repose sur le protocole OAuth 2.0. Il est nécessaire d'échanger les clés de l'application contre un jeton d'accès, qui sera mis en cache pour optimiser les performances.

import { http } from '@kit.NetworkKit';
import { image } from '@kit.ImageKit';
import { util } from '@kit.ArkTS';

const CLE_API = 'votre_cle_api';
const CLE_SECRETE = 'votre_cle_secrete';
let jetonEnMemoire: string = '';

async function obtenirJetonAcces(): Promise<string> {
  if (jetonEnMemoire) {
    return jetonEnMemoire;
  }

  const pointEntree = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${CLE_API}&client_secret=${CLE_SECRETE}`;
  const clientHttp = http.createHttp();
  
  try {
    const reponse = await clientHttp.request(pointEntree, {
      method: http.RequestMethod.POST
    });
    const donnees = JSON.parse(reponse.result as string) as Record<string, string>;
    jetonEnMemoire = donnees['access_token'];
    return jetonEnMemoire;
  } finally {
    clientHttp.destroy();
  }
}

Conversion de l'image en Base64

L'API attend l'image encodée en Base64. L'utilisation du format JPEG avec une qualité de 90 % est privilégiée pour réduire la taille du payload tout en conservant une lisibilité optimale pour les traits manuscrits.

async function encoderImageEnBase64(imageSource: image.PixelMap): Promise<string> {
  const compresseur = image.createImagePacker();
  try {
    const tampon = await compresseur.packing(imageSource, {
      format: 'image/jpeg',
      quality: 90
    });
    
    const encodeur = new util.Base64Helper();
    const octets = new Uint8Array(tampon);
    return encodeur.encodeToStringSync(octets);
  } finally {
    compresseur.release();
  }
}

Extraction et traitement du texte

La fonction principale envoie l'image à l'API et traite la réponse JSON pour extraire les mots reconnus, en appliquant un filtrage pour ne conserver que les caractères latins.

export async function analyserEcritureAnglaise(canevas: image.PixelMap): Promise<string> {
  try {
    const imageBase64 = await encoderImageEnBase64(canevas);
    const jeton = await obtenirJetonAcces();
    const urlApi = `https://aip.baidubce.com/rest/2.0/ocr/v1/handwriting?access_token=${jeton}`;
    const corpsRequete = `image=${encodeURIComponent(imageBase64)}`;

    const clientHttp = http.createHttp();
    try {
      const reponse = await clientHttp.request(urlApi, {
        method: http.RequestMethod.POST,
        header: { 'Content-Type': 'application/x-www-form-urlencoded' },
        extraData: corpsRequete
      });

      const json = JSON.parse(reponse.result as string) as Record<string, Object>;
      const blocsTexte = json['words_result'] as Array<Record<string, string>>;

      if (blocsTexte && blocsTexte.length > 0) {
        const motsNettoyes = blocsTexte
          .map(bloc => bloc['words'].replace(/[^a-zA-Z\s]/g, '').trim().toLowerCase())
          .filter(mot => mot.length > 0);

        const motsUniques = [...new Set(motsNettoyes)];
        return motsUniques.join(' ').trim();
      }
      return '';
    } finally {
      clientHttp.destroy();
    }
  } catch (erreur) {
    console.error('Échec de l\'analyse OCR:', JSON.stringify(erreur));
    return '';
  }
}

Intégration dans l'interface utilisateur

Pour capturer le contenu dessiné par l'utilisateur, on utilise l'API componentSnapshot afin de convertir le composant visuel en PixelMap, qui est ensuite transmis au service OCR.

import { componentSnapshot } from '@kit.ArkUI';
import { analyserEcritureAnglaise } from '../services/ServiceOCR';

async traiterReconnaissance() {
  if (this.enCoursDeTraitement || !this.motActuel) return;
  
  this.enCoursDeTraitement = true;
  this.messageRetour = 'Analyse en cours...';

  try {
    const capture: image.PixelMap = await componentSnapshot.get('zoneDessin');
    const texteExtrait = await analyserEcritureAnglaise(capture);
    
    this.texteReconnu = texteExtrait;
    this.validerReponse(texteExtrait);
  } catch (err) {
    this.messageRetour = 'Erreur de reconnaissance';
  } finally {
    this.enCoursDeTraitement = false;
  }
}

L'état de chargement est reflété dans l'interface via un indicateur de progression conditionnel, désactivant le bouton de soumission pendant la durée de la requête réseau.

Button() {
  Row({ space: 4 }) {
    if (this.enCoursDeTraitement) {
      LoadingProgress().width(14).height(14).color('#FFFFFF')
    }
    Text(this.enCoursDeTraitement ? 'Analyse...' : 'Reconnaître')
      .fontSize(13).fontColor('#FFFFFF')
  }
}
.enabled(!this.enCoursDeTraitement)

Traitement des données et gestion des erreurs

La réponse brute de l'API peut contenir des artefacts, des filigranes ou des erreurs de segmentation. Le processus de nettoyage consiste à normaliesr la casse, supprimer les caractères non alphabétiques et éliminer les doublons via une structure Set.

En cas d'échec, les codes d'erreur spécifiques de l'API (comme 110 pour un jeton invalide ou 17 pour un quota dépassé) doivent être interceptés pour fournir un retour visuel approprié à l'utilisateur, garantissant ainsi une expérience fluide même en cas de dégradation du service.

Étiquettes: HarmonyOS ArkTS BaiduOCR PixelMap componentSnapshot

Publié le 19 juin à 20h38