HotPDF avkodar roterade QR-symboler i en inläst PDF-sida genom att normalisera den samplade modulmatrisen genom alla åtta D4-orienteringar inne i avkodaren själv. Det yttre rotationsförsöket som fungerar för linjära symbologier kan inte fungera för QR, och att förstå varför sparar dig en dag av att jaga en avkodare som ser trasig ut men inte är det
Scenariot är tillräckligt vanligt. Inskannade leveransavier anländer som PDF:er, varje sida bär en QR-etikett, och skanneroperatören matade in en hög ark i vilken riktning facket nu accepterade. Vissa etiketter är upprätta, vissa ligger en kvartsvarv snett, några är upp-och-ner. Du anropar streckkodsavkodaren, hälften av sidorna löser sig, och andra hälften kommer tillbaka tomma utan något fel alls
Varför fixar rotation av skannmasken aldrig en roterad QR?
Därför att en QR-findarmönsters layout är medvetet asymmetrisk, och en helbildsrotation bevarar den asymmetrin i stället för att ta bort den. QR Code placerar tre findarrutor i hörnen uppe-vänster, uppe-höger och nere-vänster, och lämnar hörnet nere-höger tomt (ISO/IEC 18004:2015 §6.3.3). Det saknade hörnet är orienteringsledtråden. Rotera sidobitmappen nittio grader och gapet flyttar bara till ett annat hörn. Det finns ingen icke-trivial rotation av planet som avbildar en trehörnslayout tillbaka på sig själv, så en avkodare som bara accepterar den kanoniska arrangemanget avvisar varje försök i tur och ordning
Detta spelar roll för att den uppenbara fixen är den felaktiga. Den naturliga instinkten är att hänga omförsöket på utsidan: rendera sidan, lämna masken till avkodaren, och om det misslyckas, rotera masken och försök igen för 90, 180 och 270 grader. För Code 39 är den policyn exakt rätt, eftersom en linjär symbologi har ett start- och stoppmönster skannern kan hitta så snart strecken löper horisontellt. För QR är det fyra garanterade misslyckanden följda av en rapport om inget funnet
D4-gruppen, tillämpad på modulmatrisen
Den rätta platsen för normaliseringen är efter sampling, på den booleska modulmatrisen snarare än på pixelmasken. När avkodaren löst upp symbolen i en n gånger n-matris av mörka och ljusa moduler kan den enumerera kvadratens dihedralgrupp: fyra rotationer gånger två speglingar, åtta kandidatorienteringar totalt. För varje kandidat kontrollerar den findartriangeln, och den första kandidat vars tre findare landar i positionerna uppe-vänster, uppe-höger och nere-vänster är den sanna orienteringen. Därifrån kör den befintliga pipelinen oförändrad, för formatinformationsbitarna, zickzack-dataplaceringen och Reed-Solomon-korrigeringen antar alla en kanonisk matris och får nu en
Två egenskaper gör detta billigt. Matrisen är liten jämfört med den renderade bitmappen, så åtta transponeringar kostar betydligt mindre än åtta sidsrenderingar. Och matrisen är en ren boolesk array byggd av samplern, så ingen transform på vägen kan introducera värden som aldrig samplades
Versionsdetektering är en delbarhetssökning, inte en division
Modulantalet kan inte härledas genom att dividera den samplade bredden med en antagen modulstorlek, och att ha fel här är en subtil källa till avkodningsmisslyckanden på högupplösta renderinger. En QR-symbol av version v är 4v + 17 moduler bred, så version 1 är 21 moduler och version 40 är 177. En mask som mäter 126 pixlar bred är lika förenlig med version 1 vid sex pixlar per modul som med flera högre versioner vid mindre modulstorlekar. Linjär division väljer en av dem och har vanligen fel
Det som fungerar är en delbarhetssökning över kandidatversionerna. Gå från version 40 ned till version 1, behåll kandidaterna vars modulantal delar den samplade bredden jämnt och lämnar åtminstone tre pixlar per modul, och ta den minsta överlevande versionen. Tre-pixlar-golvet är det som hindrar sökningen från att acceptera en absurd tät läsning av en grov symbol, och minsta-version-regeln löser den kvarvarande tvetydigheten till förmån för den läsning en skanner faktiskt skulle producera
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 lämnar tillbaka en ifylld post i stället för en nollställd, vilket spelar roll för en DPI på noll eller ett resultattak på noll är ett plausibelt sätt att få tillbaka ingenting. RotationPolicy styr endast det yttre omförsöket: bdrpNone renderar en gång, bdrpFallback försöker igen med de andra orienteringarna efter ett misslyckat första pass, och bdrpAll renderar varje orientering ovillkorligen. Eftersom QR-normalisering sker inne i avkodaren löser QR-sidor sig vid första försöket under samtliga tre policyer. Policyn finns där för de linjära symbologierna som genuint behöver den
Hur bevisar du att en bitmappstransform inte hittar på pixlar?
Räkna bläcket på båda sidor och kräv att summorna matchar. En rotation är en permutation av pixlar, inget mer, så antalet icke-nollceller i utmatningen måste lika antalet i indatan. När en maskrotation i det yttre omförsöksvägen rapporterade 4800 satta celler på vägen in och 7439 på vägen ut räckte den enskilda jämförelsen för att fälla transformen utan att läsa en enda rad av dess geometri
Orsaken var banal och värd att bära med sig som en regel. En dynamisk array storlekssatt med SetLength är inte garanterad att anlända nollställd när den är ett funktionsresultat som färdas en väg runtimen inte rensar, och celler rotationen aldrig skriver bär då vilka byte som helst som fanns där innan. Vissa av de inaktuella byten är icke-noll, och icke-noll betyder bläck. Fixen är en rad, FillChar(Result[0], N, 0) innan permutationsloopen körs, och den disciplin den antyder är bredare: varje funktion som returnerar en mask- eller bitmappsbuffert bör rensa sin utmatning explicit i stället för att lita på allokeringssemantik
Vad som gjorde att defekten överlevde tre releaser är mer intressant än defekten. När QR flyttade sin orienteringshantering in i avkodaren slutade QR öva den yttre maskrotationen helt, och den enda kvarvarande konsumenten av den kodvägen var Code 39. Delad infrastruktur gömmer buggar som denna hela tiden: täckning från en funktion får en väg att se testad ut medan funktionen som faktiskt beror på den inte har någon egen. Varje väg en ny funktion slutar använda behöver ett test som fortfarande använder den
Att läsa tillbaka resultaten i sidkoordinater
Vartenda geometriskt värde avkodaren producerar uttrycks i försöksbitmappens koordinatram, och anroparen behöver det i PDF:s användarrymd. Den konverteringen körs i två steg: gör retry:ns kvartsvarv ogjort, sedan gör rendertransformen som avbildade användarrymden på bitmappen ogjord. Det som anländer i THPDFDecodedBarcode är en axelparallell begränsningsruta i användarrymd, med Left, Bottom, Right och Top enligt PDF-konventionen att Y växer uppåt, plus en moturs OrientationDegrees
Få riktningen på den andra konverteringen fel och symptomet är elakt: texten avkodar perfekt, men rutan du ritar för ett granskningsöverlägg landar på spegelbilden av den rätta positionen. Vem som helst som bygger ett gränssnitt för granskning ovanpå avkodaren bör hävda mot en känd fixtur, med en symbol placerad medvetet nära ett sidhörn så att en vänd Y-axel syns på en gång. Samma resonemang gäller varje koordinat som korsar renderingsgränsen, vilket är varför att rendera en PDF-sida till en bitmapp i Delphi är värt att förstå innan du bygger ovanpå avkodaren
Vad den inbyggda avkodaren gör och inte gör
Den inbyggda avkodaren är en avgränsad, beroendefri implementation, och den är ärlig om sina gränser i stället för att degradera tyst. Den känner igen Code 39 och QR, validerar de BCH-skyddade formatbitarna och maskmönstret innan den publicerar någon data, och den försöker inte med felreparation på skadade symboler. Om din indata är ett fotografi av en böjd etikett i ojämnt ljus är det en annan problemklass och vill ha en specialiserad motor
// Byt in din egen motor: implementera IHPDFBarcodeDecoder och skicka den
// till den avkodarkänsliga overload:en. HotPDF äger fortfarande
// sidrendering, budgeter, koordinatmappning och avduplicering
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 är där en produktionspipeline förtjänar sitt underhåll. RotationAttemptCount och DecoderCallCount talar om för dig huruvida det yttre omförsöket kördes alls, ReceivedResultCount mot AcceptedResultCount skiljer en avkodare som fann ingenting från en konfidenssterskel som avvisade allt den fann, och RenderedPixels med PeakWorkingBytes är det du grafar när ett batchjobb börjar thrasha. En tom resultatmängd plus bdsSucceeded betyder att sidan verkligen saknar en läsbar symbol, vilket är ett annat driftsfaktum än bdsBudgetExceeded
Budgetfälten förtjänar ett medvetet beslut snarare än ett förval. MaxPixels och MaxWorkingBytes finns för att DPI multiplicerar kvadratiskt: att gå från 300 till 600 DPI på en A4-sida fyrdubblar både renderkostnaden och toppallokeringen, och en otillförlitlig indata som deklarerar en enorm sidruta kan förvandla ett skannjobb till en out-of-memory-incident. Sätt taken till vad ditt sämsta legitima dokument behöver, och låt sedan bdsBudgetExceeded dirigera avvikarna till en långsammare, isolerad väg
Om dina dokument blandar maskinläsbara etiketter med utskriven text du planerar indexera parar streckkodsavkodaren sig naturligt med igenkänningsmotorn som tas upp i template-matching-OCR inne i HotPDF, och generationssidan av samma historia finns i att rita streckkoder in i en PDF med HotPDF. Båda kör på samma renderings- och budgetinfrastruktur, så en pipeline som redan sätter förnuftiga gränser för den ena får den andra nästan gratis
Rotationstolerans är en av de funktioner som är osynlig när den fungerar och rasande när den inte gör det, och ingenjörslärdomen generaliserar bortom QR: normalisera så nära den semantiska representationen du kan komma, inte på pixellagret där datan fortfarande bär varenda tillfällighet i hur den fångades. HotPDF skickar detta som del av HotPDF Delphi PDF component, tillsammans med renderings-, OCR- och sidanalysdelarna samma intagspipelines vanligen behöver