Tehnički članak

Dekodiranje rotiranih QR kodova u PDF stranama uz HotPDF

HotPDF dekodira rotirane QR simbole u učitanoj PDF strani tako što normalizuje uzorkovanu matricu modula kroz svih osam D4 orijentacija unutar samog dekoder-a. Spoljašnji rotacioni retry koji radi za linearne simbologije ne može da radi za QR, i razumevanje zašto vas štedi dan jurnjave za dekoder-om koji izgleda pokvaren a nije

Scenario je sasvim običan. Skenirani dostavni listovi stižu kao PDF-ovi, svaka strana nosi QR etiketu, a operater skenera je ubacio gomilu listova u smeru kakvom je ladica prihvatila. Neke su etikete uspravne, neke su odmakle za četvrtinu okreta, par njih je naopako. Pozovete barcode dekoder, polovina strana se razreši, a druga polovina se vrati prazna bez ijedne greške

Zašto rotacija maske za skeniranje nikada ne popravi rotirani QR?

Zato što je raspored QR finder pattern-a namerno asimetričan, i rotacija cele slike čuva tu asimetriju umesto da je ukloni. QR Code postavlja tri finder kvadrata u gornji levi, gornji desni i donji levi ugao, i ostavlja donji desni ugao prazan (ISO/IEC 18004:2015 §6.3.3). Taj ugao koji nedostaje je orijentacioni znak. Rotirajte bitmapu strane za devedeset stepeni i praznina se jednostavno preseli u drugi ugao. Ne postoji netrivijalna rotacija ravni koja raspored sa tri ugla preslikava nazad na samu sebe, pa dekoder koji prihvata samo kanonski aranžman odbiće svaki pokušaj redom

Ovo je važno jer je očigledna popravka pogrešna. Prirodan instinkt je da retry okačite spolja: renderujete stranu, uručite masku dekoder-u, i ako to padne, rotirate masku i pokušavate ponovo za 90, 180 i 270 stepeni. Za Code 39 ta politika je baš ispravna, jer linear simbologija ima start i stop pattern koji skener može naći čim štrihovi teku horizontalno. Za QR to su četiri zagarantovana neuspeha pa prijava da ništa nije nađeno

D4 grupa, primenjena na matricu modula

Pravo mesto za normalizaciju je posle uzorkovanja, na boolean mreži modula, a ne na piksel maski. Kad dekoder razreši simbol u matricu tamnih i svetlih modula n puta n, može da enumeriše dijedralnu grupu kvadrata: četiri rotacije puta dve refleksije, osam kandidatskih orijentacija ukupno. Za svakog kandidata proverava finder trougao, i prvi kandidat čija tri findera dosede u pozicije gore-levo, gore-desno i dole-levo je prava orijentacija. Od tamo postojeći pipeline radi nepromenjen, jer format informacioni bitovi, zigzag smeštaj podataka i Reed-Solomon korekcija sve pretpostavljaju kanonsku matricu i sada je dobijaju

Četiri prikaza iste HotPDF QR matrice modula pod rotacijama D4 grupe na 0, 90, 180 i 270 stepeni, koja pokazuje tri finder pattern-a koji sele uglove dok se prazan ugao sa njima kreće, pa samo kanonska orijentacija dekoder-u predstavlja findere gore-levo, gore-desno i dole-levo
Rotacija piksel maske ne može da ukloni QR finder asimetriju, pa HotPDF enumeriše D4 orijentacije na uzorkovanoj matrici modula i zadržava prvog kandidata čiji se finderi usade gore-levo, gore-desno i dole-levo

Dve osobine ovo čine jeftinim. Matrica je mala u poređenju sa renderovanom bitmapom, pa osam transpozicija košta daleko manje od osam renderovanja strane. I matrica je čist boolean niz koga je sagradio sampler, pa nijedan transform usput ne može uneti vrednosti koje nikada nisu uzorkovane

Detekcija verzije je pretraga deljivošću, a ne deljenje

Broj modula ne može se izvesti deljenjem uzorkovane širine pretpostavljenom veličinom modula, i greška tu je suptilan izvor neuspeha dekodiranja na renderima visoke rezolucije. QR simbol verzije v ima 4v + 17 modula u širinu, pa je verzija 1 široka 21 modul, a verzija 40 ih ima 177. Maska široka 126 piksela podjednako je saglasna sa verzijom 1 po šest piksela po modulu i sa nekoliko viših verzija pri manjim veličinama modula. Linearno deljenje izabere jedno od toga i obično pogreši

Radi pretraga po deljivosti preko kandidatskih verzija. Idite od verzije 40 naniže do verzije 1, zadržite kandidate čiji broj modula deli uzorkovanu širinu ravnomerno i ostavlja najmanje tri piksela po modulu, i uzmite najmanju preživelu verziju. Prag od tri piksela je ono što sprečava pretragu da prihvati apsurdno gustu interpretaciju grube simbola, i pravilo najmanje verzije razrešuje preostalu dvosmislenost u korist interpretacije koju bi skener stvarno proizveo

HotPDF obilazak detekcije verzije za QR simbol na 126 piksela širokoj uzorkovanoj maski, testirajući svaki kandidatski broj modula 4v plus 17 od verzije 40 naniže do verzije 1 na ravnu deljivost i prag modula od tri piksela pre nego što najmanja preživela verzija pobedi
Broj QR modula dolazi iz pretrage deljivošću preko kandidatskih verzija, a ne deljenjem širine maske pretpostavljenom veličinom modula, i najmanja preživela verzija razrešuje dvosmislenost
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 vraća popunjen zapis, a ne ispražnjen, što je važno jer je DPI od nule ili ograničenje rezultata od nule validno izgledajući način da ne dobijete ništa. RotationPolicy kontroliše samo spoljašnji retry: bdrpNone renderuje jednom, bdrpFallback pokušava ostale orijentacije posle paleg prvog prolaza, a bdrpAll renderuje svaku orijentaciju bezuslovno. Pošto se QR normalizacija dešava unutar dekoder-a, QR strane se razrešuju na prvom pokušaju pod bilo kojom od tri politike. Politika je tu za linearne simbologije kojima stvarno treba

Kako dokazujete da bitmap transform ne izmišlja piksele?

Prebrojite tintu sa obe strane i zahtevajte da se zbirovi poklapaju. Rotacija je permutacija piksela, ništa više, pa broj nenultih ćelija u izlazu mora biti jednak broju u ulazu. Kad je rotacija maske u spoljašnjoj retry putanji prijavila 4800 postavljenih ćelija na ulazu i 7439 na izlazu, to jedno poređenje bilo je dovoljno da osudi transform bez čitanja ijedne linije njegove geometrije

Uzrok je bio svakidašnji i vredi ga poneti kao pravilo. Dinamički niz veličan sa SetLength nije garantovano doći ispražnjen kad je rezultat funkcije koji putuje putanjom koju runtime ne čisti, i ćelije koje rotacija nikada ne upiše onda nose bajtove koji su tamo bili pre. Neki od tih zastarelih bajtova su nenulti, a nenulti znači tinta. Popravka je jedna linija, FillChar(Result[0], N, 0) pre nego što petlja permutacije proradi, a disciplina koju podrazumeva je šira: svaka funkcija koja vraća masku ili bitmap bafer treba eksplicitno da očisti svoj izlaz umesto da se oslanja na semantiku alokacije

Ono što je defektu omogućilo da preživi tri izdanja zanimljivije je od samog defekta. Čim je QR preselio svoje rukovanje orijentacijom u dekoder, QR je potpuno prestao da koristi spoljašnju rotaciju maske, i jedini preostali potrošač tog kodnog puta bio je Code 39. Deljena infrastruktura krije bagove ovakve sve vreme: pokrivenost jedne mogućnosti čini da putanja deluje testirano dok mogućnost koja stvarno zavisi od nje nema nikakvu svoju. Svaka putanja koju nova mogućnost prestane da koristi treba test koji je i dalje koristi

Čitanje rezultata nazad u koordinatama strane

Svaka geometrijska vrednost koju dekoder proizvede izražena je u koordinatnom okviru bitmape pokušaja, a pozivalac je treba u PDF user space-u. Ta konverzija ide u dve faze: poništite četvrtinu okreta koju je retry primenio, pa poništite render transform koji je preslikao user space u bitmapu. Ono što stiže u THPDFDecodedBarcode je bounding box poravnat sa osama u user space-u, sa Left, Bottom, Right i Top po PDF konvenciji da Y raste naviše, plus OrientationDegrees brojan suprotno od kazaljke

HotPDF barcode pipeline od renderovane bitmape strane kroz uzorkovanje u boolean matricu modula, D4 normalizaciju, detekciju verzije deljivošću i Reed-Solomon dekodovanje, pa dvofazna konverzija koordinata koja poništava četvrtinu okreta retry-ja i render transform pre nego što THPDFDecodedBarcode objavi Left, Bottom, Right, Top i OrientationDegrees u user space-u
QR normalizacija unutar dekoder-a omogućava da se strane razreše na prvom pokušaju, dok dvofazna konverzija koordinata pretvara rezultate bitmape pokušaja u boxove poravnate sa osama u user space-u

Pogrešite smer te druge konverzije i simptom je gadan: tekst se dekodira savršeno, ali box koji nacrtate za review overlay dosede na ogledalnoj slici prave pozicije. Svako ko gradi review interfejs nad dekoder-om treba da asertuje protiv poznate fiksature, sa simbolom namerno smeštenim blizu jednog ugla strane da se prevrnut Y osa vidi u letu. Isto rasuđivanje važi za svaku koordinatu koja prelazi granicu renderovanja, pa je renderovanje PDF strane u bitmapu u Delphi-ju vredno razumevanja pre nego što gradite nad dekoder-om

Šta će ugrađeni dekoder, a šta neće da uradi

Ugrađeni dekoder je ograničena implementacija bez zavisnosti, i pošten je o svojim granicama umesto da tiho propada. Prepoznaje Code 39 i QR, validira BCH zaštićene format bitove i mask pattern pre nego što objavi bilo kakve podatke, i ne pokušava oporavak od grešaka na oštećenim simbolima. Ako je vaš ulaz fotografija povijene etikete pod neravnomernim svetlom, to je druga klasa problema i traži specijalizovan engine

// Umetnite svoj engine: implementirajte IHPDFBarcodeDecoder i prosledite ga
// overload-u koji zna za dekoder. HotPDF i dalje vlasniči render stranice,
// budžete, mapiranje koordinata i uklanjanje duplikata
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 je mesto gde produkcijski pipeline zarađuje svoj hleb. RotationAttemptCount i DecoderCallCount vam kažu da li je spoljašnji retry uopšte radio, ReceivedResultCount naspram AcceptedResultCount razdvaja dekoder koji nije našao ništa od praga poverenja koji je odbio sve što je našao, a RenderedPixels uz PeakWorkingBytes je ono što grafikujete kad batch posao počne da se bori za memoriju. Prazan skup rezultata uz bdsSucceeded znači da strana stvarno nema čitljiv simbol, što je drugačija operativna činjenica od bdsBudgetExceeded

Budžetska polja zaslužuju namernu odluku, a ne podrazumevanu vrednost. MaxPixels i MaxWorkingBytes postoje jer se DPI množi kvadratno: prelazak sa 300 na 600 DPI na A4 strani učetvorostručuje i trošak renderovanja i vršnu alokaciju, i nepouzdan ulaz koji deklariše ogroman page box može od sken posla napraviti incident nestanka memorije. Postavite limite na ono što vaš najgori legitimni dokument treba, pa pustite bdsBudgetExceeded da izuzetke usmeri u sporiju, izolovanu putanju

Ako vaši dokumenti mešaju mašinski čitljive etikete sa štampanim tekstom koji nameravate da indeksirate, barcode dekoder se prirodno sparuje sa engine-om prepoznavanja pokrivenim u članku o template-matching OCR-u unutar HotPDF-a, a generativna strana iste priče je u članku o crtanju barkodova u PDF uz HotPDF. Oboje radi na istoj infrastrukturi renderovanja i budžeta, pa pipeline koji je već postavio razumne limite za jedno drugo dobija skoro džabe

Tolerancija na rotaciju je jedna od onih mogućnosti koje su nevidljive kad rade i raspirujuće kad ne rade, i inženjerska lekcija se generališe van QR-a: normalizujte što bliže semantičkoj reprezentaciji koliko možete da dođete, a ne na piksel sloju gde podaci i dalje nose svaku slučajnost hvatanja. HotPDF ovo isporučuje kao deo HotPDF Delphi PDF komponente, uz render, OCR i delove za analizu strana koje isti intake pipeline-i obično trebaju