HotPDF dekóduje otočené QR symboly v načtené stránce PDF normalizací vzorkované modulové matice přes všech osm orientací D4 uvnitř samotného dekodéru. Vnější rotční retry, který funguje pro lineární symbologie, nemůže fungovat pro QR a pochopení proč vám ušetří den honění dekodéru, který vypadá rozbitě, ale není
Scénář je dost obyčejný. Naskenované dodací listy přicházejí jako PDF, každá strana nese QR štítek a operátor skeneru nasypal hromadu listů jakýmkoli směrem, který zásobník přijal. Některé štítky jsou vzpřímené, některé jsou o čtvrt otočky vedle, pár je vzhůru nohama. Zavoláte dekodér čárových kódů, polovina stran se rozliší a druhá polovina se vrátí prázdná bez jediné chyby
Proč otáčení skenovací masky nikdy nespraví otočené QR?
Protože rozložení vzorů finder u QR je záměrně asymetrické a otočení celého obrázku tu asymetrii zachovává, místo aby ji odstranilo. QR Code umisťuje tři čtverce finder do rohů vlevo nahoře, vpravo nahoře a vlevo dole a roh vpravo dole nechává prázdný (ISO/IEC 18004:2015 §6.3.3). Ten chybějící roh je orientací cue. Otočíte-li bitmapu stránky o devadesát stupňů, mezera prostě přeskčí do jiného rohu. Neexistuje netriviální otočení roviny, jež by rozložení se třemi rohy namapovalo zpět na sebe, takže dekodér přijímající jen kanonické uspořádání odmítne každý pokus za sebou
Je to důležité proto, že zjevná oprava je ta špatná. Přirozený instinkt je pověsit retry ven: vyrenderovat stránku, předat masku dekodéru a když to selže, otočit masku a zkusit znovu pro 90, 180 a 270 stupňů. Pro Code 39 je tahle politika přesně správná, protože lineární symbologie má start a stop pattern, který skener najde, jakmile pásy běží vodorovně. Pro QR je to čtyři zaručená selhání následovaná hlášením nic nenalezeno
Grupa D4 aplikovaná na modulovou matici
Správné místo pro normalizaci je po vzorkování, na booleovské modulové mřížce, nikoli na pixelové masce. Jakmile dekodér rozliší symbol do matice n krát n tmavých a světlých modulů, může vypsat dihedrální grupu čtverce: čtyři otočení krát dvě zrcadlení, celkem osm kandidátních orientací. Pro každého kandidáta zkontroluje trojúhelník finderů a první kandidát, jehož tři findery dopadnou do pozic vlevo nahoře, vpravo nahoře a vlevo dole, je ta pravá orientace. Odtud stávající pipeline běží beze změny, protože formátové informační bity, cikcaské rozmístění dat a korekce Reed-Solomon předpokládají kanonickou matici a teď ji dostávají
Dvě vlastnosti dělají tohle laciným. Matice je malá ve srovnání s vyrenderovanou bitmapou, takže osm transpozic stojí daleko méně než osm renderů stránky. A matice je čisté booleovské pole stavěné vzorkovačem, takže žádná transformace cestou nemůže vnést hodnoty, které nikdy nebyly vzorkovány
Detekce verze je hledání dělitelnosti, ne dělení
Počet modulů nelze odvodit vydělením vzorkované šířky předpokládanou velikostí modulu a splést si to je subtilní zdroj selhávajícího dekódování na vysoce rozlišených renderech. QR symbol verze v je široký 4v + 17 modulů, takže verze 1 je 21 modulů a verze 40 jich je 177. Maska měřící 126 pixelů je stejně konsistentní s verzí 1 při šesti pixelech na modul jako s několika vyššími verzemi při menších velikostech modulů. Lineární dělení jeden vybere a obvykle špatně
Funguje hledání dělitelnosti mezi kandidátními verzemi. Projděte od verze 40 dolů po verzi 1, ponechte kandidáty, jejichž počet modulů dělí vzorkovanou šířku rovnoměrně a nechává aspoň tři pixely na modul, a vezměte nejmenší přeživší verzi. Třípixelové minimum je to, co zabrání hledání přijmout absurdně husté čtení hrubého symbolu a pravidlo nejmenší verze rozřeše zbylou nejednoznačnost ve prospěch čtení, které by skutečně produkoval skener
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 vrátí naplněný záznam místo vynulovaného, na čemm záleží, protože DPI nula nebo strop výsledků nula je validně vypadající způsob, jak nedostat nic. RotationPolicy řídí jen vnější retry: bdrpNone renderuje jednou, bdrpFallback zkusí ostatní orientace po neúspěšném prvním průchodu a bdrpAll renderuje každou orientaci bezpodmínečně. Protože QR normalizace se děje uvnitř dekodéru, QR strany se rozliší na první pokus pod kteroukoli z těch tří politik. Politika je tam pro lineární symbologie, které ji doopravdy potřebují
Jak dokážete, že bitmapová transformace nevynáší pixely?
Spočítejte inkoust na obou stranách a vyžadujte, aby se součty rovnaly. Otočení je permutace pixelů, nic víc, takže počet nenulových buněk ve výstupu se musí rovnat počtu na vstupu. Když otočení masky ve vnějším retry path hlásilo 4800 nastavených buněk na vstupu a 7439 na výstupu, to jediné srovnání stačilo transformaci odsoudit, aniž byste četli jediný řádek její geometrie
Příčina byla všední a stojí za odnesení jako pravidlo. Dynamické pole alokované přes SetLength nemusí dorazit vynulované, když jde o výsledek funkce putující cestou, kterou runtime nečistí, a buňky, které otočení nikdy nezapíše, pak nesou, co tam bylo za bajty dřív. Některé z těch starých bajtů jsou nenulové a nenulové znamená inkoust. Oprava je jeden řádek, FillChar(Result[0], N, 0) před spuštěním permutační smyčky, a disciplína, kterou to implikuje, je širší: jakákoli funkce vracející masku nebo bitmapový buffer má svůj výstup explicitně vymazat, místo spoléhání na alokační sémantiku
Co nechalo defekt přežít tři release, je zajímavější než defekt. Jakmile QR přestěhovalo svoje handling orientací do dekodéru, QR přestalo vnější otočení masky zcela vykonávat a jediným zbývajícím konzumentem té kódové cesty byl Code 39. Sdílená infrastruktura schovává takhle bugy neustále: pokrytí z jedné featury vypadá, že cestu testuje, zatímco featura, která na ní skutečně závisí, nemá žádné vlastní. Každá cesta, kterou nová featura přestane používat, potřebuje test, který ji pořád používá
Čtení výsledků zpět v souřadnicích stránky
Každá geometrická hodnota, kterou dekodér produkuje, je vyjádřena v souřadném rámu pokusné bitmapy a volající ji potřebuje v PDF user space. Ta konverze běží ve dvou etapách: vzít zpět čtvrt otočku, kterou aplikoval retry, pak vzít zpět render transformaci, jež namapovala user space na bitmapu. Co dorazí v THPDFDecodedBarcode, je ohraničující box zarovnaný na osy v user space, s Left, Bottom, Right a Top sledujícími PDF konvenci, že Y roste vzhůru, plus OrientationDegrees proti směru hodinových ručiček
Splést směr té druhé konverze a symptom je ošklivý: text se dekóduje dokonale, ale box, který nakreslíte pro review overlay, dopadne na zrcadlový obraz správné pozice. Kdo staví review rozhraní nad dekodérem, má asertovat proti známé fixture, se symbolem záměrně umístěným blízko nějakého rohu stránky, takže překlopená osa Y je viditelná na první pohled. Táž úvaha platí pro jakoukoli souřadnici překračující renderovací hranici, a proto stojí za pochopení článek vykreslení PDF stránky do bitmapy v Delphi, dřív než na dekodéru něco postavíte
Co vestavěný dekodér udělá a co ne
Vestavěný dekodér je ohraničená implementace bez závislostí a k limitům je upřímný, místo tichého zhoršování. Rozpoznává Code 39 a QR, validuje BCH chráněné formátové bity a maskový pattern, než publikuje jakákoli data, a nepokouší se o error recovery na poškozených symbolech. Je-li váš vstup fotografie zakřiveného štítku pod nerovnoměrným světlem, je to jiná třída problému a chce specializovaný engine
// Vyměňte vlastní engine: implementujte IHPDFBarcodeDecoder a předejte
// jej overloadu znajícímu dekodéry. HotPDF si pořád drží render stránek,
// rozpočty, souřadnicové mapování a deduplikaci
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 místo, kde produkční pipeline vydělává svou stravu. RotationAttemptCount a DecoderCallCount vám řeknou, zda vnější retry vůbec běžel, ReceivedResultCount proti AcceptedResultCount oddělí dekodér, který nic nenašel, od prahu confidence, který odmítl všechno, co našel, a RenderedPixels s PeakWorkingBytes je to, co grafíte, když dávková úloha začne šplouchat. Prázdná množina výsledků plus bdsSucceeded znamená, že stránka doopravdy nemá čitelný symbol, což je jiná operační skutečnost než bdsBudgetExceeded
Rozpočtová pole si zaslouží záměrné rozhodnutí, ne default. MaxPixels a MaxWorkingBytes existují, protože DPI násobí kvadraticky: přechod ze 300 na 600 DPI na stránce A4 znásobí čtyřnásobně renderovací náklady i špičkovou alokaci a nedůvěryhodný vstup deklarující obrovský page box dokáže ze skenovací úlohy udělat incident out-of-memory. Nastavte stropy na to, co váš nejhorší legitimní dokument potřebuje, a pak nechte bdsBudgetExceeded směrovat odlehlé hodnoty do pomalejší, izolované cesty
Míchají-li vaše dokumenty strojově čitelné štítky s tištěným textem, který plánujete indexovat, dekodér čárových kódů se přirozeně páruje s rozpoznávacím enginem pokrytým v článku template-matching OCR uvnitř HotPDF a generační strana téhož příběhu je v článku kreslení čárových kódů do PDF s HotPDF. Obojí běží na téže infrastruktuře renderování a rozpočtů, takže pipeline, která už nastavila rozumné limity pro jedno, dostane druhé téměř zadarmo
Tolerance rotace je jedna z těch featur, které jsou neviditelné, když fungují, a zuřící, když nefungují, a inženýrské poučení se zobecňuje za QR: normalizujte co nejblíž sémantické reprezentaci, jakou dosáhnete, ne na pixelové vrstvě, kde data ještě nesou každou náhodu toho, jak byla zachycena. HotPDF tohle dodává jako část HotPDF Delphi PDF komponenty, spolu s renderovacími, OCR a page-analysis kusy, které tytéž intake pipeline obvykle potřebují