HotPDF utför kinesisk och flerspråkig OCR i Delphi genom sin nativa RapidOCR-DLL-adaptor: THPDFRapidOCRDLLOptions.ForLanguage mappar en språktagg som 'zh-CN', 'zh-TW', 'ru' eller 'ar' till en matchad igenkänningsmodell och teckenordbok, och THotPDF.ApplyLoadedOCRTextLayer gör de igenkända raderna till ett osynligt, sökbart Unicode-textskikt på inskannade PDF-sidor
Att få en demo i latinskt skrift att fungera är den lätta delen. De intressanta felen börjar när du växlar till traditionell kinesiska eller ryska och utdatat blir självsäkert, välbildat nonsens, eller när varje rad tyst förlorar sitt sista tecken, eller när en arabisk sida kommer tillbaka med sina textboxar i fel ordning. Inget av detta kastar någon exception av sig självt. De språkprofiler som lades till i HotPDF v2.775.0 finns mest för att stänga de luckorna, och de fyra fällorna nedan är värda att förstå även om du aldrig rör den nativa koden, för var och en förklarar ett symtom du annars kunde lägga en dag på att jaga
Hur väljer ForLanguage en modell och ordbok?
THPDFRapidOCRDLLOptions.ForLanguage löser en tagg till en av nio profiler och returnerar alternativ som pekar på <profile>/recognition.onnx och <profile>/dictionary.txt under din modellkatalog, medan den behåller den delade detektorn, den valfria vinkelklassificeraren och tråd-, pixel- och timeout-standarderna från THPDFRapidOCRDLLOptions.Default. Metoden gemenar taggen, gör om understreck till bindestreck och trimmar omgivande blanksteg, så 'zh_TW', 'ZH-tw' och ' zh-tw ' landar alla på samma profil. Aliaser är en explicit lista i stället för en prefixmatchning: 'zh-Hant-TW' accepteras för att den är listad, medan en godtycklig regional variant som inte är listad kastar EArgumentException innan någon modell laddas
| Profil | Språk | Exempel på taggar | Fäst modell |
|---|---|---|---|
ch | Förenklad kinesiska och engelska | zh, zh-CN, zh-Hans, chi_sim | PP-OCRv4 |
chinese_cht | Traditionell kinesiska | zh-TW, zh-HK, zh-Hant, chi_tra | PP-OCRv3 |
en | Engelska | en, en-US, en-GB, eng | PP-OCRv4 |
latin | Franska, tyska, spanska, portugisiska, italienska, nederländska, turkiska | fr, de, es-419, pt-BR, tr | PP-OCRv3 |
japan | Japanska | ja, ja-JP, jpn | PP-OCRv4 |
korean | Koreanska | ko, ko-KR, kor | PP-OCRv4 |
cyrillic | Ryska, ukrainska, bulgariska, vitryska | ru, ru-RU, uk, bg | PP-OCRv3 |
arabic | Arabiska, persiska, urdu | ar, ar-SA, fa, ur | PP-OCRv4 |
devanagari | Hindi, marathi, nepalesiska | hi, mr, ne | PP-OCRv4 |
Adaptorn själv laddar aldrig ner något. Du förser filerna en gång med det medföljande hjälpskriptet, till exempel tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (eller -Language All för alla nio profiler), och hjälparen placerar en delad detektor och klassificerare på rotfilnamnen som Default förväntar. Därefter blir en förenklat kinesisk skanning sökbar med några rader. Motorpärmen är samma IHPDFOCREngine-fog som beskrivs i artikeln om in-process RapidOCR-DLL:en och dess ABI-gräns, så den här stannar fokuserad 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, delad detektor och klassificerare
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 sidolista betyder varje sida; sidor som redan har text hoppas
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;
Två detaljer i det utdatat förtjänar en notis. Den nativa pipelinen returnerar ett resultat per detekterad textrad, inte per ord, så AcceptedWordCount räknar rader här, och MinimumConfidence jämförs mot radens hela medelteckenkonfidens: en rad som medelvärdesätter 0,45 kastas som en enhet. UniqueScalarCount rapporterar hur många distinkta Unicode-skalarer textskiktet var tvunget att mappa in i sin font och ToUnicode-tabell, en nyttig sundhetskontroll att CJK-text faktiskt anlände i stället för en handfull latinska fallbacks. Håll motorinterfacet vid liv över dokument, för modellinitieringen händer i fabriken och är det dyra steget
Varför framställer att bara byta igenkänningsmodell skräp?
En CTC-igenkänningsmodell matar aldrig ut tecken, bara klassindex, och ordboken är den enda sak som gör om index 1 204 till en glyf. Byt ch/recognition.onnx mot cyrillic/recognition.onnx men behåll den kinesiska ordboken, och modellen kommer glatt att emittera giltiga kyrilliska index som gamla ordboken översätter till slumpmässiga han-tecken. Resultatet ser ut som text, passerar UTF-8-validering, och är sökbart för exakt ingenting. Det är därför ForLanguage alltid sätter RecognitionModel och CharacterDictionary tillsammans, och varför handbyggda alternativ aldrig ska ändra en utan den andra
Den uppenbara säkerhetskontrollen, att jämföra ordboksstorleken med modellens utdatabredd, är nödvändig men inte tillräcklig. Två ordböcker kan ha samma antal poster i en annan ordning, och ett av-väga-ett i ordningen förskjuter varje tecken med en code point. HotPDF kontrollerar därför i två steg när fabriken initierar modellen. Först måste utdataklassantalet vara lika med ordboksposterna plus två. Sedan, när ONNX-filen bäddar in en character-metadatalista, jämförs varje ordbokspost med den i ordning, och en missmatchning fäller initieringen med EInvalidOperation och en nativ diagnostext i stället för att framställa trolig skräp senare
"Plus två" kommer från klasslayouten. Klass 0 är CTC-blanken, klass 1 till N är ordboksraderna i filordning, och sista klassen är ett mellanslag. Vissa ordböcker bär också sin egen mellanslagspost, och den raden måste behållas exakt som den är. Det är här en välvillig Trim gör verklig skada: den gör en mellanslagspost till en tom sträng och förskjuter eller bryter tabellen. Den enda normalisering som är säker är att ta bort en avslutande vagnretur, så att en ordbok sparad med CRLF-radslut laddas korrekt, medan ett UTF-8-byte order-märke, en tom rad, eller en post som innehåller en tab avvisas. Skissen nedan visar layouten i Pascal; den är förklarande kod, inte ett HotPDF-API
// Endast illustration: klasstabellen en CTC-igenkännare förväntar
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); // radbrytning vid filslut
SetLength(Result, Last + 3);
Result[0] := ''; // klass 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: kasta bara CR
if (Entry = '') or (Pos(#9, Entry) > 0) then
raise EArgumentException.Create('Invalid dictionary entry');
Result[I + 1] := Entry; // aldrig Trim: ' ' är en klass
end;
Result[Last + 2] := ' '; // sista klass: mellanslag
// Length(Result) måste vara lika med modellens utdataklassantal
end;
Vad gör greedy CTC-avkodning egentligen?
Greedy CTC-avkodning plockar den högst poängsatta klassen vid varje tidssteg, kollapsar påföljande upprepningar till ett tecken, och kastar blankklassen; blanken är det som låter äkta dubblerade bokstäver överleva. En igenkänningsmodell tittar på en textrad som en sekvens av smala vertikala skivor, och för varje skiva, eller tidssteg, matar den ut en sannolikhet för varje klass. En rad som innehåller AA中 kan producera argmax-sekvensen A A blank A 中 space. Att kollapsa de två första A-stegen ger ett A, blanken skiljer det från nästa A, och resultatet är AA中 med den avslutande mellanslaget intakt. Utan blankregeln vore book och bok oskiljbara
Eftersom avkodaren bara är ett dussin rader är det lätt att få gränserna fel, och misslyckandena är tysta. Stannar inre argmax-loopen en klass för kort kan mellanslagsklassen aldrig vinna och varje rad kommer tillbaka utan ordmellanslag, vilket förstör frasökning på engelska och latinska sidor. Stannar yttre loopen ett tidssteg för kort försvinner sista tecknet i varje rad, vilket för en kort rad kan vara en tredjedel av texten. Och återställs inte upprepningsskyddet av en blank kollapsar dubblerade tecken som ll eller kinesiska reduplikationer som 谢谢 till ett. HotPDF-avkodaren inkluderar sista klassen och sista tidssteget, behåller blankavgränsade upprepningar, och avvisar dessutom poäng som inte är ändliga eller faller utanför 0 till 1, och varje klassantal som inte matchar ordboken. Här är samma logik som en Pascal-illustration
// Endast illustration: greedy CTC-avkodning med korrekta gränser.
// Scores håller Steps * Classes sannolikheter, en 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; // klass 0 är CTC-blanken
for Step := 0 to Steps - 1 do // inkludera sista tidssteget
begin
Best := 0;
BestScore := Scores[Step * Classes];
for C := 1 to Classes - 1 do // inkludera sista klassen (mellanslag)
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 återställer upprepningsskyddet
end;
end;
Greedy-avkodning är inte den mest träffsäkra CTC-strategi som finns; beam search med en språkmodell kan fixa vissa tvetydiga skivor. För tryckta dokument vid 300 DPI är greedy-resultatet vanligen det modellen har att erbjuda, och avkodaren är inte platsen att kompensera för modellsvagheter. Den latinska PP-OCRv3-modellen kan till exempel läsa ñ som n även på ren indata. HotPDF klistrar inte över det med efterbehandlade teckenutbyten, för en substitutionstabell som fixar spanskan bryter något annat, och ett fel tecken i ett sökbart skikt är värre än en ärlig miss
Hur ordnar HotPDF textrader, inklusive höger-till-vänster-arabiska?
HotPDF sorterar detekterade textboxar uppifrån och ner, grupperar boxar i en rad när de överlappar vertikalt med minst hälften av den mindre boxhöjden, och ordnar varje rad vänster till höger, eller höger till vänster när RightToLeft är aktiverat; tecknen inuti varje igenkänd rad vänds aldrig. Grupperingen spelar roll för en detektor ofta delar en visuell rad i flera boxar, till exempel en etikett och ett värde skilda av ett brett gap, och en ren toppkoordinatsortering skulle fläta in dem med den närliggande raden närhelst deras toppar skiljer en pixel eller två
Den arabiska profilen sätter RightToLeft := True, vilket talar om för DLL:en att ordna boxarna i varje rad efter sin högerkant, från högermarginalen inåt. Det är hela effekten. Texten modellen returnerar för en rad är redan i Unicode logisk ordning, den ordning en arabisk läsare läser och skriver den i, och det är också den ordning PDF-textextraktion och sökning förväntar. Att mekaniskt vända strängen för att få den att "se rätt ut" i en debugger skulle bryta sökning, kopiera och klistra in, och skärmläsare. Bidirektionell visning och glyfformning är visarens jobb
En motor betjänar en språkprofil. Det finns ingen automatisk skriftdetektering, så ett dokument som blandar skrifter behöver en motor per profil, tillämpad på de sidor som använder den. Eftersom ApplyLoadedOCRTextLayer tar en explicit sidolista och committar varje anrop som sin egen allt-eller-inget-transaktion är det rakt på sak
uses
SysUtils, HPDFDoc, HPDFRapidOCRRecognition;
function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
Models: THPDFRapidOCRDLLOptions;
begin
// kastar EArgumentException för en okänd tagg, före någon modell laddas
Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
Models.MaxPixels := 33554432; // utrymme för A3-sidor vid 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-raden finns av en anledning. DLL-alternativen har som standard 16 777 216 pixlar per begäran, vilket täcker A4 och US Letter vid 300 DPI gott, men en A3-sida vid 300 DPI är ungefär 3508 gånger 4961 pixlar, ungefär 17,4 miljoner, och begärandet avvisas som överbudget. Höj MaxPixels (taket är 67 108 864) eller sänk THPDFOCRTextLayerOptions.DPI för stora format. Höger-till-vänster-ordningen använder den valfria exporten HPDFRapidOCRSetReadingDirection av ABI version 1; adaptorn kräver den bara när RightToLeft är satt, så en äldre DLL fortsätter betjäna vänster-till-höger-språk och faller vid motorskapandet med en EArgumentException som namnger den saknade exporten för arabiska
Varför misslyckas nyare OCR-modeller att ladda?
HotPDF RapidOCR-DLL:en länkar en statisk ONNX Runtime 1.14, som inte kan läsa modeller sparade med ONNX IR version 10, och nyare exporter som PP-OCRv5-modeller kan kräva en nyare runtime än så; en sådan modell faller vid motorskapandet med en nativ diagnostext. Den begränsningen är anledningen till att språkpaketen är fästade vid specifika PP-OCRv3- och PP-OCRv4-igenkännar- och ordbokspar i stället för "senaste", och varför tabellen ovan blandar de två generationerna: varje fäst par är ett som laddas och verifieras under den runtime:n
Installationsprogrammet upprätthåller paraformen. Varje fil i dess manifest bär en SHA256-hash, en existerande fil med en annan hash stoppar installationen i stället för att skrivas över, och varje nerladdning landar under ett tillfälligt namn och flyttas bara på plats efter att dess hash matchar. Det skyddar mot den tysta versionen av ordboksproblemet: någon släpper en nyare recognition.onnx i en profilmapp för hand, klassantalet råkar matcha, och inget faller förrän en kund rapporterar att sökningen inte hittar ord de tydligt kan se. Vid runtime stannar adaptorn offline och hämtar aldrig en saknad modell. Igenkännaren validerar också modellformen vid laddning, och accepterar NCHW-indata med en fast höjd på 32 eller 48 pixlar eller en dynamisk höjd, som den kör vid 48
Behöver du en skrift som ingen av de nio profilerna täcker kan du fortfarande peka RecognitionModel och CharacterDictionary mot dina egna filer. Samma kontroller gäller, vilket är poängen: ett felmatchat par faller vid initieringen, inte i din kunds arkiv. För sidor där ingen RapidOCR-profil passar kopplar Tesseract-adaptorn för sökbar PDF in i samma ApplyLoadedOCRTextLayer-anrop, och för maskintryckta ASCII-formulär behöver den inbyggda mallmatchnings-OCR-motorn inga modeller alls
Snabbreferens: flerspråkig RapidOCR-checklista
- Skapa alternativ med
THPDFRapidOCRDLLOptions.ForLanguageoch behandlaEArgumentExceptionsom en tagg utan stöd, inte ett runtime-fel - Ändra
RecognitionModelochCharacterDictionarytillsammans, aldrig en ensam; lika klassantal bevisar inte lika teckenordning - Behåll ordböcker som UTF-8 utan BOM, trimma aldrig poster, och förvänta dig att modellen har N + 2 klasser: blank, N poster, mellanslag
- En egen CTC-avkodare måste täcka sista klassen och sista tidssteget och behålla upprepningar skilda av en blank
- Använd en motor per språkprofil och skicka explicita sidolistor för dokument med blandade skrifter
RightToLeftändrar bara boxordning; igenkänd text stannar i Unicode logisk ordning- Installera modeller med
Install-RapidOCRModels.ps1så att SHA256-fästningarna håller modell- och ordboksparet; sättUseAngleClassifier := Falseom du installerade med-SkipClassifier - Höj
MaxPixelsöver standardvärdet 16 777 216 innan A3 eller större sidor körs vid 300 DPI
RapidOCR-språkprofilerna, den nativa DLL-adaptorn och OCR-textskiktspipelinen är del av HotPDF Delphi PDF Component för Delphi, C++Builder och Windows FPC/Lazarus, med början i v2.775.0 för de flerspråkiga profilerna