HotPDF gör inskannade PDF-sidor sökbara med in-process RapidOCR genom HPDFCreateRapidOCRDLLOCREngine, en fabrik tillagd i v2.774.0 som laddar HotPDFRapidOCR.dll, håller ONNX-detekterings-, vinkelklassificerings- och igenkänningsmodellerna residenta i minnet, och returnerar en IHPDFOCREngine. Du skickar den motorn till THotPDF.ApplyLoadedOCRTextLayer, som renderar varje sida, kör CPU-inferens utan Python eller en barnprocess, och committar ett osynligt Unicode-textlager
Motiveringen är kostnad per sida. RapidOCR-processadaptorn som skeppades tidigare, HPDFCreateRapidOCREngine, startar en Python-arbetare för varje Recognize-anrop, och den arbetaren importerar sin runtime och laddar sina ONNX-modeller innan den läser en enda pixel. På ett arkiv på 500 sidor upprepas den startskatten 500 gånger, och distribution betyder att skeppa en Python-miljö bredvid en Delphi-körbar fil. Den nativa DLL:en laddar modellerna en gång, när du skapar motorn, och distributionen krymper till DLL:en, dess modellfiler och en teckenordbok. Det du ger upp i utbyte är förmågan att döda en fastnade igenkännare, och större delen av ingenjörsarbetet i denna adaptor handlar om att leva med det ärligt
Hur gör du en inskannad PDF sökbar med RapidOCR-DLL:en?
Att skapa en sökbar PDF med den nativa RapidOCR-DLL:en tar ett fabriksanrop och samma ApplyLoadedOCRTextLayer-anrop varje HotPDF OCR-motor använder. Fabriken bor i uniten HPDFRapidOCRRecognition och validerar ivrigt: DLL:en och modellkatalogen måste finnas, varje modell- och ordboksfil måste kunna lösas upp, ABI-versionen måste vara 1, och alla krävda exporter måste finnas före någon modell initieras. Konfigurationsmisstag kastar EArgumentException; en modell som inte laddas kastar EInvalidOperation bärande den diagnostext DLL:en skrev
uses
SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Modeller laddas här, utanför varje igenkänningsdeadline.
// Relativa modellnamn i THPDFRapidOCRDLLOptions.Default löses upp
// mot modellkatalogen.
Engine := HPDFCreateRapidOCRDLLOCREngine(
'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models');
Doc := THotPDF.Create(nil);
try
Doc.AutoLaunch := False;
if Doc.LoadFromFile(SourceFile) < 1 then
raise Exception.Create('Cannot load ' + SourceFile);
Options := THPDFOCRTextLayerOptions.Default; // 300 DPI, MinimumConfidence 0.5
// en tom sidolista betyder varje sida; sidor som redan har text hoppas
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
raise Exception.Create(string(Info.Diagnostic));
Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
' lines accepted, ', Info.DroppedWordCount, ' dropped');
Doc.SaveLoadedDocument(TargetFile);
finally
Doc.Free;
end;
end;
THPDFRapidOCRDLLOptions.Default namnger ch_PP-OCRv3_det_infer.onnx, ch_PP-OCRv3_rec_infer.onnx, ch_ppocr_mobile_v2.0_cls_infer.onnx och ppocr_keys_v1.txt, med en CPU-tråd, en inmatningsgräns på 16 777 216 pixlar och en igenkänningsdeadline på 60 000 ms. Sedan v2.775.0 byter THPDFRapidOCRDLLOptions.ForLanguage in en matchande igenkänningsmodell och ordbok för traditionell kinesiska, ryska, japanska, arabiska och andra profiler; varför modell och ordbok måste bytas tillsammans tas upp i RapidOCR-flerspråkiga modeller och CTC-ordböcker i HotPDF. Motorn rapporterar sig som RapidOCR (native DLL) i Info.EngineName, vilket håller loggar entydiga bredvid den externa Tesseract OCR-processadaptorn och den inbyggda mallmatchnings-OCR-motorn
Varför talar C-ABI bara int32_t och UTF-8-bytes?
ABI:n hos HotPDFRapidOCR.dll använder bara fastbreddade heltal, råa pekare och explicita bytelängder för Delphi, C++Builder och Free Pascal inte delar något med MSVC utöver C-anropskonventionen. En std::string, en std::vector eller en C++-exception har en layout och en upprullningsmodell som tillhör en kompilator och en runtime-library. Låt någon av dem korsa gränsen och felet är en korrupt stack eller ett heap-block frigjort av fel allokator, inte ett rent fel
ABI version 1 följer därför en kort lista av regler. Varje export är cdecl och returnerar en int32_t-status, där 1 betyder framgång och 0 betyder misslyckande. Varje funktion som kan misslyckas tar en anroparägd diagnostikbuffert och dess kapacitet i bytes; DLL:en skriver ett NUL-terminerat UTF-8-meddelande avkortat att passa, och adaptorn avkodar det med en hård terminator i sista byten av sin egen 4 096-bytesbuffert. Varje exportkropp lindas i try med både catch (const std::exception &) och catch (...), så att ett ONNX Runtime-fel, en OpenCV-assertion eller en ogiltig ordbok blir status 0 plus text, aldrig en exception som flyr in i Pascal-kod
| Export | Roll | När adaptorn löser upp den |
|---|---|---|
HPDFRapidOCRAbiVersion | Returnerar 1; varje annat värde avvisas | Först, före allt annat |
HPDFRapidOCRCreate | Laddar detekterings-, valfri klassificerings- och igenkänningsmodeller samt ordboken | I fabriken |
HPDFRapidOCRRecognize | Kör en bitmapp och emitterar ett callback per textrad | I fabriken |
HPDFRapidOCRDestroy | Frigör modellinstansen | I fabriken |
HPDFRapidOCRSetReadingDirection | Valfri höger-till-vänster-radordning, tillagd i v2.775.0 | Bara när RightToLeft är satt |
Den valfria exporten löses upp lätt med flit: en DLL från v2.774.0 som saknar den serverar fortfarande vänster-till-höger-begäranden. DLL:en laddas med LoadLibraryEx med sökflaggor som täcker DLL:ens egen mapp plus de defaultsäkra katalogerna, så ONNX Runtime- eller OpenCV-beroenden placerade bredvid HotPDFRapidOCR.dll hittas utan att röra PATH. Modell- och ordbokssökvägar färdas som UTF-8 och DLL:en konverterar dem med MultiByteToWideChar i strikt läge innan filer öppnas via bredtecken-API:er, så en modellkatalog under ett kinesiskt eller kyrilliskt användarnamn fungerar i stället för att breddas byte för byte till nonsens
En regel bor i bygget snarare än i headern. DLL:en länkar statiskt ONNX Runtime och OpenCV, och standard-CMake-konfigurationen använder den statiska release-CRT:n (/MT). Statiska bibliotek kompilerade mot /MD blandade i en /MT-DLL framställer länkfel i bästa fall och två oberoende heaps i värsta fall, så de försedda biblioteken måste matcha det CRT-läge DLL:en använder
Vad händer mellan en TBitmap och en textrad?
HotPDF räcker DLL:en en oberoende top-down BGR-ögonblicksbild av den renderade sidan, och DLL:en räcker tillbaka ett callback per igenkänd textrad med lånad UTF-8-text som adaptorn måste kopiera innan den returnerar
På Delphi tilldelar adaptorn sidbitmappen till en privat TBitmap, tvingar pf24bit, och läser rader med GetDIBits med en negativ biHeight, vilket ger top-down-rader stoppade till fyra-bytes-alignment; den stride:n skickas explicit. På FPC läser den via CreateIntfImage, för LCL scanline-skrivningar kan uppdatera den råa bilden utan att uppdatera GDI-hantelet. Anroparens bitmapp modifieras aldrig, och pixelbudgeten (MaxPixels, 16 777 216 som standard och konfigurerbar upp till 67 108 864) och gränsen på 32 767 pixlar per dimension kontrolleras innan ögonblicksbildbufferten allokeras
Inuti DLL:en stoppas ögonblicksbilden med 50 vita pixlar, textregioner detekteras med maximal sida på 1 024 pixlar, boxar ordnas i horisontella rader, och varje beskärning roteras valfritt av vinkelklassificeraren före igenkänning. Varje textrad går sedan genom ett callback som tar emot en const char*, ett byteantal, en heltalsbox i originalbildens pixlar och medelteckenkonfidensen. Textpekaren är giltig bara under callbacken, så adaptorn kopierar den omedelbart, och den är strikt med vad den accepterar:
- UTF-8 avkodas med
MB_ERR_INVALID_CHARS; en felformad sekvens fäller sidan i stället för att framställa ersättningstecken i ett sökbart lager - C0- och C1-kontrolltecken avvisas, och rader med bara blanksteg hoppas över
- Boxen måste ligga inuti bitmappen och konfidensen måste vara ett ändligt värde från 0 till 1
- Text räknas mot begärandets
MaxTextCodeUnitsmed ett hårt tak på 1 048 576 UTF-16-enheter per anrop, och tecken i supplementary plane kostar två enheter - Varje Pascal-exception inuti callbacken fångas där, lagras, och görs till en 0-retur, som får DLL:en att stanna och rapportera misslyckande; det lagrade meddelandet blir sedan diagnostexten
Två konsekvenser spelar roll för trimning. För det första är utdataenheten en rad, inte ett ord: varje rad konsumerar en MaxWords-plats, Info.AcceptedWordCount och Info.DroppedWordCount räknar rader, och sökmarkering spänner över radboxen. För det andra jämförs MinimumConfidence (0,5 som standard) mot radens medelteckenkonfidens, så en rad med ett oläsbart tecken bland tjugo rena överlever vanligen. DLL:en levererar ingen baslinje, så textlayers-pipelinen uppskattar en ur boxen. En tom sida lyckas med noll rader, och varje misslyckande tömmer partiella resultat så att fleradecommiten förblir allt-eller-inget
Modelägande och trådsäkerhet
Varje RapidOCR-DLL-motor äger exakt en modellinstans under hela sin livstid, och anrop till Recognize på den motorn serialiseras av en critical section. Att hålla IHPDFOCREngine-interfacet är det som håller modellerna varma, så det rätta mönstret för batcharbete är att skapa motorn en gång och återanvända den över dokument
procedure OcrBatch(const Files: TStrings; const OutputDir: string);
var
Models: THPDFRapidOCRDLLOptions;
Engine: IHPDFOCREngine;
Doc: THotPDF;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
I: Integer;
begin
Models := THPDFRapidOCRDLLOptions.Default;
Models.UseAngleClassifier := False; // upprätta skanningar: ingen klassificerarmodell laddas
Models.Threads := 4; // 1..64, taklagd vid antalet logiska processorer
Models.TimeoutMilliseconds := 120000; // per Recognize-anrop, kooperativt
Engine := HPDFCreateRapidOCRDLLOCREngine(
'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models', Models);
Options := THPDFOCRTextLayerOptions.Default;
for I := 0 to Files.Count - 1 do
begin
Doc := THotPDF.Create(nil);
try
Doc.AutoLaunch := False;
if (Doc.LoadFromFile(Files[I]) > 0) and
Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
Doc.SaveLoadedDocument(IncludeTrailingPathDelimiter(OutputDir) +
ExtractFileName(Files[I]))
else
Writeln(Files[I], ': ', string(Info.Diagnostic));
finally
Doc.Free;
end;
end;
end; // sista referens släppt: modeller förstörda, sedan avladdas DLL:en
Threads-värdet sätter både intra-op- och inter-op-trådantalet i varje ONNX-session, och DLL:en klamrar det till antalet aktiva processorer. Två trådar som delar en motor kör inte parallellt; den andra väntar på låset. Den väntan är inte ett blint EnterCriticalSection: adaptorn anropar TryEnterCriticalSection var 25:e ms och kontrollerar annulleringstoken och deadlinen mellan försöken, så en köad begäran fortfarande kan avbrytas eller få timeout. Behöver du äkta parallellism, skapa en motor per arbetare och acceptera att varje motor håller sin egen kopia av modellerna i minnet
Nedmonteringsordningen är fastställd av motorns destruktor: HPDFRapidOCRDestroy frigör modellinstansen först, sedan avladdar FreeLibrary DLL:en. På den nativa sidan är modellinitieringen lika noggrann; misslyckas igenkänningsmodellen efter att detektor- och klassificerarsessioner redan byggts frigörs de sessionerna innan felet rapporteras, och ordbokens klassantal kontrolleras mot modellutdatat under initieringen i stället för på första sidan
Varför kan ett nativt OCR-anrop inte dödas mitt i inferensen?
Ett nativt RapidOCR-anrop kan inte dödas mitt i inferensen för att det körs på din tråd, inuti din process, mitt i en ONNX Runtime-session som inte accepterar avbrott. Annullering i HotPDF:s DLL-adaptor är därför kooperativ: DLL:en anropar ett abort-callback före och efter detektering, efter klassificering, och efter varje igenkänd rad, och stannar vid första checkpoint där callbacken returnerar 0. En enda ONNX Run som startat kommer att slutföras först
Alternativen är värre än att vänta. TerminateThread skulle lämna CRT-heap-låset, ONNX Runtimes trådpool och vart OpenCV-läge i vilket skick de råkade vara i, och förgifta resten av processen. FreeLibrary medan ett anrop fortfarande exekverar avladdar kod som ligger på stacken. Ingen av dem kan göras säker, så adaptorn försöker dem aldrig. Deadlinen i TimeoutMilliseconds är följaktligen en kooperativ deadline, och en utlöpt deadline visar sig som ett motorfel med en timeout-diagnostext, medan en avbruten token visar sig som otlsCancelled:
// Token skapas av anroparen och delas med UI-tråden,
// som anropar Token.Cancel när användaren trycker Stopp
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
case Info.Status of
otlsCancelled:
// returneras vid nästa steg- eller radgräns; dokumentet oförändrat
Writeln('Cancelled');
otlsEngineError:
// inkluderar ett kooperativt deadline-utlöp och nativa diagnostexter
Writeln('Engine: ', string(Info.Diagnostic));
otlsBudgetExceeded:
Writeln('Budget: ', string(Info.Diagnostic));
else
Writeln(string(Info.Diagnostic));
end;
Det här är kärnavvägningen mellan HotPDF:s processadaptrar och in-process-DLL:en, och ingen sida vinner på varje rad:
- Startkostnad: Tesseract- och Python RapidOCR-adaptorerna startar en process och laddar modeller för varje sida; DLL:en laddar modeller en gång per motor
- Att stanna: en barnprocess kan termineras rakt av, och Python-arbetaren kör inuti ett kill-on-close Job Object så hela dess processträd går med den; DLL:en kan bara stanna vid steg- och radgränser
- Feltäckning: en krasch i
tesseract.exefäller en sida; ett access violation inuti DLL:en tar ner din process - Distribution: processadaptrar behöver ett installerat program eller en Python-miljö; DLL:en behöver sig själv, sina modeller och sin ordbok, matchade mot programmets bitbredd
- Minne: processadaptrar frigör allt när barnet avslutas; en DLL-motor håller sina modeller residenta tills den sista interfacetreferensen släpps
För en interaktiv skrivbordsapplikation som OCR:ar en sida i taget vinner DLL:ens responsivitet vanligen. För en server som tar in opålitliga skanningar dygnet runt är processgränsen värd sin startkostnad
Bygga och distribuera HotPDFRapidOCR.dll
HotPDFRapidOCR.dll byggs ur C++-källkoden i Native/RapidOCR med MSVC, C++17, en Windows SDK och CMake 3.20 eller senare, med ett hjälpskript som tar de nativa nätverkskällorna, ONNX Runtime- och OpenCV-katalogerna plus en plattform Win32 eller Win64. Bygg båda om du skeppar båda, för en 32-bitars Delphi-applikation kan inte ladda en 64-bitars DLL, och de statiska bibliotek du förser måste matcha målarkitekturen liksom CRT-läget
Modellsidan har sina egna kompatibilitetsgränser. Detektorn är en DB-textdetektor; igenkännaren accepterar CTC-modeller i NCHW-layout med en fast inmatningshöjd på 32 eller 48, och använder 48 för modeller med dynamisk höjd. Den medföljande statiska ONNX Runtime kan inte ladda modeller sparade med en nyare IR-version, så sena PP-OCRv5-exporter fäller initieringen med en diagnostext i stället för att ladda delvis. Ordboken måste vara UTF-8 utan BOM, i exakt modellens teckenordning, och dess klassantal måste matcha modellutdatat; CRLF-radslut accepteras. Igenkänning är offline: DLL:en laddar aldrig ner en saknad modell
Snabbreferens
- Fabrik:
HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options])iHPDFRapidOCRRecognition, tillgänglig sedan v2.774.0 i Delphi-, C++Builder- och Windows FPC/Lazarus-byggen - Håll den returnerade
IHPDFOCREnginevid liv över sidor och dokument; att släppa den förstör modellerna och avladdar DLL:en - En motor kör en igenkänning i taget; skapa flera motorer för parallella arbetare och budgetera minne för varje modellkopia
- Utdata är en post per textrad med medelteckenkonfidens, filtrerad av
THPDFOCRTextLayerOptions.MinimumConfidence - Annullering och
TimeoutMillisecondsär kooperativa; en pågående ONNX-körning slutförs alltid - Matcha DLL-bitbredd mot programmet och CRT-läget hos de statiska ONNX Runtime- och OpenCV-biblioteken mot DLL:en
- Välj en språkprofil per motor med
THPDFRapidOCRDLLOptions.ForLanguage(v2.775.0); en motor detekterar inte språk på egen hand
Den nativa RapidOCR-adaptorn, de processbaserade OCR-adaptorerna, sidrenderaren som matar dem och skrivaren av osynliga Unicode-textlager skeppas alla tillsammans i HotPDF, en nativ VCL PDF-komponent för Delphi och C++Builder. Behöver din dokumentfångst- eller arkiveringsapplikation sökbar utdata utan en Python-runtime på måldatorn ger HotPDF Delphi PDF component hela pipelinen med bara DLL:en och dess modeller kvar att distribuera