HotPDF suorittaa kiinan- ja monikielistä OCR:ää Delphissä natiivin RapidOCR-DLL-sovittimensa kautta: THPDFRapidOCRDLLOptions.ForLanguage kytkee kielitagin kuten 'zh-CN', 'zh-TW', 'ru' tai 'ar' sopivaan tunnistusmalliin ja merkistösanakirjaan, ja THotPDF.ApplyLoadedOCRTextLayer muuttaa tunnistetut rivit näkymättömäksi, haettavaksi Unicode-tekstitasoksi skannatuille PDF-sivuille
Latinankielisen demon saaminen toimimaan on helppo osa. Kiinnostavat viat alkavat, kun vaihdat perinteiseen kiinaan tai venäjään ja tuloksesta tulee itsevarmaa, hyvin muodostettua hölynpölyä, tai kun jokainen rivi menettää hiljaisesti viimeisen merkkinsä, tai kun arabiankielinen sivu palaa tekstialaatikot väärässä järjestyksessä. Mikään näistä ei nosta poikkeusta omin voimin. HotPDF v2.775.0:ssa lisätyt kielialisetukset ovat olemassa pääasiassa sulkemaan kyseiset aukot, ja alla olevat neljä ansaa on syytä ymmärtää, vaikka et koskaan koskisi natiivia koodia, koska kukin niistä selittää oireen, jonka perässä voisit muuten uhrata päivän
Miten ForLanguage valitsee mallin ja sanakirjan?
THPDFRapidOCRDLLOptions.ForLanguage ratkaisee tagin yhdeksi yhdeksästä profiilista ja palauttaa asetukset, jotka osoittavat kohteisiin <profile>/recognition.onnx ja <profile>/dictionary.txt mallikansiosi alla, säilyttäen jaetun detektorin, valinnaisen kulmaluokittelijan sekä säie-, pikseli- ja aikakatkaisuoletukset lähteestä THPDFRapidOCRDLLOptions.Default. Metodi muuntaa tagin pieniksi kirjaimiksi, muuntaa alaviivat viivoiksi ja leikkaa ympäröivät välilyönnit, joten tagit 'zh_TW', 'ZH-tw' ja ' zh-tw ' laskeutuvat kaikki samalle profiilille. Aliakset ovat eksplisiittinen luettelo etuliitevertailun sijaan: 'zh-Hant-TW' hyväksytään, koska se on luettelossa, kun taas mielivaltainen alueellinen muunnos, jota ei ole luettelossa, nostaa EArgumentExceptionn ennen kuin yksikään malli latautuu
| Profiili | Kielet | Esimerkkitagit | Lukittu malli |
|---|---|---|---|
ch | Yksinkertaistettu kiina ja englanti | zh, zh-CN, zh-Hans, chi_sim | PP-OCRv4 |
chinese_cht | Perinteinen kiina | zh-TW, zh-HK, zh-Hant, chi_tra | PP-OCRv3 |
en | Englanti | en, en-US, en-GB, eng | PP-OCRv4 |
latin | Ranska, saksa, espanja, portugali, italia, hollanti, turkki | fr, de, es-419, pt-BR, tr | PP-OCRv3 |
japan | Japani | ja, ja-JP, jpn | PP-OCRv4 |
korean | Korea | ko, ko-KR, kor | PP-OCRv4 |
cyrillic | Venäjä, ukraina, bulgaria, valkovenäjä | ru, ru-RU, uk, bg | PP-OCRv3 |
arabic | Arabia, persia, urdu | ar, ar-SA, fa, ur | PP-OCRv4 |
devanagari | Hindi, marathi, nepali | hi, mr, ne | PP-OCRv4 |
Sovitin ei koskaan lataa mitään itse. Hankit tiedostot kerran mukana toimitettavalla apuskriptillä, esimerkiksi komennolla tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (tai -Language All kaikille yhdeksälle profiilille), ja skripti sijoittaa jaetun detektorin ja luokittelijan juuritiedostonimiin, joita Default odottaa. Sen jälkeen yksinkertaistetun kiinan skannaus muuttuu haettavaksi muutamalla rivillä. Moottoriputki on sama IHPDFOCREngine-sauma, jota artikkeli in-process RapidOCR -DLL:stä ja sen ABI-rajasta kuvailee, joten tämä artikkeli pysyy kielissä
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, jaettu detektori ja luokittelija
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
// tyhjä sivuluettelo tarkoittaa jokaista sivua; sivut, joilla on jo tekstiä, ohitetaan
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;
Kaksi yksityiskohtaa kyseisessä tulosteessa ansaitsee huomion. Natiivi putki palauttaa yhden tuloksen havaittua tekstiriviä kohden, ei sanaa kohden, joten AcceptedWordCount laskee täällä rivejä, ja MinimumConfidence verrataan koko rivin keskimääräiseen merkkiluottamusarvoon: rivi, jonka keskiarvo on 0.45, pudotetaan yksikkönä. UniqueScalarCount raportoi, montako erillistä Unicode-skaalaaria tekstitason piti kartoittaa fonttiinsa ja ToUnicode-taulukkoonsa, hyödyllinen järkevyystarkistus siitä, että CJK-tekstiä oikeasti saapui sen sijaan, että olisi tullut kourallinen latinalaisia varapolkuja. Pidä moottorin rajapinta elossa asiakirjojen yli, koska mallin alustus tapahtuu tehtaassa ja on kallis vaihe
Miksi vain tunnistusmallin vaihtaminen tuottaa roskaa?
CTC-tunnistusmalli ei koskaan tulosta merkkejä, vain luokkaindeksejä, ja sanakirja on ainoa asia, joka muuttaa indeksin 1 204 glyyfiksi. Vaihda tiedosto ch/recognition.onnx tiedostoon cyrillic/recognition.onnx mutta pidä kiinalainen sanakirja, ja malli emittoi ilomieleen kelvollisia kyrillisiä indeksejä, jotka vanha sanakirja kääntää satunnaisiksi han-merkeiksi. Tulos näyttää tekstiltä, läpäisee UTF-8-validoinnin, ja on haettava täsmälleen mitään ei. Siksi ForLanguage asettaa aina arvot RecognitionModel ja CharacterDictionary yhdessä, ja siksi käsin rakennettujen asetusten ei pidä koskaan muuttaa toista ilman toista
Ilmeinen turvatarkistus, sanakirjan koon vertaaminen mallin tulosteen leveyteen, on tarpeellinen mutta ei riittävä. Kaksi sanakirjaa voi sisältää saman määrän merkintöjä eri järjestyksessä, ja yhden väärin kohdalla oleva järjestys siirtää jokaista merkkiä yhdellä koodipisteellä. HotPDF tarkistaa siksi kahdessa vaiheessa, kun tehdas alustaa mallin. Ensinnäkin tulosteen luokkamäärän on oltava yhtä suuri kuin sanakirjan merkintöjen plus kaksi. Toiseksi, kun ONNX-tiedosto upottaa character-metadataluettelon, jokainen sanakirjamerkintä verrataan siihen järjestyksessä, ja ristiriita kaataa alustuksen arvolla EInvalidOperation ja natiivilla diagnostiikalla uskottavan roskan tuottamisen sijaan myöhemmin
"Plus kaksi" tulee luokka-asettelusta. Luokka 0 on CTC-tyhjä, luokat 1–N ovat sanakirjarivit tiedostojärjestyksessä, ja viimeinen luokka on välilyönti. Jotkut sanakirjat kantavat myös oman välilyöntimerkintänsä, ja kyseisen rivin on säilyttävä täsmälleen sellaisenaan. Tässä hyvätarkoittainen Trim tekee todellista vahinkoa: se muuttaa yhden välilyönnin merkinnän tyhjäksi merkkijonoksi ja siirtää tai rikkoo taulukon. Ainoa turvallinen normalisointi on loppurivinvaihtomerkin poistaminen, joten CRLF-rivinvaihdoin tallennettu sanakirja latautuu oikein, kun taas UTF-8:n tavujärjestysmerkki, tyhjä rivi tai sarkaimen sisältävä merkintä hylätään. Alla oleva luonnos näyttää asettelun Pascalissa; kyseessä on havainnollistava koodi, ei HotPDF-API
// Vain havainnollistus: luokkataulukko, jonka CTC-tunnistin odottaa
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); // rivinvaihto tiedoston lopussa
SetLength(Result, Last + 3);
Result[0] := ''; // luokka 0: CTC-tyhjä
for I := 0 to Last do
begin
Entry := Lines[I];
if (Entry <> '') and (Entry[Length(Entry)] = #13) then
SetLength(Entry, Length(Entry) - 1); // CRLF: pudota vain CR
if (Entry = '') or (Pos(#9, Entry) > 0) then
raise EArgumentException.Create('Invalid dictionary entry');
Result[I + 1] := Entry; // ei koskaan Trimia: ' ' on luokka
end;
Result[Last + 2] := ' '; // viimeinen luokka: välilyönti
// Length(Result)in on oltava yhtä suuri kuin mallin tulosteen luokkamäärä
end;
Mitä ahne CTC-dekoodaus oikeastaan tekee?
Ahne CTC-dekoodaus poimii korkeimman pistemäärän luokan jokaisella aikavaiheella, luhistaa peräkkäiset toistot yhdeksi merkiksi ja pudottaa tyhjän luokan; tyhjä on se, mikä antaa aidosti kahdentuneiden kirjainten selviytyä. Tunnistusmalli katsoo tekstiriviä kapeiden pystysuuntaisten viipaleiden jonona, ja jokaiselle viipaleelle eli aikavaiheelle se tulostaa todennäköisyyden jokaiselle luokalle. Rivi, joka sisältää merkkijonon AA中, saattaa tuottaa argmax-jonon A A blank A 中 space. Kahden ensimmäisen A-vaiheen luhistaminen antaa yhden A:n, tyhjä erottaa sen seuraavasta A:sta, ja tulos on AA中 loppuvälilyönti ehjänä. Ilman tyhjäsääntöä sanat book ja bok olisivat erottamattomia
Koska dekooderi on vain tusinaa riviä, reunoihin on helppo tarttua väärin, ja viat ovat hiljaisia. Jos sisempi argmax-silmukka pysähtyy yhtä luokkaa vajaaksi, välilyöntiluokka ei voi koskaan voittaa, ja jokainen rivi palaa ilman sanavälejä, mikä tuhoaa fraasihakun englannin- ja latinalaisilla sivuilla. Jos ulompi silmukka pysähtyy yhtä aikavaihetta vajaaksi, jokaisen rivin viimeinen merkki katoaa, mikä lyhyellä rivillä voi olla kolmasosa tekstistä. Ja jos toistovartijaa ei nollata tyhjällä, kahdentuneet merkit kuten ll tai kiinalaiset reduplikaatiot kuten 谢谢 luhistuvat yhdeksi. HotPDF:n dekooderi sisällyttää viimeisen luokan ja viimeisen aikavaiheen, pitää tyhjällä erotetut toistot ja hylätään lisäksi pisteet, jotka eivät ole äärellisiä tai jotka putoavat välin 0–1 ulkopuolelle, sekä mikä tahansa luokkamäärä, joka ei vastaa sanakirjaa. Tässä sama logiikka Pascal-havainnollistuksena
// Vain havainnollistus: ahne CTC-dekoodaus oikeilla reunoilla.
// Scores pitää sisällään Steps * Classes todennäköisyyttä, yksi rivi aikavaihetta kohden
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; // luokka 0 on CTC-tyhjä
for Step := 0 to Steps - 1 do // sisällytä viimeinen aikavaihe
begin
Best := 0;
BestScore := Scores[Step * Classes];
for C := 1 to Classes - 1 do // sisällytä viimeinen luokka (välilyönti)
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; // tyhjä nollaa toistovartijan
end;
end;
Ahne dekoodaus ei ole tarkin saatavilla oleva CTC-strategia; beam search kielimallilla voi korjata osan monimerkityksellisistä viipaleista. Painetuille asiakirjoille 300 DPI:llä ahne tulos on yleensä se, mitä mallilla on annettavana, eikä dekooderi ole paikka kompensoida mallin heikkouksia. Latinalainen PP-OCRv3-malli voi esimerkiksi lukea merkin ñ merkkinä n jopa puhtaalla syötteellä. HotPDF ei piilota kyseistä asiaa jälkikäsittelyn merkkikorvauksilla, koska korvaustaulukko, joka korjaa espanjan, rikkoo jotain muuta, ja väärä merkki haettavassa tasossa on pahempi kuin rehellinen ohi
Miten HotPDF järjestää tekstirivit, mukaan lukien oikealta vasemmalle kulkeva arabia?
HotPDF lajittelee havaitut tekstialueet ylhäältä alas, ryhmittelee laatikot riviksi, kun ne menevät päällekkäin pystysuunnassa vähintään pienemmän laatikon korkeuden puolikkaalla, ja järjestää jokaisen rivin vasemmalta oikealle tai oikealta vasemmalle, kun RightToLeft on käytössä; jokaisen tunnistetun rivin merkkejä ei koskaan käännetä. Ryhmittely merkitsee, koska detektori usein halkaisee yhden visuaalisen rivin useisiin laatikoihin, esimerkiksi nimikkeeseen ja arvoon, joiden välissä on leveä rako, ja pelkkä yläkoordinaatin lajittelu lomittaisi ne naapuririvin kanssa aina, kun niiden yläreunat eroavat pikselin tai parin verran
Arabia-alisetus asettaa arvon RightToLeft := True, mikä kertoo DLL:lle järjestää kunkin rivin laatikot niiden oikean reunan mukaan reunamarginaalista sisäänpäin. Kyseinen on koko vaikutus. Teksti, jonka malli palauttaa riville, on jo Unicode-loogisessa järjestyksessä, siinä järjestyksessä, jossa arabin lukija lukee ja kirjoittaa sen, ja kyseinen on myös järjestys, jonka PDF-tekstin poiminta ja haku odottavat. Merkkijonon mekaaninen kääntäminen, jotta se "näyttäisi oikealta" debuggarissa, rikkoi haun, kopioinnin ja liittämisen sekä ruudunlukijat. Kaksisuuntainen näyttö ja glyyfien muotoilu ovat katseluohjelman asia
Yksi moottori palvelee yhtä kieliprofiilia. Automaattista kirjainjärjestelmäntunnistusta ei ole, joten kirjainjärjestelmiä sekoittava asiakirja tarvitsee yhden moottorin profiilia kohden, sovellettuna sivuihin, jotka käyttävät sitä. Koska ApplyLoadedOCRTextLayer ottaa eksplisiittisen sivuluettelon ja vahvistaa jokaisen kutsun omana kaikki-tai-ei mitään -transaktionaan, kyseinen on suoraviivaista
uses
SysUtils, HPDFDoc, HPDFRapidOCRRecognition;
function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
Models: THPDFRapidOCRDLLOptions;
begin
// nostaa EArgumentExceptionn tuntemattomalle tagille, ennen kuin mikään malli latautuu
Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
Models.MaxPixels := 33554432; // tilaa A3-sivuille 300 DPI:llä
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-profiili
Arabic := CreateRapidEngine('ar-SA'); // arabia-profiili, 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;
Rivi MaxPixels on siellä syystä. DLL:n asetusten oletus on 16 777 216 pikseliä pyyntöä kohden, mikä kattaa A4:n ja US Letterin 300 DPI:llä mukavasti, mutta A3-sivu 300 DPI:llä on noin 3508 kertaa 4961 pikseliä, suunnilleen 17,4 miljoonaa, ja pyyntö hylätään budjetin ylittyenä. Nosta arvoa MaxPixels (katto on 67 108 864) tai laske THPDFOCRTextLayerOptions.DPIa suuria formaatteja varten. Oikealta vasemmalle -järjestys käyttää ABI-version 1 valinnaista vientiä HPDFRapidOCRSetReadingDirection; sovitin vaatii sen vain, kun RightToLeft on asetettu, joten vanhempi DLL palvelee yhä vasemmalta oikealle -kieliä ja epäonnistuu moottorin luomisessa arvolla EArgumentException, joka nimeää puuttuvan viennin arabialle
Miksi uudemmat OCR-mallit epäonnistuvat latautumaan?
HotPDF RapidOCR -DLL linkittää staattisen ONNX Runtime 1.14:n, joka ei voi lukea ONNX IR -versiolla 10 tallennettuja malleja, ja uudemmat viennit kuten PP-OCRv5-mallit voivat vaatia tuota uudemman ajonympäristön; tällainen malli epäonnistuu moottorin luomisessa natiivilla diagnostiikalla. Kyseinen rajoite on syy siihen, että kielipaketit on lukittu tiettyihin PP-OCRv3- ja PP-OCRv4-tunnistin- ja sanakirjapareihin "uusimman" sijaan, ja siihen, miksi yllä oleva taulukko sekoittaa kaksi sukupolvea: jokainen lukittu pari on sellainen, joka latautuu ja varmistuu kyseisellä ajonympäristöllä
Asennusohjelma valvoo paritusta. Jokainen tiedosto manifestissaan kantaa SHA256-hashia, olemassa oleva tiedosto, jolla on eri hash, pysäyttää asennuksen ylikirjoittamisen sijaan, ja jokainen lataus laskeutuu väliaimaiselle nimelle ja siirtyy paikalleen vasta, kun sen hash täsmää. Se suojaa sanakirjaongelman hiljaiselta muodolta: joku pudottaa uudemman recognition.onnxin profiilikansioon käsin, luokkamäärä sattuu täsmäämään, eikä mikään epäonnistu ennen kuin asiakas raportoi, ettei haku löydä sanoja, jotka he selvästi näkevät. Ajonaikaisesti sovitin pysyy offline-tilassa eikä koskaan nouda puuttuvaa mallia. Tunnistin validoi mallin muodon lataushetkellä, hyväksyen NCHW-syötteen kiinteällä korkeudella 32 tai 48 pikseliä tai dynaamisella korkeudella, jonka se ajaa arvolla 48
Jos tarvitset kirjainjärjestelmää, jota mikään yhdeksästä profiilista ei kata, voit yhä osoittaa arvot RecognitionModel ja CharacterDictionary omiin tiedostoihisi. Samat tarkistukset pätevät, ja se on koko pointti: pariton pari epäonnistuu alustuksessa, ei asiakkaasi arkistossa. Sivuille, joilla kumpikaan RapidOCR-profiili ei sovi, artikkeli Tesseract-sovitin haettavaan PDF:ään kytkeytyy samaan ApplyLoadedOCRTextLayer-kutsuun, ja konepainetuille ASCII-lomakkeille artikkeli sisäänrakennettu mallivertailu-OCR-moottori ei tarvitse malleja lainkaan
Pikaopas: monikielinen RapidOCR-tarkistuslista
- Luo asetukset funktiolla
THPDFRapidOCRDLLOptions.ForLanguageja käsitteleEArgumentExceptiontukemattomana tagina, ei ajonaikaisena viana - Vaihda arvot
RecognitionModeljaCharacterDictionaryyhdessä, ei koskaan toista yksinään; yhtä suuret luokkamäärät eivät todista yhtä suurta merkkijärjestystä - Pidä sanakirjat UTF-8:na ilman BOM:ia, älä koskaan trimaa merkintöjä, ja odota mallilla N + 2 luokkaa: tyhjä, N merkintää, välilyönti
- Oman CTC-dekooderin on katettava viimeinen luokka ja viimeinen aikavaihe ja pidettävä tyhjällä erotetut toistot
- Käytä yhtä moottoria kieliprofiilia kohden ja anna eksplisiittiset sivuluettelot kirjainjärjestelmiä sekoittaville asiakirjoille
RightToLeftmuuttaa vain laatikkojärjestystä; tunnistettu teksti pysyy Unicode-loogisessa järjestyksessä- Asenna mallit skriptillä
Install-RapidOCRModels.ps1niin, että SHA256-lukitukset pitävä mallin ja sanakirjan parituksen; asetaUseAngleClassifier := False, jos asensit lipulla-SkipClassifier - Nosta
MaxPixelsoletuksen 16 777 216 yli ennen A3- tai suurempien sivujen ajamista 300 DPI:llä
RapidOCR-kielialisetukset, natiivi DLL-sovitin ja OCR-tekstitasoputki kuuluvat pakettiin HotPDF Delphi PDF Component Delphille, C++Builderille ja Windows FPC/Lazarukselle, alkaen versiosta v2.775.0 monikielisten profiilien osalta