بینایی ماشین — از صفر تا صد

۴۲۹۰ بازدید
آخرین به‌روزرسانی: ۲۲ اسفند ۱۴۰۲
زمان مطالعه: ۴۰ دقیقه
بینایی ماشین — از صفر تا صد

در این مطلب، با حوزه «بینایی ماشین» (Machine Vision) از منظر «علوم کامپیوتر» (Computer Science) آشنا خواهید شد. مطلب پیش رو را می‌توان به عنوان مقدمه‌ای جامع بر حوزه بینایی ماشین قلمداد کرد. علاوه بر این، جهت درک بهتر این حوزه و آشنایی بیشتر با جنبه‌های کاربردی آن، پیاده‌سازی‌های خاص انجام شده از سیستم‌های بینایی ماشین در این حوزه، برای خوانندگان و مخاطبان این مطلب نمایش داده خواهد شد.

بینایی ماشین، یکی از حوزه‌های نوظهور، پیچیده و در حال توسعه (Developing) در علوم کامپیوتر و «هوش مصنوعی» (Artificial Intelligence) محسوب می‌شود. حوزه بینایی ماشین به سه زیر شاخه مهم تقسیم‌بندی می‌شود:

  • مطابقت دو سویی (Stereo Correspondence).
  • بازسازی صحنه (Scene Reconstruction)
  • بازشناسی یا تشخیص اشیاء (Object Recognition)

در این مطلب، «تکنیک‌ها» (Techniques) و رویکردهای عمومی مرتبط با هر کدام از این زیر شاخه‌ها مورد بحث و بررسی قرار می‌گیرند.

بینایی ماشین

مقدمه‌ای بر بینایی ماشین

تا چند دهه پیش بسیاری از مردم، حوزه بینایی ماشین را متناظر با داستان‌های «علمی-تخیلی» (Science Fiction) تصور می‌کردند. ولی در یک دهه گذشته، بینایی ماشین تبدیل به یکی از حوزه‌های تحقیقاتی بالغ در علوم کامپیوتر، هوش مصنوعی و زیر شاخه‌های مرتبط آن تبدیل شده است. تحقیقات بنیادی و اساسی که توسط محققان و دانشمندان پیشین در حوزه بینایی ماشین انجام شده است، بنیان مستحکمی را برای تحقیقات بدیع و مدرن در این زمینه فراهم آورده است.

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

شاید سؤالی که برای بسیاری از خوانندگان و مخاطبان این مطلب پدید آمده باشد این است که چرا حوزه بینایی ماشین اهمیت دارد؟ دلیل اهمیت روز افزون حوزه تحقیقاتی بینایی ماشین برای دانشمندان و شرکت‌های صنعتی و تجاری چیست؟ بینایی ماشین از این جهت حائز اهمیت است که به برنامه‌های کامپیوتری اجازه می‌دهد تا وظایف و کاربردهای مختلف را به صورت «خودکار» (Automated) انجام دهند؛ وظایفی که پیش از این و برای انجام‌ آن‌ها، بهره‌گیری از فاکتور «نظارت انسانی» (Human Supervision) ضروری بود.

پیش از ادامه این مبحث لازم است یادآور شویم که می‌توانید بینایی ماشین را با استفاده از مجموعه آموزش بینایی ماشین فرادرس یاد بگیرید.

وظایفی نظیر «بازشناسی قطعات در خط تولید» (Assembly Line Part Recognition)، «بازشناسی چهره» (Face Recognition)، «وسایل نقلیه هوایی بدون سرنشین» (Unmanned Aerial Vehicles)، «بازسازی صحنه جرم» (Crime Scene Reconstruction) و حتی «وسایل نقلیه بدون سرنشین» (Unmanned Automobiles)، از جمله وظایف و کاربردهایی هستند که توسط سیستم‌های بینایی ماشین و تکنیک‌های توسعه داده شده در این حوزه کاربردی، قابلیت خودکارسازی دارند.

هدف کنونی سیستم‌های بینایی ماشین، پیاده‌سازی یک «چارچوب کلی» (Generic Framework) برای حل بسیاری از مسائل باز (و پیش از این حل نشده) در این حوزه و در نهایت، ایجاد یک سیستم عملیاتی و کاربردی در حوزه هوش مصنوعی و «روباتیکز» (Robotics) است.

تاکنون رویکردهای مختلفی برای حل مسائل موجود در زیر مجموعه‌های مختلف حوزه بینایی ماشین و جهت رسیدن به هدف نهایی آن (پیاده‌سازی یک سیستم بینایی ماشین عملیاتی و کاربردی در حوزه هوش مصنوعی و روباتیکز) ارائه شده است. با این حال، یکی از نقیصه‌های اصلی در پیاده‌سازی سیستم‌های مرتبط با بینایی ماشین، عدم وجود یک فرایند جامع و عمومی برای تولید مؤلفه‌های مدل‌‌سازی سه‌بُعدی‎، «تشخیص یا بازشناسی اشیاء» (Object Recognition) و سایر موارد است. در حال حاضر، بینایی در سیستم‌های بینایی ماشین به دو دسته زیر طبقه‌بندی می‌شوند:

  • «بینایی فعال» (Active Vision)
  • «بینایی غیر فعال یا منفعل» (Passive Vision)

در بینایی فعال، سیستم بینایی ماشین به طور مستقیم با محیط ارتباط برقرار می‌کند و به دریافت اطلاعات از محیط مبادرت می‌ورزد. تکنیک‌های نظیر RADAR ،LIDAR ،SONAR و سایر موارد، سیگنال‌هایی نظیر «صوت» (Audio)، «نور» (Light) یا «موج‌های رادیویی» (Radio Waves) را از خود ساطع می‌کند و سپس از طریق گوش دادن به سیگنال منعکس شده، سعی می‌کنند تا یک تصویر خاص را دریافت و مدل‌سازی کنند.

در این مطلب، روی تکنیک‌های بینایی غیر فعال یا بینایی منفعل تمرکز می‌شود؛ به عنوان نمونه، جمع‌آوری نور از محیط عملیاتی (همانند فرایندی که در سیستم بینایی انسان انجام می‌شود). این دسته از تکنیک‌های بینایی ماشین از آن جهت حائز اهمیت هستند که به صورت «مخفیانه» (Stealthy) عمل می‌کنند و محیط را با انتشار صدا، نور و یا موج ناهنجار مختل نمی‌کنند. علاوه بر این، پیاده‌سازی سخت‌افزار موردنیاز برای راه‌اندازی این دسته از سیستم‌های بینایی ماشین به مراتب ارزان‌تر از سیستم‌های بینایی فعال است.

بنابراین می‌توان گفت که یکی از اهداف اصلی سیستم‌های بینایی ماشین، پیاده‌سازی سیستم‌های خودکار «بازسازی صحنه» (Scene Reconstruction) و «بازشناسی اشیاء» (Object Recognition) است (خودکارسازی وظایف). در نتیجه، تحقیقات موجود در حوزه بینایی ماشین را می‌توان به سه دسته مطابقت دو سویی (Stereo Correspondence)، بازسازی صحنه و بازشناسی اشیاء تقسیم‌بندی کرد. برای آشنایی بهتر خوانندگان و مخاطبان این مطلب با مؤلفه‌های سیستم‌های بینایی ماشین، این مطلب به بخش‌های مختلفی تقسیم‌بندی می‌شود تا هر کدام از سه زیر شاخه تحقیقاتی ذکر شده مورد بررسی قرار بگیرند و در نهایت، جهت‌دهی تحقیقاتی آینده در حوزه بینایی ماشین مشخص شود.

بینایی ماشین

تفاوت بینایی ماشین و بینایی کامپیوتر

بسیاری از افراد گمان می‌کنند که بینایی ماشین و «بینایی کامپیوتر» (Computer Vision) دو اصطلاح معادل یکدیگر هستند. در حالی که بینایی ماشین و بینایی کامپیوتر اصطلاحات متفاوتی هستند که برای توصیف فناوری‌های هم‌پوشان مورد استفاده قرار می‌گیرند.

به صورت کلی، بینایی کامپیوتر به فرایند خودکارسازی «دریافت» (Capture) و «تحلیل تصاویر» (Image Analysis) گفته می‌شود. در دامنه وسیع کاربردهای عملی و تئوری حوزه بینایی کامپیوتر، تأکید سیستم‌های بینایی کامپیوتر بیشتر روی قابلیت‌های تحلیل تصاویر، استخراج اطلاعات مفید از آن‌ها و درک و فهم اشیاء یا موجودیت‌های موجود در آن‌ها است.

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

معمولا، مؤلفه‌های ابتدایی لازم برای توسعه سیستم‌های بینایی کامپیوتر و بینایی ماشین مشابه یکدیگر هستند:

  • یک دستگاه تصویربرداری یا دریافت تصویر (معمولا یک دوربین که از یک سنسور تصویر و یک لنز تشکیل شده است)
  • وجود شرایط نوری مناسب برای تعامل با محیط عملیاتی، دریافت تصویر از محیط، تحلیل تصاویر دریافت شده و تولید خروجی‌های متناسب با تحلیل انجام شده
  • یک سیستم کامپیوتری (البته در سیستم‌های تصویربرداری امروز نظیر «دوربین‌های هوشمند» (Smart Cameras)، به دلیل وجود پردازنده‌های اختصاصی، بسیاری از فرایندهای پردازش و تحلیل تصویر درون دستگاه تصویربرداری انجام می‌شود)
  • برنامه پردازش تصویر (برنامه کاربردی کد نویسی شده در زبان‌های برنامه‌نویسی نظیر پایتون، متلب و سایر موارد جهت پردازش و تحلیل تصاویر)

در طی چند سال اخیر، مرز میان سیستم‌های بینایی کامپیوتر و بینایی ماشین باریک شده است و در حال از بین رفتن است. با این حال، امروزه از اصطلاح بینایی ماشین، علاوه بر اینکه در محیط‌های صنعتی استفاده می‌شود، در محیط‌های غیر صنعتی نظیر «نظارت پیشرفته و دست بالا» (High-end Surveillance)، «بیوپزشکی» (Biomedical) و یا کاربردهای «علوم حیاتی» (Life Science) نیز مورد استفاده قرار می‌گیرد. همچنین، برای توصیف تکنیک‌هایی که با هدف بهبود قابلیت‌های «موتورهای جستجو» (Search Engines) و ارائه سرویس‌های «بازشناسی مبتنی بر تصویر» (Image-based Recognition) در جستجو ارائه شده‌اند، از اصطلاح بینایی ماشین استفاده می‌شود.

بینایی ماشین

مطابقت دو سویی (Stereo Correspondence) در بینایی ماشین

در فرایند مطابقت دو سویی، ورودی (تصویر دیجیتال یا فریم‌های ویدئویی) از دو دوربین دریافت می‌شود و «ویژگی‌های مشترک» (Shared Features) میان آن‌ها شناسایی می‌شود. خروجی فرایند مطابقت دو سویی (Stereo Correspondence)، یک «نقشه ناهمخوانی» (Disparity Map) از تصویر است. نقشه ناهمخوانی، با نام‌های دیگری نظیر نقشه نابرابری یا نقشه تمایز نیز شناخته می‌شود. نقشه ناهمخوانی یک تصویر، در اصل، یک «نقشه عمق معکوس» (Inverse Depth Map) از آن تصویر به حساب می‌آید که فاصله (دور بودن) هر کدام از پیکسل‌های موجود در تصویر، از «صفحه دوربین‌ها» (Plane of Cameras) را نشان می‌دهد.

برای این که محاسبات سیستم‌های بینایی ماشین و عملکرد آن‌ها به شکل بهینه‌ای انجام شود، دوربین‌ها باید فاصله ثابتی با یکدیگر داشته باشند و جهت آن‌ها نسبت به یکدیگر مشخص باشد. محققان معمولا با انجام آزمایشات «کالیبره کردن» (Calibration)، فاصله و زاویه دوربین‌ها از یکدیگر را به طور خودکار مشخص می‌کنند. ورودی فرایند مطابقت دو سویی، معمولا داده‌های خام دریافت شده توسط دوربین‌ها است که باید فرایندهای پردازشی خاصی به نام «اصلاح تصویر» (Image Rectification) روی آن‌ها انجام شود.

اصلاح تصویر فرایندی است که در آن یک تصویر ورودی، روی یک «سطح معمولی» (Common Surface) «نگاشت» (Map) یا «تبدیل» (Transform) می‌شود تا تصاویری که شکل طبیعی خود را از دست داده‌اند، «نرمال‌سازی» (Normalize) شوند. چنین امری بیشتر در مورد تصاویری که توسط «دوربین‌های چشم ماهی» (Fish-Eye Cameras) ضبط شده‌اند و یا تصاویری که توسط دوربین‌های با ارتفاع نابرابر گرفته شده‌اند، صدق می‌کند. نتیجه خروجی فرایند اصلاح تصویر، یک تصویر نرمال‌سازی شده از هر دوربین است. پس از اتمام فرایندهای پردازشی اصلاح تصویر، تصاویر حاصل شده را می‌توان به عنوان ورودی، به یک الگوریتم مطابقت دو سویی (Stereo Correspondence) داد تا اشتراکات موجود میان آن‌ها شناسایی شود.

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

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

در کاربردهای جهان واقعی و با در نظر گرفتن جنبه‌های اقتصادی موضوع، پیشنهاد می‌شود که به جای استفاده از دو دوربین پیشرفته و گران (جهت ثبت تصاویر)، از 6 دوربین با کارایی مناسب (و قیمت معقول) استفاده شود.

در ادامه، گام‌های لازم برای انجام فرایند مطابقت دو سویی نمایش داده شده است:

  • مرحله «تطبیق» (Matching):
    • روش مجموع مربعات فاصله‌ها (Sum of Squared Distances | SSD)
    • روش مجموع فاصله‌های مطلق (Sum of Absolute Distance | SAD)
    • روش «همبستگی متقابل نرمال شده» (Normalized Cross Correlation | NCC)
  • مرحله «تجمیع» (Aggregation):
    • روش «پنجره قابل انتقال» (Shiftable Window)
    • روش «پنجره تطابقی» (Adaptive Window)
  • مرحله «بهینه‌سازی» (Optimization):
    • رویکرد «همه برای برنده» (Winner-Take-All)
    • رویکرد «برنامه‌نویسی پویا» (Dynamic Programming)
    • رویکرد «شبیه‌سازی تبرید» (Simulated Annealing)
    • رویکرد «برش‌های گرافی» (Graph Cuts)
    • رویکرد بهینه‌سازی Scanline
  • مرحله «اصلاح یا پالایش» (Refinement):

بنابراین، الگوریتم‌های مطابقت دو سویی مرسوم را می‌توان بر اساس پیاده‌سازی آن‌ها، نوع الگوریتم‌های استفاده شده در هر مرحله و فاکتورهایی نظیر «هزینه تطبیق» (Matching Cost)، «هزینه تجمیع نواحی پشتیبانی محلی» (Local Support Region Cost Aggregation)، «بهینه‌سازی ناهمخوانی» (Disparity Optimization) و «اصلاح یا پالایش» (Refinement) دسته‌‎بندی کرد:

  • هزینه تطبیق (Matching Cost): الگوریتمی که برای تخمین زدن شباهت میان «مقادیر شدت» (Intensity Values) در یک مجموعه از تصاویر مورد استفاده قرار می‌گیرد.
  • هزینه تجمیع نواحی پشتیبانی محلی (Local Support Cost Aggregation): روشی است که از آن برای تولید تخمین‌های متناسب با هزینه تطبیق، در نواحی پشتیبانی محلی، استفاده می‌شود.
  • بهینه‌سازی ناهمخوانی (بهینه‌سازی نابرابری یا بهینه‌سازی تمایز): به فرایند مشخص کردن ناهمخوانی (عمق) در یک پیکسل داده شده اطلاق می‌شود.
  • اصلاح یا پالایش (Refinement): به فرایند استفاده از تکنیک‌های شایع جهت «هموارسازی» (Smoothing) ناهمخوانی‌های موجود در تصویر اطلاق می‌شود.

مرحله تطبیق (Matching) و تجمیع (Aggregation) در مطابقت دو سویی

در یک دسته‌بندی کلی، روش‌های مطابقت دو سویی را می‌توان به روش‌های «مطابقت مبتنی بر ویژگی» (Feature-based Matching) و «مطابقت مبتنی بر شدت» (Intensity-based Matching) تقسیم‌بندی کرد. در روش‌های مطابقت مبتنی بر ویژگی، سعی بر این است تا «لبه‌ها» (Edges) یا «گوشه‌های» (Corners) موجود، میان تصاویر مطابقت داده شوند. با این حال، عملکرد بهینه این روش‌ها، وابسته به اعمال تکنیک‌های «پیش پردازشی» (Preprocessing) و «پس پردازشی» (Post Processing) گسترده روی تصاویر است؛ روش‌هایی نظیر «تار کردن» (Blurring) و «تشخیص گرادیان» (Gradient Detection) جهت پیش پردازش تصاویر و روش‌های پس پردازش جهت «درون یابی» (Interpolating) ناهمخوانی‌های موجود میان ویژگی‌ها.

به دلیل احتمال وجود «نویز» (Noise) و «هم‌پوشانی تصویر» (Image Occlusion)، که منجر به تولید نقشه‌های ناهمخوانی غیر قابل اعتماد و «اسپارس» (Sparse) می‌شود، استفاده از روش‌های «استخراج ویژگی» (Feature Extraction) در روش‌های مطابقت دو سویی پیشنهاد نمی‌شود.

بینایی ماشین
نمایه شدت (Intensity Profile) تولید شده برای تصویر اول با استفاده از مقادیر شدت رنگ پیکسل‌های تصویر
بینایی ماشین
نمایه شدت (Intensity Profile) تولید شده برای تصویر دوم با استفاده از مقادیر شدت رنگ پیکسل‌های تصویر

بینایی ماشین

بینایی ماشین

روش‌های مطابقت مبتنی بر شدت، از Scanline (یک Scanline، یک خط یا یک سطر در «الگوی اسکن کردن راستر» (Raster Scanning Pattern) است، نظیر یک خط ویدئویی در نمایشگرهای CRT یا مانیتورهای کامپیوتر) جهت تولید «نمایه‌های شدت» (Intensity Profile) استفاده می‌کنند. رویکرد Scanline روی «تصاویر اصلاح شده» (Rectified Image) اجرا می‌شود و خطوط افقی موجود در تصویر را یکی به یکی پردازش می‌کند.

مطابقت تصویر (Image correspondence)، از طریق حرکت دادن نمایه شدت متناظر با دوربین آفست (Offset Camera) در راستای افقی و کمینه‌سازی اختلاف (یا بیشینه‌سازی مشابهت) در مقادیر شدت (Intensity)، پیکسل‌ها حاصل می‌شود. در صورتی قرار است مطابقت میان تصاویر رنگی حاصل شود، پیشنهاد می‌شود که به ازاء هر کدام از مؤلفه‌ها یا کانال‌های رنگی، یک نمایه شدت تولید و سپس، میانگین مقدار شدت حاصل محاسبه شود.

از رویکرد Scanline می‌توان برای ساختن «پنجره لغزان» (Sliding Window) استفاده کرد که محاسبات لازم برای مطابقت تصویر (Image correspondence) را به صورت محلی انجام می‌دهد. بدین صورت که ابتدا از پنجره لغزان ساخته شده، جهت تولید نمایه شدت متناظر با یک پنجره به اندازه NxN، اطراف یک پیکسل مورد نظر، استفاده می‌شود. سپس، یک پنجره با اندازه مشابه، در راستای تصویر دیگر حرکت داده می‌شود که هدف آن کمینه کردن اختلافات یا بیشینه کردن مشابهت (میان مقادیر شدت) در نمایه‌های شدت متناظر است.

معیارهای مرسومی که برای مشخص کردن مشابهت مورد استفاده قرار می‌گیرند، عبارتند از:

  • روش مجموع مربعات فاصله‌ها (Sum of Squared Distances | SSD)
  • روش مجموع فاصله‌های مطلق (Sub of Absolute Distance | SAD)
  • روش «همبستگی متقابل نرمال شده» (Normalized Cross Correlation | NCC)

با فرض اینکه $$X$$ و $$Y$$ نمایش دهنده مقدار شدت در دو پنجره باشند، تعداد $$N$$ چندتایی به فرم $$(X_{1} , Y_{1}) , ......, (X_{N} , Y_{N})$$، برای یک پنجره به اندازه NxN وجود خواهد داشت. در چنین حالتی، مقدار «همبستگی متقابل نرمال شده» (Normalized Cross Correlation | NCC) از طریق رابطه زیر به دست می‌آید:

$$N o r m a l i z e d \; c r o s s \;c o r r e l a t i o n = \frac { \Large \sum _ { i = 1 } ^ n ( X _ { i } - \overline { X } ) ( Y _ { i } - \overline { Y } ) } { \sqrt { \left [ \Large \sum _ { i = 1 } ^ n ( X _ { i } - \overline { X } ) ^ { 2 } \; \Large \sum _ { i = 1 } ^ n ( Y _ { i } - \overline { Y } ) ^ { 2 } \right ] } }$$

در این رابطه، $$\overline { X }$$ و $$\overline { Y }$$، میانگین نمونه‌های موجود در پنجره‌های متناظر را نمایش می‌دهند. مقدار رابطه همبستگی متقابل نرمال شده، بین مقادیر 1 و 1- خواهد بود؛ مقدار 1 نشان دهنده «همبستگی کامل» (Perfect Correlation) میان مقادیر شدت پنجره‌ها خواهد بود.

از رابطه زیر، جهت محاسبه مجموع مربعات فاصله‌ها (Sum of Squared Distances | SSD) استفاده می‌شود:

$$S S D = \sum _ { i = 1 } ^ n ( X _ { i } - Y _ { i } ) ^ { 2 }$$

این رابطه، مربع «فاصله اقلیدسی» (Euclidean Distance) میان $$X$$ و $$Y$$ را محاسبه می‌کند. از رابطه زیر نیز جهت محاسبه مجموع فاصله‌های مطلق (Sub of Absolute Distance | SAD) استفاده می‌شود:

$$S A D = \sum _ { i = 1 } ^ n \mid X _ { i } - Y _ { i } \mid $$

این رابطه، فاصله مطلق میان مقادیر شدت را محاسبه می‌کند. روش‌های دیگری برای محاسبه شباهت وجود دارند ولی به اندازه روش‌های نمایش داده شده معروف و پرطرفدار نیستند. عوامل متعددی نظیر «هم‌پوشانی» (Occlusion) و «نوردهی آینه‌ای» (Specular Lighting)، می‌توانند در تولید شرایط مطابقت ضعیف میان تصاویر سهیم باشند. نوردهی آینه‌ای (Specular Lighting) به انعکاساتی اطلاق می‌شود که از «سطوح غیر لابرتنی» (Non-Lambertian Surfaces) ایجاد شده باشند. سطوح غیر لابرتنی، سطوحی «پخشی» (Diffuse) هستند که نور را به طور برابر در تمامی جهات منعکس می‌کنند. این عواملی زمانی مشکل‌ساز می‌شوند که دوربین‌ها، به دلیل اختلاف چشم‌انداز یا منظر (Perspectives)، تصاویر متفاوتی را از یک صحنه دریافت می‌کنند.

مرحله بهینه‌سازی (Optimization) در مطابقت دو سویی

هدف روش‌های بهینه‌سازی ناهمخوانی این است که با مشخص کردن بهترین مجموعه از ناهمخوانی‌هایی (Disparities) که نمایش دهنده «سطح صحنه» (Scene Surface) موجود در تصویر هستند، نقشه ناهمخوانی (Disparity Map) دقیقی را از تصاویر تولید کنند.

مهم‌ترین و شایع‌ترین روش‌های بهینه‌سازی ناهمخوانی (Disparity Optimization) عبارتند از:

رویکرد «همه برای برنده» (Winner-Take-All): این رویکرد، ساده‌ترین و معمول‌ترین رویکرد برای روش‌های «تجمیع مبتنی بر پنجره» (Window-based Aggregation) محسوب می‌شود. در این روش‌، ناهمخوانی کمینه کننده مقدار هزینه، به عنوان ناهمخوانی اصلی انتخاب می‌شود.

رویکرد «برنامه‌نویسی پویا» (Dynamic Programming): در این رویکرد سعی می‌شود تا در ماتریس متشکل از هزینه‌های تطبیق دو طرفه میان دو Scanline متناظر، مسیر «بهینه سراسری» (Globally Optimized) و دارای کمترین هزینه ممکن انتخاب شود.

بینایی ماشین

رویکرد بهینه‌سازی Scanline: این رویکرد، مشابه رویکرد برنامه‌نویسی پویا است؛ با این تفاوت که هزینه هم‌پوشانی (Occlusion Cost) در آن وجود ندارد.

رویکرد «شبیه‌سازی تبرید» (Simulated Annealing): در این رویکرد، یک ناهم‌خوانی به طور تصادفی انتخاب و هزینه در آن پیکسل ارزیابی می‌شود. در مرحله بعد، این رویکرد، جهت پیدا کردن کمینه سراسری (Global Minimum)، به سمت چپ یا راست حرکت می‌کند و هزینه در این پیکسل‌ها را ارزیابی می‌کند.

رویکرد «برش‌های گرافی» (Graph Cuts): این رویکرد، از روش حرکت «مبادله آلفا-بتا» (α-β Swap) برای بهینه‌سازی ناهمخوانی‌ها استفاده می‌کند.

در سیستم‌های بینایی ماشین پس از اینکه مطابقت (Correspondence) میان تصاویر مشخص شد، از هندسه Epipolar یا Epipolar Geometry برای محاسبه نقشه ناهمخوانی (Disparity Map) استفاده می‌شود. هندسه Epipolar، شکلی از «هندسه تصویری» (Projective Geometry) محسوب می‌شود که برای مشخص کردن موقعیت سه‌بُعدی یک شیء، از منظر دو دوربین مورد استفاده قرار می‌گیرد.

بینایی ماشین
هندسه Epipolar به کار گرفته شده جهت محاسبه عمق یک پیکسل تصویر شده. در این تصویر، C1 و C2 دوربین سمت چپ و راست را نمایش می‌دهند، در حالی که e1 و e2 برای نمایش دادن خط Epipolar مورد استفاده قرار می‌گیرد. همچنین، P1 و P2 مختصات تصویر شده (Projected Coordinates) در تصویر هستند.

در یک جهان ایده‌آل و با در اختیار داشتن دوربین‌هایی که از «دقت» (Resolution) کامل و دقیقی برخوردار هستند، سیستم بینایی ماشین قادر خواهد بود موقعیت دقیق یک شیء را محاسبه کند. با این حال، از آنجایی که از دوربین‌های با دقت محدود در سیستم‌های بینایی ماشین استفاده می‌شود، زاویه «مثلث‌بندی» (Triangulation) سبب محدود شدن «دقت عمق» (Depth Resolution) در تصاویر گرفته شده خواهد شد. چنین پدیده‌ای سبب پیش‌بینی نادرست موقعیت اشیاء موجود در تصویر می‌شود؛ همچنین، محدود بودن دقت عمق در تصاویر دیجیتالی، سبب ایجاد درجه‌ای از خطای ذاتی (Inherent Error) در آن‌ها می‌شود.

به همین خاطر است که در سیستم‌های بینایی ماشین توصیه می‌شود از تصاویر گرفته شده توسط چندین دوربین، به جای دو دوربین، برای مطابقت دو سویی (Stereo Correspondence) استفاده شود. زیرا وجود دوربین‌های چندگانه در سیستم بینایی ماشین (Machine Vision)، اجازه تخمین دقیق موقعیت اشیاء موجود در تصویر را به سیستم می‌دهد؛ دلیل این امر، مشارکت چندین دوربین و داده‌های حاصل از آن‌ها، در تخمین و مشخص کردن موقعیت نهایی اشیاء در تصاویر است.

پس از اینکه موقعیت (Position) مطابقت در تصاویر مشخص شد، در مرحله بعد لازم است تا عمق (Depth) موقعیت مشخص شده، از صفحه دوربین (Plane of Camera) محاسبه شود تا از این طریق، نقشه ناهمخوانی (Disparity Map) تولید شود.

بینایی ماشین

مرحله اصلاح یا پالایش (Refinement) در مطابقت دو سویی

اصلاح یا پالایش ناهمخوانی (Disparity Refinement) به فرایند هموار کردن (Smoothing) نقشه ناهمخوانی نهایی گفته می‌شود؛ در نتیجه این فرایند، نقشه ناهمخوانی نهایی از بازه‌های گسسته شده به مقادیر پیوسته نگاشت یا تبدیل می‌شوند. برای اصلاح یا پالایش ناهمخوانی، فرایند هموارسازی نقشه ناهمخوانی با استفاده از بهترین مقادیر ناهمخوانی محاسبه شده و یا بر اساس مقادیر ناهمخوانی پیکسل‌های همسایه انجام می‌شود. در این فرایند معمولا فرض می‌شود که مقدار ناهمخوانی یک پیکسل مورد نظر، با مقدار ناهمخوانی پیکسل همسایه برابر است؛ بنابراین، هموارسازی ناهمخوانی پیکسل‌ها با استفاده از مقادیر ناهمخوانی پیکسل‌های همسایه (در سراسر تصویر)، سبب ایجاد مطابقت دو سویی دقیق‌تر در سیستم‌های بینایی ماشین خواهد شد.

پیاده‌سازی‌های مدرن انجام شده از سیستم‌های بینایی ماشین و  مطابقت دو سویی، به برنامه‌نویس و توسعه‌دهنده برنامه‌های کاربردی اجازه می‌دهد تا محاسبات هندسه Epipolar را روی «واحد پردازش گرافیکی» (Graphical Processing Unit | GPU) انجام دهند و از «سخت‌افزار بافت‌زنی تصویری» (Projective Texture Hardware) برای شتاب بخشیدن به فرایند مطابقت دو سویی در سیستم‌های بینایی ماشین استفاده کنند.

پیاده‌سازی سیستم محاسبه نقشه ناهمخوانی و مطابقت دو سویی در پایتون‌

در مثال اول، محاسبه نقشه ناهمخوانی و مطابقت دو سویی با استفاده از تصاویر گرفته شده توسط دو دوربین مختلف نمایش داده می‌شود. در ابتدا، دو تصویر به عنوان ورودی وارد سیستم محاسبه نقشه ناهمخوانی می‌شوند. دو دوربین در فاصله مشخصی از یکدیگر قرار گرفته شده‌اند. برای محاسبه نقشه ناهمخوانی، از رویکرد برش‌های گرافی (Graph Cuts) استفاده شده است.

دو تصویر گرفته شده از یک صحنه توسط دوربین‌های مختلف:

بینایی ماشین

کدهای پیاده‌سازی سیستم محاسبه نقشه ناهمخوانی در پایتون‌:

1def cut(disparity, image, threshold):
2 for i in range(0, image.height):
3  for j in range(0, image.width):
4   # keep closer object
5   if cv.GetReal2D(disparity,i,j) > threshold:
6    cv.Set2D(disparity,i,j,cv.Get2D(image,i,j))
7
8# loading the stereo pair
9left  = cv.LoadImage('scene_l.bmp',cv.CV_LOAD_IMAGE_GRAYSCALE)
10right = cv.LoadImage('scene_r.bmp',cv.CV_LOAD_IMAGE_GRAYSCALE)
11
12disparity_left  = cv.CreateMat(left.height, left.width, cv.CV_16S)
13disparity_right = cv.CreateMat(left.height, left.width, cv.CV_16S)
14
15# data structure initialization
16state = cv.CreateStereoGCState(16,2)
17# running the graph-cut algorithm
18cv.FindStereoCorrespondenceGC(left,right,
19                          disparity_left,disparity_right,state)
20
21disp_left_visual = cv.CreateMat(left.height, left.width, cv.CV_8U)
22cv.ConvertScale( disparity_left, disp_left_visual, -16 );
23cv.Save( "disparity.pgm", disp_left_visual ); # save the map
24
25# cutting the object farthest of a threshold (120)
26cut(disp_left_visual,left,120)
27
28cv.NamedWindow('Disparity map', cv.CV_WINDOW_AUTOSIZE)
29cv.ShowImage('Disparity map', disp_left_visual)
30cv.WaitKey()

نقشه‌های ناهمخوانی حاصل با مقادیر آستانه مختلف:

مثال دوم: در مثال زیر نیز دو تصویر از صحنه یکسان نمایش داده شده‌اند که توسط دو دوربین با منظرهای (چشم‌اندازها) مختلف گرفته شده‌اند.

کدهای پیاده‌سازی سیستم محاسبه نقشه ناهمخوانی و مطابقت دو سویی در پایتون‌:

1import cv2
2import random
3import numpy as np
4import matplotlib
5matplotlib.use("TkAgg")
6from matplotlib import pyplot as plt
7
8
9grayscale_max = 255
10
11
12def load_image(filename):
13    image = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
14    return image
15
16
17def show_image(title, image):
18    max_val = image.max()
19    # image = np.absolute(image)
20    image = np.divide(image, max_val)
21    # cv2.imshow(title, image)
22    cv2.imwrite(title+str(random.randint(1, 100))+'.jpg', image*grayscale_max)
23
24
25def add_padding(input, padding):
26    rows = input.shape[0]
27    columns = input.shape[1]
28    output = np.zeros((rows + padding * 2, columns + padding * 2), dtype=float)
29    output[ padding : rows + padding, padding : columns + padding] = input
30    return output
31
32
33def add_replicate_padding(image):
34    # zero_padded = add_padding(image, padding)
35    # size = image.shape[0]
36    top_row = image[0, :]
37    image = np.vstack((top_row, image))
38
39    bottom_row = image[-1, :]
40    image = np.vstack((image, bottom_row))
41
42    left_column = image[:, 0]
43    left_column = np.reshape(left_column, (left_column.shape[0], 1))
44    image = np.hstack((left_column, image))
45
46    right_column = image[:, -1]
47    right_column = np.reshape(right_column, (right_column.shape[0], 1))
48    image = np.hstack((image, right_column))
49
50    return image
51
52
53def euclid_dist(a, b):
54    distance = np.linalg.norm(a - b)
55    return distance
56
57
58def get_search_bounds(column, block_size, width):
59    disparity_range = 25
60    left_bound = column - disparity_range
61    if left_bound < 0:
62        left_bound = 0
63    right_bound = column + disparity_range
64    if right_bound > width:
65        right_bound = width - block_size + 1
66    return left_bound, right_bound
67
68
69def search_bounds(column, block_size, width, rshift):
70    disparity_range = 75
71    padding = block_size // 2
72    right_bound = column
73    if rshift:
74        left_bound = column - disparity_range
75        if left_bound < padding:
76            left_bound = padding
77        step = 1
78    else:
79        left_bound = column + disparity_range
80        if left_bound >= (width - 2*padding):
81            left_bound = width - 2*padding - 2
82        step = -1
83    return left_bound, right_bound, step
84
85
86# max disparity 30
87def disparity_map(left, right, block_size, rshift):
88
89    padding = block_size // 2
90    left_img = add_padding(left, padding)
91    right_img = add_padding(right, padding)
92
93    height, width = left_img.shape
94
95    # d_map = np.zeros((height - padding*2, width - padding*2), dtype=float)
96    d_map = np.zeros(left.shape , dtype=float)
97
98    for row in range(height - block_size + 1):
99        for col in range(width - block_size + 1):
100
101            bestdist = float('inf')
102            shift = 0
103            left_pixel = left_img[row:row + block_size, col:col + block_size]
104            l_bound, r_bound, step = search_bounds(col, block_size, width, rshift)
105
106            # for i in range(l_bound, r_bound - padding*2):
107            for i in range(l_bound, r_bound, step):
108                right_pixel = right_img[row:row + block_size, i:i + block_size]
109
110                # if euclid_dist(left_pixel, right_pixel) < bestdist :
111                ssd = np.sum((left_pixel - right_pixel) ** 2)
112                # print('row:',row,' col:',col,' i:',i,' bestdist:',bestdist,' shift:',shift,' ssd:',ssd)
113                if ssd < bestdist:
114                    bestdist = ssd
115                    shift = i
116
117            if rshift:
118                d_map[row, col] = col - shift
119            else:
120                d_map[row, col] = shift - col
121            print('Calculated Disparity at ('+str(row)+','+str(col)+') :', d_map[row,col])
122
123    return d_map
124
125
126def mean_square_error(disparity_map, ground_truth):
127    # ssd = np.sum((disparity_map - ground_truth)**2)
128    # mse = ssd/(ground_truth.shape[0]*ground_truth.shape[1])
129    mse = np.mean((disparity_map - ground_truth)**2)
130    return mse
131
132
133def consistency_map_mse_l(d_map_left, d_map_right, left_ground_truth):
134    rows, cols = d_map_left.shape
135    consistency_map = np.zeros((rows, cols))
136
137    for r in range(rows):
138        for c in range(cols):
139            left_pixel = d_map_left[r, c]
140
141            if cols > c - left_pixel > 0:
142                right_pixel = d_map_right[r, int(c - left_pixel)]
143            else:
144                right_pixel = d_map_right[r, c]
145
146            if left_pixel == right_pixel:
147                consistency_map[r, c] = left_pixel
148            else:
149                consistency_map[r, c] = 0
150
151    sum = 0
152    for r in range(rows):
153        for c in range(cols):
154            if consistency_map[r, c] != 0:
155                sum = sum + (left_ground_truth[r, c] - consistency_map[r, c]) ** 2
156
157    mse_c_left = sum / (rows * cols)
158    return mse_c_left, consistency_map
159
160
161def consistency_map_mse_r(d_map_left, d_map_right, right_ground_truth):
162    rows, cols = d_map_right.shape
163    consistency_map = np.zeros((rows, cols))
164
165    for r in range(rows):
166        for c in range(cols):
167            right_pixel = d_map_right[r, c]
168
169            if c + right_pixel < cols:
170                left_pixel = d_map_left[r, int(c + right_pixel)]
171            else:
172                left_pixel = d_map_left[r, c]
173
174            if right_pixel == left_pixel:
175                consistency_map[r, c] = right_pixel
176            else:
177                consistency_map[r, c] = 0
178
179    sum = 0
180    for r in range(rows):
181        for c in range(cols):
182            if consistency_map[r, c] != 0:
183                sum = sum + (right_ground_truth[r, c] - consistency_map[r, c]) ** 2
184
185    mse_c_right = sum / (rows * cols)
186    return mse_c_right, consistency_map
187
188
189def main():
190    l = load_image('view1.png')
191    r = load_image('view5.png')
192
193
194    # Disparity Maps
195    d_map_lr_3 = disparity_map(l, r, 3, True)
196    show_image('D_Map_lr_block3_', d_map_lr_3)
197
198    d_map_rl_3 = disparity_map(r, l, 3, False)
199    show_image('D_Map_rl_block3_', d_map_rl_3)
200
201    d_map_lr_9 = disparity_map(l, r, 9, True)
202    show_image('D_Map_lr_block9_', d_map_lr_9)
203
204    d_map_rl_9 = disparity_map(r, l, 9, False)
205    show_image('D_Map_rl_block9_', d_map_rl_9)
206
207
208    # Mean Squared Error
209    ground_truth_1 = load_image('disp1.png')
210    ground_truth_2 = load_image('disp5.png')
211
212    mse_3_lr = mean_square_error(d_map_lr_3, ground_truth_1)
213    print('MSE for view1 using block size of 3 is', mse_3_lr)
214
215    mse_3_rl = mean_square_error(d_map_rl_3, ground_truth_2)
216    print('MSE for view5 using block size of 3 is', mse_3_rl)
217
218    mse_9_lr = mean_square_error(d_map_lr_9, ground_truth_1)
219    print('MSE for view1 using block size of 9 is', mse_9_lr)
220
221    mse_9_rl = mean_square_error(d_map_lr_3, ground_truth_2)
222    print('MSE for view5 using block size of 9 is', mse_9_rl)
223
224
225    # MSE after Consistency Check
226    mse_3c_left, c_map_3cl = consistency_map_mse_l(d_map_lr_3, d_map_rl_3, ground_truth_1)
227    cv2.imwrite('consistency_map_block3_view1.jpg', c_map_3cl)
228    print('MSE for view1 after Consistency check using block size of 3 is', mse_3c_left)
229
230    mse_3c_right, c_map_3cr = consistency_map_mse_r(d_map_lr_3, d_map_rl_3, ground_truth_2)
231    cv2.imwrite('consistency_map_block3_view5.jpg', c_map_3cr)
232    print('MSE for view5 after Consistency check using block size of 3 is', mse_3c_right)
233
234    mse_9c_left, c_map_9cl = consistency_map_mse_l(d_map_lr_9, d_map_rl_9, ground_truth_1)
235    cv2.imwrite('consistency_map_block9_view1.jpg', c_map_9cl)
236    print('MSE for view1 after Consistency check using block size of 9 is', mse_9c_left)
237
238    mse_9c_right, c_map_9cr = consistency_map_mse_r(d_map_lr_9, d_map_rl_9, ground_truth_2)
239    cv2.imwrite('consistency_map_block9_view5.jpg', c_map_9cr)
240    print('MSE for view5 after Consistency check using block size of 9 is', mse_9c_right)
241
242    return
243
244
245main()

نقشه‌های ناهمخوانی حاصل:

بازسازی صحنه (Scene Reconstruction) در بینایی ماشین

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

علاوه بر این، ساختن نقشه‌های سه‌بُعدی ساختمان‌ها برای به نمایش گذاشتن «جهان‌های مجازی» (Virtual Worlds) و بازسازی لحظات خاطره‌انگیز از مسافرت یا تعطیلات خانوادگی، از جمله کاربردهای مهم فرایند بازسازی صحنه در سیستم‌های بینایی ماشین محسوب می‌شوند. بازسازی صحنه، سبب ایجاد «فریم‌های اطلاعاتی مرتب» (Ordered Information Frames) در فرمتی آشنا برای انسان‌ها می‌شود (درک تصاویر، فریم‌های ویدئویی و اطلاعات بامعنی موجود در آن‌ها را برای انسان‌ها تسهیل می‌کند).

برای بازسازی صحنه در بینایی ماشین و کاربردهای مرتبط، معمولا دو مدل دوربین (Camera Model) مختلف در نظر گرفته می‌شود؛ «دوربین کالیبره شده» (Calibrated Camera) و «دوربین کالیبره نشده» (Uncalibrated Camera). اصطلاح دوربین کالیبره شده در بینایی ماشین و کاربردهای مرتبط، به دوربینی اطلاق می‌شود که «نقطه کانونی» (Focal Points)، «میدان دید» (Field of View) و «نوع لنز» (Type of Lens) مشخص داشته باشد. در نقطه مقابل، در حوزه بینایی ماشین به دوربین‌هایی که ویژگی‌های مشخصه آن نظیر نقطه کانونی، میدان دید و نوع لنز آن مشخص نیست و تنها از طریق آزمایش هنگام عکس گرفتن می‌توان این دسته از اطلاعات را در مورد آن‌ها کشف کرد، دوربین کالیبره نشده گفته می‌شود. دوربین‌های کالیبره نشده از تکنیک‌هایی به نام «ساختار به وسیله حرکت» (Structure from Motion | SFM) برای استخراج اطلاعات موقعیتی استفاده می‌کنند.

تاکنون تحقیقات و مطالعات زیادی در مورد سیستم‌های بینایی ماشین و بازسازی صحنه منتشر شده است که از دورین‌های کالیبره نشده استفاده کرده‌اند. با این حال مطالعه سیستم‌های بینایی ماشین و بازسازی صحنه که مبتنی بر دوربین‌های کالیبره نشده هستند، از حوزه این مطلب خارج است. در این مطلب، روی بازسازی صحنه با استفاده از دوربین‌های کالیبره شده تمرکز می‌شود. از آنجایی که در دوربین‌های کالیبره شده، نقطه کانونی، میدان دید و نوع لنز مشخص است و همچنین، فاصله و جهت دوربین‌ها نسبت به یکدیگر شناخته شده است، سیستم قادر خواهد بود تا در مرحله «مطابقت دو سویی» (Stereo Correspondence) از محاسبات هندسه Epipolar استفاده کند.

همانطور که پیش از این نیز اشاره شد، از نقشه ناهمخوانی تولید شده در محله مطابقت دو سویی، می‌توان جهت مشخص کردن موقعیت سه‌بُعدی پیکسل استفاده کرد. به پیکسلی که یه موقعیت سه‌بُعدی در فضا را اشغال کرده باشد، «پیکسل حجمی» (Volumetric Pixel) یا Voxel گفته می‌شود. با تصویر کردن (Projecting) هزاران Voxel در فضای اقلیدسی، سیستم بینایی ماشین قادر به بازسازی صحنه‌هایی خواهد بود که توسط انسان‌ها قابل درک هستند. همچنین، صحنه را می‌توان از طریق رنگ کردن Voxel‌ها به وسیله میانگین گرفتن از رنگ پیکسل‌های اصلی (در تصاویر دوبُعدی)، بافت‌زنی (Texturing) کرد.

با این حال در چند دهه اخیر، با توجه به محدودیت‌های موجود در زمینه «ذخیره‌سازی داده» (Data Storage) به صورت فیزیکی، ذخیره‌سازی پیکسل‌ سه‌بُعدی امکان‌پذیر نبود. روش‌های قدیمی‌تر، از طریق انجام عملیات «درون‌نمایی» (Interpolation) در عرض «نواحی محلی» (Local Regions) و تولید کردن «مِش‌های سطحی» (Surface Meshes)، اقدام به کاهش پیچیدگی صحنه‌ها (Scene Complexity) می‌کردند. در چنین حالتی و با استفاده از نمایش‌های هندسی جوش خورده (Fused) به یکدیگر، بسیاری از پیکسل‌های سه‌بُعدی (Voxels)، در یک «مِش تقریب زده شده» (Approximated Mesh) ادغام شده و  از بین می‌روند؛ در نتیجه، پیچیدگی صحنه‌های تولید شده کاهش پیدا می‌کند تا فضای کمتری برای ذخیره‌سازی صحنه‌ها نیاز باشد.

سپس، الگوریتم‌های هموارسازی (Smoothing) روی مِش‌های (Mesh) حاصل اجرا می‌شوند تا اثر نویز در آن‌ها کاهش پیدا کند. بافت صحنه نیز با ترکیب رنگ‌بندی (Coloration) پیکسل‌های سه‌بُعدی همسایه تخمین زده می‌شود. اتخاذ چنین روشی در گذشته، برای ذخیره‌سازی پیکسل‌های سه‌بُعدی، منجر به کاهش دقت داده‌ها و از بین رفتن ویژگی‌های کوچک در تصویر می‌شد.

بسیاری از روش‌های مدرن، رویکرد متفاوتی را اتخاذ می‌کنند و از طریق درون‌یابی اطلاعات «زیرپیکسل‌های سه‌بُعدی» (Sub-Voxel) با استفاده از همسایه‌های محاصره کننده آن‌ها، جزئیات دقیق‌تر و بیشتری را به صحنه‌ها اضافه می‌کنند. در چنین حالتی، صحنه به صورت «اسپارس» (Sparse) توسط پیکسل‌های سه‌بُعدی اشغال می‌شود. در نتیجه، از روش‌‌های «نمایش داده اسپارس» (Sparse Data Representation) برای نمایش پیکسل‌های سه‌بُعدی استفاده می‌شود؛ به غیر از حالات خاصی که در آن‌ها، جهت «بازسازی متراکم صحنه» (Dense Scene Reconstruction)، پیکسل‌های سه‌بُعدی در یک «گرید» (Grid) محصور می‌شوند.

زمانی که موقعیت و جهت دوربین به مقدار اندکی تغییر کرده باشد، جهت کالیبره کردن سریع دوربین، «اطلاعات ویژگی» (Feature Information) نظیر «لبه» (Edge) گوشه (Corner) را می‌توان در صحنه ذخیره کرد. با بهبود دقت (Resolution) دوربین و یا اضافه کردن تصاویر بیشتر به مجموعه داده‌های تهیه شده برای بازسازی صحنه، می‌توان جزئیات موجود در صحنه را بهبود بخشید؛ مانند در دست گرفتن یک شیء ناشناخته و چرخاندن آن در دست، جهت ساختن یک تصویر ذهنی بهتر از ساختار آن.

به چنین روش‌هایی «ساختار به وسیله حرکت» (Structure From Motion | SFM) گفته می‌شود. حرکت دادن دوربین، سبب ایجاد تغییرات اندکی در موقعیت و جهت آن می‌شود و به سیستم اجازه می‌دهد تا تصاویر جدیدی را از صحنه بگیرد. در این جا هدف سیستم بینایی ماشین این است تا مجموعه تصاویر جدید گرفته شده را، با صحنه‌ای که پیش از این توسط مجموعه تصاویر قبلی بازسازی شده بود، ترکیب کند.

برای چنین کاری، در گام اول باید موقعیت و جهت جدید دوربین نسبت به وضعیت قبلی مشخص شود. برای چنین کاری، «ویژگی‌های اسپارس» (Sparse Features) برجسته (لبه‌ها و گوشه‌هایی در تصاویر، که با احتمال بسیار بالا، با یکدیگر مطابقت داده شده‌اند) با یکدیگر مقایسه و موقعیت و جهت جدید آن‌ها «برون‌یابی» (Extrapolate) می‌شود. به محض اینکه پارامترهای جدید دوربین مشخص شدند، می‌توان پیکسل‌های سه‌بُعدی (Voxel) جدید را به صحنه بازسازی شده اضافه کرد. همچنین، با سوار کردن دو دوربین روی یک شاسی، ضبط کردن «جریان‌های ویدئویی» (Video Streams) از هر کدام از این دوربین‌ها و حرکت دادن دوربین‌ها در یک اتاق، می‌توان به «تصاویر دو سویی» (Stereo Images) متوالی دست پیدا کرد.

استنتاج کردن و بیرون آوردن هر کدام از اشیاء موجود در صحنه بازسازی شده، برای انسان کار بسیار ساده‌ای است. در حالی که چنین کاری، فرایندی بسیار پیچیده برای یک سیستم کامپیوتری محسوب می‌شود. یک روش ساده ولی مؤثر برای شناسایی اشیاء موجود در صحنه‌های بازسازی شده، خوشه‌بندی کردن پیکسل‌های سه‌بُعدی (Voxels) بر اساس رنگ‌بندی و فاصله اقلیدسی و نگاشت کردن آن‌ها به اشیاء منحصر به فرد موجود در تصویر است. فاکتورهایی نظیر «تعامل انسانی» (Human Interaction)، «الگوریتم‌های یادگیری» (Learning Algorithms) و یا تجربه، می‌توانند به سیستم بینایی ماشین جهت ایجاد تناظر و ارتباط میان اشیاء کمک کنند (به عنوان نمونه، یک صندلی از پایه و بدنه آن تشکیل شده است و با ترکیب این دو شیء، می‌توان صندلی را در صحنه‌های بازسازی شده شناسایی کرد).

نکته شایان توجه در مورد این دسته از سیستم‌های بینایی ماشین این است که روش‌های بازسازی صحنه، یک فرض اساسی را در سیستم مطرح می‌کنند؛ صحنه‌های موجود در تصاویر، «مانا» (Static | ایستا) هستند. به عبارت دیگر، صحنه میان تصاویر ثابت است و تغییری نمی‌کند. ولی این فرضیه، فرضیه واقع‌گرایانه‌ای برای جهان واقعی نیست. برای غلبه بر تغییرات موجود در صحنه‌ها، می‌توان نرخ نمونه‌گیری (Sampling Rate) دوربین‌ها را افزایش داد و به طور منظم و دوره‌ای، صحنه‌ای که در آن پیکسل‌های سه‌بُعدی (Voxels) تغییر می‌کنند را دور انداخت.

نرخ نمونه‌گیری دوربین‌ها به این دلیل افزایش داده می‌شود که هر چقدر نرخ تغییرات میان صحنه‌ها (در واحد زمان) به صفر نزدیک می‌شود، نرخ تغییرات در حرکت اشیاء (میان صحنه‌ها) نیز به صفر همگرا می‌شود؛ در نتیجه، یک صحنه استاتیک آنی (Instantaneous Static Scene) تشکیل می‌شود.

پیاده‌سازی سیستم بازسازی صحنه در پایتون‌

با استفاده از قطعه کد زیر، تصاویر رنگی (Color Images) و تصاویر عمق (Depth Images) با یکدیگر ترکیب می‌شوند تا صحنه (Scene) سه‌بُعدی متناظر با این تصاویر بازسازی یا ساخته شود. در این قطعه کد، 1000 تصویر (متشکل از تصاویر RGB و تصاویر عمق متناظر آن‌ها) از مجموعه داده 7scenes انتخاب شده‌اند. صحنه بازسازی شده، یک صحنه متشکل از 405x267x289 پیکسل سه‌بُعدی است. داده‌های مورد استفاده از طریق لینک [+] قابل دسترسی هستند.

کدهای پیاده‌سازی سیستم بازسازی صحنه در پایتون‌:

1import numpy as np
2from skimage import measure
3try:
4    import pycuda.driver as cuda
5    import pycuda.autoinit
6    from pycuda.compiler import SourceModule
7    FUSION_GPU_MODE = 1
8except Exception as err:
9    print('Warning: %s'%(str(err)))
10    print('Failed to import PyCUDA. Running fusion in CPU mode.')
11    FUSION_GPU_MODE = 0
12
13
14class TSDFVolume(object):
15
16    def __init__(self,vol_bnds,voxel_size):
17
18        # Define voxel volume parameters
19        self._vol_bnds = vol_bnds # rows: x,y,z columns: min,max in world coordinates in meters
20        self._voxel_size = voxel_size # in meters (determines volume discretization and resolution)
21        self._trunc_margin = self._voxel_size*5 # truncation on SDF
22
23        # Adjust volume bounds
24        self._vol_dim = np.ceil((self._vol_bnds[:,1]-self._vol_bnds[:,0])/self._voxel_size).copy(order='C').astype(int) # ensure C-order contigous
25        self._vol_bnds[:,1] = self._vol_bnds[:,0]+self._vol_dim*self._voxel_size
26        self._vol_origin = self._vol_bnds[:,0].copy(order='C').astype(np.float32) # ensure C-order contigous
27        print("Voxel volume size: %d x %d x %d"%(self._vol_dim[0],self._vol_dim[1],self._vol_dim[2]))
28
29        # Initialize pointers to voxel volume in CPU memory
30        self._tsdf_vol_cpu = np.ones(self._vol_dim).astype(np.float32)
31        self._weight_vol_cpu = np.zeros(self._vol_dim).astype(np.float32) # for computing the cumulative moving average of observations per voxel
32        self._color_vol_cpu = np.zeros(self._vol_dim).astype(np.float32)
33
34        # Copy voxel volumes to GPU
35        if FUSION_GPU_MODE:
36            self._tsdf_vol_gpu = cuda.mem_alloc(self._tsdf_vol_cpu.nbytes)
37            cuda.memcpy_htod(self._tsdf_vol_gpu,self._tsdf_vol_cpu)
38            self._weight_vol_gpu = cuda.mem_alloc(self._weight_vol_cpu.nbytes)
39            cuda.memcpy_htod(self._weight_vol_gpu,self._weight_vol_cpu)
40            self._color_vol_gpu = cuda.mem_alloc(self._color_vol_cpu.nbytes)
41            cuda.memcpy_htod(self._color_vol_gpu,self._color_vol_cpu)
42
43            # Cuda kernel function (C++)
44            self._cuda_src_mod = SourceModule("""
45              __global__ void integrate(float * tsdf_vol,
46                                        float * weight_vol,
47                                        float * color_vol,
48                                        float * vol_dim,
49                                        float * vol_origin,
50                                        float * cam_intr,
51                                        float * cam_pose,
52                                        float * other_params,
53                                        float * color_im,
54                                        float * depth_im) {
55                // Get voxel index
56                int gpu_loop_idx = (int) other_params[0];
57                int max_threads_per_block = blockDim.x;
58                int block_idx = blockIdx.z*gridDim.y*gridDim.x+blockIdx.y*gridDim.x+blockIdx.x;
59                int voxel_idx = gpu_loop_idx*gridDim.x*gridDim.y*gridDim.z*max_threads_per_block+block_idx*max_threads_per_block+threadIdx.x;
60                
61                int vol_dim_x = (int) vol_dim[0];
62                int vol_dim_y = (int) vol_dim[1];
63                int vol_dim_z = (int) vol_dim[2];
64                if (voxel_idx > vol_dim_x*vol_dim_y*vol_dim_z)
65                    return;
66                // Get voxel grid coordinates (note: be careful when casting)
67                float voxel_x = floorf(((float)voxel_idx)/((float)(vol_dim_y*vol_dim_z)));
68                float voxel_y = floorf(((float)(voxel_idx-((int)voxel_x)*vol_dim_y*vol_dim_z))/((float)vol_dim_z));
69                float voxel_z = (float)(voxel_idx-((int)voxel_x)*vol_dim_y*vol_dim_z-((int)voxel_y)*vol_dim_z);
70                // Voxel grid coordinates to world coordinates
71                float voxel_size = other_params[1];
72                float pt_x = vol_origin[0]+voxel_x*voxel_size;
73                float pt_y = vol_origin[1]+voxel_y*voxel_size;
74                float pt_z = vol_origin[2]+voxel_z*voxel_size;
75                // World coordinates to camera coordinates
76                float tmp_pt_x = pt_x-cam_pose[0*4+3];
77                float tmp_pt_y = pt_y-cam_pose[1*4+3];
78                float tmp_pt_z = pt_z-cam_pose[2*4+3];
79                float cam_pt_x = cam_pose[0*4+0]*tmp_pt_x+cam_pose[1*4+0]*tmp_pt_y+cam_pose[2*4+0]*tmp_pt_z;
80                float cam_pt_y = cam_pose[0*4+1]*tmp_pt_x+cam_pose[1*4+1]*tmp_pt_y+cam_pose[2*4+1]*tmp_pt_z;
81                float cam_pt_z = cam_pose[0*4+2]*tmp_pt_x+cam_pose[1*4+2]*tmp_pt_y+cam_pose[2*4+2]*tmp_pt_z;
82                // Camera coordinates to image pixels
83                int pixel_x = (int) roundf(cam_intr[0*3+0]*(cam_pt_x/cam_pt_z)+cam_intr[0*3+2]);
84                int pixel_y = (int) roundf(cam_intr[1*3+1]*(cam_pt_y/cam_pt_z)+cam_intr[1*3+2]);
85                // Skip if outside view frustum
86                int im_h = (int) other_params[2];
87                int im_w = (int) other_params[3];
88                if (pixel_x < 0 || pixel_x >= im_w || pixel_y < 0 || pixel_y >= im_h || cam_pt_z<0)
89                    return;
90                // Skip invalid depth
91                float depth_value = depth_im[pixel_y*im_w+pixel_x];
92                if (depth_value == 0)
93                    return;
94                // Integrate TSDF
95                float trunc_margin = other_params[4];
96                float depth_diff = depth_value-cam_pt_z;
97                if (depth_diff < -trunc_margin)
98                    return;
99                float dist = fmin(1.0f,depth_diff/trunc_margin);
100                float w_old = weight_vol[voxel_idx];
101                float obs_weight = other_params[5];
102                float w_new = w_old + obs_weight;
103                weight_vol[voxel_idx] = w_new;
104                tsdf_vol[voxel_idx] = (tsdf_vol[voxel_idx]*w_old+dist)/w_new;
105                // Integrate color
106                float old_color = color_vol[voxel_idx];
107                float old_b = floorf(old_color/(256*256));
108                float old_g = floorf((old_color-old_b*256*256)/256);
109                float old_r = old_color-old_b*256*256-old_g*256;
110                float new_color = color_im[pixel_y*im_w+pixel_x];
111                float new_b = floorf(new_color/(256*256));
112                float new_g = floorf((new_color-new_b*256*256)/256);
113                float new_r = new_color-new_b*256*256-new_g*256;
114                new_b = fmin(roundf((old_b*w_old+new_b)/w_new),255.0f);
115                new_g = fmin(roundf((old_g*w_old+new_g)/w_new),255.0f);
116                new_r = fmin(roundf((old_r*w_old+new_r)/w_new),255.0f);
117                color_vol[voxel_idx] = new_b*256*256+new_g*256+new_r;
118              }""")
119
120            self._cuda_integrate = self._cuda_src_mod.get_function("integrate")
121
122            # Determine block/grid size on GPU
123            gpu_dev = cuda.Device(0)
124            self._max_gpu_threads_per_block = gpu_dev.MAX_THREADS_PER_BLOCK
125            n_blocks = int(np.ceil(float(np.prod(self._vol_dim))/float(self._max_gpu_threads_per_block)))
126            grid_dim_x = min(gpu_dev.MAX_GRID_DIM_X,int(np.floor(np.cbrt(n_blocks))))
127            grid_dim_y = min(gpu_dev.MAX_GRID_DIM_Y,int(np.floor(np.sqrt(n_blocks/grid_dim_x))))
128            grid_dim_z = min(gpu_dev.MAX_GRID_DIM_Z,int(np.ceil(float(n_blocks)/float(grid_dim_x*grid_dim_y))))
129            self._max_gpu_grid_dim = np.array([grid_dim_x,grid_dim_y,grid_dim_z]).astype(int)
130            self._n_gpu_loops = int(np.ceil(float(np.prod(self._vol_dim))/float(np.prod(self._max_gpu_grid_dim)*self._max_gpu_threads_per_block)))
131
132
133   
134    
135    #         if new_bnds[dim,1] > self._vol_bnds[dim,1]: # expand upper bounds
136    #             n_voxels_expand = int(np.ceil((new_bnds[dim,1]-self._vol_bnds[dim,1])/self._voxel_size))
137    #             new_chunk_size = np.round((self._vol_bnds[:,1]-self._vol_bnds[:,0])/self._voxel_size).astype(int)
138    #             new_chunk_size[dim] = n_voxels_expand # size of expanding region (i.e. chunk)
139                
140    #             # Initialize chunks and concatenate to current voxel volume
141    #             self._tsdf_vol_cpu = np.concatenate((self._tsdf_vol_cpu,np.ones(new_chunk_size)),axis=dim)
142    #             self._weight_vol_cpu = np.concatenate((self._weight_vol_cpu,np.zeros(new_chunk_size)),axis=dim)
143    #             self._color_vol_cpu = np.concatenate((self._color_vol_cpu,np.zeros(new_chunk_size)),axis=dim)
144    #             self._vol_bnds[dim,1] += n_voxels_expand*self._voxel_size # update voxel volume bounds
145
146
147    def integrate(self,color_im,depth_im,cam_intr,cam_pose,obs_weight=1.):
148        im_h = depth_im.shape[0]
149        im_w = depth_im.shape[1]
150
151        # Fold RGB color image into a single channel image
152        color_im = color_im.astype(np.float32)
153        color_im = np.floor(color_im[:,:,2]*256*256+color_im[:,:,1]*256+color_im[:,:,0])
154
155        # GPU mode: integrate voxel volume (calls CUDA kernel)
156        if FUSION_GPU_MODE:
157            for gpu_loop_idx in range(self._n_gpu_loops):
158                self._cuda_integrate(self._tsdf_vol_gpu,
159                                     self._weight_vol_gpu,
160                                     self._color_vol_gpu,
161                                     cuda.InOut(self._vol_dim.astype(np.float32)),
162                                     cuda.InOut(self._vol_origin.astype(np.float32)),
163                                     cuda.InOut(cam_intr.reshape(-1).astype(np.float32)),
164                                     cuda.InOut(cam_pose.reshape(-1).astype(np.float32)),
165                                     cuda.InOut(np.asarray([gpu_loop_idx,self._voxel_size,im_h,im_w,self._trunc_margin,obs_weight],np.float32)),
166                                     cuda.InOut(color_im.reshape(-1).astype(np.float32)),
167                                     cuda.InOut(depth_im.reshape(-1).astype(np.float32)),
168                                     block=(self._max_gpu_threads_per_block,1,1),grid=(int(self._max_gpu_grid_dim[0]),int(self._max_gpu_grid_dim[1]),int(self._max_gpu_grid_dim[2])))
169
170        # CPU mode: integrate voxel volume (vectorized implementation)
171        else:
172
173            # Get voxel grid coordinates
174            xv,yv,zv = np.meshgrid(range(self._vol_dim[0]),range(self._vol_dim[1]),range(self._vol_dim[2]),indexing='ij')
175            vox_coords = np.concatenate((xv.reshape(1,-1),yv.reshape(1,-1),zv.reshape(1,-1)),axis=0).astype(int)
176
177            # Voxel coordinates to world coordinates
178            world_pts = self._vol_origin.reshape(-1,1)+vox_coords.astype(float)*self._voxel_size
179
180            # World coordinates to camera coordinates
181            world2cam = np.linalg.inv(cam_pose)
182            cam_pts = np.dot(world2cam[:3,:3],world_pts)+np.tile(world2cam[:3,3].reshape(3,1),(1,world_pts.shape[1]))
183
184            # Camera coordinates to image pixels
185            pix_x = np.round(cam_intr[0,0]*(cam_pts[0,:]/cam_pts[2,:])+cam_intr[0,2]).astype(int)
186            pix_y = np.round(cam_intr[1,1]*(cam_pts[1,:]/cam_pts[2,:])+cam_intr[1,2]).astype(int)
187
188            # Skip if outside view frustum
189            valid_pix = np.logical_and(pix_x >= 0,
190                        np.logical_and(pix_x < im_w,
191                        np.logical_and(pix_y >= 0,
192                        np.logical_and(pix_y < im_h,
193                                       cam_pts[2,:] > 0))))
194
195            depth_val = np.zeros(pix_x.shape)
196            depth_val[valid_pix] = depth_im[pix_y[valid_pix],pix_x[valid_pix]]
197
198            # Integrate TSDF
199            depth_diff = depth_val-cam_pts[2,:]
200            valid_pts = np.logical_and(depth_val > 0,depth_diff >= -self._trunc_margin)
201            dist = np.minimum(1.,np.divide(depth_diff,self._trunc_margin))
202            w_old = self._weight_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]]
203            w_new = w_old + obs_weight
204            self._weight_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]] = w_new
205            tsdf_vals = self._tsdf_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]]
206            self._tsdf_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]] = np.divide(np.multiply(tsdf_vals,w_old)+dist[valid_pts],w_new)
207
208            # Integrate color
209            old_color = self._color_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]]
210            old_b = np.floor(old_color/(256.*256.))
211            old_g = np.floor((old_color-old_b*256.*256.)/256.)
212            old_r = old_color-old_b*256.*256.-old_g*256.
213            new_color = color_im[pix_y[valid_pts],pix_x[valid_pts]]
214            new_b = np.floor(new_color/(256.*256.))
215            new_g = np.floor((new_color-new_b*256.*256.)/256.)
216            new_r = new_color-new_b*256.*256.-new_g*256.
217            new_b = np.minimum(np.round(np.divide(np.multiply(old_b,w_old)+new_b,w_new)),255.);
218            new_g = np.minimum(np.round(np.divide(np.multiply(old_g,w_old)+new_g,w_new)),255.);
219            new_r = np.minimum(np.round(np.divide(np.multiply(old_r,w_old)+new_r,w_new)),255.);
220            self._color_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]] = new_b*256.*256.+new_g*256.+new_r;
221
222
223    # Copy voxel volume to CPU
224    def get_volume(self):
225        if FUSION_GPU_MODE:
226            cuda.memcpy_dtoh(self._tsdf_vol_cpu,self._tsdf_vol_gpu)
227            cuda.memcpy_dtoh(self._color_vol_cpu,self._color_vol_gpu)
228        return self._tsdf_vol_cpu,self._color_vol_cpu
229
230
231    # Get mesh of voxel volume via marching cubes
232    def get_mesh(self):
233        tsdf_vol,color_vol = self.get_volume()
234
235        # Marching cubes
236        verts,faces,norms,vals = measure.marching_cubes_lewiner(tsdf_vol,level=0)
237        verts_ind = np.round(verts).astype(int)
238        verts = verts*self._voxel_size+self._vol_origin # voxel grid coordinates to world coordinates
239
240        # Get vertex colors
241        rgb_vals = color_vol[verts_ind[:,0],verts_ind[:,1],verts_ind[:,2]]
242        colors_b = np.floor(rgb_vals/(256*256))
243        colors_g = np.floor((rgb_vals-colors_b*256*256)/256)
244        colors_r = rgb_vals-colors_b*256*256-colors_g*256
245        colors = np.floor(np.asarray([colors_r,colors_g,colors_b])).T
246        colors = colors.astype(np.uint8)
247        return verts,faces,norms,colors
248
249
250# -------------------------------------------------------------------------------
251# Additional helper functions
252
253
254# Get corners of 3D camera view frustum of depth image
255def get_view_frustum(depth_im,cam_intr,cam_pose):
256    im_h = depth_im.shape[0]
257    im_w = depth_im.shape[1]
258    max_depth = np.max(depth_im)
259    view_frust_pts = np.array([(np.array([0,0,0,im_w,im_w])-cam_intr[0,2])*np.array([0,max_depth,max_depth,max_depth,max_depth])/cam_intr[0,0],
260                               (np.array([0,0,im_h,0,im_h])-cam_intr[1,2])*np.array([0,max_depth,max_depth,max_depth,max_depth])/cam_intr[1,1],
261                                np.array([0,max_depth,max_depth,max_depth,max_depth])])
262    view_frust_pts = np.dot(cam_pose[:3,:3],view_frust_pts)+np.tile(cam_pose[:3,3].reshape(3,1),(1,view_frust_pts.shape[1])) # from camera to world coordinates
263    return view_frust_pts
264
265
266# Save 3D mesh to a polygon .ply file
267def meshwrite(filename,verts,faces,norms,colors):
268
269    # Write header
270    ply_file = open(filename,'w')
271    ply_file.write("ply\n")
272    ply_file.write("format ascii 1.0\n")
273    ply_file.write("element vertex %d\n"%(verts.shape[0]))
274    ply_file.write("property float x\n")
275    ply_file.write("property float y\n")
276    ply_file.write("property float z\n")
277    ply_file.write("property float nx\n")
278    ply_file.write("property float ny\n")
279    ply_file.write("property float nz\n")
280    ply_file.write("property uchar red\n")
281    ply_file.write("property uchar green\n")
282    ply_file.write("property uchar blue\n")
283    ply_file.write("element face %d\n"%(faces.shape[0]))
284    ply_file.write("property list uchar int vertex_index\n")
285    ply_file.write("end_header\n")
286
287    # Write vertex list
288    for i in range(verts.shape[0]):
289        ply_file.write("%f %f %f %f %f %f %d %d %d\n"%(verts[i,0],verts[i,1],verts[i,2],norms[i,0],norms[i,1],norms[i,2],colors[i,0],colors[i,1],colors[i,2]))
290    
291    # Write face list
292    for i in range(faces.shape[0]):
293        ply_file.write("3 %d %d %d\n"%(faces[i,0],faces[i,1],faces[i,2]))
294
295    ply_file.close()

فایل اجرایی:

1import numpy as np
2import cv2
3import time
4import fusion
5
6
7# (Optional) sample code to compute 3D bounds (in world coordinates) around convex hull of all camera view frustums in dataset
8print("Estimating voxel volume bounds...")
9n_imgs = 1000
10cam_intr = np.loadtxt("data/camera-intrinsics.txt",delimiter=' ')
11vol_bnds = np.zeros((3,2))
12for i in range(n_imgs):
13
14    # Read depth image and camera pose
15    depth_im = cv2.imread("data/frame-%06d.depth.png"%(i),-1).astype(float)/1000. # depth is saved in 16-bit PNG in millimeters
16    depth_im[depth_im == 65.535] = 0 # set invalid depth to 0 (specific to 7-scenes dataset)
17    cam_pose = np.loadtxt("data/frame-%06d.pose.txt"%(i)) # 4x4 rigid transformation matrix
18
19    # Compute camera view frustum and extend convex hull
20    view_frust_pts = fusion.get_view_frustum(depth_im,cam_intr,cam_pose)
21    vol_bnds[:,0] = np.minimum(vol_bnds[:,0],np.amin(view_frust_pts,axis=1))
22    vol_bnds[:,1] = np.maximum(vol_bnds[:,1],np.amax(view_frust_pts,axis=1))
23
24# ---------------------------------------------------------------------
25
26# Initialize voxel volume
27print("Initializing voxel volume...")
28tsdf_vol = fusion.TSDFVolume(vol_bnds,voxel_size=0.02)
29
30# Loop through RGB-D images and fuse them together
31t0_elapse = time.time()
32for i in range(n_imgs):
33    print("Fusing frame %d/%d"%(i+1,n_imgs))
34
35    # Read RGB-D image and camera pose
36    color_image = cv2.cvtColor(cv2.imread("data/frame-%06d.color.jpg"%(i)),cv2.COLOR_BGR2RGB)
37    depth_im = cv2.imread("data/frame-%06d.depth.png"%(i),-1).astype(float)/1000. # depth is saved in 16-bit PNG in millimeters
38    depth_im[depth_im == 65.535] = 0 # set invalid depth to 0 (specific to 7-scenes dataset)
39    cam_pose = np.loadtxt("data/frame-%06d.pose.txt"%(i)) # 4x4 rigid transformation matrix
40
41    # Integrate observation into voxel volume (assume color aligned with depth)
42    tsdf_vol.integrate(color_image,depth_im,cam_intr,cam_pose,obs_weight=1.)
43
44fps = n_imgs/(time.time()-t0_elapse)
45print("Average FPS: %.2f"%(fps))
46
47# Get mesh from voxel volume and save to disk (can be viewed with Meshlab)
48print("Saving to mesh.ply...")
49verts,faces,norms,colors = tsdf_vol.get_mesh()
50fusion.meshwrite("mesh.ply",verts,faces,norms,colors)

خروجی حاصل:

مثال دوم: قطعه کد زیر جهت بازسازی سه‌بُعدی هر صحنه (یا شیء (Object)) از روی دو تصویر یا همان تصاویر دو سویی (Stereo Images) در زبان پایتون مورد استفاده قرار می‌گیرد. تصاویر استفاده شده در این مثال، از طریق لینک‌های [+] و [+] قابل دسترسی هستند.

1import numpy as np
2import cv2
3import time
4
5SHOW_STEREOMATCHING_DISPARITY_IMG           = True
6
7STEREOMATCHING_SGBM_MIN_DISPARITY           = 0
8    # normally, it's 0 but sometimes rectification algo can shift images, so this param needs to be adjusted accordingly
9STEREOMATCHING_SGBM_N_DISPARITIES			= 512#1024
10    # in the current implementation, this param must be divisible by 16
11STEREOMATCHING_SGBM_MATCHING_WIN_SIZE		= 19
12    # it must be an odd number >= 1. Normally, it should be somewhere in the 3..11 range
13STEREOMATCHING_SGBM_DISPARITY_SMOOTH_P1		= 0		# Thuy check: max 19								# FIXED PARAM
14    # control disparity smoothness. Larger values -> smoother disparity.
15    # P1 is the penalty on disparity change by + or - 1 between neighbor pixels
16STEREOMATCHING_SGBM_DISPARITY_SMOOTH_P2		= 0		# Thuy check: max 95								# FIXED PARAM
17    # control disparity smoothness. Is the penalty on disparity change by more than 1 between neighbor pixels. P2 > P1
18STEREOMATCHING_SGBM_MAX_DISPARITY_CHECK		= 0															# FIXED PARAM
19    # max allowed diff (in pixel) in the left-right disparity check. Set it to a non-positive value to disable the check
20STEREOMATCHING_SGBM_PREFILTER_CAP			= 0															# FIXED PARAM
21    # truncation value for the prefiltered image pixels. Algo first computes x-derivative at each pixel & clips its
22    # value by [-preFilterCap, preFilterCap] interval. Result values are passed to Birchfield-Tomasi pixel cost func
23STEREOMATCHING_SGBM_UNIQUENESS_RATIO		= 15	# good range: 5 -> 15								# FIXED PARAM
24    # margin in percent by which the best (min) computed cost func value should 'win' the 2nd best value to consider
25    # the found match correct. A value within the 5-15 range is good enough
26STEREOMATCHING_SGBM_SPECKLE_WIN_SIZE		= 200	# 0 to disable. Otherwise, good range: 50 -> 200	# FIXED PARAM
27    # max size of smooth disparity regions to consider their noise speckles and invalidate. Set to 0 to disable
28    # speckle filtering. Otherwise, set in the 50-200 range
29STEREOMATCHING_SGBM_SPECKLE_RANGE   		= 2		# good range: 1 -> 2								# FIXED PARAM
30    # max disparity variation within each connected component. If do speckle filtering, set the param to a positive
31    # value, it'll be implicitly x 16. Normally, 1 or 2 is good enough
32STEREOMATCHING_SGBM_MODE					= cv2.STEREO_SGBM_MODE_SGBM_3WAY
33    # STEREO_SGBM_MODE_HH to run the full-scale 2-pass dynamic programming algo. It consumes O(W*H*numDisparities)
34    #       bytes (large for 640x480 and huge for HD-size). Mutual info cost func is not implemented. Instead, a simpler
35    #       Birchfield-Tomasi sub-pixel metric is used. Though, COLOR images are supported as well.
36    # STEREO_SGBM_MODE_SGBM: does not support COLOR images
37    # STEREO_SGBM_MODE_SGBM_3WAY: 2-3 times faster than MODE_SGBM with minimal degradation in quality and uses universal
38    #       HAL intrinsics. Does not support COLOR images
39	# Pre- & post- processing steps from K. Konolige algorithm StereoBM::operator() are included, for example:
40    #       pre-filtering (CV_STEREO_BM_XSOBEL type) and post-filtering (uniqueness check, quadratic interpolation and
41    #       speckle filtering)
42
43STEREOMATCHING_PLY_FILTER_RANGE_Z_FROM		= 0
44STEREOMATCHING_PLY_FILTER_RANGE_Z_TO		= 6
45STEREOMATCHING_PLY_FILTER_RANGE_XY			= 5000														# FIXED PARAM
46STEREOMATCHING_PLY_FILTER_BLACK_COLOR_THR	= 30
47
48
49# ************************************************************************************************
50# **********                 FUNCTIONS: DISPLAY                                         **********
51# ************************************************************************************************
52
53def ShowDisparityImageSGBM(disparityImg):
54    disImg = ResizeImage(disparityImg, 0.25)
55
56    # ref: https://rdmilligan.wordpress.com/2016/05/23/disparity-of-stereo-images-with-python-and-opencv/
57    disImg = disImg.astype(np.float32) / 16.0
58    disImg = (disImg - STEREOMATCHING_SGBM_MIN_DISPARITY) / STEREOMATCHING_SGBM_N_DISPARITIES
59    cv2.imshow('Disparity image SGBM', disImg)
60    cv2.waitKey(0)
61    return;
62
63
64# ************************************************************************************************
65# **********                 FUNCTIONS: COMMON                                          **********
66# ************************************************************************************************
67
68def ResizeImage(img, scale):
69    imgResized = cv2.resize(src=img, dsize=(0,0), fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR)
70    return imgResized;
71
72
73def RotateImage(srcImg, angleToRotate):
74    height, width = srcImg.shape[:2]
75    maxLen = max(height, width)
76    rotationMat = cv2.getRotationMatrix2D(center=(maxLen/2,maxLen/2), angle=angleToRotate, scale=1.0)
77    desImg = cv2.warpAffine(src=srcImg, M=rotationMat, dsize=(maxLen, maxLen))
78    return desImg;
79
80
81def RotateLeftAndRightImagesAccordingToOurSystem(leftImg, rightImg):
82    height, width = leftImg.shape[:2]
83    leftImg90 = RotateImage(leftImg, 90)     # 1. rotate left img 90-deg counter-clockwise (left direction)
84    rightImg270 = RotateImage(rightImg, 270) # 2. rotate right img 90-deg clockwise (right direction)
85                                             # cuz img is horizontal, but EXIF changes according to camera gravity
86    leftImgRotated = leftImg90[0:width, 0:height]              # 3. crop left img
87    rightImgRotated = rightImg270[0:width, width-height:width] # 4. crop right img
88                                    # ref (extract ROI): http://docs.opencv.org/3.2.0/d3/df2/tutorial_py_basic_ops.html
89    return leftImgRotated, rightImgRotated;
90
91
92def ConvertLeftAndRightImagesToGray(leftImg, rightImg):
93    leftImgGray = cv2.cvtColor(src=leftImg, code=cv2.COLOR_RGB2GRAY)
94    rightImgGray = cv2.cvtColor(src=rightImg, code=cv2.COLOR_RGB2GRAY)
95    return leftImgGray, rightImgGray;
96
97
98# ************************************************************************************************
99# **********                 FUNCTIONS: 3D RECONSTRUCTION                               **********
100# ************************************************************************************************
101
102def LoadCalibResult(calibFilename):
103    fs = cv2.FileStorage(source=calibFilename, flags=cv2.FILE_STORAGE_READ)
104    intrinsic  = fs.getNode('intrinsic').mat() # ref: https://github.com/opencv/opencv_contrib/issues/834
105    distortion = fs.getNode('distortion').mat()
106    rotation   = fs.getNode('rotation').mat()
107    projection = fs.getNode('projection').mat()
108    Q          = fs.getNode('Q').mat()
109    return intrinsic, distortion, rotation, projection, Q;
110
111
112def RectifyLeftAndRightImagesUsingCalibMatrices(leftImg, rightImg, \
113                                                leftIntrinsic, leftDistortion, leftRotation, leftProjection, \
114                                                rightIntrinsic, rightDistortion, rightRotation, rightProjection):
115    height, width = leftImg.shape[:2]
116
117    # precompute maps for cvRemap()
118    leftMapX, leftMapY = cv2.initUndistortRectifyMap(cameraMatrix=leftIntrinsic, distCoeffs=leftDistortion, \
119                            R=leftRotation, newCameraMatrix=leftProjection, size=(width,height), m1type=cv2.CV_16SC2)
120    rightMapX, rightMapY = cv2.initUndistortRectifyMap(cameraMatrix=rightIntrinsic, distCoeffs=rightDistortion, \
121                            R=rightRotation, newCameraMatrix=rightProjection, size=(width, height), m1type=cv2.CV_16SC2)
122                    # check for detail: http://docs.opencv.org/2.4.8/modules/imgproc/doc/geometric_transformations.html
123                    #                   http://docs.opencv.org/3.2.0/dc/dbb/tutorial_py_calibration.html
124    # rectify 2 images using maps
125    leftImgRectified = cv2.remap(src=leftImg, map1=leftMapX, map2=leftMapY, interpolation=cv2.INTER_LINEAR)
126    rightImgRectified = cv2.remap(src=rightImg, map1=rightMapX, map2=rightMapY, interpolation=cv2.INTER_LINEAR)
127    return leftImgRectified, rightImgRectified;
128
129
130def StereoMatching(leftImg, rightImg):
131    stereo = cv2.StereoSGBM_create(minDisparity     = STEREOMATCHING_SGBM_MIN_DISPARITY, \
132                                   numDisparities   = STEREOMATCHING_SGBM_N_DISPARITIES, \
133                                   blockSize        = STEREOMATCHING_SGBM_MATCHING_WIN_SIZE, \
134                                   P1               = STEREOMATCHING_SGBM_DISPARITY_SMOOTH_P1, \
135                                   P2               = STEREOMATCHING_SGBM_DISPARITY_SMOOTH_P2, \
136                                   disp12MaxDiff    = STEREOMATCHING_SGBM_MAX_DISPARITY_CHECK, \
137                                   preFilterCap     = STEREOMATCHING_SGBM_PREFILTER_CAP, \
138                                   uniquenessRatio  = STEREOMATCHING_SGBM_UNIQUENESS_RATIO, \
139                                   speckleWindowSize= STEREOMATCHING_SGBM_SPECKLE_WIN_SIZE, \
140                                   speckleRange     = STEREOMATCHING_SGBM_SPECKLE_RANGE, \
141                                   mode             = STEREOMATCHING_SGBM_MODE)
142    disparityImg = stereo.compute(leftImg, rightImg)#.astype(np.float32) / 16.0  # is 16-bit signed single-channel
143    return disparityImg;
144
145
146def StereoMatchingWithCalibFiles(leftImg, rightImg, leftCalibFilename, rightCalibFilename):
147    # load calibration files
148    leftIntrinsic, leftDistortion, leftRotation, leftProjection, leftQ = LoadCalibResult(leftCalibFilename)
149    rightIntrinsic, rightDistortion, rightRotation, rightProjection, rightQ = LoadCalibResult(rightCalibFilename)
150
151    # rotate, rectify, convert-to-gray the left and right images
152    leftImgRotated, rightImgRotated = RotateLeftAndRightImagesAccordingToOurSystem(leftImg, rightImg)
153    leftImgRectified, rightImgRectified = RectifyLeftAndRightImagesUsingCalibMatrices(leftImgRotated, rightImgRotated, \
154                                                      leftIntrinsic, leftDistortion, leftRotation, leftProjection, \
155                                                      rightIntrinsic, rightDistortion, rightRotation, rightProjection)
156    leftImgRectifiedGray, rightImgRectifiedGray = ConvertLeftAndRightImagesToGray(leftImgRectified, rightImgRectified)
157
158    # stereo matching
159    disparityImg = StereoMatching(leftImgRectifiedGray, rightImgRectifiedGray)
160    return disparityImg, leftImgRectified, leftQ;
161
162
163def CheckPlyFileExportCondition(disparityX, disparityY, disparityZ, pixelB, pixelG, pixelR):
164    # position conditions
165    condPosX = (-STEREOMATCHING_PLY_FILTER_RANGE_XY < disparityX and disparityX < STEREOMATCHING_PLY_FILTER_RANGE_XY)
166    condPosY = (-STEREOMATCHING_PLY_FILTER_RANGE_XY < disparityY and disparityY < STEREOMATCHING_PLY_FILTER_RANGE_XY)
167    condPosZ = (STEREOMATCHING_PLY_FILTER_RANGE_Z_FROM<disparityZ and disparityZ<STEREOMATCHING_PLY_FILTER_RANGE_Z_TO)
168
169    # color condition
170    condColorB = (STEREOMATCHING_PLY_FILTER_BLACK_COLOR_THR < pixelB)
171    condColorG = (STEREOMATCHING_PLY_FILTER_BLACK_COLOR_THR < pixelG)
172    condColorR = (STEREOMATCHING_PLY_FILTER_BLACK_COLOR_THR < pixelR)
173
174    return (condPosX and condPosY and condPosZ and condColorB and condColorG and condColorR);
175
176
177def SaveWorldImageToPLY(worldImg, leftImg, plyFilename):
178    worldImgHeight, worldImgWidth = worldImg.shape[:2]
179
180    with open(plyFilename, 'w') as f: # write to a new file (NOT APPEND)
181        f.write('ply\n')
182        f.write('format ascii 1.0\n')
183
184        # count possible display points
185        count = 0
186        for i in range(0, worldImgHeight):
187            for j in range(0, worldImgWidth):
188                disparityX = worldImg.item(i, j, 0) # to access all B,G,R values --> call .item() separately for all
189                disparityY = worldImg.item(i, j, 1) # http://docs.opencv.org/3.2.0/d3/df2/tutorial_py_basic_ops.html
190                disparityZ = worldImg.item(i, j, 2)
191                pixelB = leftImg.item(i, j, 0)
192                pixelG = leftImg.item(i, j, 1)
193                pixelR = leftImg.item(i, j, 2)
194                if CheckPlyFileExportCondition(disparityX, disparityY, disparityZ, pixelB, pixelG, pixelR):
195                    count = count + 1
196        f.write('element vertex ' + str(count) + '\n')
197
198        # other headers
199        f.write('property float x\n')
200        f.write('property float y\n')
201        f.write('property float z\n')
202        f.write('property uchar red\n')
203        f.write('property uchar green\n')
204        f.write('property uchar blue\n')
205        f.write('end_header\n')
206
207        # write points
208        for i in range(0, worldImgHeight):
209            for j in range(0, worldImgWidth):
210                disparityX = worldImg.item(i, j, 0)  # to access all B,G,R values --> call .item() separately for all
211                disparityY = worldImg.item(i, j, 1)  # http://docs.opencv.org/3.2.0/d3/df2/tutorial_py_basic_ops.html
212                disparityZ = worldImg.item(i, j, 2)
213                pixelB = leftImg.item(i, j, 0)
214                pixelG = leftImg.item(i, j, 1)
215                pixelR = leftImg.item(i, j, 2)
216                if CheckPlyFileExportCondition(disparityX, disparityY, disparityZ, pixelB, pixelG, pixelR):
217                    f.write(format(disparityX,'.4f') + ' ' + format(disparityY,'.4f') + ' ' + format(disparityZ,'.4f') \
218                            + ' ' + str(pixelR) + ' ' + str(pixelG) + ' ' + str(pixelB) + '\n')
219    return;
220
221
222def SaveDisparityImageToPLY(disparityImg, leftImg, perspectiveMatrix, plyFilename):
223    worldImg = cv2.reprojectImageTo3D(disparity=disparityImg, Q=perspectiveMatrix, handleMissingValues=True)
224    SaveWorldImageToPLY(worldImg, leftImg, plyFilename)
225    return;
226
227
228
229# ************************************************************************************************
230# **********                                    MAIN                                    **********
231# ************************************************************************************************
232
233leftImg = cv2.imread('0002_A01.jpeg')
234rightImg = cv2.imread('0002_A02.jpeg')
235# leftImg = cv2.imread('img2.bmp')
236# rightImg = cv2.imread('img1.bmp')
237
238start = time.time()
239disparityImg, leftImgRectified, perspectiveMatrix = StereoMatchingWithCalibFiles(leftImg, rightImg, 'A01-A02_l.txt', 'A01-A02_r.txt')
240print(time.time() - start)
241
242# if SHOW_STEREOMATCHING_DISPARITY_IMG:
243#     ShowDisparityImageSGBM(disparityImg)
244
245start = time.time()
246SaveDisparityImageToPLY(disparityImg, leftImgRectified, perspectiveMatrix, 'test.ply')
247print(time.time() - start)

پیاده‌سازی سیستم بازسازی صحنه در متلب

در قطعه کد زیر که در زبان متلب پیاده‌سازی شده است، الگوریتمی جهت کالیبره کردن یک سیستم بینایی ماشین متشکل از دو دوربین (stereo camera)، نمایش نقشه ناهمخوانی تولید شده از دو تصویر گرفته شده توسط این دوربین‌ها و در نهایت، بازسازی سه‌بُعدی صحنه گرفته شده، نمایش داده شده است. توابع این قطعه کد عبارتند از:

  • تابع خواندن تصاویر ورودی و تولید ساختار داده‌ای مناسب از آن‌ها
  • کالیبره کردن سیستم بینایی ماشین متشکل از دو دوربین، از طریق تخمین زدن پارامترهای داخلی و خارجی و ضریب «اعوجاج شعاعی» (Radial Distortion)
  • تولید نقشه ناهمخوانی به ازاء هر دو تصویر ورودی و گرفته شده از یک صحنه یکسان
  • تولید مدل بازسازی سه‌بُعدی صحنه با استفاده از «نقاط ابری» (Cloud Points)

کدهای پیاده‌سازی سیستم بازسازی صحنه، کالیبره کردن دو دوربین (stereo camera) و نمایش نقشه ناهمخوانی در متلب:

جهت دسترسی به کدهای پیاده‌سازی سیستم بازسازی صحنه، کالیبره کردن دو دوربین (stereo camera) و نمایش نقشه ناهمخوانی در متلب، روی لینک یا آیکون زیر کلیک کنید.

بازشناسی اشیاء (Object Recognition) در بینایی ماشین

در سیستم‌های بینایی ماشین و کاربردهای مرتبط، بازشناسی اشیاء به فرایند شناسایی یک شیء و موقعیت آن درون یک تصویر یا صحنه گفته می‌شود. از ابتدای پیدایش حوزه پردازش تصویر دیجیتال، بینایی ماشین و بینایی کامپیوتر، عمل بازشناسی (Recognition)، معمولا با استفاده از یک مجموعه داده مرجع و روی داده‌های تصویری دوبُعدی انجام می‌شد.

در روش‌های بازشناسی اشیاء در تصاویر دیجیتال دوبُعدی، محققان غالبا با شرایطی نظیر «هم‌پوشانی جزئی» (Partial Occlusion)، «شرایط نوری متغیر» (Partial Lighting Conditions) و «پس زمینه‌های ناهنجار و به هم ریخته» (Cluttered Background) در تصویر دیجیتال مواجه می‌شوند.

در صورت استفاده از صحنه‌های سه‌بُعدی، برخی از مشکلات ذکر شده نظیر هم‌پوشانی جزئی، در مرحله تحلیل تصویر از بین خواهند رفت (زیرا در مرحله مطابقت دو سویی (Stereo Correspondence) این مشکل برطرف می‌شود). همچنین در صحنه‌های ناهنجار و به هم ریخته، تحلیل صحنه‌های سه‌بُعدی تنها زمانی ممکن است با مشکل مواجه شود که سیستم در حال بازشناسی صحنه و یا خوشه‌بندی مجموعه پیکسل‌های سه‌بُعدی (Voxels) با هدف شناسایی اشیاء موجود در تصویر باشد.

بازشناسی اشیاء در سیستم‌های بینایی ماشین معمولا در دو مرحله «گردآوری» (Acquisition) یا «آموزش» (Training) و «بازشناسی» (Recognition) انجام می‌شود. در مرحله اول، سیستم بینایی ماشین باید اشیاء (Objects) موجود در تصویر را در حافظه خود گردآوری کند تا در مراحل بعدی بتواند آن‌ها را شناسایی کند و تشخیص دهد.

روش مرسوم برای چنین کاری به این صورت است که ابتدا یک پایگاه داده متشکل از مدل اشیاء، که شامل مجموعه‌ای از تصاویر در زوایای مختلف و شرایط نوری (Lighting Condition) متفاوت است، تشکیل می‌شود؛ پژوهش‌های مختلف نشان داده است که استفاده از چنین رویکردی، از لحاظ حافظه‌ای مقرون به صرفه نیست. یکی دیگر از روش‌هایی که برای مدل‌سازی سه‌بُعدی اشیاء مورد استفاده قرار می‌گیرد، «هندسه سازنده جسم یا هندسه توپر سازنده» (Constructive Solid Geometry | CSG) است. تحقیقات نشان داده است که استفاده از روش‌های CSG جهت مدل‌سازی اشیاء سه‌بُعدی، روش بهینه‌تری (از لحاظ حافظه) برای ذخیره اطلاعات سه‌بُعدی مرتبط با یک شیء (Object) است.

در روش CSG، از ترکیب عناصر گرافیکی پایه (Solid Primitives) با عملیاتی نظیر «اشتراک» (Intersection)، «اجتماع» (Union) و «تفاضل مجموعه‌ها» (Set Difference)، جهت شکل دادن به یک شیء استفاده می‌شود. اشیاء حاصل از طریق روش CSG را می‌توان به صورت منطقی توسط یک «درخت باینری» (Binary Tree) نمایش داد؛ در این نمایش، نودهای برگ درخت توسط عناصر گرافیکی پایه و نودهای والد نیز توسط عملیات تعریف شده میان آن‌ها (اشتراک، اجتماع و تفاضل مجموعه) نمایش داده می‌شود.

مدل «قاب سیمی» (قاب سیمی) نیز برای نمایش نمای کلی (Outline) یک مدل سه‌بُعدی مورد استفاده قرار گرفته شده است. با این حال، مشکل بزرگ مدل قاب سیمی این است که مدل‌های تولید شده از یک شیء (در چشم‌اندازهای متفاوت)، «ظاهر تصویری» (Projective Appearance) مشابهی دارند؛ در نتیجه، این روش، برای مدل‌سازی سه‌بُعدی اشیاء مختلف مطلوب نیست. علاوه بر این، روش‌های ذخیره‌سازی مبتنی بر پیکسل‌های سه‌بُعدی می‌توانند از تکنیک‌های «اشغال مکانی» (Spatial Occupancy) برای مدل‌سازی سه‌بُعدی استفاده کنند؛ در این دسته از تکنیک‌ها، پیکسل‌های سه‌بُعدی درون یک آرایه باینری سه‌بُعدی و گسسته محصور می‌شوند.

در تکنیک‌های اشغال مکانی، شیء به صورت حجمی و از طریق ترکیب کردن اطلاعات ساختاری موجود در آرایه‌های باینری ساخته می‌شود. یک نسخه عمومی از روش هندسه سازنده جسم یا هندسه توپر سازنده (CSG) در سیستمی به نام ACRONYM، که مخفف عبارت A Cone Representation of Objects Not Yet Modeled است، جهت مدل‌سازی اشیاء سه‌بُعدی پیاده‌سازی شده است.

سیستم ACRONYM از مفهومی به نام «اشیاء یا حجم‌های جاروب شده» (Swept Volumes) جهت مدل‌سازی سه‌بُعدی اشیاء استفاده می‌کند. اشیائی (حجم‌دار) نظیر «استوانه» (Cylinder)، «مکعب» (Cube)، «هرم» (Pyramid) حتی یک «بطری» (Bottle)، همگی اشیاء یا حجم‌های جاروب شده (Swept Volumes) هستند. روش کار ACRONYM بدین صورت است که ابتدا اشیاء جاروب شده (Swept Object) توسط این سیستم ساخته و سپس از طریق خوشه‌بندی کردن آن‌ها، یک مدل سه‌بُعدی از شیء ساخته می‌شود. با این حال، سیستم ACRONYM در تولید اشیاء سه‌بُعدی پیچیده با مشکل مواجه می‌شد، در نتیجه، توسعه آن توسط محققان و برنامه‌نویسان متوقف شده است.

بازشناسی اشیاء‎

فناوری‌های تشخیص یا بازشناسی اشیاء را می‌توان در قالب فناوری‌ها و سیستم‌های کامپیوتری تعریف کرد که مجموعه‌ای از وظایف مرتبط با حوزه‌های بینایی کامپیوتر (پردازش کامپیوتری تصاویر و ویدئو و درک محتوای آ‌ن‌ها)، بینایی ماشین و «پردازش تصویر» (Image Processing) را «خودکارسازی» (Automate) می‌کنند. به عبارت دیگر، تشخیص یا بازشناسی اشیاء زیر مجموعه فناوری‌ها و سیستم‌های کامپیوتری هستند که در حوزه بینایی کامپیوتر و پردازش تصویر فعالیت دارند.

«بازشناسی اشیاء» (Object Recognition) یکی از تکنیک‌های تعریف شده در حوزه بینایی ماشین و بینایی کامپیوتر محسوب می‌شود که جهت شناسایی اشیاء در تصاویر یا ویدئوها مورد استفاده قرار می‌گیرند. روش‌های پردازش تصویر، یادگیری ماشین و «یادگیری عمیق» (Deep Learning)، بهترین‌ ابزارهای موجود جهت بازشناسی اشیاء محسوب می‌شوند.

زمانی که انسان‌ها به یک تصویر نگاه می‌کنند و یا به تماشای یک ویدئو مشغول می‌شوند، به راحتی قادر به شناسایی اشخاص، اشیاء، صحنه‌ها و «جزئیات بصری» (Visual details) موجود در آن‌ها هستند. هدف سیستم‌های بازشناسی اشیاء در بینایی ماشین و بینایی کامپیوتر این است که سیستم کامپیوتری را قادر سازند تا انجام کاری که برای انسان‌ها بسیار ساده است را یاد بگیرند و بتوانند محتوای موجود در تصاویر و ویدئوها را درک کنند.

بازشناسی اشیاء یکی از فناوری‌های کلیدی در توسعه «اتومبیل‌های خودران» (Driverless Vehicles) است و آن‌ها را قادر می‌سازد تا افراد عابر پیاده، علامت توقف و سایر موارد را شناسایی کنند و میان یک عابر پیاده و پایه چراغ‌های روشنایی جاده‌ها تمایز قائل شوند. همچنین روش‌های بازشناسی اشیاء در کاربردهای متنوعی نظیر شناسی بیماری‌ها در تصویربرداری زیستی (Bioimaging)، «بازرسی صنعتی» (Industrial Inspection) و «بینایی روباتیک» (Robotic Vision) مفید واقع می‌شوند.

تفاوت بازشناسی اشیاء و تشخیص اشیاء

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

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

دسته‌بندی روش‌های بازشناسی اشیاء

به طور کلی، روش‌های بازشناسی اشیاء را می‌توان در دو رویکرد عمومی تقسیم‌بندی کرد؛ روش‌های «بازشناسی الگو» (Pattern Recognition) و روش‌های بازشناسی هندسی مبتنی بر ویژگی (Feature-based Geometric Recognition). روش‌های بازشناسی الگو روی داده‌های سطح پایین عمل می‌کنند و سعی دارند تا الگوهای بافت و رنگ‌بندی موجود در تصاویر را با عناصر مدل شده در یک پایگاه داده مطابقت دهند. از آنجایی که اشیاء ممکن است ویژگی‌های بافت و رنگ‌بندی مشترکی داشته باشند، استفاده از چنین رویکردی برای بازشناسی اشیاء، عملکرد مطلوبی را برای سیستم رقم نخواهد زد.

روش‌های بازشناسی هندسی مبتنی بر ویژگی، سعی در پیدا کردن ویژگی‌های تغییر ناپذیر دارند که به ازاء هر تصویر منحصر به فرد هستند. ویژگی‌های تغییر ناپذیر (Invariant)، زمانی که تبدیلات (Transformation) خاصی روی تصاویر اعمال می‌شوند، تغییر نمی‌کنند؛ تبدیلات استاندارد نظیر تبدیلات اقلیدسی (انتقال (Translation)، چرخش (Rotation) یا بازتاب (Reflection)) و تبدیلات همگر یا آفین (Affine Transformation) نظیر تبدیلات برشی (Sheering) و مقیاس (Scaling)، از جمله چنین تبدیلاتی هستند. لبه‌ها و گوشه‌ها از جمله ویژگی‌های تغییر ناپذیر در تصاویر هستند.

روش تبدیل ویژگی مستقل از ابعاد (Scale-Invariant Feature Transform | SIFT)، روشی برای پیدا کردن ویژگی‌های تغییر ناپذیر و مقایسه آن‌ها میان اشیاء مختلف است. در چند سال اخیر، روش‌های SIFT به موفقیت‌های بسیار خوبی در این زمینه دست پیدا کرده‌اند؛ به ویژه در تشخیص اشیاء در تصاویری که هم‌پوشانی جزئی میان اشیاء موجود در آن وجود دارد.

نتایج تحقیقات و جهت‌گیری‌های پژوهشی آینده در زمینه بینایی ماشین

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

علاوه بر این، روش پنجره‌‌های لغزان بهتر از دیگر روش‌های تجمیع عمل می‌کنند و در این میان، روش پنجره‌های تطابقی (Adaptive windows) در نواحی حاوی هم‌پوشانی، بهتر از دیگر روش‌ها عمل می‌کنند. تحقیقات انجام شده در زمینه روش‌های بهینه‌سازی ناهمخوانی نشان می‌دهد که روش‌های برش گرافی (Graph Cut) بهتر از دیگر روش‌های موجود عمل می‌کنند. روش‌های پنجره لغزان نیز علاوه بر اینکه بار محاسباتی کمتری به سیستم تحمیل می‌کنند، زمان محاسباتی کمتری نیز احتیاج دارند؛ فرایندهای روش پنجره لغزان، تنها چند ثانیه زمان می‌برد، در حالی که روش‌های برش گرافی، معمولا حداقل چند ده دقیقه، برای تکمیل فرایندهای خود، احتیاج دارند.

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

روش‌های تبدیل ویژگی مستقل از ابعاد (SIFT)، عملکرد بسیار خوبی در مطابقت اشیاء (به ویژه‌، مطابقت اشیاء در شرایط نوری متغیر و سایر موارد) از خود نشان می‌دهند و نسبت به نویز و خطاهای مرتبط با جمع‌آوری اطلاعات حساس نیستند. در حالی که روش‌های قدیمی‌تر که به صورت افزایشی (Incrementally) اقدام مدل‌سازی سه‌بُعدی اشیاء می‌کنند، یک سلسله مراتبی منطقی از اشیاء مدل شده ارائه می‌دهند، ولی بسیار به نویز و خطاهای دیگر حساس هستند.

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

در رویکردهای مبتنی بر یادگیری ماشین و پس از استخراج ویژگی‌ها از تصاویر یا فریم‌های ویدئویی، از یکی مدل‌های یادگیری ماشین، نظیر «ماشین بردار پشتیبان» (Support Vector Machine)، استفاده می‌شود تا اشیاء موجود در تصویر شناسایی شوند. سپس، موقعیت آن‌ها در تصویر مشخص می‌شود و در نهایت، اشیاء شناسایی شده در کلاس‌های از پیش تعیین شده دسته‌بندی شوند.

در مدل‌های مبتنی بر یادگیری عمیق، امکان تشخیص نقطه به نقطه اشیاء (ٍEnd-to-End Object Detection) برای توسعه‌دهندگان سیستم‌های بینایی ماشین و بینایی کامپیوتر فراهم شده است. اهمیت چنین روش‌هایی بدین خاطر است که این دسته از سیستم‌ها قادر هستند بدون تعریف صریح ویژگی‌های مرتبط با هر کدام از کلاس‌های تعریف شده، اشیاء موجود در تصویر را شناسایی کنند. این دسته از مدل‌ها، معمولا مبتنی بر «شبکه‌های عصبی پیچشی» (Convolutional Neural Network) هستند. مهم‌ترین سیستم‌های مبتنی بر یادگیری عمیق جهت بازشناسی اشیاء عبارتند از:

  • روش‌های Region Proposals (جهت شناسایی نواحی دربرگیرنده اشیاء در تصویر) نظیر R-CNN ،Fast R-CNN و Faster R-CNN.
  • روش Single Shot MultiBox Detector یا SSD.
  • روش‌‎های معروف و بسیار محبوب You Only Look Once یا YOLO.

جمع‌بندی

سیستم‌های بینایی ماشین، از طریق اصلاح و بهینه‌سازی تکنیک‌های موجود و یا ترکیب روش‌های موجود با تکنیک‌های دیگر در حوزه‌های مرتبط (نظیر بینایی کامپیوتر)، به سرعت در حال پیشرفت هستند. همچنین، زیر شاخه‌های تحقیقاتی دیگری نیز در حوزه بینایی ماشین وجود دارند که محققان می‌توانند آن‌ها را با تکنیک‌های معرفی شده در این مطلب ترکیب کنند و عملکرد آن‌ها را بهبود بخشند؛ زیر شاخه‌هایی نظیر بینایی فعال، ساختار به وسیله حرکت (Structure from Motion | SFM) و بازسازی صحنه به وسیله تصاویر گرفته شده توسط دوربین‌های کالیبره نشده.

تحقیق در مورد استفاده از تعداد زیادی دوربین کم کیفیت جهت مطابقت تصاویر و مقایسه عملکرد آن‌ها با سیستم‌هایی که از دو دوربین با کیفیت بالا استفاده می‌کنند، از جمله حوزه‌های تحقیقاتی است که می‌تواند مورد بررسی قرار بگیرد. در مورد نمایش حافظه‌ای مبتنی بر پیکسل سه‌بُعدی (Voxel-based Memory Representation) از صحنه‌ها و ذخیره‌سازی ویژگی‌های مهم موجود در صحنه‌ها نظیر گوشه‌ها و لبه‌ها، تحقیقات بسیار کمی انجام شده است. در مبحث بازسازی صحنه نیز، جای خالی تحقیقات مرتبط با تکنیک‌های خوشه‌بندی جهت شکل دادن به اشیاء، بر اساس ویژگی‌های همسایگی و رنگ‌بندی مشاهده می‌شود.

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

اگر نوشته بالا برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

^^

بر اساس رای ۳۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
Semantic Scholar
۱ دیدگاه برای «بینایی ماشین — از صفر تا صد»

تشخیص سوراخ ریز روی صفحه 2 بعدی مثلن مدارچاپی توسط یک دوربین نه چندان دقیق و گران و تعیین مختصات ان مثلن در یک ماشین cnc مونتاژ smd را توضیح دهید ممنون میشوم .

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *