A HotPDF kínai és többnyelvű OCR-t végez Delphiben a natív RapidOCR DLL adaptérján át: a THPDFRapidOCRDLLOptions.ForLanguage egy 'zh-CN', 'zh-TW', 'ru' vagy 'ar' szerű nyelvi címkét illeszkedő felismerő modellhez és karakterdictionaryhez rendel, a THotPDF.ApplyLoadedOCRTextLayer pedig a felismert sorokat láthatatlan, kereshető Unicode szövegréteggé alakítja a szkenelt PDF oldalakon
Egy latin írásos demo működésre bírása a könnyebbik oldal. Az érdekes hibák akkor kezdenek, amikor hagyományos kínaira vagy oroszra váltasz, és a kimenet magabiztos, jól formált értelmetlenséggé válik, vagy amikor minden sor csendben elveszíti az utolsó karakterét, vagy amikor egy arab oldal szövegboxai rossz sorrendben jönnek vissza. Egyik sem dob kivételt magától. A HotPDF v2.775.0-ban hozzáadott nyelvi előbeállítások főleg azért léteznek, hogy ezeket a réseket bezárják, és az alábbi négy csapda megérné a megértést akkor is, ha soha nem nyúlsz a natív kódhoz, mert mindegyik megmagyaráz egy tünetet, amit egyébként egy napig üldözgethetsz
Hogyan választ modellt és dictionaryt a ForLanguage?
A THPDFRapidOCRDLLOptions.ForLanguage egy címkét kilenc profil egyikéhez rendel, és olyan opciókat ad vissza, amik a <profile>/recognition.onnx-ra és a <profile>/dictionary.txt-re mutatnak a modellkönyvtáradban, miközben megtartja a megosztott detektort, az opcionális szögosztályozót és a thread, pixel meg timeout alapértékeket a THPDFRapidOCRDLLOptions.Default-ból. A metódus kisbetűsíti a címkét, az aláhúzásokat kötőjelre váltja, és levágja a környező whitespace-t, így a 'zh_TW', 'ZH-tw' és ' zh-tw ' mind ugyanarra a profilra landol. Az aliasok explicit lista, nem prefixillesztés: a 'zh-Hant-TW' elfogadott, mert fel van sorolva, míg egy tetszőleges, fel nem sorolt regionális változat EArgumentException-t dob, még bármely modell betöltése előtt
| Profil | Nyelvek | Példacímkék | Rögzített modell |
|---|---|---|---|
ch | Egyszerűsített kínai és angol | zh, zh-CN, zh-Hans, chi_sim | PP-OCRv4 |
chinese_cht | Hagyományos kínai | zh-TW, zh-HK, zh-Hant, chi_tra | PP-OCRv3 |
en | Angol | en, en-US, en-GB, eng | PP-OCRv4 |
latin | Francia, német, spanyol, portugál, olasz, holland, török | fr, de, es-419, pt-BR, tr | PP-OCRv3 |
japan | Japán | ja, ja-JP, jpn | PP-OCRv4 |
korean | Koreai | ko, ko-KR, kor | PP-OCRv4 |
cyrillic | Orosz, ukrán, bolgár, belarusz | ru, ru-RU, uk, bg | PP-OCRv3 |
arabic | Arab, perzsa, urdu | ar, ar-SA, fa, ur | PP-OCRv4 |
devanagari | Hindi, maráthi, nepáli | hi, mr, ne | PP-OCRv4 |
Magának az adaptérnek soha nem tölt le semmit. A fájlokat egyszer, a csomagolt helperrel szeded be, például tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (vagy -Language All mind a kilenc profilhoz), és a helper a megosztott detektort és osztályozót a gyökér fájlnevekre teszi, amiket a Default vár. Ezután egy egyszerűsített kínai szken néhány sorból kereshetővé válik. Az engine-vezetékezés ugyanaz az IHPDFOCREngine varrat, amit a cikk az in-process RapidOCR DLL-ről és az ABI határjáról ír le, így ez a cikk a nyelvekre marad kihegyezve
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, megosztott detektor és osztályozó
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
// az üres oldallista minden oldalt jelent; a már szöveggel bíró oldalak kimaradnak
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;
A kimenetnek két részlete érdemel megjegyzést. A natív pipeline detektált szövegsoronként ad egy eredményt, nem szónként, így itt az AcceptedWordCount sorokat számol, és a MinimumConfidence a teljes sor átlagos karakterkonfidenciájával vetődik össze: egy átlagosan 0.45-es sor egységként esik el. A UniqueScalarCount megmondja, hány különböző Unicode skalárt kellett a szövegrétegnek a fontjába és a ToUnicode táblájába váltania, hasznos józanész-ellenőrzés arra, hogy tényleg megérkezett a CJK szöveg, és nem egy maroknyi latin fallback. Tartsd életben az engine interfészét dokumentumok át, mert a modellinicializálás a factoryban történik, és az a drága lépés
Miért gyárt szemetet, ha csak a felismerő modellt cseréled?
Egy CTC felismerő modell soha nem karaktereket ad ki, csak osztályindexeket, és a dictionary az egyetlen dolog, ami az 1204-es indexet betűvé alakítja. Cseréld le a ch/recognition.onnx-ot cyrillic/recognition.onnx-ra, de hagyd a kínai dictionaryt, és a modell vidáman bocsát ki érvényes cirill indexeket, amiket a régi dictionary véletlenszerű Han karakterekké fordít. Az eredmény szövegnek néz ki, átment az UTF-8 validáción, és pontosan semmiért nem kereshető. Ezért állítja be a ForLanguage mindig együtt a RecognitionModel-t és a CharacterDictionary-t, és ezért nem szabad kézzel épített opcióknak az egyiket a másik nélkül változtatniuk
A kézenfekvő biztonsági teszt, a dictionary méretének és a modell kimeneti szélességének összevetése, szükséges, de nem elégséges. Két dictionary lehet ugyanannyi bejegyzéssel, de más sorrendben, és egy off-by-one a sorrendben minden karaktert egy kódponttal elcsúsztat. Ezért a HotPDF két lépcsőben ellenőriz, amikor a factory inicializálja a modellt. Először a kimeneti osztályszámnak egyenlőnek kell lennie a dictionary bejegyzéseivel plusz kettővel. Másodszor, ha az ONNX fájl character metadatalistát ágyaz be, minden dictionary bejegyzést sorrendben összevet vele, és az eltérés EInvalidOperation-dal és natív diagnosztikával buktatja az inicializálást, ahelyett hogy később hihető szemetet gyártana
A „plusz kettő" az osztályelrendezésből jön. A 0. osztály a CTC blank, az 1-től N-ig terjedő osztályok a dictionary sorai fájsorrendben, az utolsó osztály pedig egy szóköz. Egyes dictionaryk magukkal hordozzák a saját szóköz-bejegyzésüket is, és azt a sort pontosan úgy kell megtartani, ahogy van. Itt okoz valódi kárt a jóhiszemű Trim: az egy szóközből álló bejegyzést üres stringgé alakítja, és elcsúsztatja vagy eltöri a táblát. Az egyetlen biztonságos normalizálás a záró kocsiReturnType eltávolítása, így a CRLF sorszégekkel mentett dictionary helyesen töltődik be, míg egy UTF-8 byte order mark, egy üres sor vagy tabot tartalmazó bejegyzés elutasításra kerül. Az alábbi vázlat a Pascalban mutatja az elrendezést; magyarázó kód, nem HotPDF API
// Csak illusztráció: az osztálytábla, amit egy CTC felismerő vár
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); // soremelés a fájl végén
SetLength(Result, Last + 3);
Result[0] := ''; // 0. osztály: 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: csak a CR-t dobd el
if (Entry = '') or (Pos(#9, Entry) > 0) then
raise EArgumentException.Create('Invalid dictionary entry');
Result[I + 1] := Entry; // soha ne Trimeld: a ' ' egy osztály
end;
Result[Last + 2] := ' '; // utolsó osztály: szóköz
// A Length(Result)-nak egyenlőnek kell lennie a modell kimeneti osztályszámával
end;
Mit csinál valójában a greedy CTC dekódolás?
A greedy CTC dekódolás minden időlépésnél a legmagasabb pontszámú osztályt választja, az egymást követő ismétlődéseket egy karakterbe zsugorítja, és eldobja a blank osztályt; a blank az, ami lehetővé teszi, hogy a valóban megkettőzött betűk megmaradjanak. Egy felismerő modell egy szövegsort keskeny függőleges szeletek sorozataként néz, és minden szeletre, vagyis időlépésre, valószínűséget ad minden osztályra. Egy AA中-t tartalmazó sor például az A A blank A 中 szóköz argmax sorozatot produkálhatja. Az első két A lépés összezsugorítása egy A-t ad, a blank választja el a következő A-tól, az eredmény pedig a AA中 a záró szóközzel együtt. A blank szabály nélkül a book és a bok megkülönböztethetetlen lenne
Mivel a dekódór mindössze tucatnyi sor, könnyű elrontani a határokat, és a hibák csendesek. Ha a belső argmax ciklus egy osztállyal korábban áll meg, a szóköz osztály sosem nyerhet, és minden sor szóközök nélkül jön vissza, ami tönkreteszi a kifejezéskeresést az angol és latin oldalakon. Ha a külső ciklus egy időlépéssel korábban áll meg, minden sor utolsó karaktere eltűnik, ami egy rövid sornál akár a szöveg harmada is lehet. És ha az ismétlésőrt nem állítja vissza blank, az olyan megkettőzött karakterek, mint az ll, vagy kínai duplikációk, mint a 谢谢, egybe zsugorodnak. A HotPDF dekódora beleszámítja az utolsó osztályt és az utolsó időlépést, megtartja a blankkal elválasztott ismétlődéseket, és ezen felül elutasítja a végtelen vagy a 0-tól 1-ig kívül eső pontszámokat, meg bármely, a dictionaryvel nem egyező osztályszámot. Itt ugyanez a logika Pascal illusztrációként
// Csak illusztráció: greedy CTC dekódolás helyes határokkal.
// A Scores Steps * Classes valószínűséget tart, időlépésenként egy sort
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; // a 0. osztály a CTC blank
for Step := 0 to Steps - 1 do // vedd bele az utolsó időlépést
begin
Best := 0;
BestScore := Scores[Step * Classes];
for C := 1 to Classes - 1 do // vedd bele az utolsó osztályt (szóköz)
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; // a blank visszaállítja az ismétlésőrt
end;
end;
A greedy dekódolás nem a legpontosabb elérhető CTC stratégia; a nyelvi modelles beam search néhány kétértelmű szeletet tud javítani. Nyomtatott dokumentumoknál 300 DPI-n a greedy eredmény általában az, amit a modell nyújtani tud, és a dekódór nem az a hely, ahol a modell gyengeségeit kompenzálni kellene. A latin PP-OCRv3 modell például tisztán olvasott bemeneten is ñ-t olvashat n-ként. A HotPDF nem takarja be ezt utófeldolgozó karaktercserékkel, mert egy spanyolt javító cserehelyettesítő-tábla mást törik el, és egy rossz karakter a kereshető rétegben rosszabb, mint egy becsületes találatlan
Hogyan rendez sorokat a HotPDF, jobbról-balra arabot is beleértve?
A HotPDF a detektált szövegboxokat fentről lefelé rendezi, a boxokat sorba csoportosítja, ha függőlegesen legalább a kisebb box magasságának felével fedik egymást, és minden sort balról jobbra rendez, vagy jobbról balra, ha a RightToLeft be van kapcsolva; minden felismert soron belül a karaktereket soha nem fordítja meg. A csoportosítás azért számít, mert egy detektor gyakran több boxra bont egy vizuális sort, például címkére és értékre, amiket széles hézag választ el, és egy puszta felső-koordináta rendezés akkor keverné őket a szomszédos sorba, ha a felső élek egy-két pixelt elbírnak
Az arab előbeállítás RightToLeft := True-t állít, ami megmondja a DLL-nek, hogy minden sor boxait a jobb élük szerint rendezze, a jobb margótól befelé. Ez az egész hatás. A szöveg, amit a modell egy sorhoz visszaad, már Unicode logikai sorrendben van, abban a sorrendben, ahogy egy arab olvasó olvassa és begépeli, és ez az a sorrend is, amit a PDF szövegkiextrakció és keresés vár. A string mechanikus megfordítása, hogy debuggerben „jól nézzen ki", eltörné a keresést, a másolást és a képernyőolvasókat. A kétirányú megjelenítés és a glyph formázás a nézegető dolga
Egy engine egy nyelvi profilt szolgál ki. Nincs automatikus írásrendszerdetektálás, így egy írásokat keverő dokumentumnak profilnként egy engine kell, alkalmazva azokhoz az oldalakhoz, amik használják. Mivel az ApplyLoadedOCRTextLayer explicit oldalistát vesz át, és minden hívását saját, all-or-nothing tranzakcióként kötelezi el, ez egyértelmű
uses
SysUtils, HPDFDoc, HPDFRapidOCRRecognition;
function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
Models: THPDFRapidOCRDLLOptions;
begin
// EArgumentException-t dob ismeretlen címkére, még bármely modell betöltése előtt
Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
Models.MaxPixels := 33554432; // hely A3 oldalaknak 300 DPI-n
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;
A MaxPixels sor ott van, valamiért. A DLL opciók kérésenként 16 777 216 pixelre alapoznak, ami A4-et és US Lettert 300 DPI-n kényelmesen fedi, de egy A3 oldal 300 DPI-n nagyjából 3508-szor 4961 pixel, durván 17,4 millió, és a kérést budgettúllépésként elutasítják. Emeld meg a MaxPixels-t (a plafon 67 108 864), vagy vidd lejjebb a THPDFOCRTextLayerOptions.DPI-t nagy formátumoknál. A jobbról-balra rendezés az ABI 1-es verziójának opcionális HPDFRapidOCRSetReadingDirection exportját használja; az adaptér csak akkor követeli meg, ha a RightToLeft be van állítva, így egy öregebb DLL továbbra is kiszolgálja a balról jobbra nyelveket, és engine-létrehozáskor bukik el olyan EArgumentException-dal, ami megnevezi a hiányzó exportot az arabhoz
Miért nem töltődnek be az újabb OCR modellek?
A HotPDF RapidOCR DLL statikus ONNX Runtime 1.14-et linkel, ami nem tud az ONNX IR 10-es verziójával mentett modelleket olvasni, és az olyan újabb exportok, mint a PP-OCRv5 modellek, ennél is újabb runtime-ot követelhetnek; ilyen modell engine-létrehozáskor bukik el natív diagnózissal. Ez a megszorítás az oka annak, hogy a nyelvcsomagok konkrét PP-OCRv3 és PP-OCRv4 felismerő-dictionary párokra vannak rögzítve a „legfrissebb" helyett, és azért keveri a fenti táblázat a két generációt: minden rögzített pár olyan, ami azon a runtime-on betöltődik és hitelesít
A telepítő kikényszeríti a párost. A manifestjében minden fájl SHA256 hash-t hordoz, egy eltérő hashű meglévő fájl megállítja a telepítést felülírás helyett, és minden letöltés ideiglenes néven landol, és csak azután kerül a helyére, hogy a hash-e egyezett. Ez véd a dictionary probléma csendes változata ellen: valaki kézzel tesz egy újabb recognition.onnx-ot egy profil mappájába, az osztályszám véletlenül egyezik, és semmi nem bukik el, amíg egy ügyfél nem jelenti, hogy a keresés nem talál szavakat, amiket nyilvánvalóan lát. Futásidőben az adaptér offline marad, és soha nem húz le hiányzó modellt. A felismerő betöltéskor a modell alakját is validálja, elfogadva 32 vagy 48 pixeles rögzített magasságú vagy dinamikus magasságú NCHW bemenetet, amit 48-on futtat
Ha olyan írásrendszerre van szükséged, amit egyik kilenc profil sem fed, akkor is ráállíthatod a RecognitionModel-t és a CharacterDictionary-t a saját fájljaidra. Ugyanazok az ellenőrzések élnek, és ez a lényeg: egy eltérő pár inicializáláskor bukik el, nem az ügyfeled archívumában. Azoknál az oldalaknál, ahol egyik RapidOCR profil sem illik, a Tesseract adaptér kereshető PDF-hez ugyanabba az ApplyLoadedOCRTextLayer hívásba dugózik, gépnyomtatott ASCII űrlapokhoz pedig a beépített template-matching OCR enginnek nincs is szüksége modellekre
Gyorsreferencia: többnyelvű RapidOCR ellenőrzőlista
- Hozd létre az opciókat
THPDFRapidOCRDLLOptions.ForLanguage-dal, és aEArgumentException-t nem támogatott címként kezeld, nem futásidejű hibaként - A
RecognitionModel-t és aCharacterDictionary-t együtt változtasd, soha nem egyet önállóan; az egyenlő osztályszám nem bizonyítja az egyenlő karakterrendet - Tartsd a dictionaryket BOM nélküli UTF-8-ban, soha ne Trimeld a bejegyzéseket, és számolj azzal, hogy a modellnek N + 2 osztálya van: blank, N bejegyzés, szóköz
- Egy egyéni CTC dekódonak az utolsó osztályt és az utolsó időlépést is le kell fednie, és meg kell tartania a blankkal elválasztott ismétlődéseket
- Nyelvi profilnként egy enginet használj, és adj explicit oldalistákat írásokat keverő dokumentumoknál
- A
RightToLeftcsak a boxok sorrendjét változtatja; a felismert szöveg Unicode logikai sorrendben marad - Telepítsd a modelleket
Install-RapidOCRModels.ps1-fel, hogy a SHA256 csapok tartsák a modell-dictionary párost; állítsdUseAngleClassifier := False-ra, ha-SkipClassifier-rel telepítettél - Emeld a
MaxPixels-t a 16 777 216-os alapérték fölé, mielőtt A3 vagy nagyobb oldalakat futtatsz 300 DPI-n
A RapidOCR nyelvi előbeállítások, a natív DLL adaptér és az OCR szövegréteg-pipeline a HotPDF Delphi PDF Component részei Delphihez, C++Builderhez és Windows FPC/Lazarushoz, a többnyelvű profilok a v2.775.0-tól