Artikel Teknis

Dekode Kode QR Berotasi di Halaman PDF dengan HotPDF

HotPDF mendekode simbol QR berotasi di halaman PDF termuat dengan menormalkan matriks modul hasil sampling melalui kedelapan orientasi D4 di dalam decoder itu sendiri. Retry rotasi dari luar yang bekerja untuk symbology linear tidak bisa bekerja untuk QR, dan memahami alasannya menghemat satu hari Anda mengejar decoder yang kelihatan rusak padahal tidak

Skenarionya biasa saja. Surat jalan hasil scan datang sebagai PDF, tiap halaman membawa label QR, dan operator scanner memasukkan tumpukan kertas dengan arah mana pun yang diterima tray. Sebagian label tegak, sebagian meleset seperempat putaran, sebagian lagi terbalik. Anda memanggil decoder barcode, separuh halaman resolve, separuhnya lagi kembali kosong tanpa error sama sekali

Mengapa memutar scan mask tak pernah memperbaiki QR berotasi?

Karena tata letak finder pattern QR sengaja dibuat asimetris, dan rotasi seluruh gambar mempertahankan asimetri itu alih-alih menghapusnya. QR Code menaruh tiga persegi finder di sudut kiri-atas, kanan-atas dan kiri-bawah, dan membiarkan sudut kanan-bawah kosong (ISO/IEC 18004:2015 §6.3.3). Sudut yang hilang itulah penunjuk orientasinya. Putar bitmap halaman sembilan puluh derajat dan celahnya sekadar pindah ke sudut lain. Tidak ada rotasi non-trivial atas bidang yang memetakan tata letak tiga sudut kembali ke dirinya sendiri, jadi decoder yang hanya menerima susunan kanonik akan menolak setiap percobaan bergantian

Ini penting karena solusi yang tampak jelas justru yang salah. Naluri alami adalah menggantung retry di luar: render halaman, serahkan mask ke decoder, dan kalau gagal, putar mask lalu coba lagi pada 90, 180 dan 270 derajat. Untuk Code 39 kebijakan itu tepat sekali, karena symbology linear punya pola start dan stop yang bisa ditemukan scanner begitu batang-batangnya berjalan horizontal. Untuk QR, itu empat kegagalan terjamin yang ditutup laporan tidak ditemukan apa pun

Grup D4, diterapkan pada matriks modul

Tempat yang benar untuk normalisasi adalah setelah sampling, pada grid modul boolean alih-alih pada mask piksel. Begitu decoder menyelesaikan simbol menjadi matriks n kali n modul gelap dan terang, ia bisa mengenumerasi grup dihedral persegi: empat rotasi kali dua refleksi, delapan orientasi kandidat total. Untuk tiap kandidat ia memeriksa segitiga finder, dan kandidat pertama yang ketiga finder-nya mendarat di posisi kiri-atas, kanan-atas dan kiri-bawah adalah orientasi yang sebenarnya. Dari sana pipeline yang sudah ada berjalan tanpa perubahan, karena bit format information, penempatan data zigzag, dan koreksi Reed-Solomon semuanya mengasumsikan matriks kanonik dan kini mendapatkannya

Empat rendering matriks modul QR HotPDF yang sama di bawah rotasi grup D4 pada 0, 90, 180 dan 270 derajat, menunjukkan ketiga finder pattern berpindah sudut sementara sudut kosong bergerak bersama mereka, sehingga hanya orientasi kanonik yang menyajikan finder di kiri-atas, kanan-atas dan kiri-bawah kepada decoder
Memutar mask piksel tidak bisa menghapus asimetri finder QR, jadi HotPDF mengenumerasi orientasi D4 pada matriks modul hasil sampling dan menyimpan kandidat pertama yang finder-nya mendarat di kiri-atas, kanan-atas dan kiri-bawah

Dua properti membuat ini murah. Matriksnya kecil dibanding bitmap hasil render, jadi delapan transpose jauh lebih murah daripada delapan render halaman. Dan matriksnya array boolean bersih yang dibangun sampler, sehingga transform apa pun di sepanjang jalan tak bisa memperkenalkan nilai yang tidak pernah di-sampling

Deteksi versi adalah pencarian keterbagian, bukan pembagian

Jumlah modul tidak bisa diturunkan dengan membagi lebar hasil sampling dengan ukuran modul yang diasumsikan, dan keliru di sini adalah sumber kegagalan dekode yang halus pada render resolusi tinggi. Simbol QR versi v lebarnya 4v + 17 modul, jadi versi 1 adalah 21 modul dan versi 40 adalah 177. Mask yang terukur 126 piksel sama konsistennya dengan versi 1 pada enam piksel per modul maupun beberapa versi lebih tinggi pada ukuran modul lebih kecil. Pembagian linear memilih salah satunya dan biasanya salah

Yang bekerja adalah pencarian keterbagian atas versi kandidat. Telusuri dari versi 40 turun ke versi 1, simpan kandidat yang jumlah modulnya membagi habis lebar hasil sampling dan menyisakan sekurangnya tiga piksel per modul, dan ambil versi bertahan yang terkecil. Batas bawah tiga piksel itulah yang menghentikan pencarian menerima pembacaan absurdnya padat atas simbol yang kasar, dan aturan versi-terkecil menyelesaikan ambiguitas tersisa demi pembacaan yang benar-benar akan dihasilkan scanner

Langkah deteksi versi HotPDF untuk simbol QR pada mask hasil sampling 126 piksel, menguji tiap jumlah modul kandidat 4v plus 17 dari versi 40 turun ke versi 1 untuk keterbagian habis dan batas bawah modul tiga piksel sebelum versi bertahan terkecil menang
Jumlah modul QR berasal dari pencarian keterbagian atas versi kandidat, bukan dari membagi lebar mask dengan ukuran modul yang diasumsikan, dan versi bertahan terkecil menyelesaikan ambiguitasnya
var
  Pdf: THotPDF;
  Options: THPDFBarcodeDecodeOptions;
  Codes: THPDFDecodedBarcodes;
  Info: THPDFBarcodeDecodeInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('delivery-notes.pdf');
    Options := THPDFBarcodeDecodeOptions.Default;
    Options.DPI := 300;
    Options.RotationPolicy := bdrpFallback;
    Options.MinimumConfidence := 0.5;
    Options.MaxResults := 16;
    if Pdf.DecodeLoadedPageBarcodes(0, Options, Codes, Info) then
      for I := 0 to High(Codes) do
        if Codes[I].Symbology = bsyQRCode then
          Writeln(Codes[I].Text, '  at ',
            Format('%.0f', [Codes[I].OrientationDegrees]), ' degrees');
  finally
    Pdf.Free;
  end;
end;

THPDFBarcodeDecodeOptions.Default mengembalikan record yang sudah terisi alih-alih yang dinolkan, dan itu penting karena DPI nol atau batas hasil nol adalah cara yang tampak sah untuk mendapatkan kosong. RotationPolicy mengendalikan retry luar saja: bdrpNone merender sekali, bdrpFallback mencoba ulang orientasi lain setelah pass pertama gagal, dan bdrpAll merender setiap orientasi tanpa syarat. Karena normalisasi QR terjadi di dalam decoder, halaman QR resolve pada percobaan pertama di bawah ketiga kebijakan. Kebijakannya ada untuk symbology linear yang benar-benar membutuhkannya

Bagaimana membuktikan transform bitmap tidak mengarang piksel?

Hitung tinta di kedua sisi dan syaratkan totalnya cocok. Rotasi adalah permutasi piksel, tidak lebih, jadi jumlah sel non-nol di output harus sama dengan jumlah di input. Ketika rotasi mask di jalur retry luar melaporkan 4800 sel terisi masuk dan 7439 keluar, satu perbandingan itu cukup untuk menjatuhkan transform itu tanpa membaca satu baris geometrinya

Penyebabnya membosankan dan layak dibawa pulang sebagai aturan. Dynamic array yang diukur dengan SetLength tidak dijamin tiba dalam keadaan nol ketika ia hasil fungsi yang menempuh jalur yang tidak dibersihkan runtime, dan sel yang tak pernah ditulis rotasi membawa byte apa pun yang ada di sana sebelumnya. Sebagian byte basi itu non-nol, dan non-nol berarti tinta. Perbaikannya satu baris, FillChar(Result[0], N, 0) sebelum loop permutasi berjalan, dan disiplin yang tersirat lebih luas: fungsi apa pun yang mengembalikan buffer mask atau bitmap seharusnya membersihkan output-nya secara eksplisit alih-alih mengandalkan semantik alokasi

Apa yang membuat cacat itu selamat tiga rilis lebih menarik daripada cacatnya sendiri. Begitu QR memindahkan penanganan orientasinya ke dalam decoder, QR berhenti menjalankan rotasi mask luar sama sekali, dan satu-satunya konsumen tersisa jalur kode itu adalah Code 39. Infrastruktur bersama menyembunyikan bug semacam ini sepanjang waktu: coverage dari satu fitur membuat sebuah jalur tampak teruji sementara fitur yang benar-benar bergantung padanya tidak punya apa-apa sendiri. Setiap jalur yang berhenti dipakai fitur baru butuh test yang tetap memakainya

Membaca hasil kembali dalam koordinat halaman

Setiap nilai geometris yang dihasilkan decoder dinyatakan dalam frame koordinat bitmap percobaan, dan pemanggil membutuhkannya dalam user space PDF. Konversinya berjalan dua tahap: batalkan seperempat putaran yang diterapkan retry, lalu batalkan transform render yang memetakan user space ke bitmap. Yang tiba di THPDFDecodedBarcode adalah bounding box sejajar sumbu dalam user space, dengan Left, Bottom, Right dan Top mengikuti konvensi PDF bahwa Y bertambah ke atas, plus OrientationDegrees berlawanan arah jarum jam

Pipeline barcode HotPDF dari bitmap halaman hasil render melalui sampling menjadi matriks modul boolean, normalisasi D4, deteksi versi keterbagian dan dekode Reed-Solomon, lalu konversi koordinat dua tahap yang membatalkan seperempat putaran retry dan transform render sebelum THPDFDecodedBarcode menerbitkan Left, Bottom, Right, Top dan OrientationDegrees dalam user space
Normalisasi QR di dalam decoder membuat halaman resolve pada percobaan pertama, sementara konversi koordinat dua tahap mengubah hasil bitmap percobaan menjadi box user space yang sejajar sumbu

Keliru arah pada konversi kedua itu dan gejalanya jahat: teks terdekode sempurna, tapi box yang Anda gambar untuk overlay review mendarat di citra cermin posisi yang benar. Siapa pun yang membangun antarmuka review di atas decoder sebaiknya assert terhadap fixture yang diketahui, dengan simbol yang diletakkan sengaja dekat satu sudut halaman sehingga sumbu Y yang terbalik terlihat sekilas. Nalar yang sama berlaku untuk koordinat apa pun yang menyeberangi batas rendering, dan itulah kenapa merender halaman PDF ke bitmap di Delphi layak dipahami sebelum Anda membangun di atas decoder

Apa yang decoder bawaan lakukan dan tidak lakukan

Decoder bawaan adalah implementasi berbatas tanpa dependensi, dan ia jujur soal batasnya alih-alih menurun secara diam-diam. Ia mengenali Code 39 dan QR, memvalidasi bit format yang dilindungi BCH dan pola mask sebelum menerbitkan data apa pun, dan tidak mencoba pemulihan error pada simbol yang rusak. Kalau input Anda foto label melengkung di bawah cahaya tak merata, itu kelas masalah berbeda dan butuh engine terspesialisasi

// Tukar dengan engine Anda sendiri: implementasikan IHPDFBarcodeDecoder dan
// berikan ke overload yang sadar decoder. HotPDF tetap memiliki rendering
// halaman, anggaran, pemetaan koordinat, dan de-duplikasi
if not Pdf.DecodeLoadedPageBarcodes(PageIndex, MyDecoder, Options,
     Codes, Info) then
  case Info.Status of
    bdsBudgetExceeded:
      Log('raise MaxPixels or lower DPI: ' + string(Info.Diagnostic));
    bdsRenderError:
      Log('page did not render: ' + string(Info.Diagnostic));
    bdsDecoderError:
      Log(string(Info.DecoderName) + ' failed: ' + string(Info.Diagnostic));
  end;

THPDFBarcodeDecodeInfo adalah tempat pipeline produksi membuktikan nilainya. RotationAttemptCount dan DecoderCallCount memberi tahu apakah retry luar berjalan sama sekali, ReceivedResultCount dibanding AcceptedResultCount memisahkan decoder yang tak menemukan apa pun dari threshold confidence yang menolak semua temuannya, dan RenderedPixels berikut PeakWorkingBytes adalah yang Anda grafikkan saat batch job mulai thrashing. Himpunan hasil kosong plus bdsSucceeded berarti halamannya memang tak punya simbol terbaca, fakta operasional yang berbeda dari bdsBudgetExceeded

Field anggaran layak mendapat keputusan yang disengaja, bukan bawaan. MaxPixels dan MaxWorkingBytes ada karena DPI berlipat kuadrat: naik dari 300 ke 600 DPI pada halaman A4 melipatempatkan biaya render dan alokasi puncaknya, dan input tak terpercaya yang mendeklarasikan page box raksasa bisa mengubah pekerjaan scan menjadi insiden out-of-memory. Atur cap sesuai kebutuhan dokumen sah terburuk Anda, lalu biarkan bdsBudgetExceeded mengarahkan pencilan ke jalur yang lebih lambat dan terisolasi

Kalau dokumen Anda mencampur label terbaca mesin dengan teks cetak yang Anda rencanakan indeks, decoder barcode berpasangan alami dengan engine pengenalan yang dibahas di OCR template-matching di dalam HotPDF, dan sisi generasi dari kisah yang sama ada di menggambar barcode ke PDF dengan HotPDF. Keduanya berjalan di atas infrastruktur rendering dan anggaran yang sama, jadi pipeline yang sudah menyetel batas waras untuk yang satu mendapat yang lain hampir gratis

Toleransi rotasi adalah salah satu fitur yang tak terlihat saat bekerja dan menaikkan darah saat tidak, dan pelajaran rekayasanya menggeneralisasi melampaui QR: normalisasilah sedekat mungkin dengan representasi semantik, bukan di lapisan piksel tempat data masih membawa setiap kecelakaan cara ia diambil. HotPDF mengirim ini sebagai bagian dari komponen PDF Delphi HotPDF, berdampingan dengan bagian rendering, OCR dan analisis halaman yang biasanya dibutuhkan pipeline intake yang sama