HotPDF menjalankan OCR Tionghoa dan multibahasa di Delphi lewat adapter DLL RapidOCR natifnya: THPDFRapidOCRDLLOptions.ForLanguage memetakan tag bahasa seperti 'zh-CN', 'zh-TW', 'ru', atau 'ar' ke model recognition dan character dictionary yang berpasangan, dan THotPDF.ApplyLoadedOCRTextLayer mengubah baris-baris yang dikenali menjadi text layer Unicode tak terlihat yang searchable di halaman PDF hasil scan
Membuat demo aksara Latin bekerja itu bagian mudahnya. Kegagalan yang menarik baru mulai ketika Anda beralih ke Tionghoa Tradisional atau Rusia dan outputnya berubah jadi omong kosong yang percaya diri dan rapi bentuknya, atau ketika setiap baris diam-diam kehilangan karakter terakhirnya, atau ketika halaman Arab kembali dengan text box-nya dalam urutan yang salah. Tak satu pun dari itu me-raise exception dengan sendirinya. Preset bahasa yang ditambahkan di HotPDF v2.775.0 ada terutama untuk menutup celah-celah itu, dan empat jebakan di bawah pantas dipahami meski Anda tak pernah menyentuh code natifnya, karena masing-masing menjelaskan satu gejala yang bisa membuat Anda menghabiskan sehari mengejarnya
Bagaimana ForLanguage memilih model dan dictionary?
THPDFRapidOCRDLLOptions.ForLanguage me-resolve sebuah tag ke salah satu dari sembilan profil dan mengembalikan opsi yang menunjuk ke <profile>/recognition.onnx dan <profile>/dictionary.txt di bawah direktori model Anda, sambil mempertahankan detector bersama, angle classifier opsional, serta default thread, piksel, dan timeout dari THPDFRapidOCRDLLOptions.Default. Method itu me-lowercase tag-nya, mengubah underscore menjadi hyphen, dan memangkas whitespace di sekelilingnya, sehingga 'zh_TW', 'ZH-tw', dan ' zh-tw ' semuanya mendarat di profil yang sama. Alias adalah daftar eksplisit, bukan prefix match: 'zh-Hant-TW' diterima karena tercantum, sementara varian regional arbitrer yang tak tercantum me-raise EArgumentException sebelum model mana pun dimuat
| Profil | Bahasa | Contoh tag | Model ter-pin |
|---|---|---|---|
ch | Tionghoa Sederhana dan Inggris | zh, zh-CN, zh-Hans, chi_sim | PP-OCRv4 |
chinese_cht | Tionghoa Tradisional | zh-TW, zh-HK, zh-Hant, chi_tra | PP-OCRv3 |
en | Inggris | en, en-US, en-GB, eng | PP-OCRv4 |
latin | Prancis, Jerman, Spanyol, Portugis, Italia, Belanda, Turki | fr, de, es-419, pt-BR, tr | PP-OCRv3 |
japan | Jepang | ja, ja-JP, jpn | PP-OCRv4 |
korean | Korea | ko, ko-KR, kor | PP-OCRv4 |
cyrillic | Rusia, Ukraina, Bulgaria, Belarus | ru, ru-RU, uk, bg | PP-OCRv3 |
arabic | Arab, Persia, Urdu | ar, ar-SA, fa, ur | PP-OCRv4 |
devanagari | Hindi, Marathi, Nepali | hi, mr, ne | PP-OCRv4 |
Adapter itu sendiri tak pernah mengunduh apa pun. Anda memprovisionkan filenya sekali dengan helper bawaan, misalnya tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (atau -Language All untuk semua sembilan profil), dan helper menempatkan detector serta classifier bersama di nama file root yang diharapkan Default. Setelah itu, scan Tionghoa Sederhana jadi searchable dengan beberapa baris. Perpipaan engine-nya adalah seam IHPDFOCREngine yang sama yang dijelaskan di artikel tentang DLL RapidOCR in-process dan boundary ABI-nya, jadi yang ini fokus ke bahasa
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, detector dan classifier bersama
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
// daftar halaman kosong berarti semua halaman; halaman yang sudah punya teks dilewati
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;
Dua detail di output itu pantas dicatat. Pipeline native mengembalikan satu hasil per baris teks yang terdeteksi, bukan per kata, jadi AcceptedWordCount di sini menghitung baris, dan MinimumConfidence dibandingkan terhadap mean character confidence satu baris penuh: baris yang rata-ratanya 0,45 dibuang sebagai satu kesatuan. UniqueScalarCount melaporkan berapa banyak Unicode scalar berbeda yang harus dipetakan text layer ke font dan tabel ToUnicode-nya, sanity check yang berguna bahwa teks CJK benar-benar datang alih-alih segelintir fallback Latin. Jaga interface engine tetap hidup lintas dokumen, karena inisialisasi model terjadi di factory dan itulah langkah termahalnya
Kenapa mengganti hanya model recognition menghasilkan sampah?
Model recognition CTC tak pernah meng-output karakter, hanya indeks class, dan dictionary adalah satu-satunya yang mengubah indeks 1.204 menjadi sebuah glyph. Tukar ch/recognition.onnx dengan cyrillic/recognition.onnx tapi pertahankan dictionary Tionghoa, dan model itu akan dengan senang hati memancarkan indeks Kiril yang valid yang oleh dictionary lama diterjemahkan menjadi karakter Han acak. Hasilnya tampak seperti teks, lolos validasi UTF-8, dan searchable untuk ketiadaan absolut. Itulah kenapa ForLanguage selalu men-set RecognitionModel dan CharacterDictionary bersamaan, dan kenapa opsi buatan tangan tak boleh mengubah salah satunya tanpa yang lain
Pengecekan keamanan yang tampak jelas, membandingkan ukuran dictionary dengan lebar output model, perlu tapi tak cukup. Dua dictionary bisa berjumlah entri sama dalam urutan berbeda, dan off-by-one pada urutan menggeser setiap karakter satu code point. HotPDF karenanya mengecek dalam dua tahap ketika factory menginisialisasi model. Pertama, jumlah class output harus sama dengan entri dictionary plus dua. Kedua, ketika file ONNX menyematkan daftar metadata character, setiap entri dictionary dibandingkan dengannya secara berurutan, dan ketidakcocokan menggagalkan inisialisasi dengan EInvalidOperation plus diagnostik native alih-alih menghasilkan sampah yang plausibel nanti
"Plus dua" itu datang dari layout class. Class 0 adalah CTC blank, class 1 sampai N adalah baris dictionary sesuai urutan file, dan class terakhir adalah spasi. Beberapa dictionary juga membawa entri spasi milik sendiri, dan baris itu harus dipertahankan persis apa adanya. Di sinilah Trim yang niatnya baik melakukan kerusakan sungguhan: ia mengubah entri satu spasi menjadi string kosong dan menggeser atau merusak tabelnya. Satu-satunya normalisasi yang aman adalah membuang carriage return di ekor, sehingga dictionary yang tersimpan dengan line ending CRLF dimuat dengan benar, sementara byte order mark UTF-8, baris kosong, atau entri yang memuat tab ditolak. Sketsa di bawah menunjukkan layoutnya di Pascal; itu code penjelas, bukan API HotPDF
// Ilustrasi saja: tabel class yang diharapkan recognizer CTC
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); // newline di ujung file
SetLength(Result, Last + 3);
Result[0] := ''; // class 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: buang hanya CR-nya
if (Entry = '') or (Pos(#9, Entry) > 0) then
raise EArgumentException.Create('Invalid dictionary entry');
Result[I + 1] := Entry; // jangan pernah Trim: ' ' adalah class
end;
Result[Last + 2] := ' '; // class terakhir: spasi
// Length(Result) harus sama dengan jumlah class output model
end;
Sebenarnya apa yang dilakukan decode CTC greedy?
Decode CTC greedy memilih class berskor tertinggi di setiap time step, menciutkan pengulangan berurutan menjadi satu karakter, dan membuang class blank; blank itulah yang membolehkan huruf yang memang berdobel untuk selamat. Model recognition memandang satu baris teks sebagai sekuens irisan vertikal yang sempit, dan untuk tiap irisan, atau time step, ia meng-output probabilitas untuk setiap class. Baris yang memuat AA中 mungkin menghasilkan sekuens argmax A A blank A 中 space. Menciutkan dua step A pertama memberi satu A, blank memisahkannya dari A berikutnya, dan hasilnya AA中 dengan spasi ekornya utuh. Tanpa aturan blank, book dan bok tak akan bisa dibedakan
Karena decodeurnya cuma belasan baris, gampang sekali salah di boundary-nya, dan kegagalannya senyap. Kalau loop argmax dalam berhenti satu class lebih awal, class spasi tak akan pernah menang dan setiap baris kembali tanpa spasi kata, yang menghancurkan pencarian frasa di halaman Inggris dan Latin. Kalau loop luar berhenti satu time step lebih awal, karakter terakhir setiap baris lenyap, yang untuk baris pendek bisa sepertiga teksnya. Dan kalau repeat guard tak di-reset oleh blank, karakter berdobel seperti ll atau reduplikasi Tionghoa seperti 谢谢 menciut jadi satu. Decoder HotPDF menyertakan class terakhir dan time step terakhir, menjaga pengulangan yang dipisah blank, dan tambahan pula menolak skor yang tak finite atau jatuh di luar 0 sampai 1, serta jumlah class mana pun yang tak cocok dengan dictionary. Berikut logika yang sama sebagai ilustrasi Pascal
// Ilustrasi saja: decode CTC greedy dengan boundary yang benar.
// Scores menyimpan Steps * Classes probabilitas, satu baris per time step
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; // class 0 adalah CTC blank
for Step := 0 to Steps - 1 do // sertakan time step terakhir
begin
Best := 0;
BestScore := Scores[Step * Classes];
for C := 1 to Classes - 1 do // sertakan class terakhir (spasi)
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; // blank me-reset repeat guard
end;
end;
Decode greedy bukan strategi CTC paling akurat yang tersedia; beam search dengan language model bisa memperbaiki sebagian irisan ambigu. Untuk dokumen cetak pada 300 DPI, hasil greedy biasanya adalah yang bisa ditawarkan model, dan decoder bukan tempat mengompensasi kelemahan model. Model PP-OCRv3 Latin misalnya bisa membaca ñ sebagai n bahkan pada input bersih. HotPDF tak menutupi itu dengan replacement karakter pasca-proses, karena tabel substitusi yang memperbaiki Spanyol justru merusak hal lain, dan satu karakter yang salah di searchable layer lebih buruk daripada satu miss yang jujur
Bagaimana HotPDF mengurutkan baris teks, termasuk Arab kanan-ke-kiri?
HotPDF mengurutkan text box yang terdeteksi dari atas ke bawah, mengelompokkan box menjadi satu baris ketika overlap vertikalnya setidaknya separuh tinggi box yang lebih kecil, dan mengurutkan tiap baris kiri ke kanan, atau kanan ke kiri ketika RightToLeft aktif; karakter di dalam tiap baris yang dikenali tak pernah dibalik. Pengelompokannya penting karena detector sering membelah satu baris visual menjadi beberapa box, misalnya label dan nilai yang dipisah celah lebar, dan sort koordinat-atas murni akan menyilangkannya dengan baris tetangga kapan pun bagian atas mereka berbeda satu dua piksel
Preset Arab men-set RightToLeft := True, yang menyuruh DLL mengurutkan box di tiap baris berdasarkan tepi kanannya, dari margin kanan ke dalam. Itu seluruh efeknya. Teks yang dikembalikan model untuk satu baris sudah dalam urutan logis Unicode, urutan yang dibaca dan diketik pembaca Arab, dan itu juga urutan yang diharapkan ekstraksi teks serta pencarian PDF. Membalik string secara mekanis agar "tampak benar" di debugger justru merusak pencarian, copy-paste, dan screen reader. Tampilan bidireksional dan glyph shaping adalah urusan viewer
Satu engine melayani satu profil bahasa. Tak ada deteksi aksara otomatis, jadi dokumen yang mencampur aksara butuh satu engine per profil, diterapkan ke halaman-halaman yang memakainya. Karena ApplyLoadedOCRTextLayer menerima daftar halaman eksplisit dan meng-commit tiap panggilan sebagai transaksi all-or-nothing miliknya sendiri, itu lugas saja
uses
SysUtils, HPDFDoc, HPDFRapidOCRRecognition;
function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
Models: THPDFRapidOCRDLLOptions;
begin
// me-raise EArgumentException untuk tag tak dikenal, sebelum model mana pun dimuat
Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
Models.MaxPixels := 33554432; // ruang untuk halaman A3 pada 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'); // profil chinese_cht
Arabic := CreateRapidEngine('ar-SA'); // profil arabic, 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;
Baris MaxPixels itu ada karena alasannya. Opsi DLL default ke 16.777.216 piksel per permintaan, yang mencover A4 dan US Letter pada 300 DPI dengan lega, tapi halaman A3 pada 300 DPI sekitar 3508 kali 4961 piksel, kira-kira 17,4 juta, dan permintaannya ditolak sebagai over budget. Naikkan MaxPixels (langit-langitnya 67.108.864) atau turunkan THPDFOCRTextLayerOptions.DPI untuk format besar. Pengurutan kanan-ke-kiri memakai export opsional HPDFRapidOCRSetReadingDirection milik versi ABI 1; adapter hanya mewajibkannya ketika RightToLeft diset, jadi DLL yang lebih tua tetap melayani bahasa kiri-ke-kanan dan gagal saat penciptaan engine dengan EArgumentException yang menyebut export yang hilang untuk Arab
Kenapa model OCR yang lebih baru gagal dimuat?
DLL RapidOCR HotPDF menautkan ONNX Runtime 1.14 statis, yang tak bisa membaca model yang disimpan dengan ONNX IR version 10, dan export yang lebih baru seperti model PP-OCRv5 bisa menuntut runtime yang lebih baru dari itu; model semacam itu gagal saat penciptaan engine dengan diagnostik native. Batasan itulah alasannya language pack ter-pin ke pasangan recognizer dan dictionary PP-OCRv3 serta PP-OCRv4 tertentu alih-alih "yang terbaru", dan kenapa tabel di atas mencampur dua generasi itu: setiap pasangan ter-pin adalah yang termuat dan terverifikasi di bawah runtime itu
Installer menegakkan pasangan itu. Setiap file di manifest-nya membawa hash SHA256, file yang sudah ada dengan hash berbeda menghentikan instalasi alih-alih ditimpa, dan tiap unduhan mendarat di bawah nama sementara dan hanya berpindah ke tempatnya setelah hash-nya cocok. Itu melindungi dari versi senyap dari masalah dictionary: seseorang meletakkan recognition.onnx yang lebih baru ke folder profil dengan tangan, jumlah class-nya kebetulan cocok, dan tak ada yang gagal sampai pelanggan melapor bahwa pencarian tak menemukan kata yang jelas-jelas mereka lihat. Saat runtime, adapter tetap offline dan tak pernah mengambil model yang hilang. Recognizer juga memvalidasi bentuk model saat load, menerima input NCHW dengan tinggi tetap 32 atau 48 piksel atau tinggi dinamis, yang ia jalankan pada 48
Kalau Anda butuh aksara yang tak tercover sembilan profil itu, Anda masih bisa menunjukkan RecognitionModel dan CharacterDictionary ke file milik Anda sendiri. Pengecekan yang sama berlaku, dan memang itu intinya: pasangan yang tak cocok gagal saat inisialisasi, bukan di arsip pelanggan Anda. Untuk halaman yang tak cocok dengan profil RapidOCR mana pun, adapter Tesseract untuk searchable PDF menyambung ke panggilan ApplyLoadedOCRTextLayer yang sama, dan untuk formulir ASCII cetak mesin engine OCR template-matching bawaan tak butuh model sama sekali
Referensi cepat: checklist RapidOCR multibahasa
- Ciptakan opsi dengan
THPDFRapidOCRDLLOptions.ForLanguagedan perlakukanEArgumentExceptionsebagai tag yang tak didukung, bukan fault runtime - Ubah
RecognitionModeldanCharacterDictionarybersamaan, tak pernah satu saja; jumlah class yang sama tak membuktikan urutan karakter yang sama - Simpan dictionary sebagai UTF-8 tanpa BOM, jangan pernah memangkas entri, dan harapkan model punya N + 2 class: blank, N entri, spasi
- Decoder CTC custom harus mencover class terakhir dan time step terakhir serta menjaga pengulangan tetap dipisah blank
- Pakai satu engine per profil bahasa dan teruskan daftar halaman eksplisit untuk dokumen campuran aksara
RightToLefthanya mengubah urutan box; teks yang dikenali tetap dalam urutan logis Unicode- Instal model dengan
Install-RapidOCRModels.ps1agar pin SHA256 menjaga pasangan model dan dictionary; setUseAngleClassifier := Falsejika Anda menginstal dengan-SkipClassifier - Naikkan
MaxPixelsdi atas default 16.777.216 sebelum menjalankan halaman A3 atau lebih besar pada 300 DPI
Preset bahasa RapidOCR, adapter DLL native, dan pipeline text layer OCR adalah bagian dari HotPDF Delphi PDF Component untuk Delphi, C++Builder, dan Windows FPC/Lazarus, dimulai dari v2.775.0 untuk profil multibahasanya