Artikel Teknis

RapidOCR Native DLL: OCR In-Process di HotPDF untuk Delphi

HotPDF membuat halaman PDF hasil scan jadi searchable dengan RapidOCR in-process lewat HPDFCreateRapidOCRDLLOCREngine, sebuah factory yang ditambahkan di v2.774.0 yang memuat HotPDFRapidOCR.dll, menahan model deteksi, klasifikasi sudut, dan recognition ONNX tetap resident di memori, serta mengembalikan sebuah IHPDFOCREngine. Engine itu Anda teruskan ke THotPDF.ApplyLoadedOCRTextLayer, yang merender tiap halaman, menjalankan inferensi CPU tanpa Python atau proses anak, dan meng-commit text layer Unicode yang tak terlihat

Motivasinya adalah biaya per halaman. Adapter proses RapidOCR yang terbit lebih dulu, HPDFCreateRapidOCREngine, menyalakan worker Python untuk setiap panggilan Recognize, dan worker itu meng-import runtime-nya serta memuat model ONNX-nya sebelum membaca satu piksel pun. Di arsip 500 halaman, pajak start-up itu berulang 500 kali, dan deployment berarti mengirim lingkungan Python di samping executable Delphi. DLL native memuat model sekali, saat Anda menciptakan engine-nya, dan deployment menyusut menjadi DLL-nya, file modelnya, dan satu character dictionary. Yang Anda serah sebagai gantinya adalah kemampuan membunuh recognizer yang macet, dan sebagian besar engineering di adapter ini adalah soal hidup dengannya secara jujur

Bagaimana membuat PDF hasil scan jadi searchable dengan DLL RapidOCR?

Membuat searchable PDF dengan DLL RapidOCR native butuh satu panggilan factory dan panggilan ApplyLoadedOCRTextLayer yang sama yang dipakai setiap engine OCR HotPDF. Factory-nya tinggal di unit HPDFRapidOCRRecognition dan memvalidasi dengan serobot: direktori DLL dan model harus ada, setiap file model dan dictionary harus resolve, versi ABI harus 1, dan semua export wajib harus tersedia sebelum model mana pun diinisialisasi. Kesalahan konfigurasi me-raise EArgumentException; model yang gagal dimuat me-raise EInvalidOperation yang membawa teks diagnostik yang ditulis DLL

Urutan validasi factory RapidOCR DLL HotPDF untuk HPDFCreateRapidOCRDLLOCREngine: path dan file model harus ada, HPDFRapidOCRAbiVersion harus mengembalikan 1, export wajib harus resolve, dan HPDFRapidOCRCreate harus menginisialisasi model, dengan EArgumentException atau EInvalidOperation di-raise dengan serobot sebelum recognition mana pun jalan, yang terakhir membawa teks diagnostik native
Validasi sengaja serobot: masalah konfigurasi me-raise sebelum model mana pun terinisialisasi, sehingga path atau ABI yang buruk tak pernah sampai ke deadline recognition
uses
  SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Model dimuat di sini, di luar deadline recognition apa pun.
  // Nama model relatif di THPDFRapidOCRDLLOptions.Default di-resolve
  // terhadap direktori model.
  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
    // daftar halaman kosong berarti semua halaman; halaman yang sudah punya teks dilewati
    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 menyebut ch_PP-OCRv3_det_infer.onnx, ch_PP-OCRv3_rec_infer.onnx, ch_ppocr_mobile_v2.0_cls_infer.onnx, dan ppocr_keys_v1.txt, dengan satu CPU thread, limit input 16.777.216 piksel, dan deadline recognition 60.000 ms. Sejak v2.775.0, THPDFRapidOCRDLLOptions.ForLanguage menukar masuk model recognition dan dictionary yang cocok untuk Traditional Chinese, Rusia, Jepang, Arab, dan profil lain; kenapa model dan dictionary harus berganti bersama dibahas di model multibahasa dan CTC dictionary RapidOCR di HotPDF. Engine melaporkan dirinya sebagai RapidOCR (native DLL) di Info.EngineName, yang menjaga log tetap jelas di samping adapter proses Tesseract OCR eksternal dan engine OCR template-matching bawaan

Kenapa C ABI hanya berbicara int32_t dan byte UTF-8?

ABI milik HotPDFRapidOCR.dll hanya memakai integer fixed-width, pointer mentah, dan panjang byte eksplisit karena Delphi, C++Builder, dan Free Pascal tak berbagi apa pun dengan MSVC selain calling convention C. Sebuah std::string, std::vector, atau exception C++ punya layout dan model unwinding yang milik satu compiler dan satu runtime library. Biarkan salah satunya melintasi boundary, kegagalannya adalah stack korup atau heap block yang di-free oleh allocator yang salah, bukan error yang bersih

Versi ABI 1 karenanya mengikuti daftar aturan pendek. Setiap export adalah cdecl dan mengembalikan status int32_t, di mana 1 berarti sukses dan 0 berarti gagal. Setiap fungsi yang bisa gagal menerima buffer diagnostik milik caller beserta kapasitasnya dalam byte; DLL menulis pesan UTF-8 yang diakhiri NUL dan dipotong agar muat, dan adapter men-decode-nya dengan terminator keras di byte terakhir buffer 4.096 byte miliknya sendiri. Tiap badan export dibungkus try dengan catch (const std::exception &) dan catch (...) sekaligus, sehingga error ONNX Runtime, assertion OpenCV, atau dictionary yang invalid menjadi status 0 plus teks, tak pernah exception yang lolos ke code Pascal

ExportPeranKapan adapter me-resolve-nya
HPDFRapidOCRAbiVersionMengembalikan 1; nilai lain ditolakPertama, sebelum apa pun
HPDFRapidOCRCreateMemuat model deteksi, klasifikasi opsional, recognition, dan dictionaryDi factory
HPDFRapidOCRRecognizeMenjalankan satu bitmap dan memancarkan satu callback per baris teksDi factory
HPDFRapidOCRDestroyMembebaskan instance modelDi factory
HPDFRapidOCRSetReadingDirectionUrutan baris kanan-ke-kiri opsional, ditambah di v2.775.0Hanya ketika RightToLeft diset

Export opsional itu di-resolve secara lazy dengan sengaja: DLL v2.774.0 yang tak memilikinya tetap melayani permintaan kiri-ke-kanan. DLL dimuat dengan LoadLibraryEx dengan flag pencarian yang mencakup folder DLL itu sendiri plus direktori safe default, sehingga dependensi ONNX Runtime atau OpenCV yang diletakkan di samping HotPDFRapidOCR.dll ditemukan tanpa menyentuh PATH. Path model dan dictionary berkelana sebagai UTF-8 dan DLL mengonversinya dengan MultiByteToWideChar dalam mode strict sebelum membuka file lewat API wide-character, sehingga direktori model di bawah nama pengguna Tionghoa atau Kiril bekerja alih-alih di-widen byte demi byte menjadi omong kosong

Satu aturan tinggal di build, bukan di header. DLL itu menautkan secara statis ONNX Runtime dan OpenCV, dan konfigurasi CMake default memakai static release CRT (/MT). Static library yang di-compile terhadap /MD yang tercampur ke DLL /MT menghasilkan link error paling bagus dan dua heap independen paling buruk, jadi library yang diprovisionkan harus cocok dengan mode CRT mana pun yang dipakai DLL

Apa yang terjadi antara sebuah TBitmap dan satu baris teks?

HotPDF menyerahkan snapshot BGR top-down independen dari halaman hasil render ke DLL, dan DLL menyerahkan kembali satu callback per baris teks yang dikenali dengan teks UTF-8 pinjaman yang harus disalin adapter sebelum kembali

Di Delphi, adapter meng-assign bitmap halaman ke TBitmap privat, memaksa pf24bit, dan membaca baris dengan GetDIBits memakai biHeight negatif, yang menghasilkan baris top-down yang di-pad ke align empat byte; stride itu diteruskan eksplisit. Di FPC ia membaca lewat CreateIntfImage, karena penulisan scanline LCL bisa memperbarui image mentah tanpa me-refresh handle GDI. Bitmap milik caller tak pernah dimodifikasi, dan anggaran piksel (MaxPixels, default 16.777.216 dan bisa dikonfigurasi sampai 67.108.864) serta limit 32.767 piksel per dimensi dicek sebelum buffer snapshot dialokasikan

Pipeline RapidOCR DLL HotPDF dari bitmap ke text layer: adapter meng-snapshot halaman sebagai pf24bit BGR top-down, DLL mem-pad, mendeteksi, mengurutkan dan mengenali crop, menyerahkan satu callback per baris dengan teks UTF-8 pinjaman, box dan confidence, dan adapter memvalidasi tiap baris sebelum commit text layer
Piksel melintasi ABI sekali sebagai snapshot, baris kembali satu callback per waktu, dan tak ada apa pun yang mencapai searchable layer sampai semua pengecekan lolos

Di dalam DLL, snapshot di-pad dengan 50 piksel putih, region teks dideteksi dengan sisi maksimum 1.024 piksel, box diurutkan menjadi baris horizontal, dan tiap crop secara opsional dirotasi oleh angle classifier sebelum recognition. Tiap baris teks lalu melewati callback yang menerima const char*, jumlah byte, box integer dalam piksel image asli, dan mean character confidence. Pointer teks hanya valid selama callback, jadi adapter menyalinnya seketika, dan ia ketat soal apa yang diterimanya:

  • UTF-8 di-decode dengan MB_ERR_INVALID_CHARS; sekuens yang malformed menggagalkan halaman alih-alih menghasilkan replacement character di searchable layer
  • Karakter kontrol C0 dan C1 ditolak, dan baris yang isinya whitespace saja dilewati
  • Box harus berada di dalam bitmap dan confidence harus nilai finite dari 0 sampai 1
  • Teks dihitung terhadap MaxTextCodeUnits milik permintaan dengan langit-langit keras 1.048.576 unit UTF-16 per panggilan, dan karakter supplementary-plane berbiaya dua unit
  • Exception Pascal apa pun di dalam callback ditangkap di situ, disimpan, dan diubah menjadi return 0, yang membuat DLL berhenti dan melaporkan kegagalan; pesan tersimpan itu lalu menjadi diagnostiknya

Dua konsekuensi penting untuk tuning. Pertama, satuan output adalah baris, bukan kata: tiap baris mengonsumsi satu slot MaxWords, Info.AcceptedWordCount dan Info.DroppedWordCount menghitung baris, dan highlight pencarian membentang di box baris. Kedua, MinimumConfidence (default 0,5) dibandingkan terhadap mean character confidence baris itu, sehingga baris dengan satu karakter tak terbaca di antara dua puluh yang bersih biasanya selamat. DLL tak menyuplai baseline, jadi pipeline text layer mengestimasi satu dari box. Halaman kosong sukses dengan nol baris, dan kegagalan apa pun membersihkan hasil parsial agar commit multi-halaman tetap all-or-nothing

Kepemilikan model dan thread safety

Tiap engine RapidOCR DLL memiliki tepat satu instance model sepanjang hidupnya, dan panggilan Recognize ke engine itu diserialisasi oleh critical section. Memegang interface IHPDFOCREngine itulah yang menjaga model tetap hangat, jadi pola yang benar untuk kerja batch adalah menciptakan engine sekali dan memakai ulangnya lintas dokumen

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;    // scan tegak: tak ada model classifier yang dimuat
  Models.Threads := 4;                   // 1..64, di-clamp ke jumlah logical processor
  Models.TimeoutMilliseconds := 120000;  // per panggilan Recognize, kooperatif
  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;  // referensi terakhir dilepas: model dihancurkan, lalu DLL di-unload

Nilai Threads menyetel jumlah thread intra-op dan inter-op sekaligus dari tiap sesi ONNX, dan DLL men-clamp-nya ke jumlah processor aktif. Dua thread yang berbagi satu engine tak berjalan paralel; yang kedua menunggu lock. Wait itu bukan EnterCriticalSection buta: adapter memanggil TryEnterCriticalSection tiap 25 ms dan mengecek cancellation token serta deadline di antara percobaan, sehingga permintaan yang mengantre tetap bisa dibatalkan atau habis waktu. Kalau Anda butuh paralelisme sungguhan, ciptakan satu engine per worker dan terima bahwa tiap engine memegang salinan modelnya sendiri di memori

Urutan teardown ditetapkan oleh destructor engine: HPDFRapidOCRDestroy membebaskan instance model lebih dulu, lalu FreeLibrary meng-unload DLL. Di sisi native, inisialisasi model sama rapihnya; ketika model recognition gagal setelah sesi detector dan classifier sudah dibangun, sesi-sesi itu dilepas sebelum error dilaporkan, dan jumlah class dictionary dicek terhadap output model saat inisialisasi, bukan di halaman pertama

Kenapa panggilan OCR native tak bisa dibunuh di tengah inferensi?

Panggilan RapidOCR native tak bisa dibunuh di tengah inferensi karena ia jalan di thread Anda, di dalam proses Anda, di tengah sesi ONNX Runtime yang tak menerima interupsi. Pembatalan di adapter DLL milik HotPDF karenanya kooperatif: DLL memanggil abort callback sebelum dan sesudah deteksi, sesudah klasifikasi, dan sesudah tiap baris yang dikenali, dan berhenti di checkpoint pertama tempat callback mengembalikan 0. Satu Run ONNX yang sudah mulai akan menyelesaikan dirinya lebih dulu

Alternatifnya lebih buruk daripada menunggu. TerminateThread akan membiarkan heap lock CRT, thread pool ONNX Runtime, dan state OpenCV mana pun dalam kondisi apa pun yang kebetulan mereka sedang berada, meracuni sisa prosesnya. FreeLibrary selagi panggilan masih dieksekusi meng-unload code yang sedang ada di stack. Tak satu pun bisa dibuat aman, jadi adapter tak pernah mencobanya. Deadline di TimeoutMilliseconds karenanya deadline kooperatif, dan deadline yang lewat muncul sebagai engine error dengan diagnostik timed-out, sementara token yang dibatalkan muncul sebagai otlsCancelled:

// Token dibuat oleh caller dan dibagikan dengan UI thread,
// yang memanggil Token.Cancel saat pengguna menekan Stop
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
  case Info.Status of
    otlsCancelled:
      // dikembalikan di boundary stage atau baris berikutnya; dokumen tak berubah
      Writeln('Cancelled');
    otlsEngineError:
      // termasuk kedaluwarsa deadline kooperatif dan diagnostik native
      Writeln('Engine: ', string(Info.Diagnostic));
    otlsBudgetExceeded:
      Writeln('Budget: ', string(Info.Diagnostic));
  else
    Writeln(string(Info.Diagnostic));
  end;

Inilah trade-off inti antara adapter proses milik HotPDF dan DLL in-process, dan tak ada sisi yang menang di setiap baris:

Trade-off adapter OCR HotPDF: adapter proses menyalakan worker dan memuat model di setiap halaman tapi bisa dibunuh dan menampung crash, sedangkan DLL RapidOCR in-process memuat model sekali, berhenti hanya di checkpoint kooperatif, berbagi address space, dan di-deploy sebagai DLL dengan model dan dictionary-nya
Pilih per workload: aplikasi desktop satu halaman per waktu untung dengan DLL yang hangat, sedangkan server yang menelan scan tak terpercaya pantas membayar wall proses
  • Biaya start-up: adapter Tesseract dan Python RapidOCR menyalakan proses dan memuat model untuk setiap halaman; DLL memuat model sekali per engine
  • Menghentikan: proses anak bisa di-terminate bulat-bulat, dan worker Python jalan di dalam Job Object kill-on-close sehingga seluruh process tree-nya ikut; DLL hanya bisa berhenti di boundary stage dan baris
  • Penampungan fault: crash di tesseract.exe menggagalkan satu halaman; access violation di dalam DLL membawa proses Anda ikut tumbang
  • Deployment: adapter proses butuh program terinstal atau lingkungan Python; DLL butuh dirinya, modelnya, dan dictionary-nya, yang cocok dengan bitness aplikasi
  • Memori: adapter proses melepas semuanya saat anaknya keluar; engine DLL menahan modelnya resident sampai referensi interface terakhir dilepas

Untuk aplikasi desktop interaktif yang meng-OCR satu halaman per waktu, responsivitas DLL biasanya menang. Untuk server yang menelan scan tak terpercaya sepanjang waktu, boundary proses pantas membayar biaya start-upnya

Membangun dan men-deploy HotPDFRapidOCR.dll

HotPDFRapidOCR.dll dibangun dari source C++ di Native/RapidOCR dengan MSVC, C++17, sebuah Windows SDK, dan CMake 3.20 atau lebih baru, memakai script helper yang menerima direktori sumber jaringan native, ONNX Runtime, dan OpenCV plus platform Win32 atau Win64. Bangun keduanya jika Anda mengirim keduanya, karena aplikasi Delphi 32-bit tak bisa memuat DLL 64-bit, dan static library yang Anda provisionkan harus cocok dengan arsitektur target sekaligus mode CRT

Sisi model punya limit kompatibilitasnya sendiri. Detector-nya adalah DB text detector; recognizer menerima model CTC berlayout NCHW dengan tinggi input tetap 32 atau 48, dan memakai 48 untuk model bertinggi dinamis. ONNX Runtime statis bawaan tak bisa memuat model yang disimpan dengan versi IR lebih baru, jadi export PP-OCRv5 terbaru gagal inisialisasi dengan diagnostik alih-alih termuat sebagian. Dictionary harus UTF-8 tanpa BOM, dalam urutan karakter model yang persis, dan jumlah class-nya harus cocok dengan output model; line ending CRLF diterima. Recognition itu offline: DLL tak pernah mengunduh model yang hilang

Referensi cepat

  • Factory: HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options]) di HPDFRapidOCRRecognition, tersedia sejak v2.774.0 di build Delphi, C++Builder, dan Windows FPC/Lazarus
  • Jaga IHPDFOCREngine yang dikembalikan tetap hidup lintas halaman dan dokumen; melepaskannya menghancurkan model dan meng-unload DLL
  • Satu engine menjalankan satu recognition pada satu waktu; ciptakan beberapa engine untuk worker paralel dan anggarkan memori untuk tiap salinan model
  • Output adalah satu entri per baris teks dengan mean character confidence, difilter oleh THPDFOCRTextLayerOptions.MinimumConfidence
  • Pembatalan dan TimeoutMilliseconds itu kooperatif; run ONNX yang sedang berjalan selalu selesai
  • Cocokkan bitness DLL dengan aplikasi dan mode CRT static library ONNX Runtime serta OpenCV dengan DLL
  • Pilih profil bahasa per engine dengan THPDFRapidOCRDLLOptions.ForLanguage (v2.775.0); satu engine tak mendeteksi bahasa sendirian

Adapter RapidOCR native, adapter OCR berbasis proses, page renderer yang menyuapi mereka, dan penulis text layer Unicode tak terlihat semuanya dikirim bersama di HotPDF, komponen PDF VCL native untuk Delphi dan C++Builder. Kalau aplikasi document capture atau arsip Anda butuh output searchable tanpa runtime Python di mesin target, komponen PDF Delphi HotPDF menyediakan seluruh pipeline dengan hanya DLL dan modelnya yang tersisa untuk di-deploy