نمایش/عدم نمایش سایدبار
رفتن به بالای صفحه
أَللّهُمَّ ارْزُقْنی شَفاعَةَ الْحُسَیْنِ یَومَ الْوُرُودِ
مهدی دمیرچیلو

آموزش ماژول Miscellaneous Image Transformations در OpenCV

497

به نام خدا : تو این مطلب به ماژول Miscellaneous Image Transformations میپردازیم که 6 تا تابع داره!؛ این ماژول زیر مجموعه ای از ماژول Image Processing هستش؛ تو این مطلب به معرفی توابع threshold و adaptiveThreshold ( ایجاد تصاویر باینری )، blendLinear ( ترکیب خطی دو تصویر )، distanceTransform ( فاصله تا نزدیک ترین پیکسل 0 )، floodFill ( رنگ کردن یه بخش مجزا! ) و تابع integral میپردازیم.

آموزش ماژول Miscellaneous Image Transformations در OpenCV

 

نوع شمارشی ThresholdTypes

این enum چون تو چندتا تابع مشترک هستش، اینجا توضیحش میدم؛ تعریف این enum به صورت زیر هستش :

توضیح گزینه ها : در زیر فرمول محاسبه هر گزینه و یا توضیحی ازش رو مشاهده میکنید.

THRESH_BINARY : -

\( \text{dst} (x,y) = \begin{cases} \text{maxval} & \text{if src} (x,y) > \text{thresh} \\ 0 & \text{otherwise} \end{cases} \)

THRESH_BINARY_INV : -

\( \text{dst} (x,y) = \begin{cases} 0 & \text{if src} (x,y) > \text{thresh} \\ \text{maxval} & \text{otherwise} \end{cases} \)

THRESH_TRUNC : -

\( \text{dst} (x,y) = \begin{cases} \text{threshold} & \text{if src} (x,y) > \text{thresh} \\ \text{src} (x,y) & \text{otherwise} \end{cases} \)

THRESH_TOZERO : -

\( \text{dst} (x,y) = \begin{cases} \text{src} (x,y) & \text{if src} (x,y) > \text{thresh} \\ 0 & \text{otherwise} \end{cases} \)

THRESH_TOZERO_INV : -

\( \text{dst} (x,y) = \begin{cases} 0 & \text{if src} (x,y) > \text{thresh} \\ \text{src} (x,y) & \text{otherwise} \end{cases} \)

THRESH_MASK : در مورد این گزینه، سایت OpenCV توضیح خاصی نداده، بقیه سایت ها هم که جستجو کردم سریع، چیز خاصی پیدا نکردم، فعلا این گزینه رو فاکتور میگیرم تا بعدا ببینیم چی پیش میاد!

THRESH_OTSU : پرچم، از الگوریتم Otsu برای انتخاب مقدار آستانه بهینه استفاده کنید.

THRESH_TRIANGLE : پرچم، از الگوریتم Triangle برای انتخاب مقدار آستانه بهینه استفاده کنید.

 

توجه : مقادیر THRESH_OTSU و THRESH_TRIANGLE با مقادیر دیگه ( بغیر از THRESH_MASK )، AND بیتی میشن و کارشون همونطور که توضیح دادم در بالا، اینه که میاد مقدار مناسب و بهینه برای آستانه رو پیدا میکنن و اعمال میکنن و دیگه نیازی نی که ما مثلا مقدار 127 تنظیم کنیم ( از این عدد 127 برا تعیین آستانه تو کدهای نمونه ام، استفاده کردم که در ادامه مطلب میرسیم بهشون )؛ مثلا THRESH_BINARY | THRESH_OTSU یعنی از روش THRESH_BINARY استفاده بشه و مقدار آستانه، به کمک روش THRESH_OTSU تعیین بشه و نه توسط مقدار ثابتی که به تابع ( تابع threshold، پارامتر thresh ) میدیم؛ در مورد فرمول و و یا روش کار THRESH_OTSU و THRESH_TRIANGLE هم تا این لحظه چیزی تو سایت OpenCV ندیدم من که توضیح داده باشه.

 

خب بریم 2 تا عکس رو ببینیم که سایت OpenCV ارائه داده، مثال های خوبی برای فهم بهتر این موضوع هستند :

تصویر 1 :

OpenCV ThresholdTypes

تصویر 2 :

OpenCV ThresholdTypes

 

ایجاد تصویر گرادیان خطی در OpenCV : در تصویر بالا، برای ایجاد اون تصویر گرادیانت ورودی، میتویند از کد زیر استفاده کنید تا همچین تصویری رو ایجاد کنید :

نتیجه کد بالا :

 Creating Linear Gradient in OpenCV

 

کد مرتبط با تصویر 2 : به کمک کد زیر هم میتونید اون تصویر دومی هه، رو ایجاد کنید ( تصویر ورودی هم، همین تصویر گرادیانت هستش که در بالا ایجادش کردیم )، تو این کد انواع گزینه های ThresholdTypes رو تست کردیم که میتونید کد رو اجرا کنید و نتایج رو مشاهده کنید، باید مثل تصویر-2 باشه.

1) تابع threshold

1) تابع threshold : یک آستانه سطح ثابت را به عناصر یک آرایه چند کاناله اعمال میکند؛ این تابع معمولاً برای ایجاد یک تصویر دو سطحی / تصویر 2 مقداره ( تصویر باینری ) از یک تصویر در مقیاس خاکستری مورد استفاده قرار میگیره یا برای حذف نویز، یعنی فیلتر کردن پیکسل هایی با مقادیر خیلی کوچک یا خیلی بزرگ استفاده میشه؛ به کمک نوع شمارشی ThresholdTypes، میتونید مد کاری این تابع رو تعیین کنید که خب هر مد محاسبات خودشو داره که در قسمت "نوع شمارشی ThresholdTypes" توضیح دادم.

توجه : در حال حاضر روش های Otsu و Triangle فقط برای تصاویر تک کانال 8 بیتی اجرا میشوند.

 

تعریف تابع :

توضیح پارامترها :

  • src : آرایه ورودی؛ چند کاناله، 8-بیتی یا 32-بیتی اعشاری.
  • dst : آرایه خروجی؛ با اندازه، نوع و تعداد کانالی برابر با src.
  • thresh : مقدار آستانه. ( اگه میخواید مقدار آستانه، به صورت خودکار پیدا بشه، گزینه های THRESH_OTSU و یا THRESH_TRIANGLE رو با نوع آستانه مدنظرتون، AND بیتی کنید، که در اینصورت، مقدار پارامتر thresh لحاظ نمیشه در محاسبات؛ در قسمت توضیح نوع شمارشی ThresholdTypes، در این باره صحبت کردیم، نیاز به توضیح بیشتر نی. )
  • maxval : مقدار حداکثر؛ مورد استفاده در آستانه های THRESH_BINARY و THRESH_BINARY_INV.
  • type : نوع آستانه؛ به کمک نوع شمارشی ThresholdTypes مقدار دهی میشود.

 

کد نمونه : در قسمت توضیح نوع شمارشی ThresholdTypes یه کد نمونه قرار دادم، در قسمت تابع adaptiveThreshold هم یه کد نمونه میزارم تا هم مثالی بشه برای هر دو تابع threshold و adaptiveThreshold و هم مقایسه ای بشه بین این دو تابع. ^_^

2) تابع adaptiveThreshold

2) تابع adaptiveThreshold : یک آستانه تطبیقی به آرایه ورودی اعمال میکند و یه تصویر باینری ایجاد میکند؛ تابع طبق فرمول های زیر، یک تصویر در مقیاس خاکستری را به یک تصویر باینری تبدیل میکند :

THRESH_BINARY : 

\( dst(x,y) = \begin{cases} \text{maxValue} & \text{if } src(x,y) > T(x,y) \\ 0 & \text{otherwise} \end{cases} \)

THRESH_BINARY_INV : 

\( dst(x,y) = \begin{cases} 0 & \text{if } src(x,y) > T(x,y) \\ \text{maxValue} & \text{otherwise} \end{cases} \)

که \( T(x,y) \) آستانه ای است که به صورت جداگانه برای هر پیکسل محاسبه می شود ( که خب نحوه محاسبه اش، بستگی به به پارامتر adaptiveMethod داره ).

 

تعریف تابع :

توضیح پارامترها :

  • src : تصویر ورودی 8-بیتی و 1-کاناله ( CV_8UC1 )
  • dst : تصویر خروجی؛ با اندازه و نوعی برابر با src.
  • maxValue : این پارامتر نباید 0 تنظیم شود.
  • adaptiveMethod : الگوریتم آستانه تطبیقی مورد استفاده؛ به کمک نوع شمارشی cv::AdaptiveThresholdTypes مقداردهی میشود؛ از ترکیب BORDER_REPLICATE | BORDER_ISOLATED برای پردازش مرزها استفاده میشود. ( برای مطالعه بیشتر درباره مرزها / حاشیه ها / Border ها به تابع copyMakeBorder مراجعه کنید )
  • thresholdType : نوع آستانه که باید THRESH_BINARY و یا THRESH_BINARY_INV باشد، بقیه مدها پشتیبانی نمیشن توسط این تابع. ( به نوع شمارشی cv::ThresholdTypes مراجعه کنید. )
  • blockSize : اندازه همسایگی پیکسلی که برای محاسبه مقدار آستانه پیکسل استفاده میشود : 3، 5، 7 و...
  • C : ثابت کسر شده از میانگین ( mean ) یا میانگین وزنی ( weighted mean ) ( به جزئیات زیر مراجعه کنید ). به طور معمول، مثبت است اما ممکن است صفر یا منفی نیز باشد.

 

AdaptiveThresholdTypes : تعریف این نوع شمارشی به صورت زیر هستش :

توضیح گزینه ها :

  • ADAPTIVE_THRESH_MEAN_C : مقدار آستانه \( T(x,y) \) یک میانگین \( blockSize×blockSize \) از همسایگی \( (x,y) \) منهای \( C \) است؛ در این روش، \( T(x,y) \) به کمک فیلتر boxFilter محاسبه میشه.
  • ADAPTIVE_THRESH_GAUSSIAN_C : مقدار آستانه \( T(x,y) \) یک مجموع وزنی ( weighted sum ) ( همبستگی متقابل ( cross-correlation ) با یک پنجره گاوسی ) \( blockSize×blockSize \) از همسایگی \( (x,y) \) منهای \( C \) است؛ فرمول محاسبه سیگما پیش فرض ( انحراف معیار / standard deviation ) برای blockSize مشخص شده در توضیحات تابع getGaussianKernel ارائه شده؛ در این روش، \( T(x,y) \) به کمک فیلتر GaussianBlur محاسبه میشه.

 

پیاده سازی تابع adaptiveThreshold در C++ : این قسمت ضروری نیست ولی خب برای دونستن و فهم بیشتر تابع adaptiveThreshold بنظرم مفیده؛ پیاده سازی کامل این تابع در مسیر زیر قرار داره :

[opencv]/modules/imgproc/src/thresh.cpp

که خب من پیاده سازی ساده این تابع رو نوشتم، که در زیر مشاهده میکنید :

حالا اگه بخوایم بیشتر ریز بشیم رو موضوع، باید بریم ببینیم این \( T(x,y) \) چطوری به کمک اون دو تا تابع ( boxFilter و GaussianBlur ) محاسبه شدن، که خب این موضوع ارتباطی با این مطلب نداره و مربوطه به مطلب فیلترها ( آموزش ماژول Image Filtering در OpenCV ) !!!

 

کد نمونه : تو این کد نمونه، انواع حالت های تابع adaptiveThreshold رو تست کردم، هم با تابعی که OpenCV معرفی کرده و هم با تابعی که خودم نوشتم ( فقط خواستم ببینید که نتیجه هردو یکسانه، تا از پیاده سازی صحیح تابع فوق، صحبح کدهای تابع نوشته شده، اطمینان حاصل کنید! )

نتیجه کد بالا : اگه QT رو نصب نکرده باشید تو بسته OpenCV ( بسته های پیشفرض OpenCV که تو Github ارائه شده، QT رو فعال ندارند )، نمیتونید نتیجه رو مثل حالتی که من میبینیم، ببینید، فلذا موقعی که کد رو تست میکنید، خروجی شما شبیه خروجی مال من، مقدار عددی هر پیکسل رو نشون نمیده؛ من رو یه آرایه که خودم ایجاد کردم، این تابع رو اعمال کردم ( برا بحث آموزشی؛ هرچند چون محاسبات فیلتر های boxFilter و GaussianBlur رو نگفتم، منظورم کدهای داخلی این دو فیلتر هستش، فلذا فلواقع بحث آموزشی در کار نیست! چون تو محاسبات داخلی GaussianBlur به مشکل خوردم و ... محاسبات boxFilter که سادس، همسایه هارو جمع میکنی، تقسیم بر تعدادشون میکنی، کل کار این فیلتر همینه؛ ولی خب تو فیلتر GaussianBlur، نحوه اعمال هسته این فیلتر به آرایه ورودی مشکل داشتم و... دیگه نتیجه این شد که محاسبات داخلی این 2 فیلتر رو نگم، ولی خب حتما در آینده در مطلب مربوط به فیلترها این مبحث رو تکمیل میکنم )

SRC
آموزش تابع adaptiveThreshold در OpenCV
dst21 و dst22 dst11 و dst12
آموزش تابع adaptiveThreshold در OpenCV آموزش تابع adaptiveThreshold در OpenCV
dst41 و dst42 dst31 و dst32
آموزش تابع adaptiveThreshold در OpenCV آموزش تابع adaptiveThreshold در OpenCV

 

مقایسه توابع threshold و adaptiveThreshold : تو این کد نمونه، یه تصویر ورودی رو به هر دو تابع میدیم تا نتیجه هر کدوم رو ببینم، مثال خوبی باید بشه :

نتیجه کد بالا : حالا بنظرتون تصویر باینری کدوم تابع بهتره؟

dst3 [ threshold ] src
مقایسه توابع threshold و adaptiveThreshold مقایسه توابع threshold و adaptiveThreshold
dst2 [ adaptiveThreshold - GAUSSIAN ] dst1 [ adaptiveThreshold - MEAN ]
مقایسه توابع threshold و adaptiveThreshold مقایسه توابع threshold و adaptiveThreshold

 

3) تابع blendLinear

3) تابع blendLinear : ترکیب خطی دو تصویر را انجام میدهد.

\( \text{dst}(i,j) = \text{weights1}(i,j)*\text{src1}(i,j) + \text{weights2}(i,j)*\text{src2}(i,j) \)

 

تعریف تابع :

توضیح پارامترها :

  • src1 : نوعش CV_8UC و یا CV_32FC باید باشه، تعداد کانال هاش مهم نیست ( 1 تا 4 ).
  • src2 : اندازه و نوعش برابر با src1 هستش.
  • weights1 و weights2 : نوعشون CV_32FC1 و اندازه ای برابر با src1 دارند.
  • dst : تصویر خروجی؛ ( اگر اندازه و نوع مشابهی با src1 نداشته باشد ایجاد / create میشود! )

 

کد نمونه : تو این کد اومدم مقایسه ای بین نتیجه تابع addWeighted و blendLinear انجام دادم.

نتیجه کد بالا :

LinuxLogo.jpg WindowsLogo.jpg
OpenCV addWeighted vs blendLinear OpenCV addWeighted vs blendLinear
blendLinear_dst.jpg addWeighted_dst.jpg
OpenCV addWeighted vs blendLinear OpenCV addWeighted vs blendLinear

 

فرق توابع addWeighted و blendLinear : ( با فرض این که مقدار gamma در تابع addWeighted برابر با 0 هستش! ) تابع addWeighted که در این مطلب ( آموزش ماژول Operations on arrays در OpenCV ) معرفی شده، فرمولش هم قرار داده شده، تعریف تابع blendLinear و فرمولش رو هم که در بالا مشاهده میکنید؛ با یه مقایسه ساده میشه متوجه شد که تابع addWeighted، یه مقدار ثابت ( alpha و beta ) رو در تصاویر ورودی اعمال میکنه اما تابع blendLinear، برای هر سلول ( پیکسل ) هر تصویر، یه مقدار مجزا ( ماتریس های weights1 و weights2 ) رو اعمال میکنه؛ پس ساده بگم که تابع addWeighted، مقادیر ثابتی رو در src1 و src2 اعمال میکنه، اما تابع blendLinear، ماتریس هایی را در  src1 و src2 اعمال میکنه.

 

4) تابع distanceTransform

4) تابع distanceTransform : فاصله تقریبی یا دقیق هر پیکسل از تصویر باینری ورودی تا نزدیکترین پیکسل صفر را محاسبه میکند؛ واضح است که برای پیکسل های صفر، فاصله صفر خواهد بود!؛ وقتی maskSize == DIST_MASK_PRECISE و distanceType == DIST_L2، تابع الگوریتم شرح داده شده در [74] را اجرا میکند، این الگوریتم با کتابخانه TBB موازی شده است؛ در موارد دیگر از الگوریتم شرح داده شده در [30] استفاده میشود.

OpenCV distanceTransform

برای یک پیکسل، این تابع کوتاه‌ترین مسیر را تا نزدیک‌ترین پیکسل صفر مییابد که شامل جابجایی‌های اساسی : افقی، عمودی، مورب یا حرکت شوالیه ( حرکت شوالیه فقط برای یک ماسک 5×5 موجوده؛ حرکت شوالیه در واقع همون شیوه حرکت اسب در بازی شطرنج هستش، البته اگه درست متوجه شده باشم! ) میباشد؛ جابجایی های افقی ( آبی ) و عمودی ( قرمز ) و مورب ( سبز ) و شوالیه ( صورتی ) رو در عکس بالا، با رنگهای مجزا مشخص کردم؛ فاصله کلی به صورت مجموع این فواصل اساسی محاسبه میشود؛ از آنجایی که تابع فاصله باید متقارن باشد، همه جابجایی‌های افقی و عمودی باید مقدار یکسانی داشته باشند ( که با a نشان داده میشود )، همه جابجایی‌های مورب باید مقدار یکسانی داشته باشند ( مشخص شده با b )، و تمام حرکات شوالیه باید مقدار یکسانی داشته باشند. ( که با c مشخص میشود )؛ برای a و b و c، OpenCV از مقادیر پیشنهاد شده در مقاله اصلی استفاده میکند ( سایت OpenCV چیزی درباره مقدار a و b و c برای انواع دیگه DistanceTypes نگفته! ) :

  • DIST_L1 : a = 1, b = 2
  • DIST_L2 :
    • 3 x 3 : a=0.955, b=1.3693
    • 5 x 5 : a=1, b=1.4, c=2.1969
  • DIST_C : a = 1, b = 1

داده ای که تابع در پارامتر labels ارائه میده، در واقع همون نمودار ورنوی ( Voronoi diagram ) هستش که در این مطلب ( آموزش ماژول Planar Subdivision در OpenCV ) قبلا بهش پرداختیم؛ البته مطلب مربوط به این بحث در سایت OpenCV از کلمه discrete Voronoi diagram ( نمودار گسسته Voronoi ) استفاده کرده که خب فرق "نمودار ورنوی" با "نمودار گسسته ورنوی" چیه و اصلا فرقی دارن یا هردو یک چیز هستند رو نمیدونم، اولین باره میشنوم، بعدا اگه مطالعه کردم در این زمینه، این قسمتو تکمیل میکنم؛ در این بخش، مرکز ورونی رو با نقاط سیاه نشون میدیم و هر ناحیه ورنوی هم یه رنگ مجزا به خودشو داره؛ داستان اصلی نمودار ورنوی ( خلاصه مطلب Planar Subdivision ) اینه که "تمام نقاط داخل هر ناحیه ای به نقطه تولیدکننده ( مرکز / نقطه-سیاه-رنگ ) آن ناحیه نزدیکتر میباشد!".

 

تعاریف تابع :

توضیح پارامترها :

  • src : تصویر ورودی؛ 8-بیتی و 1-کاناله ( باینری )
  • dst : تصویر خروجی با فواصل محاسبه شده؛ 8-بیتی یا 32-بیتی اعشاری، 1-کاناله، با اندازه ای برابر با src.
  • labels : آرایه خروجی 2 بعدی حاوی برچسب ها؛ دارای نوع CV_32SC1 و اندازه ای برابر با src.
  • distanceType : نوع فاصله؛ به کمک نوع شمارشی DistanceTypes مقدار دهی میشه.
  • maskSize : اندازه ماسک تبدیل فاصله؛ به کمک نوع شمارشی DistanceTransformMasks مقدار دهی میشه.
  • labelType : نوع آرایه برچسب برای ساخت؛ به کمک نوع شمارشی DistanceTransformLabelTypes مقدار دهی میشه.

 

نوع شمارشی DistanceTypes : -

در کد بالا، فرمول محاسبه هر روش جلوش نوشته شده، نمیخوام فرمولها رو بگم، فقط یه چند تا نکته میگم :

برای DIST_C و DIST_L1، فاصله به طور دقیق محاسبه میشود و اندازه ماسک، روی مقدار DIST_MASK_3 قرار میگیرد زیرا یک ماسک 3×3 همان نتیجه 5×5 یا هر دیافراگم بزرگتر را میدهد؛ فلذا تغییر پارامتر maskSize، تغیری در خروجی ایجاد نمیکند!

برای DIST_L2، فاصله ( فاصله اقلیدسی / Euclidean distance ) را میتوان تنها با یک خطای نسبی محاسبه کرد؛ به طور معمول، برای تخمین سریع فاصله درشت ( coarse distance )، از DIST_MASK_3 ( ماسک 3×3 ) استفاده میشود؛ برای تخمین دقیقتر فاصله، از DIST_MASK_5 ( ماسک 5×5 ) استفاده میشود، برای محاسبه دقیق فاصله از DIST_MASK_PRECISE استفاده میشود.

توجه 1 : اگر از DIST_L2 استفاده میکنید و اگر از تعریف اول تابع استفاده کنید، تغییر در maskSize، تغییری در خروجی ایجاد نمیکنه! ( دلیلشو نمیدونم! )، اما اگه از تعریف دوم تابع استفاده کنید، مشکلی وجود ندارد.

توجه 2 : فقط گزینه های DIST_L1 و DIST_L2 و DIST_C برا من کار کرد، بقیه گزینه ها نمیدونم چرا کار نکردن و خطا میداد!

 

نوع شمارشی DistanceTransformMasks : -

 

نوع شمارشی DistanceTransformLabelTypes : یه سوال، اگه چند تا پیکسل 0 کنار هم بودن، چه اتفاقی میوفته؟ این نوع شمارشی، دقیقا به همین منظور ایجاد شده!؛ شاید توضیحات زیر رو بخونید و متوجه نشید، اما اگه پارامتر خروچی labels رو به صورت عکس نمایش بدید ( cv::imshow ) یا مقادیرشو در خروجی چاپ کنید ( std::cout )، کاملا متوجه داستان میشید؛ تو مثال آموزشی که برای این تابع زدم، دقیقا همین کارو کردم.

توضیح گزینه ها :

  • DIST_LABEL_CCOMP : به هر جزء متصل از صفرها در src ( و همچنین تمام پیکسل های غیر صفر نزدیک به مؤلفه متصل ) همان برچسب اختصاص داده میشود.
  • DIST_LABEL_PIXEL : هر پیکسل صفر ( و تمام پیکسل های غیر صفر نزدیک به آن ) برچسب مخصوص به خود را دارد.

 

کد نمونه 1 : کد مناسب برای بحث آموزشی!؛ تو این کد یه آرایه با نام src ایجاد کردم که باینری هستش ( حاوی مقادیر 0 و 1 هستش ) فلذا نیازی نداریم از تابع threshold استفاده کنیم؛ تو این کد، 3 نوع DistanceTypes ( موارد DIST_L1 و DIST_L2 و DIST_C ) رو تست کردیم، در هر مورد DistanceTypes، هردو حالت DistanceTransformLabelTypes رو هم تست کردیم؛ فلذا یه src داریم؛ 3 تا dst داریم و 6 تا labels داریم، هر بار که labelType رو تغییر میدیم، dst که تغییر نمیکنه که، فقط پارامتر labels تغییر میکنه، سر همین 3 تا dst داریم و 6 تا labels ( خلاصه کلام این که تمامی حالات ممکنه رو تست کردم؛ البته شما باید پارامتر های distanceType و labelType رو دستی تغییر بدید، تا تمام حالات ممکنه رو تست کنید، قبلا من این کارو کردم که نتیجشو در خروجی گزاشتم؛ سعی ام این بود که این کد تا حد امکان ساده باشه! )؛

خب حالا از نتایج ( عکس های ) زیر چی میشه فهمید؟ خب تو تصویر SRC، که خب یه تصویر باینری هستش ( چون فقط 2 تا مقدار داره، 0 و 1 )، مقادیر 0 رو زمینه شونو سیاه کردم تا مشخص باشن، تو تصویر DST، فاصله هر پیکسل نسبت به نزدیک ترین پیکسل 0 محاسبه شده و مقدارش در خونه مربوطه قرار داده شده، که خب نحوه محاسبه هر روش متفاوته که پیشتر فرمول و مقادیر a/b/c مورد نیاز برای هر distanceType رو ذکر کردیم؛ تو دو تصویر labels که خب در هر دو حالت labelType تست کردم تا فرقشونو متوجه بشید؛ تو گزینه DIST_LABEL_CCOMP، میاد صفرهای متصل به همدیگه رو 1 منطقه براشون درنظر میگیره و تو گزینه DIST_LABEL_PIXEL همچین خبری نی و هر صفر، یه منطقه مخصوص به خودشو داره ( بحث های مربوط به نمودار ورنوی / Voronoi diagram )؛ تو تصاویر labels ها به وضوح میشه دید که نقاط 0 ( در تصویر src )، در واقع مرکز ناحیه های نمودار ورنوی هستند؛ درباره مرکز هر ناحیه ورنوی پیشتر گفتم و دوباره میگم : "تمام نقاط داخل هر ناحیه ای به نقطه تولیدکننده ( مرکز / نقطه-سیاه-رنگ ) آن ناحیه نزدیکتر میباشد!"، که خب اصل داستان نمودار ورنوی هستش؛ برای توضیحات مرتبط با نمودار ورنوی به این مطلب ( آموزش ماژول Planar Subdivision در OpenCV ) مراجعه کنید.

حالا اگه به داده های labels نیازی ندارید، میتونید از تعریف دوم تابع استفاده کنید!

در مورد نحوه محاسبات فاصله در تصویر dst، هم طبق فرمول هایی که دربالا دیدید و مقادیر a/b/c، مقدار فاصله هر خونه 1 تا نزدیک ترین خونه 0 رو محاسبه کنید تا روش کار دستتون بیاد، چیز ساده ای هستش.

تصاویر زیر رو در صفحه جدید باز کنید و در اندازه اصلی ببینید!!!

نتایج حالت DIST_L1 : 

OpenCV distance Transform Tutorial

نتایج حالت DIST_L2 : 

OpenCV distance Transform Tutorial

نتایج حالت DIST_C : 

OpenCV distance Transform Tutorial

 

کد نمونه 2 :

خب این کد رو نا چند تا تصویر تست کردم و در زیر نتایج و تصاویر ورودی رو براتون قرار میدم :

تصویر ورودی 1 :

OpenCV distance Transform Tutorial

تصاویر خروجی 1 : فقط برا حالت DIST_L2 ( در هر دو مد DIST_LABEL_CCOMP و DIST_LABEL_PIXEL )؛ بقیه حالت ها رو برید خودتون تست کنید، حوصله ندارم!

OpenCV distance Transform Tutorial

 

تصویر ورودی 2 :

OpenCV distance Transform Tutorial

تصاویر خروجی 2 : فقط در حالت DIST_L1 تست کردم، بقیه اش به عهده خودتون.

OpenCV distance Transform Tutorial

 

در تصاویر بالا، تصویر dst بکار میاد تو پروژه ها، تصویر dst2 برا فهم داستانه وقتی دارید با کنترل های نرم افزار ور میرید، تصویر dst3 هم که برا بحث نمودار ورنوی هستش.

 

5) تابع floodFill

5) تابع floodFill : یه منطقه ای از عکس رو رنگ آمیزی میکنه؛ این تابع مثل ابزار Paint Bucket در فوتوشاپ کار میکنه ( تو نرم افزار Paint ویندوز، اسمش اینه : Fill with color )؛ همون سطل رنگ!، که یه منطقه مشابه یی رو یکجا رنگ میکنه؛ که خب این ابزار ( Paint Bucket ) یه گزینه دیگه با نام Tolerance داره که میزان اختلاف رنگ قابل قبول رو هم میشه تنظیم کرد، مثلا یه نقطه ای رو انتخاب میکنید که رنگ زمینه اش 127 هستش، Tolerance رو هم 2 تنظیم میکنید، از اون پیکسل اولیه ( seed pixel ) شروع به رنگ آمیزی میکنه، رنگ های همسایه اگه بین بازه 125 تا 129 بود رو یه محدوده در نظر میگیره و رنگ آمیزی رو انجام میده؛ ولی خب تو این تابع floodFill یه Tolerance برا محدوده رنگ های بزرگتر از مقدار رنگ ما داریم و یه محدوده برا رنگ هایی کوچکتر ( یعنی تو مثال قبل، یه محدوده برا رنگهای بزرگتر از 127 و یه محدوده برا رنگ های کوچکتر از 127؛ 2 تا Tolerance داریم! ) که خب تو این تابع بهشون میگن loDiff و upDiff؛ همین چیزایی که الان گفتم رو بریم به زبون اصولی ( توضیحات سایت OpenCV ) ببینیم :

توجه : در توضیحات زیر، این داستان floating range ( محدوده شناور ) و fixed range ( محدوده ثابت ) رو احتمالا متوجه نمیشید، در ادامه مطلب، در قسمت توضیح پرچم FLOODFILL_FIXED_RANGE، متوجه داستان محدوه ثابت و شناور میشید!

یک جزء متصل ( connected component )، که از پیکسل اولیه ( seed pixel )، شروع میشود را  با رنگ داده شده پر میکند؛ اتصال با نزدیکی رنگ/روشنایی پیکسل های همسایه تعیین میشود؛ پیکسل در \( (x, y) \) متعلق به دامنه رنگ آمیزی مجدد در نظر گرفته میشود اگر :

1) در مورد تصویر خاکستری و محدوده اعشاری ( floating range ) :

\( \text{src} (x',y')- \text{loDiff} \leq \text{src} (x,y) \leq \text{src} (x',y')+ \text{upDiff} \)

2) در صورت تصویر خاکستری و محدوده ثابت ( fixed range ) :

\( \text{src} ( \text{seedPoint} .x, \text{seedPoint} .y)- \text{loDiff} \leq \text{src} (x,y) \leq \text{src} ( \text{seedPoint} .x, \text{seedPoint} .y)+ \text{upDiff} \)

3) در مورد یک تصویر رنگی و محدوده اعشاری ( floating range ) :

\( \text{src} (x',y')_r- \text{loDiff} _r \leq \text{src} (x,y)_r \leq \text{src} (x',y')_r+ \text{upDiff} _r \)

\( \text{src} (x',y')_g- \text{loDiff} _g \leq \text{src} (x,y)_g \leq \text{src} (x',y')_g+ \text{upDiff} _g \)

\( \text{src} (x',y')_b- \text{loDiff} _b \leq \text{src} (x,y)_b \leq \text{src} (x',y')_b+ \text{upDiff} _b \)

4) در صورت تصویر رنگی و محدوده ثابت ( fixed range ) :

\( \text{src} ( \text{seedPoint} .x, \text{seedPoint} .y)_r- \text{loDiff} _r \leq \text{src} (x,y)_r \leq \text{src} ( \text{seedPoint} .x, \text{seedPoint} .y)_r+ \text{upDiff} _r \)

\( \text{src} ( \text{seedPoint} .x, \text{seedPoint} .y)_g- \text{loDiff} _g \leq \text{src} (x,y)_g \leq \text{src} ( \text{seedPoint} .x, \text{seedPoint} .y)_g+ \text{upDiff} _g \)

\( \text{src} ( \text{seedPoint} .x, \text{seedPoint} .y)_b- \text{loDiff} _b \leq \text{src} (x,y)_b \leq \text{src} ( \text{seedPoint} .x, \text{seedPoint} .y)_b+ \text{upDiff} _b \)

که در آن \( src(x',y') \) مقدار یکی از همسایه های پیکسلی است که از قبل به مولفه ( component ) تعلق دارد؛ یعنی برای اضافه شدن به جزء متصل ( connected component )، رنگ/روشنایی پیکسل باید به اندازه کافی نزدیک به :

  • رنگ/روشنایی یکی از همسایگان آن که قبلاً به جزء متصل ( connected component ) در صورت وجود محدوده شناور ( floating range ) تعلق دارد.
  • رنگ/روشنایی نقطه seed در صورت محدوده ثابت ( fixed range ).

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

 

تعاریف تابع :

توضیح پارامترها :

  • image : تصویر ورودی/خروجی؛ این تصویر باید 1 یا 3 کاناله، 8 بیتی یا اعشاری باشد؛ این تصویر توسط تابع اصلاح میشود مگر اینکه پرچم FLOODFILL_MASK_ONLY رو فعال کرده باشید. ( برای جزئیات بیشتر به پارامتر flags مراجعه کنید. )
  • mask : ماسک عملیاتی که باید یک تصویر 8 بیتی 1-کاناله، 2 پیکسل عریض تر و 2 پیکسل بلندتر از تصویر باشد ( دلیلشو نفهمیدم ولی خب اگه اینکار رو نکنید، تابع خطا میده! احتمالا برا بحث ایجاد border باشه تا محاسبات در لبه رو بتونه انجام بده، شایدم هم دلیل دیگه ای داره! )؛ اگر یک Mat خالی ارسال شود به طور خودکار ایجاد میشود؛ از آنجایی که این پارامتر ورودی و خروجی است، شما باید مسئولیت مقداردهی اولیه آن را بر عهده بگیرید؛ Flood-filling نمی تواند از پیکسل های غیر-صفر در ماسک ورودی عبور کند؛ به عنوان مثال، یک خروجی آشکارساز لبه ( edge detector ) میتواند به عنوان یک ماسک برای توقف پر شدن لبه ها استفاده شود؛ در خروجی، پیکسل‌های موجود در ماسک مربوط به پیکسل‌های پر شده در تصویر بر روی 1 یا به مقدار مشخص شده در پرچم‌ها مطابق زیر تنظیم میشوند؛ علاوه بر این، تابع برای ساده کردن پردازش داخلی، border ماسک را با ماسک پر میکند؛ بنابراین می‌توان از یک ماسک در تماس‌های متعدد با تابع استفاده کرد تا مطمئن شد که قسمت‌های پر شده با هم همپوشانی ندارند.
  • seedPoint : نقطه شروع! ( Seed Point : نقطه شروع، نقطه اولیه، پیکسل شروع، پیکسل اولیه؛ نقطه یا پیکسل فرقی نداره، هر دو صحیح هستش. )
  • newVal : مقدار جدید پیکسل های دامنه رنگ آمیزی شده.
  • loDiff : حداکثر تفاوت روشنایی/رنگ پایینی بین پیکسل مشاهده شده فعلی و یکی از همسایگان آن متعلق به مؤلفه ( component )، یا یک پیکسل اولیه ( seed pixel ) به مؤلفه ( component ) اضافه شده است. ( تمام پیکسل هایی که اختلاف مقدارشون با پیکسل seedPoint، از مقدار loDiff، کمتر باشه، جزو دامنه رنگ آمیزی حساب میشن و رنگ میشن  )
  • upDiff : حداکثر تفاوت روشنایی/رنگ بالایی بین پیکسل مشاهده شده فعلی و یکی از همسایگان آن متعلق به مؤلفه ( component )، یا یک پیکسل اولیه ( seed pixel ) که به مؤلفه ( component ) اضافه میشود. ( تمام پیکسل هایی که اختلاف مقدارشون با پیکسل seedPoint، از مقدار upDiff، کمتر باشه، جزو دامنه رنگ آمیزی حساب میشن و رنگ میشن  )
  • rect : پارامتر خروجی اختیاری؛ توسط تابع بر روی حداقل مستطیل محدود دامنه رنگ آمیزی شده تنظیم شده است.
  • flags : پرچم های عملیات :
    • 8 بیت اول ( بیت های 0 تا 7 ) حاوی یک مقدار connectivity ( اتصال / قابلیت اتصال ) است؛ مقدار پیش‌فرض 4 به این معنی است که تنها چهار پیکسل همسایه ( آنهایی که یک لبه / edge مشترک دارند ) در نظر گرفته میشوند؛ مقدار 8 به این معنی است که هشت پیکسل نزدیکترین همسایه ( آنهایی که یک گوشه / corner مشترک دارند ) در نظر گرفته میشوند.
    • 8 بیت بعدی ( بیت های 8 تا 15 ) حاوی مقداری بین 1 تا 255 است که با آن ماسک پر میشود ( مقدار پیش فرض 1 است )؛ به عنوان مثال، مقدار "4 | ( 255 << 8 )" چهار تا از نزدیکترین همسایه ها را در نظر میگیرد و ماسک را با مقدار 255 پر میکند؛ تصویر mask ( به پارامتر ورودی/خروجی mask مراجعه کنید ) یه تصویر خاکستری هستش، که خب مقدار هر پیکسل بین 0 تا 255 میتونه باشه که خب مقدار خونه های رنگ آمیزی شده در تصویر mask، به کمک این 8 بیت تعیین میشه؛ به عبارت دیگه تابع floodFill، خروجی رو هم در پارامتر image به ما میده و هم ماسکشو در پارامتر mask به ما میده و مقدار خونه های فعال در پارامتر mask به کمک همین 8 بیت تعیین میشه؛ که خب بهتره که مقدارش 255 باشه ( به جهت این که موقعی که میخواید تصویر mask رو در خروجی نشون بدید، قابل دیدن باشه )
    • 2 بیت بعدی ( بیت های 16 و 17 ) به کمک نوع شمارشی FloodFillFlags مقدار دهی میشوند :
      • FLOODFILL_FIXED_RANGE ( بیت 16 ام ) : اگر این بیت فعال شود، تفاوت بین پیکسل فعلی و پیکسل اولیه ( seed pixel ) در نظر گرفته میشود ( یعنی محدوده ثابت است؛ fixed range )؛ در غیر این صورت، تفاوت بین پیکسل های همسایه در نظر گرفته می شود ( یعنی محدوده شناور است؛ floating range ).
      • FLOODFILL_MASK_ONLY ( بیت 17 ام ) : اگر این بیت فعال شود، تابع پارامتر image رو تغییر نمیده ( فلذا پارامتر newVal هم نادیده گرفته میشه )، و فقط ماسک رو با مقدار مشخص شده در بیت های 8 تا 15 از پارامتر flags پر میکند که در بالا مفصل دربارش صحبت کردیم؛ این گزینه فقط در تعرفی از تابع floodFill که دارای پارامتر mask هستش معنی و کاربرد داره!!!

 

توجه : در پارامتر flags، در مورد 8 بیت اول، درباره گوشه ( Corner ) و لبه ( Edge ) صحبت کردم، فرق لبه و گوشه رو حتما میدونید، اما یه مثالی میزنم تا در عمل حسش کنید!؛ مثلا در تصویر 3 در 3 زیر ( در سطح پیکسل )، پیکسل A با تمامی پیکسل ها ( B و C )، گوشه مشترک دارد اما پیکسل A فقط با پیکسل های B، لبه مشترک دارد؛ الان فک کنم کامل متوجه داستان Corner و Edge شده باشید.

OpenCV Edge Corner in Pixels

برای اطمینان از حرف های من، میتونید کد زیر رو تست کنید : کد زیر رو یه بررسی کنید، به صحت حرف های من در بالا پی میبرید.

 

توجه : از آنجایی که ماسک بزرگتر از تصویر پر شده است، یک پیکسل \( (x, y) \) در تصویر با پیکسل \( (x+1,y+1) \) در ماسک مطابقت دارد.

 

نوع شمارشی FloodFillFlags : 

 

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

نتیجه کد بالا :

تصویر ورودی :

OpenCV floodFill Sample Code

تصاویر خروجی :

OpenCV floodFill Sample Code

 

6) تابع integral

6) تابع integral : انتگرال یک تصویر را محاسبه میکند؛ این تابع، یک یا چند تصویر انتگرال را برای تصویر منبع به صورت زیر محاسبه میکند :

$$ \text{sum} (X,Y) = \sum _{x<X, y<Y} \text{image} (x,y) $$

$$ \text{sqsum} (X,Y) = \sum _{x<X, y<Y} \text{image} (x,y)^2 $$

$$ \text{tilted} (X,Y) = \sum _{y<Y, abs(x-X+1) \leq (Y-y-1)} \text{image} (x,y) $$

با استفاده از این تصاویر انتگرال، میتوانید مجموع ( sum )، میانگین ( mean ) و انحراف معیار ( standard deviation ) را روی یک ناحیه مستطیل شکل خاص به سمت راست یا چرخش تصویر در یک زمان ثابت محاسبه کنید، به عنوان مثال :

$$ \sum _{x_1 \leq x < x_2, \, y_1 \leq y < y_2} \text{image} (x,y) = \text{sum} (x_2,y_2)- \text{sum} (x_1,y_2)- \text{sum} (x_2,y_1)+ \text{sum} (x_1,y_1) $$

به عنوان مثال، این امکان را فراهم می کند تا یک همبستگی سریع بلوک ( fast block correlation ) یا محو کردن سریع ( fast blurring ) با اندازه پنجره متغیر انجام شود؛ در مورد تصاویر چند کاناله، مجموع ها ( sums ) برای هر کانال به طور مستقل جمع میشود.

به عنوان یک مثال عملی، شکل بعدی محاسبه انتگرال یک مستطیل مستقیم Rect(3،3،3،2) و یک مستطیل کج Rect(5،1،2،3) را نشان می دهد.

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

OpenCV integral

 

تعاریف تابع : -

توضیح پارامترها :

  • src : تصویر ورودی ( با اندازه \( W×H \) )، با نوع 8-بیتی یا اعشاری ( CV_32F یا CV_64F ).
  • sum : تصویر انتگرال ( با اندازه \( (W+1)×(H+1) \) )، با نوع عدد صحیح 32 بیتی یا اعشاری ( CV_32F یا CV_64F ).
  • sqsum : تصویر انتگرال برای مقادیر پیکسل مربع ( با اندازه \( (W+1)×(H+1) \) )، با نوع CV_64F.
  • tilted : انتگرال برای تصویری که 45 درجه چرخیده است ( با اندازه \( (W+1)×(H+1) \) )، با همان نوع داده ای برابر با پارامتر sum.
  • sdepth : عمق دلخواه تصاویر انتگرال tilted ( کج شده )؛ CV_32S یا CV_32F یا CV_64F.
  • sqdepth : عمق دلخواه تصویر انتگرال sqsum ( پیکسل مربعی )؛ CV_32F یا CV_64F.

 

کد نمونه 1 :

نتیجه کد بالا :

opencv integral example code

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

 

کد نمونه 2 :

نتیجه کد بالا :

خروجی ورودی
 Adaptive Threshold integral مقایسه توابع threshold و adaptiveThreshold

 

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

 

 

منابع : 

 

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

جهت شادی روح امام موسی صدر (ره) و تمامی شهدا و سلامتی سید حسن نصرالله و تمامی نیروهای مقاوت صلوات.

اما موسی صدر

 

تعداد مطالب : 367 تا
جنگ ما فتح قدس را به همراه خواهد داشت. [ امام خمینی (ره) ]
بقیه جلسات : آموزش OpenCV
ارسال دیدگاه
0