HotPDF декодира завъртени QR символи в заредена PDF страница, като нормализира семплираната module матрица през всичките осем D4 ориентации в самия декодер. Външното завъртане-повторение, работещо за линейни символики, не може да работи за QR, а разбирането защо ви спестява ден гонене на декодер, който изглежда счупен, но не е
Сценарият е съвсем обикновен. Сканирани товарителници пристигат като PDF-и, всяка страница носи QR етикет, а операторът на скенера е заредил куп листове в посоката, която тавата е приела. Някои етикети са прави, други са обърнати на четвърт оборот, трети са с главата надолу. Викате баркод декодера, половината страници се разрешават, а другата половина се връща празна без нито една грешка
Защо завъртането на scan маската никога не оправя завъртен QR?
Защото подредбата на QR finder pattern е нарочно асиметрична, а завъртане на цялото изображение запазва тази асиметрия, вместо да я маха. QR Code поставя три finder квадрата в горния ляв, горния десен и долния ляв ъгъл, и оставя долния десен ъгъл празен (ISO/IEC 18004:2015 §6.3.3). Този липсващ ъгъл е подсказката за ориентация. Завъртете ли bitmap-а на страницата на деветдесет градуса, празнината просто се мести в друг ъгъл. Няма нетривиално завъртане на равнината, което да върне three-corner подредбата върху самата себе си, така че декодер, приемащ само каноничната подредба, ще отхвърли всеки опит подред
Това има значение, защото очевидната поправка е грешната. Естественият инстинкт е да закачите повторението отвън: рендирайте страницата, подайте маската на декодера, а при провал завъртете маската и опитайте пак на 90, 180 и 270 градуса. За Code 39 тази политика е точно правилна, защото линейна символика има start и stop модел, който скенерът намира щом чертите тръгнат хоризонтално. За QR това са четири гарантирани провала, следвани от доклад, че нищо не е намерено
Групата D4, приложена върху module матрицата
Правилното място за нормализацията е след семплирането, върху булевата module решетка, а не върху пиксел маската. Щом декодерът е разрешил символа в n на n матрица от тъмни и светли модули, той може да изброи диедралната група на квадрата: четири завъртания по две отражения, осем кандидатни ориентации общо. За всеки кандидат проверява finder триъгълника, а първият кандидат, чиито три finder-а кацат в горния ляв, горния десен и долния ляв позиции, е истинската ориентация. Оттам нататък съществуващият pipeline върви непроменен, защото format information битове, зигзаг разположението на данните и Reed-Solomon корекцията всички приемат канонична матрица и сега получават една
Два свойства правят това евтино. Матрицата е малка спрямо рендерирания bitmap, така че осем транспонации струват далеч по-малко от осем рендерирания на страница. И матрицата е чист булев масив, строен от семплера, така че нито една трансформация по пътя не може да внесе стойности, които никога не са семплирани
Откриването на версия е търсене на делимост, не деление
Броят модули не може да се изведе като разделите семплираната широчина на предполагаем размер на модул, а разминаването тук е фин източник на провали в декодирането на високорезолюционни рендерирания. QR символ от версия v е широк 4v + 17 модула, така че версия 1 е 21 модула, а версия 40 – 177. Маска, измерваща 126 пиксела ширина, е еднакво съвместима с версия 1 по шест пиксела на модул и с няколко по-високи версии при по-малки размери на модул. Линейното деление избира една от тях и обикновено греши
Работещото е търсене на делимост по кандидатните версии. Минете от версия 40 надолу до версия 1, задръжте кандидатите, чийто брой модули дели семплираната широчина нацело и оставя поне три пиксела на модул, и вземете най-малката оцеляла версия. Три-пикселовият под е това, което спира търсенето да приеме абсурдно плътно четене на груб символ, а правилото за най-малка версия разрешава останалата двусмисленост в полза на четенето, което скенер реално би произвел
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 връща попълнен запис, а не нулиран, което има значение, защото DPI нула или таван за резултати нула са годно изглеждащ начин да получите нищо. RotationPolicy контролира само външното повторение: bdrpNone рендира веднъж, bdrpFallback опитва другите ориентации след провален първи пас, а bdrpAll рендира всяка ориентация безусловно. Тъй като QR нормализацията става в декодера, QR страници се разрешават на първия опит при всяка от трите политики. Политиката е там за линейните символики, които реално се нуждаят от нея
Как доказвате, че bitmap трансформация не измисля пиксели?
Пребройте мастилото от двете страни и изисквайте сборовете да съвпадат. Завъртане е пермутация на пиксели, нищо повече, така че броят на ненулевите клетки в изхода трябва да равнява броя във входа. Когато завъртане на маска във външния retry път докладва 4800 зададени клетки на входа и 7439 на изхода, това единствено сравнение стигна да осъди трансформацията, без да се чете ред от нейната геометрия
Причината беше банална и си заслужава като правило. Динамичен масив, размериран с SetLength, не е гарантирано нулиран, когато е резултат от функция, минаваща по път, който runtime-ът не изчиства, и клетки, които завъртането никога не пише, носят каквито байтове е имало преди. Някои от тези застояли байтове са ненулеви, а ненулево значи мастило. Поправката е един ред, FillChar(Result[0], N, 0) преди цикъла на пермутацията, а дисциплината, която предполага, е по-широка: всяка функция, връщаща маска или bitmap буфер, трябва изрично да изчиства изхода си, вместо да разчита на семантиката на алокацията
Това, което позволи на дефекта да оцелее три release-а, е по-интересно от дефекта. Щом QR премести обработката на ориентацията си в декодера, QR престана да упражнява изобщо външното завъртане на маска, и единственият останал консуматор на този кодов път беше Code 39. Споделена инфраструктура крие бъгове като този постоянно: покритие от една възможност кара път да изглежда тестван, докато възможността, реално зависеща от него, няма никаква собствена. Всеки път, който нова възможност спре да ползва, се нуждае от тест, който пак го ползва
Прочитане на резултатите обратно в координати на страницата
Всяка геометрична стойност, произведена от декодера, е изразена в координатната рамка на attempt bitmap-а, а викащият се нуждае от нея в PDF user space. Конверсията върви на две стъпки: отменете четвърт оборота, приложен от повторението, после отменете render трансформацията, съпоставила user space на bitmap-а. Това, което пристига в THPDFDecodedBarcode, е axis-aligned bounding box в user space, с Left, Bottom, Right и Top по PDF конвенцията, че Y расте нагоре, плюс обратно-часовникови OrientationDegrees
Объркате ли посоката на втората конверсия, симптомът е гаден: текстът декодира перфектно, но кутийката, която чертаете за review overlay, каца на огледалния образ на правилната позиция. Всеки, строещ review интерфейс върху декодера, трябва да утвърждава срещу известен fixture, със символ, поставен нарочно близо до някой ъгъл на страницата, така че обърната Y ос да се вижда с един поглед. Същото разсъждение важи за всяка координата, минаваща границата на рендирането, което е причината рендирането на PDF страница в bitmap в Delphi да си заслужава разбирането, преди да строите върху декодера
Какво вграденият декодер може и какво не може
Вграденият декодер е ограничена имплементация без зависимости и е честен относно лимитите си, вместо да се влошава тихо. Разпознава Code 39 и QR, валидира BCH-защитените format битове и mask pattern преди да опубликува каквито и да е данни, и не се опитва да възстановява повредени символи. Ако входът ви е фотография на извита етикет под неравномерна светлина, това е друг клас проблем и иска специализиран двигател
// Сменете с ваш двигател: имплементирайте IHPDFBarcodeDecoder и го подайте
// на overload-а, осъзнаващ декодери. HotPDF пак притежава рендиране на страница,
// бюджети, координатно съпоставяне и дедупликация
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 е мястото, където продукционен pipeline изкарва хляба си. RotationAttemptCount и DecoderCallCount ви казват дали външното повторение изобщо е тръгвало, ReceivedResultCount срещу AcceptedResultCount разделя декодер, намерил нищо, от confidence праг, отхвърлил всичко намерено, а RenderedPixels с PeakWorkingBytes е това, което графите, когато batch работа започне да се дави. Празен набор резултати плюс bdsSucceeded значи, че страницата наистина няма четим символ, което е различен оперативен факт от bdsBudgetExceeded
Бюджетните полета заслужават съзнателно решение, а не подразбиращо се. MaxPixels и MaxWorkingBytes съществуват, защото DPI умножава квадратично: преминаването от 300 на 600 DPI върху A4 страница четвори и рендер разхода, и пикова алокация, а недоверен вход, деклариращ огромна page box, може да превърне scan работа в out-of-memory инцидент. Задайте таваните според нуждите на най-лошия легитимен документ, после оставете bdsBudgetExceeded да насочва отклоненията към по-бавен, изолиран път
Ако документите ви смесват machine-readable етикети с печатен текст, който възнамерявате да индексирате, баркод декодерът се съчетава естествено с разпознавателния двигател, обхванат в template-matching OCR в HotPDF, а генериращата страна на същата история е в чертаене на баркодове в PDF с HotPDF. И двете вървят на една и съща render и бюджет инфраструктура, така че pipeline, вече задал разумен лимит за едната, получава другата почти наготово
Устойчивостта на завъртане е една от тези възможности, невидими, когато работят, и вбесяващи, когато не, а инженерният урок се обобщава отвъд QR: нормализирайте възможно най-близо до семантичното представяне, не на пикселния слой, където данните още носят всяка случайност на това как са уловени. HotPDF ship-ва това като част от HotPDF Delphi PDF компонента, редом с рендер, OCR и парчетата за анализ на страници, от които обикновено се нуждаят същите intake pipeline-и