Teknisk artikkel

HotPDF kinesisk og flerspråklig OCR med RapidOCR i Delphi

HotPDF utfører kinesisk og flerspråklig OCR i Delphi gjennom sin native RapidOCR DLL-adapter: THPDFRapidOCRDLLOptions.ForLanguage mapper en språk-tag som 'zh-CN', 'zh-TW', 'ru' eller 'ar' til en matchende gjenkjenningsmodell og tegnordbok, og THotPDF.ApplyLoadedOCRTextLayer gjør de gjenkjente linjene om til et usynlig, søkbart Unicode-tekstlag på skannede PDF-sider

Å få en latinsk skrift-demo til å virke, er den lette delen. De interessante feilene starter når du bytter til tradisjonell kinesisk eller russisk, og utgangen blir til selvsikker, velformet vrøvl, eller når hver linje i stillhet mister sitt siste tegn, eller når en arabisk side kommer tilbake med tekstboksene i feil rekkefølge. Ingen av delene reiser et unntak av seg selv. Språkforhåndsinnstillingene lagt til i HotPDF v2.775.0 finnes mest for å lukke de gapene, og de fire fellene nedenfor er verdt å forstå selv om du aldri rører den native koden, for hver enkelt forklarer et symptom du ellers kunne brukt en dag på å jage

Hvordan velger ForLanguage en modell og ordbok?

THPDFRapidOCRDLLOptions.ForLanguage løser en tag til én av ni profiler og returnerer alternativer som peker på <profile>/recognition.onnx og <profile>/dictionary.txt under modellkatalogen din, mens den beholder den delte detektoren, den valgfrie vinkelklassifisereren, og tråd-, piksel- og tidsavbrudd-standardene fra THPDFRapidOCRDLLOptions.Default. Metoden gjør tag-en om til små bokstaver, gjør understreker om til bindestreker og trimmer omgivende mellomrom, så 'zh_TW', 'ZH-tw' og ' zh-tw ' lander alle på samme profil. Aliaser er en eksplisitt liste i stedet for et prefiksmatch: 'zh-Hant-TW' godtas fordi den er listet, mens en vilkårlig regional variant som ikke er listet, reiser EArgumentException før noen modell lastes

HotPDF ForLanguage profil-løsning for THPDFRapidOCRDLLOptions: tagger som zh_TW, ZH-tw og zh-TW normaliseres og matches mot ni listete profiler, som hver fester en gjenkjenningsmodell og ordbok som alltid settes sammen, mens en ulistet tag reiser EArgumentException før noen modell lastes
Én tag velger ett festet modell-ordbok-par; detektoren, klassifisereren og budsjettene forblir delte, og en ukjent tag feiler raskt i stedet for å laste noe
ProfilSpråkEksempeltaggerFestet modell
chForenklet kinesisk og engelskzh, zh-CN, zh-Hans, chi_simPP-OCRv4
chinese_chtTradisjonell kinesiskzh-TW, zh-HK, zh-Hant, chi_traPP-OCRv3
enEngelsken, en-US, en-GB, engPP-OCRv4
latinFransk, tysk, spansk, portugisisk, italiensk, nederlandsk, tyrkiskfr, de, es-419, pt-BR, trPP-OCRv3
japanJapanskja, ja-JP, jpnPP-OCRv4
koreanKoreanskko, ko-KR, korPP-OCRv4
cyrillicRussisk, ukrainsk, bulgarsk, hviterussiskru, ru-RU, uk, bgPP-OCRv3
arabicArabisk, persisk, urduar, ar-SA, fa, urPP-OCRv4
devanagariHindi, marathi, nepalihi, mr, nePP-OCRv4

Adapteren selv laster aldri ned noe. Du foreslår filene én gang med den medfølgende hjelperen, for eksempel tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (eller -Language All for alle ni profiler), og hjelperen plasserer en delt detektor og klassifikator ved rotfilnavnene Default forventer. Etter det blir en forenklet kinesisk skann søkbar med noen få linjer. Motor-rørleggingen er samme IHPDFOCREngine-søm beskrevet i artikkelen om in-process RapidOCR DLL-en og dens ABI-grense, så denne holder seg fokusert på språk

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

procedure MakeChineseScanSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Models: THPDFRapidOCRDLLOptions;
  Layer: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // ch/recognition.onnx + ch/dictionary.txt, delt detektor og klassifikator
  Models := THPDFRapidOCRDLLOptions.ForLanguage('zh-CN');
  Engine := HPDFCreateRapidOCRDLLOCREngine(
    'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models', Models);
  Doc := THotPDF.Create(nil);
  try
    Doc.AutoLaunch := False;
    if Doc.LoadFromFile(SourceFile) < 1 then
      raise Exception.Create('Cannot load ' + SourceFile);
    Layer := THPDFOCRTextLayerOptions.Default;  // 300 DPI, MinimumConfidence 0.5
    // en tom sideliste betyr hver side; sider som allerede har tekst hoppes over
    if not Doc.ApplyLoadedOCRTextLayer([], Engine, Layer, Info) then
      raise Exception.Create(string(Info.Diagnostic));
    Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
      ' lines, ', Info.UniqueScalarCount, ' distinct characters');
    Doc.SaveLoadedDocument(TargetFile);
  finally
    Doc.Free;
  end;
end;

To detaljer i den utgangen fortjener en merknad. Den native pipelinen returnerer ett resultat per detektert tekstlinje, ikke per ord, så AcceptedWordCount teller linjer her, og MinimumConfidence sammenlignes mot hele linjens gjennomsnittlige tegnkonfidens: en linje som gjennomsnittlig ligger på 0.45, slippes som en enhet. UniqueScalarCount rapporterer hvor mange distinkte Unicode-skalarer tekstlaget måtte mappe inn i sin font- og ToUnicode-tabell, en nyttig fornuftssjekk på at CJK-tekst faktisk ankom i stedet for en neve latinske fallbacks. Hold motor-grensesnittet i live på tvers av dokumenter, for modellinitialisering skjer i fabrikken og er det kostbare steget

Hvorfor gir det å bytte bare gjenkjenningsmodellen vrøvl?

En CTC-gjenkjenningsmodell gir aldri ut tegn, bare klasseindekser, og ordboken er det eneste som gjør indeks 1 204 om til et glyf. Bytt ch/recognition.onnx mot cyrillic/recognition.onnx men behold den kinesiske ordboken, og modellen vil gjerne emitte gyldige kyrilliske indekser som den gamle ordboken oversetter til tilfeldige Han-tegn. Resultatet ser ut som tekst, passerer UTF-8-validering, og er søkbart for nøyaktig ingenting. Det er derfor ForLanguage alltid setter RecognitionModel og CharacterDictionary sammen, og hvorfor håndbygde alternativer aldri bør endre den ene uten den andre

Den åpenbare sikkerhetssjekken, å sammenligne ordbokens størrelse med modellutgangens bredde, er nødvendig men ikke tilstrekkelig. To ordbøker kan ha samme antall oppføringer i en annen rekkefølge, og en avvik-på-én i rekkefølgen forskjøver hvert tegn med ett kodepunkt. HotPDF sjekker derfor i to steg når fabrikken initialiserer modellen. Først må utgangsklasseantallet være lik ordboksoppføringene pluss to. For det andre, når ONNX-filen innbaker en character-metadataliste, sammenlignes hver ordboksoppføring med den i rekkefølge, og et avvik feiler initialiseringen med EInvalidOperation og en native diagnostikk i stedet for å produsere troverdig vrøvl senere

«Pluss to» kommer fra klasseoppsettet. Klasse 0 er CTC-blanken, klassene 1 til N er ordboklinjene i filrekkefølge, og siste klasse er et mellomrom. Noen ordbøker bærer også sin egen mellomromsoppføring, og den linjen må beholdes nøyaktig som den er. Det er der en velment Trim gjør skikkelig skade: den gjør en enkelt-mellomrom-oppføring om til en tom streng og forskjøver eller knuser tabellen. Den eneste normaliseringen som er trygg, er å fjerne en etterfølgende carriage return, så en ordbok lagret med CRLF-linjeslutt lastes korrekt, mens et UTF-8 byte order mark, en tom linje, eller en oppføring som inneholder en tab, avvises. Skissen nedenfor viser oppsettet i Pascal; det er forklarende kode, ikke et HotPDF-API

HotPDF CTC-klasetabell-oppsett for RapidOCR-ordbøker: klasse 0 er blanken, klassene 1 til N er ordboklinjene i filrekkefølge med enhver enslig mellomromsoppføring beholdt, og siste klasse er et mellomrom, noe som gir N pluss 2 utgangsklasser fabrikken verifiserer mot modellen, metadata inkludert
Ordboken er det eneste som gjør klasseindekser om til tegn, så størrelsen, rekkefølgen og mellomromsoppføringen verifiseres før én eneste side gjenkjennes
// Kun illustrasjon: klasstabellen en CTC-gjenkjenner forventer
uses
  SysUtils, IOUtils;

function BuildCTCClassTable(const FileName: string): TArray<string>;
var
  Text, Entry: string;
  Lines: TArray<string>;
  I, Last: Integer;
begin
  Text := TEncoding.UTF8.GetString(TFile.ReadAllBytes(FileName));
  if (Text <> '') and (Text[1] = #$FEFF) then
    raise EArgumentException.Create('Dictionary must be UTF-8 without a BOM');
  Lines := Text.Split([#10]);
  Last := High(Lines);
  if (Last >= 0) and (Lines[Last] = '') then
    Dec(Last);                                   // linjeskift på slutten av filen
  SetLength(Result, Last + 3);
  Result[0] := '';                               // klasse 0: CTC blank
  for I := 0 to Last do
  begin
    Entry := Lines[I];
    if (Entry <> '') and (Entry[Length(Entry)] = #13) then
      SetLength(Entry, Length(Entry) - 1);       // CRLF: dropp bare CR
    if (Entry = '') or (Pos(#9, Entry) > 0) then
      raise EArgumentException.Create('Invalid dictionary entry');
    Result[I + 1] := Entry;                      // aldri Trim: ' ' er en klasse
  end;
  Result[Last + 2] := ' ';                       // siste klasse: mellomrom
  // Length(Result) må være lik modellens utgangsklasseantall
end;

Hva gjør greedy CTC-dekoding egentlig?

Greedy CTC-dekoding velger klassen med høyest poengsum ved hvert tidssteg, slår sammen etterfølgende repetisjoner til ett tegn, og dropper blankklassen; blanken er det som lar genuint doblede bokstaver overleve. En gjenkjenningsmodell ser på en tekstlinje som en sekvens av smale vertikale skiver, og for hver skive, eller tidssteg, gir den ut en sannsynlighet for hver klasse. En linje som inneholder AA中 kan produsere argmax-sekvensen A A blank A 中 space. Å slå sammen de to første A-stegene gir én A, blanken skiller den fra neste A, og resultatet er AA中 med det etterfølgende mellomrommet intakt. Uten blankregelen ville book og bok vært uatskillelige

HotPDF GreedyCTCDecode-gjennomgang: seks tidssteg stemmer argmax-klassene A, A, blank, A, et Han-tegn og mellomrom, etterfølgende repetisjoner slås sammen, blanken nullstiller repetisjonsvakten så en genuint dobbel bokstav overlever, og tre grensebuger slipper i stillhet ordmellomrom, siste tegn, eller doblede tegn
Dekoderen er et dusin linjer, og hver grense betyr noe: ta med siste klasse, ta med siste steg, og la bare en blank skille repetisjoner

Siden dekoderen bare er et dusin linjer, er det lett å få grensene galt, og feilene er stille. Stopper den indre argmax-løkken én klasse før, kan mellomromsklassen aldri vinne, og hver linje kommer tilbake uten ordmellomrom, noe som knuser frasesøk på engelske og latinske sider. Stopper den ytre løkken ett tidssteg før, forsvinner siste tegn i hver linje, noe som for en kort linje kan være en tredjedel av teksten. Og hvis repetisjonsvakten ikke nullstilles av en blank, kollapser doblede tegn som ll eller kinesiske reduplikasjoner som 谢谢 til én. HotPDF-dekoderen inkluderer siste klasse og siste tidssteg, beholder blank-separerte repetisjoner, og avviser i tillegg poengsummer som ikke er endelige eller faller utenfor 0 til 1, og ethvert klasseantall som ikke matcher ordboken. Her er samme logikk som en Pascal-illustrasjon

// Kun illustrasjon: greedy CTC-dekoding med korrekte grenser.
// Scores holder Steps * Classes sannsynligheter, én rad per tidssteg
function GreedyCTCDecode(const Scores: array of Single;
  Steps, Classes: Integer; const Characters: array of string): string;
var
  Step, C, Best, Previous: Integer;
  BestScore: Single;
begin
  if (Classes < 3) or (Length(Characters) <> Classes) or
    (Length(Scores) <> Steps * Classes) then
    raise EArgumentException.Create('Model output does not match the dictionary');
  Result := '';
  Previous := 0;                            // klasse 0 er CTC-blanken
  for Step := 0 to Steps - 1 do             // ta med siste tidssteg
  begin
    Best := 0;
    BestScore := Scores[Step * Classes];
    for C := 1 to Classes - 1 do            // ta med siste klasse (mellomrom)
      if Scores[Step * Classes + C] > BestScore then
      begin
        Best := C;
        BestScore := Scores[Step * Classes + C];
      end;
    if (Best <> 0) and (Best <> Previous) then
      Result := Result + Characters[Best];
    Previous := Best;                       // en blank nullstiller repetisjonsvakten
  end;
end;

Greedy-dekoding er ikke den mest nøyaktige CTC-strategien tilgjengelig; beam search med en språkmodell kan fikse noen tvetydige skiver. For trykte dokumenter ved 300 DPI er det greedy-resultatet som regel det modellen har å by på, og dekoderen er ikke stedet å kompensere for modellsvakheter. Den latinske PP-OCRv3-modellen kan for eksempel lese ñ som n selv på ren input. HotPDF parkerer ikke det over med etterbehandlings-tegnerstattninger, for en substitusjonstabell som fikser spansk, knuser noe annet, og et feil tegn i et søkbart lag er verre enn et ærlig bom

Hvordan ordner HotPDF tekstlinjer, inkludert høyre-til-venstre arabisk?

HotPDF sorterer detekterte tekstbokser ovenfra og ned, grupperer bokser i en rad når de overlapper vertikalt med minst halvparten av den minste bokshøyden, og ordner hver rad venstre til høyre, eller høyre til venstre når RightToLeft er påskrudd; tegnene inne i hver gjenkjent linje reverseres aldri. Grupperingen betyr noe fordi en detektor ofte splitter én visuell linje i flere bokser, for eksempel en etikett og en verdi skilt av et bredt gap, og en ren toppkoordinat-sortering ville flettet dem inn med nabolinjen når som helst toppene deres avviker med en piksel eller to

Den arabiske forhåndsinnstillingen setter RightToLeft := True, som forteller DLL-en å ordne boksene i hver rad etter høyre kant, fra høyremargen og innover. Det er hele effekten. Teksten modellen returnerer for en linje er allerede i Unicode logisk rekkefølge, rekkefølgen en arabisk leser leser og skriver den i, og det er også rekkefølgen PDF-tekstekstraksjon og søk forventer. Å mekanisk reversere strengen for å få den til å «se riktig ut» i en debugger ville knuse søk, kopier og lim inn, og skjermlesere. Toveis visning og glyff-forming er viewerens jobb

Én motor betjener én språkprofil. Det finnes ingen automatisk skriptdeteksjon, så et dokument som blander skript trenger én motor per profil, anvendt på sidene som bruker den. Siden ApplyLoadedOCRTextLayer tar en eksplisitt sideliste og committer hvert kall som sin egen alt-eller-ingenting-transaksjon, er det rett frem

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
  Models: THPDFRapidOCRDLLOptions;
begin
  // reiser EArgumentException for en ukjent tag, før noen modell lastes
  Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
  Models.MaxPixels := 33554432;          // plass til A3-sider ved 300 DPI
  Result := HPDFCreateRapidOCRDLLOCREngine(
    'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models', Models);
end;

procedure OCRMixedArchive(Doc: THotPDF);
var
  Chinese, Arabic: IHPDFOCREngine;
  Layer: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  Chinese := CreateRapidEngine('zh-TW');  // chinese_cht-profil
  Arabic := CreateRapidEngine('ar-SA');   // arabic-profil, RightToLeft = True
  Layer := THPDFOCRTextLayerOptions.Default;
  if not Doc.ApplyLoadedOCRTextLayer([0, 1, 2], Chinese, Layer, Info) then
    raise Exception.Create(string(Info.Diagnostic));
  if not Doc.ApplyLoadedOCRTextLayer([3], Arabic, Layer, Info) then
    raise Exception.Create(string(Info.Diagnostic));
end;

MaxPixels-linjen er der av en grunn. DLL-alternativene faller tilbake på 16 777 216 piksler per forespørsel, som dekker A4 og US Letter ved 300 DPI komfortabelt, men en A3-side ved 300 DPI er omtrent 3508 ganger 4961 piksler, rundt 17,4 millioner, og forespørselen avvises som over budsjett. Hev MaxPixels (taket er 67 108 864) eller senk THPDFOCRTextLayerOptions.DPI for store formater. Høyre-til-venstre-ordning bruker den valgfrie HPDFRapidOCRSetReadingDirection-eksporten av ABI versjon 1; adapteren krever den bare når RightToLeft er satt, så en eldre DLL fortsetter å betjene venstre-til-høyre-språk og feiler ved motoroppretting med en EArgumentException som navngir den manglende eksporten for arabisk

Hvorfor feiler nyere OCR-modeller å laste?

HotPDF RapidOCR DLL-en lenker en statisk ONNX Runtime 1.14, som ikke kan lese modeller lagret med ONNX IR versjon 10, og nyere eksporter som PP-OCRv5-modeller kan kreve en nyere runtime enn det; en slik modell feiler ved motoroppretting med en native diagnostikk. Den begrensningen er grunnen til at språkpakkene er festet til spesifikke PP-OCRv3- og PP-OCRv4-gjenkjenner- og ordbok-par i stedet for «siste», og hvorfor tabellen over blander de to generasjonene: hvert festet par er ett som lastes og verifiseres under den runtime-en

Installereren håndhever paringen. Hver fil i manifestet dens bærer en SHA256-hash, en eksisterende fil med en annen hash stopper installasjonen i stedet for å bli overskrevet, og hver nedlasting lander under et midlertidig navn og flyttes bare på plass etter at hashen matcher. Det beskytter mot den stille versjonen av ordbok-problemet: noen slipper en nyere recognition.onnx inn i en profilmappe for hånd, klasseantallet tilfeldigvis matcher, og ingenting feiler før en kunde rapporterer at søket ikke finner ord de åpenbart kan se. Ved kjøretid forblir adapteren offline og henter aldri en manglende modell. Gjenkjenneren validerer også modellformen ved lasting, og godtar NCHW-input med en fast høyde på 32 eller 48 piksler eller en dynamisk høyde, som den kjører ved 48

Trenger du et skript ingen av de ni profilene dekker, kan du fortsatt peke RecognitionModel og CharacterDictionary mot dine egne filer. Samme sjekker gjelder, noe som er poenget: et feilparret par feiler ved initialisering, ikke i kundens arkiv. For sider der ingen RapidOCR-profil passer, kobler Tesseract-adapteren for søkbar PDF seg inn i samme ApplyLoadedOCRTextLayer-kall, og for maskinskrivne ASCII-skjemaer trenger den innebygde mal-sammenlignings-OCR-motoren ingen modeller i det hele tatt

Hurtigreferanse: flerspråklig RapidOCR-sjekkliste

  • Opprett alternativer med THPDFRapidOCRDLLOptions.ForLanguage og behandle EArgumentException som en ustøttet tag, ikke en runtime-feil
  • Endre RecognitionModel og CharacterDictionary sammen, aldri én alene; like klasseantall beviser ikke lik tegnrekkefølge
  • Hold ordbøker som UTF-8 uten BOM, trim aldri oppføringer, og forvent at modellen har N + 2 klasser: blank, N oppføringer, mellomrom
  • En custom CTC-dekoder må dekke siste klasse og siste tidssteg og holde repetisjoner skilt av en blank
  • Bruk én motor per språkprofil og send eksplisitte sidelister for dokumenter med blandede skript
  • RightToLeft endrer bare boksrekkefølge; gjenkjent tekst forblir i Unicode logisk rekkefølge
  • Installer modeller med Install-RapidOCRModels.ps1 slik at SHA256-festinger holder modell- og ordbok-paring; sett UseAngleClassifier := False hvis du installerte med -SkipClassifier
  • Hev MaxPixels over standardverdien 16 777 216 før du kjører A3- eller større sider ved 300 DPI

RapidOCR språkforhåndsinnstillingene, den native DLL-adapteren og OCR-tekstlagspipelinen er del av HotPDF Delphi PDF Component for Delphi, C++Builder og Windows FPC/Lazarus, fra og med v2.775.0 for de flerspråklige profilene