HotPDF udfører kinesisk og flersproget OCR i Delphi gennem sin native RapidOCR-DLL-adapter: THPDFRapidOCRDLLOptions.ForLanguage mapper et sprogtag som 'zh-CN', 'zh-TW', 'ru' eller 'ar' til en matchende recognition-model og tegndictionary, og THotPDF.ApplyLoadedOCRTextLayer omdanner de genkendte linjer til et usynligt, søgbart Unicode-tekstlag på scannede PDF-sider
At få en latin-script-demo til at virke er den lette del. De interessante fejl starter, når du skifter til Traditionelt kinesisk eller russisk, og outputtet bliver til selvsikker, velformet volapyk, eller når hver linje stille mister sit sidste tegn, eller når en arabisk side kommer tilbage med sine tekstboxes i forkert rækkefølge. Ingen af delene rejser en exception af sig selv. Sprogprofilerne, der kom i HotPDF v2.775.0, findes mest for at lukke de huller, og de fire fælder nedenfor er værd at forstå, selv hvis du aldrig rører den native kode, for hver enkelt forklarer et symptom, du ellers kunne bruge en dag på at jage
Hvordan vælger ForLanguage en model og en dictionary?
THPDFRapidOCRDLLOptions.ForLanguage opløser et tag til én af ni profiler og returnerer options, der peger på <profile>/recognition.onnx og <profile>/dictionary.txt under din model-mappe, mens den beholder den delte detector, den valgfrie angle classifier og tråd-, pixel- og timeout-defaults fra THPDFRapidOCRDLLOptions.Default. Metoden lowercaser tagget, omdanner underscores til bindestreger og trimmer omgivende whitespace, så 'zh_TW', 'ZH-tw' og ' zh-tw ' alle lander på samme profil. Aliaser er en eksplicit liste frem for et prefix-match: 'zh-Hant-TW' accepteres, fordi den er listet, mens en vilkårlig regional variant, der ikke er listet, rejser EArgumentException, før nogen model loades
| Profil | Sprog | Eksempel-tags | Fastlåst model |
|---|---|---|---|
ch | Forenklet kinesisk og engelsk | zh, zh-CN, zh-Hans, chi_sim | PP-OCRv4 |
chinese_cht | Traditionelt kinesisk | zh-TW, zh-HK, zh-Hant, chi_tra | PP-OCRv3 |
en | Engelsk | en, en-US, en-GB, eng | PP-OCRv4 |
latin | Fransk, tysk, spansk, portugisisk, italiensk, hollandsk, tyrkisk | fr, de, es-419, pt-BR, tr | PP-OCRv3 |
japan | Japansk | ja, ja-JP, jpn | PP-OCRv4 |
korean | Koreansk | ko, ko-KR, kor | PP-OCRv4 |
cyrillic | Russisk, ukrainsk, bulgarsk, hviderussisk | ru, ru-RU, uk, bg | PP-OCRv3 |
arabic | Arabisk, persisk, urdu | ar, ar-SA, fa, ur | PP-OCRv4 |
devanagari | Hindi, marathi, nepalesisk | hi, mr, ne | PP-OCRv4 |
Adapteren selv downloader aldrig noget. Du provisionerer filerne én gang med det medfølgende hjælperscript, for eksempel tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (eller -Language All for alle ni profiler), og hjælperen placerer en delt detector og classifier ved de rodfilnavne, Default forventer. Derefter bliver en forenklet kinesisk scanning søgbar med få linjer. Engine-sømmen er samme IHPDFOCREngine, som er beskrevet i artiklen om in-process RapidOCR-DLL'en og dens ABI-grænseflade, så denne forbliver fokuseret på sprog
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 detector og classifier
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 betyder alle sider; sider med tekst i forvejen skippes
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 det output fortjener en note. Den native pipeline returnerer ét resultat pr. detekteret tekstlinje, ikke pr. ord, så AcceptedWordCount tæller linjer her, og MinimumConfidence sammenlignes med den gennemsnitlige tegn-confidence for hele linjen: en linje, der i gennemsnit ligger på 0.45, droppes som én enhed. UniqueScalarCount melder, hvor mange distinkte Unicode-scalarer tekstlaget måtte mappe ind i sin font og ToUnicode-tabel, et nyttigt sanity-tjek på, at CJK-tekst faktisk ankom i stedet for en håndfuld latin-fallbacks. Hold engine-interfacet i live på tværs af dokumenter, for model-initialisering sker i factoryen og er det dyre trin
Hvorfor giver det volapyk kun at skifte recognition-modellen?
En CTC-recognition-model udsender aldrig tegn, kun klasseindeks, og dictionary'en er det eneste, der omsætter indeks 1.204 til et glyph. Byt ch/recognition.onnx ud med cyrillic/recognition.onnx, men behold den kinesiske dictionary, og modellen vil gladelig udsende gyldige kyrilliske indeks, som den gamle dictionary oversætter til tilfældige Han-tegn. Resultatet ligner tekst, består UTF-8-validering og er søgbart for præcis ingenting. Det er derfor, ForLanguage altid sætter RecognitionModel og CharacterDictionary sammen, og hvorfor håndbyggede options aldrig må ændre den ene uden den anden
Det oplagte sikkerhedstjek, at sammenligne dictionary-størrelsen med model-outputtets bredde, er nødvendigt men ikke tilstrækkeligt. To dictionaries kan have samme antal poster i en anden rækkefølge, og en off-by-one i rækkefølgen flytter hvert tegn ét kodepunkt. HotPDF tjekker derfor i to trin, når factoryen initialiserer modellen. Først skal output-klasseantallet svare til dictionary-posterne plus to. Dernæst, når ONNX-filen indlejrer en character-metadataliste, sammenlignes hver dictionary-post med den i rækkefølge, og et mismatch fejler initialiseringen med EInvalidOperation og en native diagnostik i stedet for senere at producere plausibelt volapyk
"Plus to" kommer fra klasse-layoutet. Klasse 0 er CTC-blanken, klasserne 1 til N er dictionary-linjerne i filrækkefølge, og den sidste klasse er et mellemrum. Nogle dictionaries bærer også deres egen mellemrums-post, og den linje skal bevares præcis som den er. Det er dér, en velmenende Trim gør rigtig skade: den omdanner en mellemrums-post til en tom streng og flytter eller knækker tabellen. Den eneste normalisering, der er sikker, er at fjerne et afsluttende carriage return, så en dictionary gemt med CRLF-linjeslutninger loader korrekt, mens en UTF-8 byte order mark, en tom linje eller en post, der indeholder et tab, afvises. Skitsen nedenfor viser layoutet i Pascal; det er forklarende kode, ikke et HotPDF-API
// Kun illustration: den klasstabel, en CTC-recognizer 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 ved filens slutning
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: drop kun CR'en
if (Entry = '') or (Pos(#9, Entry) > 0) then
raise EArgumentException.Create('Invalid dictionary entry');
Result[I + 1] := Entry; // aldrig Trim: ' ' er en klasse
end;
Result[Last + 2] := ' '; // sidste klasse: mellemrum
// Length(Result) skal svare til model-outputtets klasseantal
end;
Hvad gør greedy CTC-dekodning egentlig?
Greedy CTC-dekodning vælger den højest scorende klasse ved hvert time step, kollapser sammenhængende gentagelser til ét tegn og dropper blank-klassen; blanken er dét, der gør det muligt for ægte fordoblede bogstaver at overleve. En recognition-model kigger på en tekstlinje som en sekvens af smalle lodrette skiver, og for hver skive, hvert time step, udsender den en sandsynlighed for hver klasse. En linje med AA中 kan producere argmax-sekvensen A A blank A 中 space. At kollapse de to første A-steps giver ét A, blanken adskiller det fra næste A, og resultatet er AA中 med det afsluttende mellemrum intakt. Uden blank-reglen ville book og bok være uadskillelige
Eftersom dekoderen kun er et dusin linjer, er det let at ramme grænserne forkert, og fejlene er stille. Stopper det indre argmax-loop én klasse for tidligt, kan mellemrumsklassen aldrig vinde, og hver linje kommer tilbage uden ordmellemrum, hvilket ødelægger sætningssøgning på engelske og latinske sider. Stopper det ydre loop ét time step for tidligt, forsvinder sidste tegn i hver linje, hvilket for en kort linje kan være en tredjedel af teksten. Og nulstilles gentagelsesvagten ikke af en blank, kollapser fordoblede tegn som ll eller kinesiske reduplikationer som 谢谢 til ét. HotPDF's dekoder medtager sidste klasse og sidste time step, beholder blank-adskilte gentagelser og afviser desuden scores, der ikke er endelige eller falder uden for 0 til 1, og ethvert klasseantal, der ikke matcher dictionary'en. Her er samme logik som en Pascal-illustration
// Kun illustration: greedy CTC-dekodning med korrekte grænser.
// Scores holder Steps * Classes sandsynligheder, én række pr. time step
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 // medtag sidste time step
begin
Best := 0;
BestScore := Scores[Step * Classes];
for C := 1 to Classes - 1 do // medtag sidste klasse (mellemrum)
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 nulstiller repeat-vagten
end;
end;
Greedy-dekodning er ikke den mest præcise CTC-strategi, der findes; beam search med en sprogmodel kan redde nogle tvetydige skiver. For printede dokumenter ved 300 DPI er greedy-resultatet normalt det, modellen har at byde på, og dekoderen er ikke stedet at kompensere for modelsvagheder. Den latinske PP-OCRv3-model kan for eksempel læse ñ som n selv på rent input. HotPDF papirer det ikke over med post-processing-tegnerstatninger, for en substitutionstabel, der fikser spansk, knækker noget andet, og et forkert tegn i et søgbart lag er værre end et ærligt miss
Hvordan sorterer HotPDF tekstlinjer, inklusive højre-til-venstre arabisk?
HotPDF sorterer detekterede tekstboxes fra top til bund, grupperer boxes i en række, når de overlapper lodret med mindst halvdelen af den mindste box-højde, og sorterer hver række venstre mod højre, eller højre mod venstre, når RightToLeft er slået til; tegnene inde i hver genkendt linje vendes aldrig. Grupperingen betyder noget, for en detector deler ofte én visuel linje i flere boxes, for eksempel en label og en værdi adskilt af et bredt mellemrum, og en ren top-koordinat-sortering ville flette dem ind med nabolinjen, hver gang deres tops afviger med en pixel eller to
Den arabiske profil sætter RightToLeft := True, hvilket beder DLL'en om at sortere boxene i hver række efter deres højre kant, fra højre margen og ind. Det er hele effekten. Teksten, modellen returnerer for en linje, er allerede i Unicode logisk rækkefølge, den rækkefølge, en arabisk læser læser og taster den i, og det er også den rækkefølge, PDF-tekst-ekstraktion og søgning forventer. Mekanisk at vende strengen for at få den til at "se rigtig ud" i en debugger ville knække søgning, kopier og indsæt og screen readers. Bidirektional visning og glyph shaping er viewerens job
Én engine serverer én sprogprofil. Der er ingen automatisk script-detektion, så et dokument, der blander scripts, behøver én engine pr. profil, anvendt på de sider, der bruger den. Eftersom ApplyLoadedOCRTextLayer tager en eksplicit sideliste og committer hvert kald som sin egen alt-eller-intet-transaktion, er det ligetil
uses
SysUtils, HPDFDoc, HPDFRapidOCRRecognition;
function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
Models: THPDFRapidOCRDLLOptions;
begin
// rejser EArgumentException ved ukendt tag, før nogen model loades
Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
Models.MaxPixels := 33554432; // plads 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-profilen
Arabic := CreateRapidEngine('ar-SA'); // arabic-profilen, 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 af en grund. DLL-options har 16.777.216 pixels pr. request som default, hvilket dækker A4 og US Letter ved 300 DPI fint, men en A3-side ved 300 DPI er omkring 3508 gange 4961 pixels, cirka 17,4 millioner, og requesten afvises som over budget. Hæv MaxPixels (loftet er 67.108.864) eller sænk THPDFOCRTextLayerOptions.DPI for store formater. Højre-til-venstre-sortering bruger den valgfrie HPDFRapidOCRSetReadingDirection-export af ABI-version 1; adapteren kræver den kun, når RightToLeft er sat, så en ældre DLL bliver ved med at servere venstre-til-højre-sprog og fejler ved engine-oprettelse med en EArgumentException, der navngiver den manglende export for arabisk
Hvorfor fejler nyere OCR-modeller at loade?
HotPDF's RapidOCR-DLL linker en statisk ONNX Runtime 1.14, som ikke kan læse modeller gemt med ONNX IR-version 10, og nyere eksporter såsom PP-OCRv5-modeller kan behøve et nyere runtime end det; en sådan model fejler ved engine-oprettelse med en native diagnostik. Den begrænsning er grunden til, at sprogpakkerne er fastlåst til specifikke PP-OCRv3- og PP-OCRv4-recognizer- og dictionary-par i stedet for "seneste", og til, at tabellen ovenfor blander de to generationer: hvert fastlåst par er ét, der loader og verificerer under det runtime
Installeren håndhæver parringen. Hver fil i dens manifest bærer en SHA256-hash, en eksisterende fil med en anden hash stopper installationen i stedet for at blive overskrevet, og hver download lander under et midlertidigt navn og flyttes først på plads, efter dens hash matcher. Det beskytter mod den stille udgave af dictionary-problemet: nogen smider en nyere recognition.onnx i hånden ned i en profilmappe, klasseantallet passer tilfældigvis, og intet fejler, før en kunde melder, at søgningen ikke finder ord, de åbenlyst kan se. Ved runtime forbliver adapteren offline og henter aldrig en manglende model. Recognizeren validerer også modelformen ved load-tid og accepterer NCHW-input med en fast højde på 32 eller 48 pixels eller en dynamisk højde, som den kører ved 48
Behøver du et script, ingen af de ni profiler dækker, kan du stadig pege RecognitionModel og CharacterDictionary på dine egne filer. Samme tjek gælder, og det er pointen: et mismatchet par fejler ved initialisering, ikke i din kundes arkiv. Til sider, hvor ingen RapidOCR-profil passer, kobler Tesseract-adapteren til søgbar PDF sig på samme ApplyLoadedOCRTextLayer-kald, og til maskinprintede ASCII-formularer behøver den indbyggede template-matching OCR-engine slet ingen modeller
Hurtig reference: flersproget RapidOCR-tjekliste
- Opret options med
THPDFRapidOCRDLLOptions.ForLanguageog behandlEArgumentExceptionsom et ikke-understøttet tag, ikke en runtime-fejl - Skift
RecognitionModelogCharacterDictionarysammen, aldrig én alene; lige klasseantal beviser ikke lige tegnrækkefølge - Hold dictionaries som UTF-8 uden BOM, trim aldrig poster, og forvent, at modellen har N + 2 klasser: blank, N poster, mellemrum
- En tilpasset CTC-dekoder skal dække sidste klasse og sidste time step og holde gentagelser adskilt af en blank
- Brug én engine pr. sprogprofil og giv eksplicitte sidelister med videre for dokumenter med blandede scripts
RightToLeftændrer kun box-rækkefølge; genkendt tekst forbliver i Unicode logisk rækkefølge- Installér modeller med
Install-RapidOCRModels.ps1, så SHA256-pins holder model- og dictionary-parringen; sætUseAngleClassifier := False, hvis du installerede med-SkipClassifier - Hæv
MaxPixelsover 16.777.216-defaulten, før du kører A3 eller større sider ved 300 DPI
RapidOCR-sprogprofilerne, den native DLL-adapter og OCR-tekstlags-pipelinen er en del af HotPDF Delphi PDF Component til Delphi, C++Builder og Windows FPC/Lazarus, fra v2.775.0 for de flersprogede profiler