بینایی ماشین — از صفر تا صد
در این مطلب، با حوزه «بینایی ماشین» (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):
- روش «گرادیان کاهشی تکراری» (Iterative Gradient Descent)
- روش «برازش منحنی» (Curve-Fitting)
بنابراین، الگوریتمهای مطابقت دو سویی مرسوم را میتوان بر اساس پیادهسازی آنها، نوع الگوریتمهای استفاده شده در هر مرحله و فاکتورهایی نظیر «هزینه تطبیق» (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) در روشهای مطابقت دو سویی پیشنهاد نمیشود.
روشهای مطابقت مبتنی بر شدت، از 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)
- رویکرد «برنامهنویسی پویا» (Dynamic Programming)
- رویکرد «شبیهسازی تبرید» (Simulated Annealing)
- رویکرد «برشهای گرافی» (Graph Cuts)
- رویکرد بهینهسازی Scanline
رویکرد «همه برای برنده» (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) محسوب میشود که برای مشخص کردن موقعیت سهبُعدی یک شیء، از منظر دو دوربین مورد استفاده قرار میگیرد.
در یک جهان ایدهآل و با در اختیار داشتن دوربینهایی که از «دقت» (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) اقدام مدلسازی سهبُعدی اشیاء میکنند، یک سلسله مراتبی منطقی از اشیاء مدل شده ارائه میدهند، ولی بسیار به نویز و خطاهای دیگر حساس هستند.
همانطور که پیش از این نیز اشاره شد، مهمترین روشهای بازشناسی اشیا، معمولا از رویکردهای مبتنی بر یادگیری ماشین و یا مدلهای مبتنی بر یادگیری عمیق برای شناسایی اشیاء موجود در تصویر استفاده میکنند. در رویکردهای مبتنی بر یادگیری ماشین، ابتدا ویژگیهای مرتبط با اشیاء موجود در تصویر، با استفاده از روشهای خاصی استخراج میشود. مهمترین روشهای استخراج ویژگی در رویکردهای مبتنی بر یادگیری ماشین عبارتند از:
- روش تشخیص اشیا Viola–Jones مبتنی بر ویژگیهای Haar
- رویکرد تبدیل ویژگی مستقل از ابعاد (Scale-Invariant Feature Transform | SIFT)
- روشهای مبتنی بر ویژگیهای هیستوگرام گرادیانهای جهتدار (Histogram of Oriented Gradients | HOG)
در رویکردهای مبتنی بر یادگیری ماشین و پس از استخراج ویژگیها از تصاویر یا فریمهای ویدئویی، از یکی مدلهای یادگیری ماشین، نظیر «ماشین بردار پشتیبان» (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) جهت انجام فرایندهای مرتبط با بینایی ماشین (مطابقت دو سویی، بازسازی صحنه و بازشناسی اشیاء)، انتظار میرود موقعیت پیکسلهای سهبُعدی با دقت بیشتری مشخص شود و جزئیات بیشتری جهت بازشناسی اشیاء در تصویر، در اختیار سیستمهای بینایی ماشین قرار بگیرد.
اگر نوشته بالا برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای داده کاوی و یادگیری ماشین
- آموزش اصول و روشهای داده کاوی (Data Mining)
- مجموعه آموزشهای هوش مصنوعی
- نقشه دانش فناوریهای هوش مصنوعی و دستهبندی آنها — راهنمای جامع
- تشخیص اشیا در پایتون — راهنمای کاربردی
- بینایی کامپیوتر چیست؟ — به زبان ساده
- پیادهسازی سیستم تشخیص و ردیابی خودرو در پایتون — راهنمای جامع
^^
تشخیص سوراخ ریز روی صفحه 2 بعدی مثلن مدارچاپی توسط یک دوربین نه چندان دقیق و گران و تعیین مختصات ان مثلن در یک ماشین cnc مونتاژ smd را توضیح دهید ممنون میشوم .