تشخیص پلاک در آگهی‌های خودرو با استفاده از بینایی ماشین-بخش دوم

مقدمه

این نوشته، قسمت دوم از مقاله مربوط به سرویس محو پلاک خودرو در دیوار است. در نوشته اول به تعیین مسئله و بیان اهداف‌مان پرداختیم و راه‌حل‌های موجود را بررسی کردیم. هدف ما از نگارش نوشته اول این بود که پیش‌زمینه لازم را درباره کاری که انجام داده‌ایم داشته‌باشید. در این نوشته، به بررسی راهکارهایی که امتحان کرده‌ایم می‌پردازیم و روند پیاده‌سازی سرویس را توضیح می‌دهیم.

چه راهکارهایی را در نظر گرفتیم؟

سرویس محو پلاک دارای دو مولفه مهم است:

۱. یافتن موقعیت پلاک در تصویر

۲. محو پلاک (مات کردن یا اعمال واترمارک)

عمل محو پلاک به تنهایی پیچیده نیست. چالش اصلی مشخص کردن محل پلاک در تصویر است. در نوشته اول، سه روش کلی را برای آن مشخص کردیم:

۱. الگوریتم‌های پردازش تصویر

۲. مدل‌های شبکه عصبی تشخیص شئ

۳. مدل‌های شبکه عصبی بخش‌بندی

در گام نخست الگوریتم‌های پردازش تصویر برای ما جذابیت بیشتری داشتند. استفاده از آن‌ها ساده است، کارکرد واضحی دارند، نیازی به فراهم آوردن دیتاست به منظور یادگیری نیست و همچنین سرعت اجرای به مراتب بالا‌تری نسبت به شبکه‌های عصبی دارند. این دلایل باعث شد در ابتدا به سراغ این روش‌ها برویم؛ به امید اینکه خروجی کار دقت مطلوبی داشته باشد.

در صورت موفق نبودن راهکار اول، به عنوان یک راهکار جایگزین می‌بایست بین مدل‌های تشخیص شئ و مدل‌های بخش‌بندی یک راه حل را انتخاب می‌کردیم. چرا که متاسفانه امکان تست کردن و مقایسه کردن هر دو روش به دلیل هزینه بالای آن‌ها وجود نداشت. توسعه یک مدل شبکه‌ عصبی شامل مراحل مطالعه و تحقیق، آماده‌سازی دیتا، انتخاب معماری، پیاده‌سازی، آموزش مدل و ارزیابی آن بر روی دیتای تستی است که همه‌‌ی حالت‌های ممکن را در بر‌ داشته‌باشد. در نتیجه لازم بود که به انتخاب یک راهکار بسنده کنیم. در انتها با استناد به بررسی‌ها و تجربه‌های گذشته، مدل‌های تشخیص شئ را به دلایل زیر انتخاب کردیم:

  • مدل تشخیص شئ برای هر پلاک یک BBox تولید می‌کند و این دقیقا همان چیزی بود که برای محو پلاک نیاز داشتیم. به صورت شهودی، یافتن BBox عمل ساده‌تری نسبت به مشخص کردن تمام پیکسل‌های یک شئ است. بنابراین انتظار داریم که خطای این کار کمتر باشد.
  • تقریبا تمام مدل‌های بخش‌بندیِ قدرت‌مندی که وجود دارند، از نوع سنگین هستند. بعضی‌‌ از آن‌ها حتی بر روی GPU به کندی (کمتر از 3 FPS) اجرا می‌شوند، در نتیجه عملکرد آن‌ها بر روی CPU کاملا نامطلوب خواهد بود. بنابراین نیاز بود یکی از این دو راه را پیش بگیریم: بک‌بن این مدل‌ها را با شبکه‌های سبک‌تری جایگزین کنیم یا به طور کلی به سراغ معماری‌های سبک‌تر و ساده‌تر برویم. در هر صورت باید این پیامد را می‌پذیرفتیم که مدل‌ میزان قابل‌توجهی خطا خواهد داشت. هر‌چند امکان خطا در یک مدل هوش‌مصنوعی، امری طبیعی‌ست و ما هیچ‌گاه انتظار خروجی ۱۰۰درصد را نداشته‌ایم، اما این خطاها در مدل‌های بخش‌بندی به میزان بیش‌تری نسبت به مدل‌های تشخیص شئ خودنمایی کرده و کار را سخت‌تر می‌کنند. به عنوان مثال این مدل‌ها معمولا برای پیکسل‌های کرانه (Boundary) اشیا، خطای بیشتری دارند و تعدادی قابل توجهی پیکسل ممکن است False Positive و False Negative شوند. از آن‌جایی که می‌خواهیم چهارضلعی پلاک را به طور دقیق مشخص کنیم، باید الگوریتمی پس‌پردازشی طراحی کنیم که نسبت به این خطاها مقاوم باشد و بتواند چهارضلعی پلاک را با توجه به خروجی مدل تشخیص دهد. ممکن است بتوانیم این مشکل را با اعمال تعدادی تبدیل مورفولوژیک حل کنیم، اما در نهایت دقیقا معلوم نیست این کار چه میزان از مشکل را حل می‌کند.
  • در بخش‌بندی معنایی، متمایز کردن نمونه‌های یک کلاس از یکدیگر اهمیتی ندارد. به عنوان مثال، اگر چند پلاک خودرو در تصویر داشته باشیم، تمایزی بین پیکسل‌های مربوط به هر پلاک‌ تشخیص داده نمی‌شود. این مشکل، در ادامه کار ما را پیچیده می‌کند چون‌که به داشتن این تمایز نیاز داریم. از‌ آن‌جایی‌که مسئله‌ مورد نظر ما یک کلاس بیشتر ندارد، شاید بتوان این مشکل را در پس‌پردازش حل کرد. یک راه‌حل احتمالا این است که پیکسل‌های پلاک‌ها را کلاستر کنیم تا مشخص شود هر پیکسل متعلق به کدام پلاک است. اما اندازه کلاستر رو چند در نظر بگیریم؟ راه‌حل دیگر این است که مولفه‌های هم‌بند را به کمک Connected Component Analysis پیدا کنیم. اما این‌ کار برای چند درصد موارد پاسخگوست؟

ممکن است این سوال برای شما پیش‌آمده باشد که برای حل مشکل متمایز کردن نمونه‌ها، چرا به سراغ بخش‌بندی نمونه‌ای نرویم؟ نکته‌ی مهم در اینجا این است که اکثر این شبکه‌ها بر پایه‌ مدل‌های تشخیص شئ هستند. بنابراین مسئله بیشتر وابسته به این است که مولفه تشخیص شئ چقدر خوب کار می‌کند. در نتیجه به‌جای بخش‌بندی نمونه‌ای، بهتر نیست در ابتدا سراغ شبکه‌های تشخیص شئ برویم و در صورت نیاز، معماری مربوط به بخش‌بندی را به آن‌ها اضافه کنیم؟

دلایلی که به آن‌ها اشاره کردیم، الزاما به این معنی نیستند که مدل‌های بخش‌بندی انتخاب بدی هستند. اما اگر بخواهیم در کنار مدل‌های تشخیص شئ به آن‌ها نگاه کنیم و مقایسه کنیم کدام یک چالش‌های کمتری برای ما دارد، در این صورت مدل‌های تشخیص شئ انتخاب بهتری خواهند بود.

بخش اول - مشخص کردن محل پلاک

قدم اول - استفاده از روش‌های پردازش تصویر

برای این قسمت، ما تقریبا تمام الگوریتم‌هایی که در نوشته اول نام بردیم، امتحان کردیم.

در ابتدا برای انجام Edge Detection از Canny استفاده کردیم. بر روی آرگومان‌های کران بالا و پایین رنگ‌ Canny یک عمل بهینه‌سازی را پیش بردیم به این صورت که این کران را با استفاده از توزیع رنگ‌ها، در هر چنل رنگ تصویر ورودی محاسبه می‌کردیم. کار دیگری که برای بهبود آن انجام دادیم این بود که متوجه شدیم اگر قبل از اعمال Canny، یک Gaussian Blur پیاده‌سازی کنیم، نتایج بهتری خواهیم داشت. در نهایت چند تبدیل مورفولوژیک (Morphological Transformations) را برای حذف نویز‌ها انجام دادیم.

از میان الگوریتم‌هایی که امتحان کردیم، Hough Transform و یافتنِ کانتورِ مربوط به پلاک، بهترین نتایج را داشتند. اما هر یک، دچار نقص‌های مختلفی بودند.

در تبدیل Hough، این هدف را دنبال می‌کردیم که پاره‌خط نسبتا موازی‌ای که تشکیل یک چهارضلعی می‌دهند را پیدا کنیم. از آن‌جایی که رزولوشن تصویر ورودی خیلی بالا نبود، در مواردی که پلاک در تصویر نسبتا کوچک بود، تبدیل Hough به درستی نمی‌توانست پاره‌خط‌های چهارضلعی پلاک را پیدا کند زیرا این پاره‌خط چند تکه و شکسته بود. تلاش کردیم که تبدیل Hough را با پارامتر‌های مختلف اجرا کنیم اما برای هر نمونه متفاوت بود و از یک قانون کلی پیروی نمی‌کرد.

در تلاشی که برای یافتن کانتور پلاک داشتیم، به نتایج بهتری رسیدیم اما همچنان کاستی‌هایی وجود داشت. نکته‌ای که باید در نظر داشت این بود که تمام الگوریتم‌هایی که امتحان کردیم، وابسته به انجام بهینه Edge Detection هستند. در صورتی‌که در این بخش خطایی وجود داشته باشد، این خطا به الگوریتم‌های پس از آن نیز نشر داده می‌شود. هنگامی که روشنایی تصویر یا زاویه آن غیرطبیعی بود، این خطا در جداسازی کرانه‌ها خودش را بیشتر نشان می‌داد و این به تنهایی موجب می‌شد کانتور پلاک با اشکال اطراف آن ترکیب شود و یا اینکه کانتور آن به صورت ناقص بدست آید.

در نهایت بهترین نتیجه‌ای که به‌دست آوردیم، پیدا کردن ۶۰درصد پلاک‌ها به صورت کاملا صحیح بود. این میزان با هدف ۹۵٪ ای که تعیین کرده بودیم فاصله زیادی داشت! یک راه‌حلی که برای بهبود نتیجه در نظر داشتیم این بود که تمام این روش‌ها را با هم ترکیب کنیم و به کمک یکدیگر، نقص‌های آن‌ها را پوشش دهیم. اما در واقعیت امیدوار نبودیم که به آن دقتی که مدنظر داریم می‌توانیم برسیم و در عین حال باید حواس‌مان به زمان محدودی که در اختیار داشتیم می‌بود. بنابراین تصمیم گرفتیم که به سراغ شبکه‌های عصبی برویم و در صورت نیاز، از روش‌های پردازش تصویر نیز استفاده کنیم.

قدم دوم - آماده‌سازی دیتاست پلاک

از آن‌جایی که تصمیم گرفته بودیم از مدل‌های شبکه عصبی استفاده کنیم، لازم بود که دیتاستی از تصاویر پلاک به همراه Annotation آن‌ها (همان Ground truth) داشته باشیم. برای این کار حدود ۲۰۰۰۰ تصویر از خودرو آماده کردیم و تیمی دیگر مسئول آن شد که با استفاده از ابزار VGG Annotator، گوشه‌های تمام پلاک‌های موجود در تصاویر را مشخص کنند. مزیت مشخص کردن نقاط گوشه این است که BBox هر پلاک را می‌توانیم با استفاده از نقاط گوشه محاسبه کنیم و در مدل‌های تشخیص شئ از آن‌ها استفاده کنیم. هم‌چنین اگر لازم بود که اسکلت اصلی پلاک را داشته باشیم، می‌توانیم مستقیما از نقاط استفاده کنیم.

در نگاه اول ممکن است این‌گونه به نظر نرسد اما زمان قابل توجهی را صرف آماده سازی دیتاست کردیم. با توجه به نیازهای‌مان، دیتاست را در چند مرحله آماده کردیم و در هر مرحله می‌بایست Annotation دیتای جدید را با Annotation قدیمی ترکیب می‌کردیم. در هر گام نیاز بود که فایل‌های JSON بدست آمده از VGG Annotator را از لحاظ صحت و کیفیت بررسی کرده و Annotation ها را به فرمت استاندارد COCO تبدیل می‌کردیم. مزیت استفاده از این فرمت این بود که می‌توانستیم از پکیج pycocotools به منظور Visualization دیتا و همچنین از معیارهای آن برای ارزیابی دقت مدل استفاده کنیم.

به طور کلی آماده سازی دیتا همواره برای ما عذاب آور بود و تخمین‌هایی که از زمان انجام شدن کار داشتیم، غالبا اشتباه از آب در می‌آمد.

قدم سوم - ارائه یک مدل تشخیص شئ (YOLO)

خوشبختانه طی چند سال اخیر شبکه‌های عصبی تشخیص شئ پیشرفت بسیار چشمگیری از نظر دقت داشتند که لیستی از بهترین‌ها را اینجا می‌توانید ببینید. در کنار دقت، نکته‌ مهم دیگری که وجود دارد این است که شبکه‌، سرعت اجرای کمتر از ۴۰۰میلی‌ثانیه بر روی CPU داشته باشد. واژه CPU اینجا کلیدی است چرا که سرعت اجرا بر روی CPU نسبت به GPU چندین برابر کم‌تر است و اکثر شبکه‌های قدرتمندی که امروزه وجود دارد بیشتر از۲ ثانیه زمان برای اجرا لازم دارند. بنابراین لازم بود بین سرعت و دقت شبکه، سبک سنگین کنیم و به سراغ شبکه‌های کمی ضعیف‌تر برویم.

در نهایت به سه مدل زیر رسیدیم:

  • YOLO3
  • Detectron2
  • SSD

برای آموزش YOLO3 از این پیاده‌سازی استفاده کردیم (در آن زمان هنوز YOLO4 منتشر نشده بود). این پیاده‌سازی متکی بر فریم‌ورک Darknet است. صادقانه بگوییم این فریم‌ورک مناسبی برای توسعه دادن نیست. اما سه علت مهمی وجود داشت که باعث شد آن را انتخاب کنیم:

۱. مدلی که روی ‌دارک‌نت آموزش داده شده را می‌توان با استفاده بک‌اند OpenCV نیز اجرا کرد و جالب‌ اینکه در این مورد خاص، به میزان ۲۰ درصد اجرا سریع‌تر نسبت به پیاده‌سازی‌های TensorFlow و PyTorch خواهد بود.

۲. این پیاده‌سازی multi-scale training انجام می‌دهد، به این معنی که هنگام آموزش مدل، تصاویر ورودی را با سایز‌های مختلف به شبکه می‌دهد. این در حالی‌ست که در پیاده‌سازی‌های دیگر این قاعده رعایت نشده یا اینکه لاجیک پیاده‌سازی آن با مقاله اصلی متفاوت بوده است.

۳. آموزش مدل خیلی راحت انجام شد. کافی بود Annotation ها را به فرمتی که دارک‌نت می‌خواهد تبدیل می‌کردیم.

نتیجه آموزش این شد دقت مدل بر روی دیتای تست به 97% Precision و 94% Recall با آستانه IoU 0.5 رسید و همچنین به Average IoU 0.78 برای BBoxهای پیش‌بینی شده رسیدیم که برای ما خیلی مطلوب بود. سرعت اجرا چیزی حدود ۳۰۰میلی‌ثانیه بر روی CPU با ۸ هسته بود. بعد از آن، محو پلاک با استفاده از Gaussian Blur را اضافه کردیم که چیزی حدود ۲۵میلی‌ثانیه بر این زمان اجرایی اضافه کرد.

مورد دیگری که امتحان کردیم Detectron2 بود؛ کتاب‌خانه‌ای از Facebook AI Research که به منظور تشخیص شئ و بخش‌بندی به کار می‌رود. معماری شبکه‌ای که Detectron از آن پشتیبانی می‌کند، شبکه‌های Faster R-CNN بهینه شده با بک‌بن‌های متفاوت است. ما برای آموزش، یک نسخه سبک‌ از آن را انتخاب کردیم و در کمال تعجب آموزش مدل، کمتر از۱۰ دقیقه طول کشید! نتایجی که به آن رسیدیم تا حد زیادی مشابه YOLO بود (حدود یک درصد بهتر بود) و روند آموزش نیز ساده بود. اما نکته قابل توجهی که وجود داشت این بود که زمان اجرای آن کمی بیشتر از ۱‌ثانیه بود در حالی که زمان اجرای YOLO یک سوم این مقدار بود.

برای شبکه SSD در نظر داشتیم که آن را با یک بک‌بن سبک مثل MnasNet امتحان کنیم اما در نهایت منصرف شدیم چون قدیمی شده بود و اینکه طبق آمار رسمی، دقت آن نسبت به YOLO بر روی دیتاست COCO کم‌تر بود. از طرفی از نتایجی که با استفاده از YOLO گرفته بودیم راضی بودیم و برای همین با این مدل پیش رفتیم.

تا اینجای کار ما به ۳ معیار اصلی‌ که برای محو پلاک تعیین کرده بودیم، دست پیدا کرده بودیم. از این لحظه به بعد تمرکز‌مان را روی توسعه سرویس به منظور چسباندن واترمارک بر پلاک گذاشتیم.

قدم چهارم - طراحی مدلی برای مشخص نمودن گوشه‌های پلاک

تا اینجای کار ما توانستیم Bounding Box (یا به طور خلاصه‌تر BBox) مربوط به هر پلاک را مشخص کنیم. ولی آیا این برای چسباندن واترمارک کافی‌ست؟ در حالت‌هایی که پلاک کمی چرخش به علت زاویه تصویر گرفته شده دارد، باعث می‌شود BBox با پلاک فیت نشده و مساحت بیشتری را پوشش دهد. مثال‌های زیر نشان می‌دهد که برای اعمال واترمارک در این حالت‌ها نیاز داریم چهارضلعی پلاک را به طور دقیق به دست بیاوریم تا بتون که برای اعمال واترمارک در این حالت‌ها نیاز داریم چهارضلعی پلاک را به طور دقیق به دست بیاوریم تا بتونیم واترمارکی با کیفیت مناسب داشته باشیم. اما این‌کار را چطور می‌توان با محدودیت‌های مدل YOLO مورد نظر مان انجام دهیم؟

روندی را که پیش گرفتیم این بود که در ابتدا با استفاده از مدل YOLO، تمام نمونه پلاک‌های موجود در تصویر را پیدا کنیم و سپس برای هر نمونه، به طور محلی مشخص کنیم که چهارضلعی پلاک دقیقا به چه صورت است. پس مسئله جدید این خواهد بود که با داشتن یک تصویر محلی از پلاک، به طور دقیق‌تر چهارضلعی آن را مشخص کنیم.

برای این کار یک‌بار دیگر سراغ روش‌های پردازش تصویر رفتیم و به طور خاص Hough Transform و Largest Contour را امتحان کردیم. اما متاسفانه نتایج‌ با آن‌که نسبت به حالت اول بهبود زیادی داشتند، در حالت‌هایی که تصویر از زوایای خیلی کج ثبت می‌شد، خطا بالا می‌رفت و در مواردی که روشنایی تصویر غیرعادی بود (خیلی تاریک یا خیلی روشن)، الگوریتم Edge Detection خطای زیادی در جداسازی کرانه‌ها داشت و این خطا در گام‌های بعدی نیز نشر داده می‌شد. تلاش‌هایی برای میزان کردن Edge Detection روی این موارد داشتیم اما نتایج خیلی دلچسب نبود.

اینجا بود که تصمیم گرفتیم از یک مدل CNN سبک برای این‌کار استفاده کنیم. انتظار داشتیم پس از اینکه تصویر پلاک را به مدل دادیم، چهار راس گوشه مربوط به پلاک را پیش‌بینی کند. اگر چهار راس پلاک را داشته باشیم، در این صورت می‌توانیم واترمارک را به درستی بر آن اعمال کنیم.

اگر به یاد داشته باشید Annotation‌های مربوط به چهار گوشه پلاک را در دیتاست‌ داشتیم. بنابراین از جهت دیتا محدودیتی برای این کار وجود نداشت. برای شبکه از معماری Resnet-18 استفاده کردیم که کوچک‌ترین شبکه از خانواده ResNet است. برای ورودی مدل، تصویر پلاک را به یک سایز مشخص در می آوریم و برای خروجی مدل، مختصات گوشه‌های پلاک را با توجه به طول و عرض تصویر، به گونه‌ای نرمال می‌کنیم تا در بازه‌ی ۱- و ۱ قرار گیرد. یعنی اگر مختصات X یک راس در وسط تصویر باشد، به ۰ تبدیل می‌شود و اگر به ۱- تبدیل شود، به این معنی است که در ابتدای تصویر قرار داشته‌است. تصویر زیر یک مثال از این نرمال‌سازی است:

با توجه به تبدیل فوق، تابع خطا را Smooth L1 در نظر گرفتیم که برای این ‌گونه پیش‌بینی‌ها رایج است. کل روند آموزش مدل را با استفاده کتاب‌خانه fastai انجام دادیم. fastai یک کتاب‌خانه متکی بر PyTorch است که خیلی کارها مثل Data Generation ،Augmentation ،Transfer Learning ،Visualization و Training را راحت کرده است. همچنین یک کورس آنلاین برای این کتاب‌خانه وجود دارد که علاوه بر اینکه آموزش استفاده از آن را بیان می‌کند، محتوای مناسبی برای یادگیری Deep Learning می‌باشد. مزیت مهمی که این کتاب‌خانه برای کار ما داشت، اعمال Geometric Augmentation بر روی Annotation‌های مربوط به راس‌های گوشه پلاک بود. از مزیت‌های دیگر می‌تونیم به Learning Rate Finder و Super-Convergence اشاره کنیم که زمان آموزش مدل را خیلی کم می‌کند و برای آزمایش‌های متعدد مناسب است.

نتیجه کار، دقت قابل توجه در پیش‌بینی گوشه‌ها بود به صورتی که با تقریب خوبی، این نقاط مشخص می‌شدند. در تصاویر زیر می‌توانید چند نمونه از خروجی‌های کار را مشاهده کنید.

متاسفانه مشکلی که وجود داشت، زمان اجرای این مدل بود که بر خلاف اینکه مدل سبکی بود، حدود ۲۵۰میلی‌ثانیه طول می‌کشید. همچنین لحظه‌ای که زمان اجرای YOLO و پردازش‌های مربوط به اعمال واترمارک را اضافه کردیم، این زمان تا ۷۰۰میلی‌ثانیه پیش رفت و این عدد بیش‌تر از حد مجاز ۴۰۰میلی‌ثانیه که در نظر داشتیم، بود. با بررسی‌هایی که برای امکان سبک‌تر کردن مدل داشتیم، به این نتیجه رسیدیم که حتی اگر زمان اجرای مدل جدید تا ۱۰۰ میلی‌ثانیه پایین بیاید، با اینکه کاهش نسبتا محسوسی در دقت پیش‌بینی نقاط خواهیم داشت، همچنان در این شرایط زمان اجرای نهایی برای ما، مناسب نخواهد بود. از آن‌جایی که هزینه‌ زمانی‌ مدل تشخیص شئ YOLO (۳۰۰ میلی ثانیه) به تنهایی زیاد است، تصمیم گرفتیم به دنبال راه‌حلی بگردیم که دو مرحله‌ای نباشد. اگر بتوانیم این دو مدل را به نحوی ترکیب کنیم، شاید بتوانیم به سرعتی که مدنظر ما بوده، برسیم.

قدم پنجم - بازطراحی شبکه عصبی EfficientDet

به این نقطه رسیدیم که چطور می‌توان مدل تشخیص شئ را با مدل یافتن نقاط گوشه ترکیب کنیم چرا که پایپ‌لاین قبلی از نظر سرعت، بهینگی لازم را نداشت. برای ترکیب این دو مدل، یکی از بزرگ‌ترین چالش‌ها این است که ورودی مدل نقاط گوشه، یک تصویر پلاک و خروجی آن ۸ عدد بوده که مختصات X, Y نسبی چهار نقطه گوشه را مشخص می‌کرد. اگر بخواهیم مدل تشخیص شئ را با مدل نقاط گوشه ترکیب کنیم، تعداد خروجی‌ها وابسته به تعداد پلاک‌های موجود در تصویر خواهد بود. بنابراین پیش‌بینی مختصات نقاط گوشه جوابگو نخواهد بود و شیوه‌‌ی انکود کردن نقاط را می‌بایست تغییر دهیم.

راه‌حلی که به آن رسیدیم، مشابه مدل‌های شبکه‌ عصبی تخمین ژست (Top-Down Pose Estimation) است. ما برای هر پلاک چهار نقطه گوشه داریم. می‌خواهیم بدانیم این‌ها در چه پیکسل‌هایی از تصویر حضور دارند. فرض کنیم هر نقطه گوشه، یک کلاس خاص است. به طور مثال نقطه گوشه بالای چپ هر پلاکی کلاس ۱، نقطه گوشه بالای راست کلاس ۲، و برای دو نقطه دیگر نیز به همین‌ شکل نام‌گذاری کنیم. مسئله این است که هر پیکسل در تصویر متعلق به کدام یک از این چهار کلاس است. از آن‌جایی که می‌خواهیم Multi-Label Classification انجام دهیم، یک کلاس دیگر به اسم Background باید در نظر بگیریم تا عدم وجود نقطه گوشه در پیکسل را نشان دهد. بنابراین در این حالت تعداد خروجی‌ها برابر تعداد پیکسل‌های تصویر *۵ خواهد بود. حال اگر بتوانیم به مدل تشخیص شئ، یک شاخه دیگر به منظور پیش‌بینی محل حضور نقاط گوشه اضافه کنیم، این مسئله حل خواهد شد.

اما مشکل به این‌جا ختم نمی‌شود. فرض کنیم که تصویر ورودی ما دارای ۳ پلاک خودرو باشد. بنابراین سه نقطه از هر کلاس در تصویر حضور دارند. این سه نقطه را چطور برای هر کلاس پیدا کنیم؟ اگر تنها یک پلاک در تصویر وجود داشت، به طور مثال برای کلاس ۱، در جایی از تصویر که احتمال حضورِ ماکزیمم داشت را به عنوان نقطه گوشه انتخاب می‌کردیم. حال که سه پلاک داریم، آیا با انتخاب ۳ نقطه‌ای که احتمال‌شان بیشینه است، این مشکل حل می‌شود؟ خیر!چرا؟

گفتیم که برای هر پیکسل در تصویر پیش‌بینی می‌کنیم که هر کلاس، با چه احتمالی در آن پیکسل حضور دارد. بنابراین اگر سایز تصویر ورودی ۵۱۲ * ۵۱۲ باشد، برای هر کلاس، خروجی‌ به اندازه ۵۱۲ * ۵۱۲ خواهیم داشت. به این خروجی‌ها، اصطلاحا نقشه‌ حرارت (Heatmap) آن کلاس گفته می‌شود که احتمال حضور آن کلاس در قسمت‌های مختلف تصویر را نشون می‌دهد. در ابتدا برای یافتن نقاط گوشه هر پلاک، BBox این‌ها را که مدل، پیش‌بینی کرده بدست می‌آوریم. سپس به ازای هر پلاک، BBox متناظرش را بر روی نقشه‌ حرارت تمام کلاس‌ها قرار می‌دهیم و داخل آن قسمت خاص از نقشه، برای هر کلاس (نقطه گوشه)، به دنبال پیکسلی که در آن احتمال حضور ماکزیمم است، می‌گردیم. این تا حدی مشابه کاری است که برخی مدل‌های تخمین ژست برای پیدا کردن اسکلت بدن انجام می‌دهند.

به طور خلاصه‌ برای ترکیب دو مدل تشخیص شئ و مدل یافتن نقاط گوشه، تغییرات زیر را باید انجام دهیم:

۱. شیوه کد کردن نقاط را از مختصات، به نقشه‌ حرارتی که کل عکس در بر می‌گیرد تغییر دهیم.

۲. پس‌پردازشی بر روی نقشه حرارت به منظور یافتن نقاط گوشه هر پلاک داشته باشیم.

۳. یک مدل تشخیص شئ پیدا کنیم که بتوان شاخه جدید به آن اضافه کرد تا نقشه‌های حرارت را پیش‌بینی کند.

چه مدل تشخیص شئ ای را انتخاب کنیم؟ خوشبختانه در زمانی که ما دنبال یک معماری جایگزین برای YOLOمی‌گشتیم، مقاله‌ی شبکه EfficientDet منتشر شد (دسامبر ۲۰۱۹). درباره‌ی معماری این شبکه، توضیحی کلی در نوشته اول ارائه کردیم. شبکه مذکور علاوه بر این‌که از YOLOسبک‌تر است، دقت تشخیص بیشتری دارد.

حال برای پیش‌بینی نقشه حرارت نقاط گوشه، یک شاخه جدید پس از لایه‌های BiFPN اضافه کردیم. در ابتدا ویژگی‌ها در مقیاس‌های مختلف را ترکیب می‌کنیم و سپس با اضافه کردن تعدادی Convolution Transpose Block، نقشه‌ حرارتی با مقیاس برابر با تصویر ورودی تولید می‌کنیم.

پس از این تغییرات، چند بار مدل را آموزش دادیم اما نتایج به هیچ وجه قانع کننده نبود! نقشه‌های حرارت مربوط به کلاس در بسیاری از موارد با یکدیگر اشتراک داشتند که این اتفاق خوبی نبود و پس‌پردازش ها را خراب می کردند. بنابراین دو تغییر اساسی دیگر نیز برای بهبود پیش‌بینی نقشه‌حرارت اعمال کردیم:

۱. سراغ انکودینگ گاوسی (به جای انکودینگ ۰ و ۱) برای ساخت ground truth رفتیم که در این مقاله توضیحات لازم آن داده شده.

۲. تابع خطای Focal Loss که برای Binary Classification پیاده‌سازی شده را به گونه‌ای تغییر دادیم که با Softmax سازگار شود و همچنین از انکودینگ گاوسی پشتیبانی کند (به جای ۰ و ۱، با توزیع احتمال بتواند کار کند).

اضافه کردن این موارد موجب شد مدلی به اصطلاح Multi-task داشته باشیم که علاوه بر این‌که پلاک‌های موجود در تصویر را پیدا کرده، موقعیت نقاط گوشه را نیز مشخص می‌کند.

بخش دوم - اعمال واترمارک بر روی تصویر

پس از اینکه BBox و نقاط گوشه هر پلاک را بدست آوردیم، نوبت به اعمال واترمارک می‌رسد.

ارزیابی نقاط گوشه

انتظار داریم چهار نقطه ‌گوشه‌ای که بدست آوردیم، تشکیل چهارضلعی‌ای تا حد زیادی شبیه به متوازی الاضلاع بدهند اما همیشه خروجی به این شکل نیست. ممکن است که زاویه تصویر گرفته شده بد بوده یا اینکه مدل ما همراه با خطا پیش‌بینی کرده باشد.

در وهله اول می‌بایست مطمئن شویم نقاط را به ترتیب ساعت‌گرد داریم. برای این منظور، از Radial Sweep استفاده می‌کنیم. سپس بررسی‌های زیر را به‌جهت اطمینان از کیفیت واترمارک انجام می‌دهیم:

۱. چهارضلعی حتما محدب باشد.

۲. نسبت اضلاع مقابل به یک‌دیگر معقولانه باشد.

۳. اندازه زوایای مقابل نزدیک هم باشند.

در صورتی که تمام شرایط فوق برقرار نباشند، به جای اعمال واترمارک، تنها پلاک را مات می‌کنیم.

اعمال واترمارک

این یکی ازبخش‌هایی بود که حین انجام آن، با چالش‌هایی مواجه شدیم که اصلا انتظارش را نداشتیم. برای اعمال واترمارک دیوار بر روی پلاک، لازم بود که تبدیل پرسپکتیو (Perspective Transformation) انجام دهیم که برای این کار به کتاب‌خانه OpenCV نیاز داشتیم. مشکلی که وجود داشت، کیفیت بسیار پایین واترمارک بود به گونه‌ای که محتوای آن شطرنجی دیده می‌شد. یکی از علت‌های این اتفاق، رزولوشن متوسط تصویر بوده و از آن‌جایی که پلاک بخش کوچکی از این تصویر است، نمی‌توانیم انتظار وضوح بالایی داشته باشیم. برای حل این مشکل، چندین راه‌حل مختلف از جمله تبدیل از اندازه‌های متفاوت، تغییر سایز به‌علاوه‌ی تبدیل و روش‌های درون‌یابی مختلف مثل Cubic و Linear را امتحان کردیم که هیچ‌کدام نتیجه جالبی نداشت.

اما زمانی‌که این تبدیل را با کتابخانه Pillow انجام دادیم، کیفیت تصویر به صورت محسوسی بهتر شد که این عجیب بود. بهینه‌سازی دیگری که در بهبود کیفیت کمک کرد این بود که، ابتدا واترمارک دیوار را به چند برابر بزرگ‌تر از اندازه واترمارکی که نهایتا می‌خواهیم روی پلاک بزنیم تبدیل کرده و سپس با تغییر سایز، واترمارک تبدیل شده را به اندازه نهایی‌‌اش تغییر دهیم. استفاده از درون‌یابی‌های BICUBIC و BILINEAR در تبدیل‌های فوق نیز کمک کننده بود. پس از تحقیقات بیشتر به کتاب‌خانه Pillow-SIMD برخوردیم که یک نسخه بهینه شده از Pillow است و عملکرد آن نیز بهتر است. ما در نهایت از همین کتاب‌خانه استفاده کردیم.

تنظیم روشنایی

روشنایی تصویر در آگهی‌ها می‌تواند متفاوت باشد و این مولفه‌ی مهمی است که ما باید در واترمارک لحاظ می‌کردیم. به منظور نزدیک کردن روشنایی واترمارک به تصویر ورودی، مقادیر پیکسل واترمارک را با توجه به اختلاف مقداری که میانگین پیکسل‌های آن در هر کانال رنگ با میانگین پیکسل‌ها در تصویر ورودی دارند، با اعمال ضریب مناسبی انتقال می‌دهیم. در نهایت پس از این تغییرات، واترمارک روی تصویر ورودی قرار داده می‌شود.

ارزیابی

به‌ منظور ارزیابی عملکرد مدل واترمارک‌ هنگام آموزش مدل، از ابزار پکیج pycocotools استفاده کردیم. در این پکیج متریک‌های متنوعی مطابق با استانداردهای دیتاست COCO برای سنجش دقت مدل‌های تشخیص شئ، تخمین نقطه و … پیاده‌سازی شده است.

برای دقت تشخیص، با محاسبه برای ۱۰۱ نقطه بازیابی و ۱۰ IoU مختلف، به 61.2 mAP رسیدیم. برای کیفیت پیش‌بینی محل واترمارک متاسفانه نتوانستیم از معیار OKS استفاده کنیم و به ارزیابی بر روی ۱۰۰۰ تصویر از آگهی‌های دیوار بسنده کردیم.

طی ارزیابی‌ای که از آگهی‌ها داشتیم، حدود ۹۷٪ از پلاک‌های اصلی را محو کردیم و تقریبا به اندازه ۰٪ پلاک اشتباه تشخیص دادیم. کیفیت واترمارک از نظر ما، رضایت‌بخش بود. برخی از آن‌ها را در زیر می‌توانید مشاهده کنید:

برخی از بهینه‌سازی‌هایی که برای اجرای مدل شبکه عصبی و تبدیل‌های پردازش تصویر داشتیم باعث شدند سرعت اجرای نهایی سرویس را به ۲۲۰ میلی‌ثانیه کاهش دهیم که خوشبختانه بهبود قابل توجه‌ای نسبت به سرویس ۷۰۰میلی‌ثانیه‌ای پیشین ما بود.

جمع‌بندی

این پروژه فرصت خوبی را برای ما فراهم کرد تا بتوانیم بررسی جامعی بر روی انواع معماری‌های شبکه عصبی و الگوریتم‌های پردازش تصویر داشته باشیم و آن‌ها را از نظر سرعت و دقت مقایسه کنیم. شاید جذاب‌ترین بخش این پروژه این بود که سعی کردیم قدم به قدم حرکت کنیم و چالش‌های جدیدی که با آن‌ها مواجه می‌شدیم را یک به یک برطرف کنیم. دفعات متعددی بود که به بن‌بست رسیده بودیم و ایده‌ای برای پیشبرد کار نداشتیم. گاهی اوقات هم نتایج چند هفته‌ای مان با شکست مواجه می‌شد که ناامید کننده بود و ناگزیر بودیم سرنخ دیگری را دنبال کنیم. اما از عواملی که در موفقیت ما در انجام پروژه اثرگذار بود، انعطاف‌پذیری بالا در برنامه‌ریزی‌های‌مان بود که با توجه به شرایط و نیازمندی‌هایی که در هر مرحله داشتیم، می‌توانستیم میان فریم‌ورک‌های Darknet و Tensorflow و PyTorch سوئیچ کنیم. اما مهم‌تر از همه، کار گروهی و روحیه تیمی بود که چه در ایده‌پردازی و چه در پیاده‌سازی، تجربه لذت بخشی را برای ما رقم زد.

این پروژه نمونه کوچکی از کارهای چالش برانگیز و جذابی‌ست که در تیم داده‌ی دیوار پیش می‌بریم. امیدواریم مطالعه‌ی این دو نوشته به شما کمک کرده باشد تا آشنایی بیش‌تری با فضای کاری دیوار کسب کنید.

برای اطلاع از فرصت‌های شغلی در دیوار و پیوستن به ما به این لینک سر بزنید.