HotPDF atlieka kinų ir daugiakalbį OCR Delphi per savą natyvų RapidOCR DLL adapterį: THPDFRapidOCRDLLOptions.ForLanguage kalbos žymą, tokią kaip 'zh-CN', 'zh-TW', 'ru' arba 'ar', susieja su atitinkančiu atpažinimo modeliu ir simbolių žodynu, o THotPDF.ApplyLoadedOCRTextLayer atpažintas eilutes paverčia nematomu, ieškomu Unicode teksto sluoksniu nuskenuotuose PDF puslapiuose
Lotyniško rašto demo paleidimas – lengvoji dalis. Įdomios nesėkmės prasideda, kai perjungiate į tradicinę kinų ar rusų kalbą ir išvestis virsta pasitikinčia, taisyklingos formos beprotybe, kai kiekviena eilutė tylioje praranda paskutinįjį simbolį arba kai arabų puslapis grįžta su teksto langeliais netinkama tvarka. Joks iš šių pats nekelia išimties. HotPDF v2.775.0 pridėti kalbų profiliai egzistuoja daugiausia tam, kad užtupestų tuos plyšius, o keturi žemiau esantys spąstai verti supratimo net jei natyvaus kodo neliestumėte niekada, nes kiekvienas paaiškina simptomą, už kurį kitaip galėtumėte gaišti dieną
Kaip ForLanguage renkasi modelį ir žodyną?
THPDFRapidOCRDLLOptions.ForLanguage žymą išspręndžia į vieną iš devynių profilių ir grąžina nustatymus, rodančius į <profile>/recognition.onnx ir <profile>/dictionary.txt žemiau jūsų modelių katalogo, bendrąjį aptikiklį, pasirinktinį kampų klasifikatorių ir gijų, pikselių bei laiko limitų numatytąsias iš THPDFRapidOCRDLLOptions.Default palikdama. Metodas žymą nuleidžia į mažąsias raides, pabraukimus paverčia brūkšneliais ir nukerpa aplinkinius tarpus, tad 'zh_TW', 'ZH-tw' ir ' zh-tw ' visi atkrenta ant to paties profilio. Aliasing yra aiškus sąrašas, o ne priešdėlio atitikimas: 'zh-Hant-TW' priimama, nes įvardyta, o savavališkas regioninis variantas, sąraše nesantis, kelia EArgumentException dar prieš įkeliant bet kurį modelį
| Profilis | Kalbos | Žymių pavyzdžiai | Fiksuotas modelis |
|---|---|---|---|
ch | Supaprastintoji kinų ir anglų | zh, zh-CN, zh-Hans, chi_sim | PP-OCRv4 |
chinese_cht | Tradicinė kinų | zh-TW, zh-HK, zh-Hant, chi_tra | PP-OCRv3 |
en | Anglų | en, en-US, en-GB, eng | PP-OCRv4 |
latin | Prancūzų, vokiečių, ispanų, portugalų, italų, olandų, turkų | fr, de, es-419, pt-BR, tr | PP-OCRv3 |
japan | Japonų | ja, ja-JP, jpn | PP-OCRv4 |
korean | Korėjiečių | ko, ko-KR, kor | PP-OCRv4 |
cyrillic | Rusų, ukrainiečių, bulgarų, baltarusių | ru, ru-RU, uk, bg | PP-OCRv3 |
arabic | Arabų, persų, urdu | ar, ar-SA, fa, ur | PP-OCRv4 |
devanagari | Hindi, maratų, nepaliečių | hi, mr, ne | PP-OCRv4 |
Pats adapteris nieko neparsiunčia. Failus parūpinkite kartą su pridėtuoju pagalbininku, pavyzdžiui tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (arba -Language All visiems devyniems profiliams), o pagalbininkas padeda bendrąjį aptikiklį ir klasifikatorių prie šakninių failų vardų, kurių tikisi Default. Po to supaprastintosios kinų skenuotė tampa ieškoma keliomis eilutėmis. Variklio pajungimas – ta pati IHPDFOCREngine siūlė, aprašyta straipsnyje apie RapidOCR DLL procese ir jo ABI ribą, tad šis lieka prie kalbų
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, bendras aptikiklis ir klasifikatorius
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
// tuščias puslapių sąrašas reiškia visus puslapius; puslapiai, turintys tekstą, praleidžiami
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;
Dvi tos išvesties detalės nusipelno pastabos. Natyvus srautas grąžina po vieną rezultatą aptiktai teksto eilutei, o ne žodžiui, tad AcceptedWordCount čia skaičiuoja eilutes, o MinimumConfidence lyginama su visos eilutės vidutine simbolių pasitikėjimo reikšme: eilutė, kurios vidurkis 0.45, numetama kaip vienetas. UniqueScalarCount praneša, kiek atskirų Unicode skaliarų teksto sluoksniui teko susieti su savu šriftu ir ToUnicode lentele – naudinga sąžiningumo patikra, kad CJK tekstas tikrai atkeliavo, o ne saujelė lotyniškų atsarginių. Laikykite variklio sąsają gyvą per dokumentus, nes modelių inicializacija vyksta faktorijoje ir yra brangus žingsnis
Kodėl keičiant tik atpažinimo modelį gaunamas šlamštas?
CTC atpažinimo modelis niekada neišduoda simbolių – tik klasių indeksus, o žodynas yra vienintelis dalykas, paverčiantis 1 204 indeksą į glifą. Pakeiskite ch/recognition.onnx į cyrillic/recognition.onnx, bet palikite kinų žodyną, ir modelis mielai išduos teisėtus kirilicos indeksus, kuriuos senasis žodynas išvers į atsitiktinius Han simbolius. Rezultatas atrodo kaip tekstas, praeina UTF-8 patikrą ir yra ieškomas lygiai nieko. Todėl ForLanguage visada nustato RecognitionModel ir CharacterDictionary kartu, o rankomis sudėti nustatymai niekada neturėtų keisti vieno be kito
Akivaizdi saugumo patikra – žodyno dydžio lyginimas su modelio išvesties pločiu – būtina, bet nepakankama. Du žodynai gali turėti tą patį įrašų skaičių kita tvarka, ir vienetinė paklaida tvarkoje pastumpia kiekvieną simbolį per vieną kodo tašką. HotPDF todėl tikrina dviem etapais, kai faktorija inicializuoja modelį. Pirma, išvesties klasių skaičius privalo būti lygus žodyno įrašams plius du. Antra, kai ONNX failas įterpia character metaduomenų sąrašą, kiekvienas žodyno įrašas su juo lyginamas tvarka, ir nesutapimas žlugdo inicializaciją su EInvalidOperation ir natyvia diagnostika vietoj tikėtino šlamšto vėliau
„Plius du“ atsiranda iš klasių išdėstymo. 0 klasė yra CTC blank, 1–N klasės – žodyno eilutės failo tvarka, o galutinė klasė – tarpas. Kai kurie žodynai dar neša savą tarpų įrašą, ir ta eilutė privalo likti lygiai tokia, kokia yra. Būtent čia geraširdiškas Trim padaro tikrą žalą: vieno tarpo įrašą paverčia tuščia eilute ir pastumpia arba sulaužo lentelę. Vienintelė saugi normalizacija – pabaigos grąžinimo simbolio pašalinimas, tad CRLF eilučių pabaigomis išsaugotas žodynas įsikelia teisingai, o UTF-8 baitų eiliškumo žyma, tuščia eilutė arba įrašas su tabuliatoriumi atmetami. Eskizas žemiau rodo išdėstymą Pascal kalba; tai paaiškinamasis kodas, ne HotPDF API
// Tik iliustracija: klasės lentelė, kurios tikisi CTC atpažintojas
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); // nauja eilutė failo gale
SetLength(Result, Last + 3);
Result[0] := ''; // klasė 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: nupjauname tik CR
if (Entry = '') or (Pos(#9, Entry) > 0) then
raise EArgumentException.Create('Invalid dictionary entry');
Result[I + 1] := Entry; // niekada Trim: ' ' yra klasė
end;
Result[Last + 2] := ' '; // galutinė klasė: tarpas
// Length(Result) privalo būti lygus modelio išvesties klasių skaičiui
end;
Ką godus CTC dekodavimas iš tikrųjų daro?
Godus CTC dekodavimas kiekvienu laiko žingsniu renkasi aukščiausiai įvertintą klasę, suspaudžia iš eilės einančius kartojimus į vieną simbolį ir numeta blank klasę; blank yra tai, kas leidžia tikroms dviguboms raidėms išgyventi. Atpažinimo modelis į teksto eilutę žiūri kaip į siaurų vertikalių pjūvių seką, ir kiekvienam pjūviui – laiko žingsniui – išduoda tikimybę kiekvienai klasei. Eilutė su AA中 gali pagimdyti argmax seką A A blank A 中 space. Pirmų dviejų A žingsnių suspaudimas duoda vieną A, blank jį atskiria nuo kito A, o rezultatas – AA中 su nepaliesta galine tarpine. Be blank taisyklės book ir bok būtų neatskiriami
Kadangi dekoduotojas – vos keliolika eilučių, ribas lengva padaryti neteisingai, o nesėkmės tylia. Jei vidinis argmax ciklas sustoja viena klasę ankščiau, tarpo klasė niekada nelaimi, ir kiekviena eilutė grįžta be žodžių tarpų – kas suardo frazių paiešką anglų ir lotynų puslapiuose. Jei išorinis ciklas sustoja vienu laiko žingsniu ankščiau, kiekvienos eilutės paskutinis simbolis dingsta, o trumpai eilutei tai gali būti trečdalis teksto. O jei kartojimo apsaugos neatstato blank, dvigubi simboliai, tokie kaip ll, ar kinų kartojimai, tokie kaip 谢谢, suspaudžiami į vieną. HotPDF dekoduotojas įtraukia paskutinę klasę ir paskutinį laiko žingsnį, išlaiko blank atskirtus kartojimus, be to atmeta įverčius, kurie nėra baigtiniai arba krenta už 0–1 ribų, ir bet kokį klasių skaičių, neatitinkantį žodyno. Štai ta pati logika kaip Pascal iliustracija
// Tik iliustracija: godus CTC dekodavimas su teisingomis ribomis.
// Scores laiko Steps * Classes tikimybių, po eilutę laiko žingsniui
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; // klasė 0 yra CTC blank
for Step := 0 to Steps - 1 do // įtraukite paskutinį laiko žingsnį
begin
Best := 0;
BestScore := Scores[Step * Classes];
for C := 1 to Classes - 1 do // įtraukite paskutinę klasę (tarpas)
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; // blank atstato kartojimo apsaugą
end;
end;
Godus dekodavimas nėra tiksliausia iš turimų CTC strategijų; spindulio paieška su kalbos modeliu gali ištaisyti kai kuriuos dviprasmiškus pjūvius. Spausdintiems dokumentams ties 300 DPI godus rezultatas paprastai ir yra tai, ką modelis turi, o dekoduotojas – ne vieta kompensuoti modelio silpnybes. Lotynų PP-OCRv3 modelis, pavyzdžiui, gali skaityti ñ kaip n net ant švaraus įvedimo. HotPDF to neplombuoja po-apdorojimo simbolių pakeitimais, nes pakaitų lentelė, ištaisanti ispanų, sulaužo ką nors kita, o netinkamas simbolis ieškomame sluoksnyje blogesnis už sąžiningą praleidimą
Kaip HotPDF rikiuoja teksto eilutes, įskaitant iš dešinės į kairę rašomą arabų?
HotPDF aptiktus teksto langelius rikiuoja iš viršaus į apačią, langelius sugrupuoja į eilutę, kai jie vertikaliai persidengia bent per pusę mažesniojo langelio aukščio, ir kiekvieną eilutę tvarko iš kairės į dešinę arba iš dešinės į kairę, kai įjungta RightToLeft; simboliai kiekvienos atpažintos eilutės viduje niekada neapverčiami. Grupavimas svarbus, nes aptikiklis dažnai vieną vizualinę eilutę skelia į kelis langelius – pavyzdžiui, etiketę ir reikšmę, atskirtas plačiu tarpu – o grynas viršutinės koordinatės rikiavimas juos supintų su gretima eilute, kai tik jų viršūnės skiriasi pikseliu ar dviem
Arabų profilis nustato RightToLeft := True, kas liepia DLL kiekvienos eilutės langelius tvarkyti pagal jų dešinę kraštinę, nuo dešinės paraštės į vidų. Tai visas efektas. Tekstas, kurį modelis grąžina eilutei, jau yra Unicode loginėje tvarkoje – tokioje, kokia arabų skaitytojas jį skaito ir renkasi, – ir būtent tos tvarkos tikisi PDF teksto ištrauka ir paieška. Mechaniškai apversti eilutę, kad debugeryje „atrodytų teisingai“, reikštų sulaužyti paiešką, kopijavimą ir įklijavimą bei ekrano skaitykles. Dvikryptis rodymas ir glifų formavimas – peržiūros programos darbas
Vienas variklis aptarnauja vieną kalbos profilį. Automatinio rašto aptikimo nėra, tad dokumentas, maišantis raštus, reikalauja po variklį profilui, taikomo puslapiams, kurie jį naudoja. Kadangi ApplyLoadedOCRTextLayer priima aiškų puslapių sąrašą ir kiekvieną kvietimą įrašo kaip atskirą viskas-arba-nieko transakciją, tai paprasta
uses
SysUtils, HPDFDoc, HPDFRapidOCRRecognition;
function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
Models: THPDFRapidOCRDLLOptions;
begin
// kelia EArgumentException dėl nežinomos žymos, dar prieš įkeliant modelius
Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
Models.MaxPixels := 33554432; // vietos A3 puslapiams ties 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 profilis
Arabic := CreateRapidEngine('ar-SA'); // arabic profilis, 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 eilutė ten ne šiaip. DLL nustatymų numatytoji – 16 777 216 pikselių užklausai, ko užtenka A4 ir US Letter ties 300 DPI su atsarga, bet A3 puslapis ties 300 DPI yra apie 3508 į 4961 pikselius, maždaug 17,4 milijono, ir užklausa atmetama kaip viršijanti biudžetą. Kelkite MaxPixels (lubos – 67 108 864) arba žeminkite THPDFOCRTextLayerOptions.DPI dideliems formatams. Iš dešinės į kairę tvarka naudoja pasirinktinį ABI versijos 1 eksportą HPDFRapidOCRSetReadingDirection; adapteris jo reikalauja tik nustačius RightToLeft, tad senesnė DLL tebeaptarnauja iš kairės į dešinę kalbas, o kuriant variklį žlunga su EArgumentException, įvardijančia dingusį eksportą arabų kalbai
Kodėl naujesni OCR modeliai neįsikelia?
HotPDF RapidOCR DLL susieja statinį ONNX Runtime 1.14, kuris negali skaityti modelių, išsaugotų su ONNX IR versija 10, o naujesni eksportai, kaip PP-OCRv5 modeliai, gali reikalauti naujesnio runtime; toks modelis žlunga kuriant variklį su natyvia diagnostika. Tas apribojimas yra priežastis, kodėl kalbų paketai fiksuoti prie konkrečių PP-OCRv3 ir PP-OCRv4 atpažintojo bei žodyno porų vietoj „naujausios“, ir kodėl aukščiau esanti lentelė maišo abi kartas: kiekviena fiksuotoji pora – tokia, kuri įsikelia ir patvirtinama to runtime sąlygomis
Diegimo įrankis prižiūri poravimą. Kiekvienas manifeste esantis failas neša SHA256 hashą, esamas failas su kitoku hashu sustabdo diegimą vietoj perrašymo, o kiekvienas parsisiuntimas atsirenka po laikinu vardu ir į vietą keliauja tik jam hashui sutapus. Tai saugo nuo tyliosios žodyno problemos versijos: kas nors rankomis įmeta naujesnį recognition.onnx į profilio aplanką, klasių skaičius atsitiktinai sutampa, ir niekas nesugriūva, kol klientas nepraneša, kad paieška neranda žodžių, kuriuos jis aiškiai mato. Veikimo metu adapteris lieka atsijungęs ir dingusio modelio niekada neparsiunčia. Atpažintojas dar patvirtina modelio formą įkeliant – priima NCHW įvedimą su fiksuotu 32 arba 48 pikselių aukščiu arba dinamišku aukščiu, kurį paleidžia ties 48
Jeigu jums reikia rašto, kurio nė vienas iš devynių profilių nedengia, vis tiek galite nukreipti RecognitionModel ir CharacterDictionary į savuosius failus. Tos pačios patikros galioja – ir tai yra esmė: nesutampanti pora žlunga inicializacijos metu, o ne kliento archyve. Puslapiams, kuriems netinka nė vienas RapidOCR profilis, Tesseract adapteris ieškomam PDF įsijungia į tą patį ApplyLoadedOCRTextLayer kvietimą, o mašininio spaudimo ASCII formoms vidinis šablonų atitikimo OCR variklis iš viso apsieina be modelių
Trumpa atmintinė: daugiakalbio RapidOCR sąrašas
- Kurkite nustatymus su
THPDFRapidOCRDLLOptions.ForLanguageirEArgumentExceptiontraktuokite kaip nepalaikomą žymą, o ne runtime gedimą - Keiskite
RecognitionModelirCharacterDictionarykartu, niekada vieno atskirai; lygūs klasių skaičiai neįrodo lygios simbolių tvarkos - Laikykite žodynus UTF-8 be BOM, niekada nekirpkite įrašų ir tikėkitės modelio su N + 2 klasėmis: blank, N įrašų, tarpas
- Pasirinktinis CTC dekoduotojas privalo dengti paskutinę klasę ir paskutinį laiko žingsnį ir išlaikyti kartojimus, atskirtus blank
- Naudokite po variklį kalbos profiliui ir perduokite aiškius puslapių sąrašus kelių raštų dokumentams
RightToLeftkeičia tik langelių tvarką; atpažintas tekstas lieka Unicode loginėje tvarkoje- Įdiekite modelius su
Install-RapidOCRModels.ps1, kad SHA256 fiksacijos laikytų modelio ir žodyno poravimą; nustatykiteUseAngleClassifier := False, jei diegėte su-SkipClassifier - Pakelkite
MaxPixelsvirš 16 777 216 numatytosios prieš paleisdami A3 ar didesnius puslapius ties 300 DPI
RapidOCR kalbų profiliai, natyvus DLL adapteris ir OCR teksto sluoksnio srautas – HotPDF Delphi PDF Component dalis, skirto Delphi, C++Builder ir Windows FPC/Lazarus, nuo v2.775.0 su daugiakalbiais profiliais