Image Segmentation with Watershed Algorithm
اهداف
- ما یاد خواهیم گرفت که از تقسیم بندی تصویر مبتنی بر مارکر با استفاده از الگوریتم segmentation استفاده کنیم
- خواهیم دید: cv.watershed ()
مقدمه
هر تصویر در مقیاس خاکستری را می توان به عنوان یک سطح توپوگرافی مشاهده کرد که در آن شدت بالا قله ها و تپه ها را نشان می دهد در حالی که شدت کم دره ها را نشان می دهد. شما شروع به پر کردن هر دره جدا شده (حداقل محلی) با آب (برچسب) رنگی متفاوت می کنید. با افزایش آب ، بسته به قله ها (شیب ها) نزدیک ، آب از دره های مختلف ، بدیهی است که با رنگ های مختلف شروع به ادغام می کند. برای جلوگیری از آن ، در مکانهایی که آب با هم ادغام می شود موانعی ایجاد می کنید. شما کار پر کردن آب و ایجاد موانع را ادامه می دهید تا زمانی که تمام قله ها زیر آب قرار بگیرند. سپس موانعی که ایجاد کردید ، نتیجه تقسیم بندی را به شما می دهد. این "فلسفه" پشت حوزه است. برای درک آن با کمک برخی از انیمیشن ها می توانید به صفحه وب CMM در حوزه آبخیز مراجعه کنید.
اما این روش به دلیل نویز یا هرگونه بی نظمی دیگر در تصویر ، نتیجه بیش از اندازه ای به شما می دهد. بنابراین OpenCV یک الگوریتم حوزه آبخیز مبتنی بر مارکر را اجرا کرد که در آن شما مشخص می کنید که همه نقاط دره باید ادغام شوند و کدام یک نیستند. این یک تقسیم بندی تصویر تعاملی است. کاری که ما انجام می دهیم این است که برای شی object خودمان که می شناسیم برچسب های مختلفی بزنیم. ناحیه ای را که از پیش زمینه بودن یا شی بودن آن مطمئن هستیم با یک رنگ (یا شدت) برچسب بزنید ، ناحیه ای را که از پس زمینه بودن یا غیر جسم بودن آن مطمئن هستیم با رنگ دیگری برچسب بزنید و در آخر ناحیه ای را که از هیچ چیز مطمئن نیستیم ، آن را با 0. برچسب گذاری کنید. این نشانگر ما است. سپس الگوریتم حوزه را اعمال کنید. سپس مارکر ما با برچسب هایی که دادیم به روز می شود و مرز اشیا مقدار 1 خواهد داشت.
کد
در زیر نمونه ای از نحوه استفاده از Transform distance همراه با water shade برای تقسیم بندی اشیا objects لمس کننده متقابل را خواهیم دید.
تصویر سکه های زیر را در نظر بگیرید ، سکه ها با یکدیگر در تماس اند. حتی اگر تصویر را در حالت آستانه قرار دهید ، باز هم تماس آن ها اجتناب ناپذیر است.
ما با پیدا کردن برآورد تقریبی سکه ها شروع می کنیم. برای این کار ، می توانیم از آستانه دوتایی سازی Otsu استفاده کنیم.
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('coins.png')
gray = cv2.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
خروجی قطعه کد:
اکنون باید هر گونه صدای سفید کوچک را در تصویر حذف کنیم. برای آن می توان از دهانه ریخت شناسی استفاده کرد. برای از بین بردن هرگونه سوراخ کوچک در جسم ، می توانیم از بسته شدن ریخت شناسی استفاده کنیم. بنابراین ، اکنون به طور قطع می دانیم که منطقه نزدیک مرکز اشیا fore پیش زمینه است و مناطق بسیار دورتر از جسم پس زمینه هستند. فقط منطقه ای که مطمئن نیستیم منطقه مرزی سکه ها است.
بنابراین باید منطقه ای را که مطمئن هستیم سکه هستند ، استخراج کنیم. فرسایش پیکسل های مرزی را از بین می برد. بنابراین هرچه باقی مانده است ، می توانیم مطمئن باشیم که سکه است. اگر اشیا یکدیگر را لمس نکنند ، کارایی خوبی خواهد داشت. اما از آنجا که آنها یکدیگر را لمس می کنند ، گزینه خوب دیگر یافتن تغییر شکل فاصله و اعمال آستانه مناسب است. بعد باید منطقه ای را پیدا کنیم که مطمئن هستیم سکه نیست. برای این منظور ، نتیجه را گشاد می کنیم. اتساع باعث افزایش مرز شی به پس زمینه می شود. به این ترتیب ، از آنجا که منطقه مرزی برداشته شده است ، می توانیم اطمینان حاصل کنیم که هر منطقه ای که در پس زمینه واقع شده باشد ، پس زمینه باشد. تصویر زیر را ببینید.
مناطق باقیمانده مناطقی است که ما هیچ تصوری نداریم ، خواه سکه باشد یا زمینه. الگوریتم حوزه باید آن را پیدا کند. این نواحی معمولاً در حوالی مرزهای سکه ها هستند که پیش زمینه و پس زمینه به هم می رسند (یا حتی دو سکه مختلف به هم می رسند). ما آن را مرز می نامیم. این را می توان از کم کردن Sure_fg area از Sure_bg area بدست آورد.
# noise removal
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
# sure background area
sure_bg = cv2.dilate(opening,kernel,iterations=3)
# Finding sure foreground area
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)
نتیجه را ببینید در تصویر آستانه دار ، برخی از مناطق سکه را بدست می آوریم که از سکه اطمینان داریم و اکنون جدا شده اند. (در بعضی موارد ، شما ممکن است فقط به تقسیم بندی پیش زمینه علاقه مند باشید ، نه جدا کردن اجسام متقابل لمس. در این حالت ، شما نیازی به استفاده از تبدیل فاصله ندارید ، فقط فرسایش کافی است. فرسایش فقط روش دیگری برای استخراج مطمئن منطقه پیش زمینه است ، یعنی همه.)
اکنون به طور قطع می دانیم کدام منطقه از سکه ها ، پس زمینه و همه آنها هستند. بنابراین مارکر ایجاد می کنیم (آرایه ای با همان اندازه تصویر اصلی است ، اما با نوع داده int32) و مناطق داخل آن را برچسب گذاری می کنیم. مناطقی که ما به طور قطعی می شناسیم (چه پیش زمینه و چه پس زمینه) با هر عدد صحیح مثبت برچسب گذاری شده اند ، اما عدد صحیح مختلف متفاوت است و منطقه ای که به طور قطع نمی دانیم صفر است. برای این کار ما از cv.connectedComponents () استفاده می کنیم. این پس زمینه تصویر را با 0 برچسب گذاری می کند ، سپس سایر اشیا with با عدد صحیح از 1 برچسب گذاری می شوند.
اما می دانیم که اگر زمینه با 0 مشخص شود ، حوزه آبخیز آن را به عنوان منطقه ناشناخته در نظر می گیرد. بنابراین می خواهیم آن را با عدد صحیح مختلف علامت گذاری کنیم. در عوض ، ما منطقه ناشناخته را که توسط ناشناخته تعریف شده است ، با 0 مشخص خواهیم کرد.
# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers+1
# Now, mark the region of unknown with zero
markers[unknown==255] = 0
نتیجه نشان داده شده در نقشه JET را مشاهده کنید. منطقه آبی تیره منطقه ناشناخته را نشان می دهد. سکه های مطمئنی با مقادیر مختلف رنگ شده اند. منطقه باقیمانده که از پس زمینه مطمئن هستند در مقایسه با منطقه ناشناخته با رنگ آبی روشن تر نشان داده می شوند.
اکنون مارکر ما آماده است. وقت مرحله آخر است ، حوضه آبریز را اعمال کنید. سپس تصویر نشانگر اصلاح می شود. منطقه مرزی با -1 مشخص خواهد شد.
markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]
نتیجه را در زیر مشاهده کنید. برای بعضی از سکه ها ، منطقه ای که آنها لمس می کنند به درستی تقسیم بندی شده اند و برای برخی دیگر ، اینگونه نیستند.