HotPDF سمبلهای QR چرخیده را در صفحه PDF بارگذاریشده با نرمالکردن ماتریس ماژول نمونهبرداریشده از میان هر هشت جهت D4، درون خود کدگشا، کدگشایی میکند. تلاش مجدد با چرخش بیرونی که برای symbologyهای خطی جواب میدهد برای QR کار نمیکند، و فهمیدن چرایی یک روز دنبالکردن یک کدگشا را که خراب به نظر میرسد ولی نیست نجاتتان میدهد
سناریو آنقدرهای معمولی است. برگههای تحویل اسکنشده بهصورت PDF میرسند، هر صفحه یک برچسب QR دارد، و اپراتور اسکنر یک دسته برگه را در هر جهتی که سینی قبول کرد فرستاده. بعضی برچسبها ایستادهاند، بعضی یک ربع چرخش اختلاف دارند، چندتایی هم وارونهاند. کدگشا بارکد را صدا میزنید، نیمی از صفحات حل میشوند و نیم دیگر بدون هیچ خطایی خالی برمیگردند
چرا چرخاندن ماسک اسکن هرگز یک QR چرخیده را fix نمیکند؟
چون چیدمان الگوی finder در QR عمداً نامتقارن است، و چرخش کل تصویر آن عدمتقارن را حفظ میکند نه حذفش. QR Code سه مربع finder را در گوشههای بالا-چپ، بالا-راست و پایین-چپ میگذارد و گوشه پایین-راست را خالی میگذارد (ISO/IEC 18004:2015 §6.3.3). همان گوشه غایب نشانه جهتگیری است. بیتمپ صفحه را نود درجه بچرخانید و حفره صرفاً به گوشه دیگری میرود. هیچ چرخش بدیهیای از صفحه وجود ندارد که چیدمان سهگوشهای را دوباره روی خودش نگاشت کند، پس کدگشایی که فقط چیدمان متعارف را قبول میکند هر تلاش را به نوبت رد میکند
این مهم است چون fix بدیهی همان fix غلط است. غریزه طبیعی این است که retry را به بیرون آویزان کنید: صفحه را رندر کنید، ماسک را به کدگشا بدهید، و اگر شکست خورد ماسک را بچرخانید و برای 90 و 180 و 270 درجه دوباره امتحان کنید. برای Code 39 این سیاست دقیقاً درست است، چون یک symbology خطی الگوی start و stopای دارد که اسکنر وقتی خطها افقی شدند پیدایش میکند. برای QR چهار شکست تضمینشده است و بعد گزارشی که میگوید هیچچیز پیدا نشد
گروه D4، اعمالشده روی ماتریس ماژول
جای درست نرمالسازی بعد از نمونهبرداری است، روی گرید ماژول بولی نه روی ماسک پیکسلی. وقتی کدگشا سمبل را به یک ماتریس n در n از ماژولهای تیره و روشن حل کرده، میتواند گروه دوجهته مربع را بشمارد: چهار چرخش ضربدر دو انعکاس، هشت جهت نامزد رویهم. برای هر نامزد مثلث finder را چک میکند، و اولین نامزدی که سه finder آن در جایگاههای بالا-چپ، بالا-راست و پایین-چپ فرود بیاید جهت واقعی است. از آن به بعد خط لوله موجود بدون تغییر اجرا میشود، چون بیتهای format information، چیدمان زیگزاگی دیتا و تصحیح Reed-Solomon همگی یک ماتریس متعارف فرض میکنند و حالا یکی میگیرند
دو property این را ارزان میکنند. ماتریس در مقایسه با بیتمپ رندرشده کوچک است، پس هشت transpose خیلی کمتر از هشت رندر صفحه هزینه دارد. و ماتریس یک آرایه بولی تمیز است که sampler ساخته، پس هیچ transformی در مسیر نمیتواند مقداری وارد کند که هرگز نمونهبرداری نشده
تشخیص نسخه یک جستجوی بخشپذیری است نه یک تقسیم
شمار ماژول را نمیشود با تقسیم پهنای نمونهبرداریشده بر یک اندازه ماژول فرضی به دست آورد، و غلطگرفتنش منبع ظریفی از شکستهای کدگشایی روی رندرهای با تفکیکپذیری بالا است. سمبل QR با نسخه v به پهنای 4v + 17 ماژول است، پس نسخه 1، ۲۱ ماژول است و نسخه 40، ۱۷۷تا. ماسکی که ۱۲۶ پیکسل پهنا دارد هم با نسخه 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 فقط retry بیرونی را کنترل میکند: bdrpNone یک بار رندر میکند، bdrpFallback بعد از یک گذر اول ناموفق جهتهای دیگر را دوباره امتحان میکند، و bdrpAll هر جهت را بیقید و شرط رندر میکند. چون نرمالسازی QR درون کدگشا اتفاق میافتد، صفحات QR تحت هر سه سیاست در همان تلاش اول حل میشوند. این سیاست برای symbologyهای خطیای است که واقعاً به آن نیاز دارند
چطور اثبات میکنید یک transform بیتمپی پیکسل از خودش نمیسازد؟
جوه را در دو طرف بشمارید و الزام کنید مجموعها برابر باشند. چرخش یک جایگشت از پیکسلها است، نه بیشتر، پس تعداد سلولهای غیرصفر در خروجی باید برابر تعدادشان در ورودی باشد. وقتی یک چرخش ماسک در مسیر retry بیرونی 4800 سلول ستشده ورود و 7439 سلول خروج گزارش کرد، همین یک مقایسه کافی بود تا transform را محکوم کند بدون خواندن حتی یک خط از هندسهاش
علت پیشپاافتاده بود و ارزش دارد بهعنوان یک قاعده با خودتان ببرید. آرایه پویایی که با SetLength اندازه گرفته شده تضمینی ندارد که صفرشده برسد وقتی نتیجه یک تابع است که مسیری را طی میکند که runtime پاکش نمیکند، و سلولهایی که چرخش هرگز نمینویسد همان بایتهای قبلی را حمل میکنند. بعضی از آن بایتهای کهنه غیرصفرند، و غیرصفر یعنی جوهر. fix یک خط است، FillChar(Result[0], N, 0) پیش از اجرای حلقه جایگشت، و انضباطی که تحمیل میکند فراتر میرود: هر تابعی که ماسک یا بافر بیتمپ برمیگرداند باید خروجیاش را صریح پاک کند نه اینکه به معناشناسی تخصیص تکیه کند
چیزی که این عیب را سه انتشار زنده نگه داشت جالبتر از خود عیب است. وقتی QR مدیریت جهتش را به کدگشا منتقل کرد، QR دیگر اصلاً چرخش ماسک بیرونی را ورز نمیداد، و تنها consumer باقیمانده آن مسیر کد Code 39 بود. زیرساخت مشترک همیشه همینجوری باگ قایم میکند: پوشش یک قابلیت مسیری را تستشده نشان میدهد در حالی که قابلیتی که واقعاً به آن وابسته است هیچ پوشش خودش را ندارد. هر مسیری که یک قابلیت جدید دست از استفادهاش برمیدارد به تستی نیاز دارد که هنوز استفادهاش کند
خواندن نتیجهها در مختصات صفحه
هر مقدار هندسی که کدگشا تولید میکند در چارچوب مختصاتی بیتمپ تلاش بیان شده، و caller به آن در فضای کاربری PDF نیاز دارد. این تبدیل در دو مرحله اجرا میشود: خنثیکردن چرخش ربعی که retry اعمال کرد، بعد خنثیکردن transform رندر که فضای کاربری را روی بیتمپ نگاشت کرد. چیزی که در THPDFDecodedBarcode میرسد یک جعبه محیطی هممحور در فضای کاربری است، با Left و Bottom و Right و Top مطابق قرارداد PDF که Y رو به بالا رشد میکند، بهعلاوه یک OrientationDegrees پادساعتگرد
جهت آن تبدیل دوم را غلط بگیرید و علامت کثیف است: متن بینقص کدگشایی میشود، ولی جعبهای که برای یک overlay بازبینی میکشید روی تصویر آینهایِ موقعیت درست فرود میآید. هر کسی که روی کدگشا یک interface بازبینی میسازد باید روی یک fixture معروف assert بگذارد، با سمبی که عمداً نزدیک یک گوشه صفحه گذاشته شده تا محور Y وارونه یک نگاه پیدا باشد. همان استدلال روی هر مختصاتی که مرز رندر را رد میکند صادق است، به همین دلیل است که رندر صفحه PDF به بیتمپ در Delphi ارزش فهمیدن دارد قبل از اینکه روی کدگشا بسازید
کدگشای داخلی چه میکند و چه نمیکند
کدگشای داخلی یک پیادهسازی محدوددامنه و بدون وابستگی است، و درباره محدودیتهایش صادق است نه اینکه بیسروصدا افت کند. Code 39 و QR را میشناسد، بیتهای format محافظتشده با BCH و الگوی ماسک را پیش از انتشار هر دادهای اعتبارسنجی میکند، و روی سمبلهای آسیبدیده تلاشی برای بازیابی خطا نمیکند. اگر ورودی شما عکسی از یک برچسب خمیده زیر نور ناهمگن است، آن یک رده مسئله دیگر است و موتور تخصصی میخواهد
// موتور خودتان را جایگزین کنید: 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 جایی است که خط لوله production نانش را درمیآورد. RotationAttemptCount و DecoderCallCount به شما میگویند اصلاً retry بیرونی اجرا شده یا نه، ReceivedResultCount در برابر AcceptedResultCount کدگشایی را که هیچ چیز پیدا نکرده از آستانه اطمینانی را که همه چیزهای پیدا شده را رد کرده جدا میکند، و RenderedPixels همراه PeakWorkingBytes چیزی است که وقتی یک کار دستهای شروع به تلاطم میکند ترسیمش میکنید. مجموعه نتیجه خالی بهعلاوه bdsSucceeded یعنی صفحه واقعاً هیچ سمبل خوانایی ندارد، که یک واقعیت عملیاتی متفاوت از bdsBudgetExceeded است
فیلدهای بودجه ارزش یک تصمیم آگاهانه دارند نه یک پیشفرض. MaxPixels و MaxWorkingBytes به این دلیل وجود دارند که DPI بهطور مربعی ضرب میشود: رفتن از ۳۰۰ به ۶۰۰ DPI روی یک صفحه A4 هم هزینه رندر و هم پیک تخصیص را چهار برابر میکند، و یک ورودی نامطمئن که جعبه صفحه غولآسایی اعلام کند میتواند یک کار اسکن را به یک حادثه کمبود حافظه تبدیل کند. سقفها را به اندازه بدترین سند مشروعتان ست کنید، بعد اجازه بدهید bdsBudgetExceeded خارجازقاعدهها را به مسیری کندتر و ایزوله هدایت کند
اگر اسنادتان برچسبهای ماشینخوان را با متن چاپیای که قصد نمایهکردنش را دارید قاطی میکنند، کدگشا بارکد بهطور طبیعی با موتور بازشناسی که در OCR تطبیق قالب درون HotPDF پوشش داده شد جفت میشود، و سمت تولید همان ماجرا در کشیدن بارکد در PDF با HotPDF است. هر دو روی همان زیرساخت رندر و بودجه اجرا میشوند، پس خط لولهای که از قبل برای یکی سقفهای منطقی ست کرده دومی را تقریباً مجانی میگیرد
تحمل چرخش از آن قابلیتهاست که وقتی کار میکند نامرئی است و وقتی نمیکند خشمگینکننده، و درس مهندسیاش از QR فراتر میرود: نرمالسازی را هرچه به بازنمایی معنایی نزدیکتر است انجام دهید، نه در لایه پیکسل که داده هنوز هر اتفاق نحوه گرفتنش را حمل میکند. HotPDF این را بهعنوان بخشی از HotPDF Delphi PDF component عرضه میکند، کنار قطعههای رندر، OCR و تحلیل صفحه که همان خط لولههای دریافت معمولاً لازمشان دارند