بینایی ماشین

ساخت وبلاگ

بینایی ماشین، یکی از حوزه‌های نوظهور، پیچیده و در حال توسعه (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) در روش‌های مطابقت دو سویی پیشنهاد نمی‌شود.

نمایه شدت (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 چندتایی به فرم (X1,Y1),……,(XN,YN)
، برای یک پنجره به اندازه NxN وجود خواهد داشت. در چنین حالتی، مقدار «همبستگی متقابل نرمال شده» (Normalized Cross Correlation | NCC) از طریق رابطه زیر به دست می‌آید:
Normalizedcrosscorrelation=∑ni=1(Xi–¯¯¯¯¯X)(Yi–¯¯¯¯Y)√[∑ni=1(Xi–¯¯¯¯¯X)2∑ni=1(Yi–¯¯¯¯Y)2]
 
در این رابطه، ¯¯¯¯¯X
و ¯¯¯¯Y
، میانگین نمونه‌های موجود در پنجره‌های متناظر را نمایش می‌دهند. مقدار رابطه همبستگی متقابل نرمال شده، بین مقادیر 1 و 1- خواهد بود؛ مقدار 1 نشان دهنده «همبستگی کامل» (Perfect Correlation) میان مقادیر شدت پنجره‌ها خواهد بود.
از رابطه زیر، جهت محاسبه مجموع مربعات فاصله‌ها (Sum of Squared Distances | SSD) استفاده می‌شود:
SSD=n∑i=1(Xi–Yi)2
 
این رابطه، مربع «فاصله اقلیدسی» (Euclidean Distance) میان X
و Y
را محاسبه می‌کند. از رابطه زیر نیز جهت محاسبه مجموع فاصله‌های مطلق (Sub of Absolute Distance | SAD) استفاده می‌شود:
SAD=n∑i=1∣Xi–Yi∣
 
این رابطه، فاصله مطلق میان مقادیر شدت را محاسبه می‌کند. روش‌های دیگری برای محاسبه شباهت وجود دارند ولی به اندازه روش‌های نمایش داده شده معروف و پرطرفدار نیستند. عوامل متعددی نظیر «هم‌پوشانی» (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) محسوب می‌شود که برای مشخص کردن موقعیت سه‌بُعدی یک شیء، از منظر دو دوربین مورد استفاده قرار می‌گیرد.

هندسه 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) استفاده شده است.
دو تصویر گرفته شده از یک صحنه توسط دوربین‌های مختلف:

کدهای پیاده‌سازی سیستم محاسبه نقشه ناهمخوانی در پایتون‌:
def cut(disparity, image, threshold): for i in range(0, image.height): for j in range(0, image.width): # keep closer object if cv.GetReal2D(disparity,i,j) > threshold: cv.Set2D(disparity,i,j,cv.Get2D(image,i,j)) # loading the stereo pair left = cv.LoadImage('scene_l.bmp',cv.CV_LOAD_IMAGE_GRAYSCALE) right = cv.LoadImage('scene_r.bmp',cv.CV_LOAD_IMAGE_GRAYSCALE) disparity_left = cv.CreateMat(left.height, left.width, cv.CV_16S) disparity_right = cv.CreateMat(left.height, left.width, cv.CV_16S) # data structure initialization state = cv.CreateStereoGCState(16,2) # running the graph-cut algorithm cv.FindStereoCorrespondenceGC(left,right, disparity_left,disparity_right,state) disp_left_visual = cv.CreateMat(left.height, left.width, cv.CV_8U) cv.ConvertScale( disparity_left, disp_left_visual, -16 ); cv.Save( "disparity.pgm", disp_left_visual ); # save the map # cutting the object farthest of a threshold (120) cut(disp_left_visual,left,120) cv.NamedWindow('Disparity map', cv.CV_WINDOW_AUTOSIZE) cv.ShowImage('Disparity map', disp_left_visual) cv.WaitKey()
نقشه‌های ناهمخوانی حاصل با مقادیر آستانه مختلف:

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

کدهای پیاده‌سازی سیستم محاسبه نقشه ناهمخوانی و مطابقت دو سویی در پایتون‌:
import cv2 import random import numpy as np import matplotlib matplotlib.use("TkAgg") from matplotlib import pyplot as plt grayscale_max = 255 def load_image(filename): image = cv2.imread(filename, cv2.IMREAD_GRAYSCALE) return image def show_image(title, image): max_val = image.max() # image = np.absolute(image) image = np.divide(image, max_val) # cv2.imshow(title, image) cv2.imwrite(title+str(random.randint(1, 100))+'.jpg', image*grayscale_max) def add_padding(input, padding): rows = input.shape[0] columns = input.shape[1] output = np.zeros((rows + padding * 2, columns + padding * 2), dtype=float) output[ padding : rows + padding, padding : columns + padding] = input return output def add_replicate_padding(image): # zero_padded = add_padding(image, padding) # size = image.shape[0] top_row = image[0, :] image = np.vstack((top_row, image)) bottom_row = image[-1, :] image = np.vstack((image, bottom_row)) left_column = image[:, 0] left_column = np.reshape(left_column, (left_column.shape[0], 1)) image = np.hstack((left_column, image)) right_column = image[:, -1] right_column = np.reshape(right_column, (right_column.shape[0], 1)) image = np.hstack((image, right_column)) return image def euclid_dist(a, b): distance = np.linalg.norm(a - b) return distance def get_search_bounds(column, block_size, width): disparity_range = 25 left_bound = column - disparity_range if left_bound width: right_bound = width - block_size + 1 return left_bound, right_bound def search_bounds(column, block_size, width, rshift): disparity_range = 75 padding = block_size // 2 right_bound = column if rshift: left_bound = column - disparity_range if left_bound = (width - 2*padding): left_bound = width - 2*padding - 2 step = -1 return left_bound, right_bound, step # max disparity 30 def disparity_map(left, right, block_size, rshift): padding = block_size // 2 left_img = add_padding(left, padding) right_img = add_padding(right, padding) height, width = left_img.shape # d_map = np.zeros((height - padding*2, width - padding*2), dtype=float) d_map = np.zeros(left.shape , dtype=float) for row in range(height - block_size + 1): for col in range(width - block_size + 1): bestdist = float('inf') shift = 0 left_pixel = left_img[row:row + block_size, col:col + block_size] l_bound, r_bound, step = search_bounds(col, block_size, width, rshift) # for i in range(l_bound, r_bound - padding*2): for i in range(l_bound, r_bound, step): right_pixel = right_img[row:row + block_size, i:i + block_size] # if euclid_dist(left_pixel, right_pixel) c - left_pixel > 0: right_pixel = d_map_right[r, int(c - left_pixel)] else: right_pixel = d_map_right[r, c] if left_pixel == right_pixel: consistency_map[r, c] = left_pixel else: consistency_map[r, c] = 0 sum = 0 for r in range(rows): for c in range(cols): if consistency_map[r, c] != 0: sum = sum + (left_ground_truth[r, c] - consistency_map[r, c]) ** 2 mse_c_left = sum / (rows * cols) return mse_c_left, consistency_map def consistency_map_mse_r(d_map_left, d_map_right, right_ground_truth): rows, cols = d_map_right.shape consistency_map = np.zeros((rows, cols)) for r in range(rows): for c in range(cols): right_pixel = d_map_right[r, c] if c + right_pixel
نقشه‌های ناهمخوانی حاصل:

بازسازی صحنه (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 پیکسل سه‌بُعدی است. داده‌های مورد استفاده از طریق لینک [+] قابل دسترسی هستند.
کدهای پیاده‌سازی سیستم بازسازی صحنه در پایتون‌:
import numpy as np from skimage import measure try: import pycuda.driver as cuda import pycuda.autoinit from pycuda.compiler import SourceModule FUSION_GPU_MODE = 1 except Exception as err: print('Warning: %s'%(str(err))) print('Failed to import PyCUDA. Running fusion in CPU mode.') FUSION_GPU_MODE = 0 class TSDFVolume(object): def __init__(self,vol_bnds,voxel_size): # Define voxel volume parameters self._vol_bnds = vol_bnds # rows: x,y,z columns: min,max in world coordinates in meters self._voxel_size = voxel_size # in meters (determines volume discretization and resolution) self._trunc_margin = self._voxel_size*5 # truncation on SDF # Adjust volume bounds 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 self._vol_bnds[:,1] = self._vol_bnds[:,0]+self._vol_dim*self._voxel_size self._vol_origin = self._vol_bnds[:,0].copy(order='C').astype(np.float32) # ensure C-order contigous print("Voxel volume size: %d x %d x %d"%(self._vol_dim[0],self._vol_dim[1],self._vol_dim[2])) # Initialize pointers to voxel volume in CPU memory self._tsdf_vol_cpu = np.ones(self._vol_dim).astype(np.float32) self._weight_vol_cpu = np.zeros(self._vol_dim).astype(np.float32) # for computing the cumulative moving average of observations per voxel self._color_vol_cpu = np.zeros(self._vol_dim).astype(np.float32) # Copy voxel volumes to GPU if FUSION_GPU_MODE: self._tsdf_vol_gpu = cuda.mem_alloc(self._tsdf_vol_cpu.nbytes) cuda.memcpy_htod(self._tsdf_vol_gpu,self._tsdf_vol_cpu) self._weight_vol_gpu = cuda.mem_alloc(self._weight_vol_cpu.nbytes) cuda.memcpy_htod(self._weight_vol_gpu,self._weight_vol_cpu) self._color_vol_gpu = cuda.mem_alloc(self._color_vol_cpu.nbytes) cuda.memcpy_htod(self._color_vol_gpu,self._color_vol_cpu) # Cuda kernel function (C++) self._cuda_src_mod = SourceModule(""" __global__ void integrate(float * tsdf_vol, float * weight_vol, float * color_vol, float * vol_dim, float * vol_origin, float * cam_intr, float * cam_pose, float * other_params, float * color_im, float * depth_im) { // Get voxel index int gpu_loop_idx = (int) other_params[0]; int max_threads_per_block = blockDim.x; int block_idx = blockIdx.z*gridDim.y*gridDim.x+blockIdx.y*gridDim.x+blockIdx.x; int voxel_idx = gpu_loop_idx*gridDim.x*gridDim.y*gridDim.z*max_threads_per_block+block_idx*max_threads_per_block+threadIdx.x; int vol_dim_x = (int) vol_dim[0]; int vol_dim_y = (int) vol_dim[1]; int vol_dim_z = (int) vol_dim[2]; if (voxel_idx > vol_dim_x*vol_dim_y*vol_dim_z) return; // Get voxel grid coordinates (note: be careful when casting) float voxel_x = floorf(((float)voxel_idx)/((float)(vol_dim_y*vol_dim_z))); float voxel_y = floorf(((float)(voxel_idx-((int)voxel_x)*vol_dim_y*vol_dim_z))/((float)vol_dim_z)); float voxel_z = (float)(voxel_idx-((int)voxel_x)*vol_dim_y*vol_dim_z-((int)voxel_y)*vol_dim_z); // Voxel grid coordinates to world coordinates float voxel_size = other_params[1]; float pt_x = vol_origin[0]+voxel_x*voxel_size; float pt_y = vol_origin[1]+voxel_y*voxel_size; float pt_z = vol_origin[2]+voxel_z*voxel_size; // World coordinates to camera coordinates float tmp_pt_x = pt_x-cam_pose[0*4+3]; float tmp_pt_y = pt_y-cam_pose[1*4+3]; float tmp_pt_z = pt_z-cam_pose[2*4+3]; 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; 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; 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; // Camera coordinates to image pixels int pixel_x = (int) roundf(cam_intr[0*3+0]*(cam_pt_x/cam_pt_z)+cam_intr[0*3+2]); int pixel_y = (int) roundf(cam_intr[1*3+1]*(cam_pt_y/cam_pt_z)+cam_intr[1*3+2]); // Skip if outside view frustum int im_h = (int) other_params[2]; int im_w = (int) other_params[3]; if (pixel_x = im_w || pixel_y = im_h || cam_pt_z self._vol_bnds[dim,1]: # expand upper bounds # n_voxels_expand = int(np.ceil((new_bnds[dim,1]-self._vol_bnds[dim,1])/self._voxel_size)) # new_chunk_size = np.round((self._vol_bnds[:,1]-self._vol_bnds[:,0])/self._voxel_size).astype(int) # new_chunk_size[dim] = n_voxels_expand # size of expanding region (i.e. chunk) # # Initialize chunks and concatenate to current voxel volume # self._tsdf_vol_cpu = np.concatenate((self._tsdf_vol_cpu,np.ones(new_chunk_size)),axis=dim) # self._weight_vol_cpu = np.concatenate((self._weight_vol_cpu,np.zeros(new_chunk_size)),axis=dim) # self._color_vol_cpu = np.concatenate((self._color_vol_cpu,np.zeros(new_chunk_size)),axis=dim) # self._vol_bnds[dim,1] += n_voxels_expand*self._voxel_size # update voxel volume bounds def integrate(self,color_im,depth_im,cam_intr,cam_pose,obs_weight=1.): im_h = depth_im.shape[0] im_w = depth_im.shape[1] # Fold RGB color image into a single channel image color_im = color_im.astype(np.float32) color_im = np.floor(color_im[:,:,2]*256*256+color_im[:,:,1]*256+color_im[:,:,0]) # GPU mode: integrate voxel volume (calls CUDA kernel) if FUSION_GPU_MODE: for gpu_loop_idx in range(self._n_gpu_loops): self._cuda_integrate(self._tsdf_vol_gpu, self._weight_vol_gpu, self._color_vol_gpu, cuda.InOut(self._vol_dim.astype(np.float32)), cuda.InOut(self._vol_origin.astype(np.float32)), cuda.InOut(cam_intr.reshape(-1).astype(np.float32)), cuda.InOut(cam_pose.reshape(-1).astype(np.float32)), cuda.InOut(np.asarray([gpu_loop_idx,self._voxel_size,im_h,im_w,self._trunc_margin,obs_weight],np.float32)), cuda.InOut(color_im.reshape(-1).astype(np.float32)), cuda.InOut(depth_im.reshape(-1).astype(np.float32)), 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]))) # CPU mode: integrate voxel volume (vectorized implementation) else: # Get voxel grid coordinates xv,yv,zv = np.meshgrid(range(self._vol_dim[0]),range(self._vol_dim[1]),range(self._vol_dim[2]),indexing='ij') vox_coords = np.concatenate((xv.reshape(1,-1),yv.reshape(1,-1),zv.reshape(1,-1)),axis=0).astype(int) # Voxel coordinates to world coordinates world_pts = self._vol_origin.reshape(-1,1)+vox_coords.astype(float)*self._voxel_size # World coordinates to camera coordinates world2cam = np.linalg.inv(cam_pose) cam_pts = np.dot(world2cam[:3,:3],world_pts)+np.tile(world2cam[:3,3].reshape(3,1),(1,world_pts.shape[1])) # Camera coordinates to image pixels pix_x = np.round(cam_intr[0,0]*(cam_pts[0,:]/cam_pts[2,:])+cam_intr[0,2]).astype(int) pix_y = np.round(cam_intr[1,1]*(cam_pts[1,:]/cam_pts[2,:])+cam_intr[1,2]).astype(int) # Skip if outside view frustum valid_pix = np.logical_and(pix_x >= 0, np.logical_and(pix_x = 0, np.logical_and(pix_y 0)))) depth_val = np.zeros(pix_x.shape) depth_val[valid_pix] = depth_im[pix_y[valid_pix],pix_x[valid_pix]] # Integrate TSDF depth_diff = depth_val-cam_pts[2,:] valid_pts = np.logical_and(depth_val > 0,depth_diff >= -self._trunc_margin) dist = np.minimum(1.,np.divide(depth_diff,self._trunc_margin)) w_old = self._weight_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]] w_new = w_old + obs_weight self._weight_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]] = w_new tsdf_vals = self._tsdf_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]] 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) # Integrate color old_color = self._color_vol_cpu[vox_coords[0,valid_pts],vox_coords[1,valid_pts],vox_coords[2,valid_pts]] old_b = np.floor(old_color/(256.*256.)) old_g = np.floor((old_color-old_b*256.*256.)/256.) old_r = old_color-old_b*256.*256.-old_g*256. new_color = color_im[pix_y[valid_pts],pix_x[valid_pts]] new_b = np.floor(new_color/(256.*256.)) new_g = np.floor((new_color-new_b*256.*256.)/256.) new_r = new_color-new_b*256.*256.-new_g*256. new_b = np.minimum(np.round(np.divide(np.multiply(old_b,w_old)+new_b,w_new)),255.); new_g = np.minimum(np.round(np.divide(np.multiply(old_g,w_old)+new_g,w_new)),255.); new_r = np.minimum(np.round(np.divide(np.multiply(old_r,w_old)+new_r,w_new)),255.); 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; # Copy voxel volume to CPU def get_volume(self): if FUSION_GPU_MODE: cuda.memcpy_dtoh(self._tsdf_vol_cpu,self._tsdf_vol_gpu) cuda.memcpy_dtoh(self._color_vol_cpu,self._color_vol_gpu) return self._tsdf_vol_cpu,self._color_vol_cpu # Get mesh of voxel volume via marching cubes def get_mesh(self): tsdf_vol,color_vol = self.get_volume() # Marching cubes verts,faces,norms,vals = measure.marching_cubes_lewiner(tsdf_vol,level=0) verts_ind = np.round(verts).astype(int) verts = verts*self._voxel_size+self._vol_origin # voxel grid coordinates to world coordinates # Get vertex colors rgb_ tablighat...

ما را در سایت tablighat دنبال می کنید

برچسب : نویسنده : agahya بازدید : 136 تاريخ : يکشنبه 24 بهمن 1400 ساعت: 22:37