Tehnički članak

HotPDF dekodira rotirane QR kodove u PDF stranicama

HotPDF dekodira rotirane QR simbole na učitanoj PDF stranici tako da uzorkovanu matricu modula normalizira kroz svih osam D4 orijentacija unutar samog dekodera. Vanjski retry s rotacijom koji radi kod linearnih simbologija za QR ne može raditi, a razumijevanje zašto vam ušteđuje dan potjere za dekoderom koji izgleda pokvaren a nije

Scenarij je sasvim običan. Skenirane otpremnice stižu kao PDF-ovi, svaka stranica nosi QR naljepnicu, a operater je hrpu listova ubacio u ladaicu bilo kojim smjerom koji je ona prihvatila. Neke su naljepnice uspravne, neke su okrenute za četvrtinu okreta, nekoliko ih je naopako. Pozovete barkod dekoder, pola stranica se razriješi, a druga polovica se vrati prazna bez ijedne greške

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

Jer je raspored QR finder uzoraka namjerno asimetričan, a rotacija cijele slike tu asimetriju čuva umjesto da je ukloni. QR Code stavlja tri finder kvadrata u gornji lijevi, gornji desni i donji lijevi kut, a donji desni kut ostavlja prazan (ISO/IEC 18004:2015 §6.3.3). Taj nedostajući kut je orijentacijski trag. Rotirajte bitmapu stranice za devedeset stupnjeva i praznina se jednostavno preseli u neki drugi kut. Ne postoji netrivijalna rotacija ravnine koja bi raspored s tri kuta preslikala natrag na samu sebe, pa će dekoder koji prihvaća samo kanonski raspored odbiti svaki pokušaj zaredom

To je bitno jer je očigledan fix pogrešan. Prirodan instinkt je objesiti retry izvana: renderirajte stranicu, predajte masku dekoderu, i ako to ne uspije, rotirajte masku i pokušajte ponovno na 90, 180 i 270 stupnjeva. Za Code 39 ta je politika točno prava, jer linearna simbologija ima start i stop uzorak koji skener nađe čim se crte vode horizontalno. Za QR to su četiri zajamčena neuspjeha nakon kojih slijedi izvještaj da ništa nije pronađeno

D4 grupa, primijenjena na matricu modula

Ispravno mjesto za normalizaciju je nakon uzorkovanja, na boolean rešetki modula a ne na pikselnoj maski. Kad dekoder riješi simbol u matricu tamnih i svijetlih modula veličine n puta n, može enumerirati dihedralnu grupu kvadrata: četiri rotacije puta dvije refleksije, ukupno osam kandidatskih orijentacija. Za svakog kandidata provjerava finder trokut, i prvi kandidat čija tri findera slijede na pozicije gore-lijevo, gore-desno i dolje-lijevo je prava orijentacija. Odatle postojeći pipeline teče nepromijenjen, jer format informacijski bitovi, zigzag smještaj podataka i Reed-Solomon korekcija svi pretpostavljaju kanonsku matricu i sad je dobivaju

Četiri renderiranja iste HotPDF QR matrice modula pod rotacijama D4 grupe na 0, 90, 180 i 270 stupnjeva, pokazujući kako se tri finder uzorka sele iz kuta u kut dok se s njima seli i prazan kut, pa samo kanonska orijentacija dekoderu nudi findere gore-lijevo, gore-desno i dolje-lijevo
Rotacija pikselne maske ne može ukloniti QR finder asimetriju, pa HotPDF enumerira D4 orijentacije na uzorkovanoj matrici modula i zadržava prvog kandidata čiji se finderi nađu gore-lijevo, gore-desno i dolje-lijevo

Dvije osobine čine ovo jeftinim. Matrica je mala u usporedbi s renderiranom bitmapom, pa osam transpozicija košta daleko manje od osam renderiranja stranice. I matrica je čist boolean array koji gradi sampler, pa nijedan transform usput ne može unijeti vrijednosti koje nikad nisu uzorkovane

Detekcija verzije je pretraga djeljivosti, ne dijeljenje

Broj modula se ne može izvesti dijeljenjem uzorkovane širine s pretpostavljenom veličinom modula, i pogriješiti tu znači suptilan izvor neuspjeha dekodiranja na renderima visoke rezolucije. QR simbol verzije v je širok 4v + 17 modula, pa je verzija 1 široka 21 modul, a verzija 40 njih 177. Maska širine 126 piksela jednako je kompatibilna s verzijom 1 po šest piksela po modulu i s višim verzijama po manjim veličinama modula. Linearno dijeljenje odabere jedno od toga i obično je u krivu

Ono što radi je pretraga djeljivosti po kandidatskim verzijama. Idite od verzije 40 dolje do verzije 1, zadržite kandidate čiji broj modula deli uzorkovanu širinu bez ostatka i ostavlja najmanje tri piksela po modulu, i uzmite najmanju preživjelu verziju. Prag od tri piksela sprječava pretragu da prihvati apsurdno gusto čitanje grubog simbola, a pravilo najmanje verzije razrješuje preostalu dvosmislenost u korist čitanja koje bi skener stvarno proizveo

HotPDF obilazak za detekciju verzije QR simbola na uzorkovanoj maski od 126 piksela, testirajući svaki kandidatski broj modula 4v plus 17 od verzije 40 dolje do verzije 1 na ravnu djeljivost i prag od tri piksela po modulu prije nego najmanja preživjela verzija pobijedi
Broj QR modula dolazi iz pretrage djeljivosti po kandidatskim verzijama, ne iz dijeljenja širine maske pretpostavljenom veličinom modula, a najmanja preživjela verzija razrješ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 record a ne ispražnjen, što je bitno jer je DPI od nule ili limit rezultata od nule validno izgledajući način da ne dobijete ništa. RotationPolicy kontrolira samo vanjski retry: bdrpNone renderira jednom, bdrpFallback pokušava ostale orijentacije nakon neuspjelog prvog prolaza, a bdrpAll renderira svaku orijentaciju bezuvjetno. Budući da se QR normalizacija dešava unutar dekodera, QR stranice se razriješe u prvom pokušaju pod bilo kojom od tri politike. Politika je tu za linearne simbologije koje je stvarno trebaju

Kako dokazati da bitmap transform ne izmišlja piksele?

Izbrojite tintu na obje strane i zahtijevajte da se zbrojevi poklope. Rotacija je permutacija piksela, ništa više, pa broj ne-nultih ćelija u izlazu mora biti jednak broju u ulazu. Kad je rotacija maske u vanjskom retry putu javila 4800 postavljenih ćelija na ulazu i 7439 na izlazu, ta je jedna usporedba bila dovoljna da osudi transform bez čitanja ijedne linije njegove geometrije

Uzrok je bio svakidašnji i vrijedi ga ponijeti kao pravilo. Dinamički array dimenzioniran s SetLength nije zajamčeno da dođe nuliran kad je function result na putu koji runtime ne čisti, pa ćelije koje rotacija nikad ne zapiše nose bajtove koji su tamo bili prije. Neki od tih starih bajtova su ne-nulti, a ne-nulti znači tintu. Fix je jedna linija, FillChar(Result[0], N, 0) prije nego što krene petlja permutacije, a disciplina koju implicira je šira: svaka funkcija koja vraća masku ili bitmap buffer treba izričito očistiti svoj izlaz umjesto da se oslanja na semantiku alokacije

Ono što je defektu omogućilo da preživi tri izdanja je zanimljivije od samog defekta. Čim je QR preselio svoju obradu orijentacije u dekoder, QR je potpuno prestao koristiti vanjsku rotaciju maske, a jedini preostali potrošač tog code puta je bio Code 39. Dijeljena infrastruktura ovakve bugove krije cijelo vrijeme: pokrivenost jedne funkcije čini da put izgleda testirano dok funkcija koja stvarno ovisi o njemu nema vlastitih testova. Svaki put koji nova funkcija prestane koristiti treba test koji ga i dalje koristi

Čitanje rezultata natrag u koordinatama stranice

Svaka geometrijska vrijednost koju dekoder proizvede izražena je u koordinatnom okviru attempt bitmape, a pozivatelju treba u PDF user spaceu. Ta konverzija teče u dva stupnja: poništite četvrtinu okreta koju je retry primijenio, zatim poništite render transform koji je mapirao user space na bitmapu. Ono što stigne u THPDFDecodedBarcode je axis-aligned bounding box u user spaceu, s Left, Bottom, Right i Top po PDF konvenciji da Y raste prema gore, plus OrientationDegrees brojan suprotno od kazaljke

HotPDF barkod pipeline od renderirane bitmape stranice kroz uzorkovanje u boolean matricu modula, D4 normalizaciju, detekciju verzije djeljivošću i Reed-Solomon dekodiranje, pa dvostupanjsku koordinatnu konverziju koja poništava četvrtinu okreta retrya i render transform prije nego THPDFDecodedBarcode objavi Left, Bottom, Right, Top i OrientationDegrees u user spaceu
QR normalizacija unutar dekodera dopušta da se stranice razriješe u prvom pokušaju, a dvostupanjska koordinatna konverzija pretvara rezultate attempt bitmape u axis-aligned boxeve u user spaceu

Pogriješite li smjer te druge konverzije, simptom je gadan: tekst se dekodira savršeno, ali box koji nacrtate za review overlay sleti na zrcalnu sliku prave pozicije. Tko god gradi review sučelje na vrhu dekodera treba se assertati protiv poznatog fixturea, sa simbolom namjerno smještenim blizu jednog kuta stranice da se okrenuta Y os vidi iz prve. Isti se način razmišljanja primjenjuje na svaku koordinatu koja prelazi render granicu, pa je renderiranje PDF stranice u bitmapu u Delphiju vrijedno razumijevanja prije nego što gradite na vrhu dekodera

Što ugrađeni dekoder hoće a što neće

Ugrađeni dekoder je ograničena implementacija bez dependencija, i pošten je oko svojih granica umjesto da se tiho pokvari. Prepoznaje Code 39 i QR, validira BCH-zaštićene format bitove i mask pattern prije nego objavi bilo koje podatke, i ne pokušava oporavak od grešaka na oštećenim simbolima. Ako je vaš ulaz fotografija zakrivljenog naljepnica pod neravnomjernim svjetlom, to je druga klasa problema i traži specijalizirani engine

// Zamijenite svoj engine: implementirajte IHPDFBarcodeDecoder i proslijedite
// ga overloadu koji zna za dekodere. HotPDF i dalje posjeduje render stranice,
// budžete, mapiranje koordinata i de-duplikaciju
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 ono mjesto gdje produkcijski pipeline dolazi do izražaja. RotationAttemptCount i DecoderCallCount vam kažu je li vanjski retry uopće trčao, ReceivedResultCount naspram AcceptedResultCount razdvaja dekoder koji nije našao ništa od praga pouzdanosti koji je odbacio sve što je našao, a RenderedPixels s PeakWorkingBytes je ono što grafite kad batch posao krene u thrashing. Prazan result set plus bdsSucceeded znači da stranica stvarno nema čitljiv simbol, što je operativno drugačija činjenica od bdsBudgetExceeded

Budget polja zaslužuju promišljenu odluku a ne zadanu postavku. MaxPixels i MaxWorkingBytes postoje jer se DPI množi kvadratično: prelazak s 300 na 600 DPI na A4 stranicičetverostruči i cijenu rendera i vršnu alokaciju, a nepouzdani ulaz koji deklarira ogroman page box može scan posao pretvoriti u out-of-memory incident. Postavite limite na ono što vaš najgori legitimni dokument treba, pa pustite bdsBudgetExceeded da outliere usmjeri na sporiji, izolirani put

Ako vaši dokumenti miješaju mašinski čitljive naljepnice s tiskanim tekstom koji planirate indeksirati, barkod dekoder se prirodno upari s recognition engineom obrađenim u template-matching OCR-u unutar HotPDF-a, a generacijska strana iste priče je u crtanju barkodova u PDF s HotPDF-om. Oboje trči na istoj render i budget infrastrukturi, pa pipeline koji već postavlja pametne limite za jedno drugo dobiva gotovo besplatno

Tolerancija na rotaciju je jedna od onih mogućnosti koje su nevidljive kad rade i razjarujuće kad ne rade, a inženjerska lekcija se generalizira i preko QR-a: normalizirajte što bliže semantičkoj reprezentaciji, ne na pikselnom sloju gdje podaci još nose svaku slučajnost toga kako su uhvaćeni. HotPDF ovo isporučuje kao dio HotPDF Delphi PDF komponente, uz render, OCR i page-analysis dijelove koje isti intake pipelineovi obično trebaju