آموزش PyTorch برای مبتدیان – ناحیه بندی معنایی با استفاده از Torchvision

در قسمت پنجم دوره آموزشی PyTorch برای مبتدیان ، می خواهیم به بررسی مبحث ناحیه بندی معنایی ( Semantic Segmentation ) با استفاده از Torchvision بپردازیم. با ما همراه باشید.

۱- ناحیه بندی معنایی چیست؟

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

بیایید با نحوه ناحیه بندی معنایی آشنا شویم. فرض کنید تصویر زیر را به عنوان ورودی داریم :

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

منبع : Pexels

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

تصویر موتور سوار ناحیه بندی شده

تصویر ناحیه بندی معنایی شده

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

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

۲- کاربردهای ناحیه بندی معنایی

متداول ترین موارد استفاده ناحیه بندی معنایی عبارتند از :

۱-۲ رانندگی خودکار

ناحیه بندی تصویر برای خودرو های خودران

ناحیه بندی معنایی صحنه جاده (منبع : مجموعه داده CityScapes)

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

۲-۲ ناحیه بندی چهره

تصویر ناحیه بندی شده چهره

ناحیه بندی چهره (منبع)

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

۳-۲ ناحیه بندی اشیا خانگی در محیط بسته

ناحیه بندی تصویر برای شناسایی فضای داخلی

ناحیه بندی محیط خانگی (منبع)

به نظر شما این کجا کاربرد دارد؟ در AR (واقعیت افزوده) و VR (واقعیت مجازی). برنامه های AR می توانند محیط داخلی را ناحیه بندی کنند تا موقعیت صندلی ها ، میزها ، افراد ، دیوار و سایر اشیا مشابه را درک کنند و بنابراین می توانند اشیا مجازی را به طور موثر شبیه سازی کرده و نمایش دهند.

۴-۲ سنجش جغرافیایی زمین

سنجش جغرافیایی زمین با ناحیه بندی

ناحیه بندی تصاویر ماهواره ای (منبع)

سنجش جغرافیایی زمین ( Geo Land Sensing ) روشی برای دسته بندی هر پیکسل از تصاویر ماهواره ای به دسته ای است که می توانیم پوشش سطحی زمین هر منطقه را تفکیک کنیم. به عنوان مثال اگر در منطقه ای جنگل زدایی گسترده ای انجام شده باشد ، می توان اقدامات مناسب را انجام داد. علاوه براین، کاربرد های بیشتری با استفاده از ناحیه بندی معنایی بر روی تصاویر ماهواره ای وجود دارد.

حال که با چند کاربرد مهم ناحیه بندی آشنا شدیم، بیایید ببینیم چگونه می توان با استفاده از PyTorch و Torchvision ناحیه بندی معنایی را انجام داد. در اینجا یک فایل ویدیویی آورده شده که می تواند به شما یک دید کلی بدهد.

۳- ناحیه بندی معنایی با استفاده از Torchvision

ما برای ناحیه بندی معنایی ، به دو مدل مبتنی بر یادگیری عمیق خواهیم پرداخت؛ Fully Convolutional Network (FCN) و DeepLab v3. این مدل ها روی زیرمجموعه ای از مجموعه داده های COCO Train 2017 که با مجموعه داده های PASCAL VOC مطابقت دارد، آموزش دیده اند. در مجموع ۲۰ دسته توسط این مدل ها پشتیبانی می شوند.

برای دنبال کردن این آموزش و کد می توانید از Colab Notebook استفاده کنید.

۱-۳ ورودی و خروجی

قبل از شروع ، بیایید با ورودی و خروجی مدل ها آشنا شویم.

این مدل ها تصویر ۳ کاناله (RGB) که با میانگین و انحراف معیار Imagenet نرمال شده باشد را به عنوان ورودی دریافت می کنند ، یعنی mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225]

بنابراین ، ابعاد ورودی بصورت [Ni x Ci x Hi x Wi] است که،

  • Ni : اندازه دسته
  • Ci : تعداد کانال (که ۳ است)
  • Hi : طول تصویر
  • Wi : عرض تصویر

و ابعاد خروجی بصورت  [No x Co x Ho x Wo] است که،

  • No : اندازه دسته (همان Ni)
  • Co : تعداد کلاس های مجموعه داده
  • Ho : طول تصویر (که تقریباً در همه موارد با Hi برابر است)
  • Wo : عرض تصویر (که تقریباً در همه موارد با Wi برابر است)

توجه : خروجی مدل هایtorchvision  یک OrpedDict است و نه یک torch.Tensor. در حین استنباط (eval() mode ) ، خروجی که یک OrbledDict است فقط یک کلید out دارد. این کلید out خروجی را می دهد و مقادیر مربوطه به قالب [No x Co x Ho x Wo]  هستند.

خب، حالا آماده ایم …

۲-۳ FCN بر اساس Resnet-101

FCN ( Fully Convolutional Networks ) یکی از اولین کار های موفقیت ناحیه بندی معنایی به کمک شبکه های عصبی است. اکنون بیایید ببینیم که چگونه از این مدل در Torchvision استفاده کنیم.

۱-۲-۳ بارگذاری مدل

FCN را بارگذاری می کنیم :

from torchvision import models
fcn = models.segmentation.fcn_resnet101(pretrained=True).eval()

به همین راحتی ! حالا ما یک مدل آموزش دیده FCN بر اساس Resnet101 داریم. اگر مدل از قبل در حافظه پنهان وجود نداشته باشد، پرچم pretrained=True مدل را دانلود می کند. روش eval. آن را در حالت استنباط بارگذاری می کند.

۲-۲-۳ بارگذاری تصویر

from PIL import Image
import matplotlib.pyplot as plt
import torch
!wget -nv https://static.independent.co.uk/s3fs-public/thumbnails/image/2018/04/10/19/pinyon-jay-bird.jpg -O bird.png
img = Image.open('./bird.png')
plt.imshow(img); plt.show()

سپس یک تصویر می گیریم. ما تصویر یک پرنده را مستقیماً از طریق یک URL دانلود و ذخیره می کنیم. همانطور که در کد مشاهده خواهید کرد، ما برای بارگذاری تصویر از PIL استفاده می کنیم.

from PIL import Image
import matplotlib.pyplot as plt
import torch
!wget -nv https://static.independent.co.uk/s3fs-public/thumbnails/image/2018/04/10/19/pinyon-jay-bird.jpg -O bird.png
img = Image.open('./bird.png')
plt.imshow(img); plt.show()

بارگذاری تصویر پرنده

۳-۲-۳ پیش پردازش تصویر

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

بنابراین، برای مراحل پیش پردازش ، موارد زیر را انجام می دهیم.

  • تغییر اندازه تصویر به (۲۵۶×۲۵۶)
  • مرکز آن را به ابعاد (۲۲۴×۲۲۴) برش دهید
  • آن را به Tensor تبدیل کنید. تمام مقادیر تصویر ، به جای بازه اصلی [۰ ، ۲۵۵] به بازه [۰ ، ۱] مقیاس بندی می شوند.
  • آن ها را با مقادیر مشخص Imagenet نرمال سازی می کنیم، یعنی mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225]

و در نهایت، ابعاد تصویر را از [C x H x W] به  [ 1x C x H x W] تغییر می دهیم. این کار الزامی است زیرا هنگام عبور از شبکه به یک دسته نیاز داریم.

# Apply the transformations needed
import torchvision.transforms as T
trf = T.Compose([T.Resize(256),
                 T.CenterCrop(224),
                 T.ToTensor(), 
                 T.Normalize(mean = [0.485, 0.456, 0.406], 
                             std = [0.229, 0.224, 0.225])])
inp = trf(img).unsqueeze(0)

بیایید ببینیم کد بالا چه کاری انجام می دهد.

Torchvision توابع پرکاربردی دارد. یکی از آن ها Transforms است که برای پیش پردازش تصاویر استفاده می شود. T.Compose تابعی است شامل لیستی از انواع تبدیلات است. به کمک این تابع می توانیم دسته ای از تصاویر را عبور دهیم و تمام تغییرات مورد نیاز به همه تصاویر اعمال خواهد شد.

بیایید نگاهی به تبدیلات اعمال شده بر روی تصاویر بیندازیم :

  • Resize (256) : اندازه تصویر را به ابعاد ۲۵۶×۲۵۶ تغییر می دهد.
  • CenterCrop (224) : قطعه مرکزی تصویر را به اندازه ۲۲۴×۲۲۴ برش می دهد.
  • ()ToTensor : تصویر را به حالت torch تبدیل می کند. تنسور و مقادیر را در بازه [۰ ، ۱] مقیاس بندی  می کند.
  • Normalize (mean,std) : تصویر را با میانگین و انحراف معیار داده شده نرمال می کند.

۴-۲-۳ عبور رو به جلو در شبکه

حالا که یک تصویر پیش پردازش شده و آماده داریم، می خواهیم آن را از مدل عبور داده و نتیجه خروجی را ببینیم.

همانطور که قبلاً ذکر شد، خروجی مدل OrpedDict است بنابراین برای بدست آوردن خروجی مدل باید کلید out برداریم.

# Pass the input through the net
out = fcn(inp)['out']
print (out.shape)

torch.Size([1, 21, 224, 224])

بنابراین ، out  خروجی نهایی مدل است. همانطور که می بینید و قبلا هم اشاره شد، اندازه آن  [ 1x 21 x H x W] است. از آنجا که این مدل روی ۲۱ کلاس آموزش دیده است، خروجی ۲۱ کانال دارد.

حالا کاری که ما باید انجام دهیم این است که این خروجی ۲۱ کاناله را به یک تصویر دو بعدی یا یک تصویر ۱ کاناله تبدیل کنیم، که هر پیکسل از آن تصویر مربوط به یک کلاس باشد.

هر پیکسل از تصویر دو بعدی ( به ابعاد[H x W]  )، دارای یک برچسب کلاس متناظر است. توجه داشته باشید که هر پیکسل(x,y)  در این تصویر دو بعدی مربوط به عددی بین ۰ تا ۲۰ است که یک کلاس را نشان می دهد.

حال سوال این است که چگونه می توانیم از این تصویر فعلی با ابعاد [ ۱x 21 x H x W] به تصویر مورد نظر برسیم؟

بسیار ساده است! ما برای هر موقعیت پیکسل یک شاخص حداکثر درنظر می گیریم که نشان دهنده کلاس است.

import numpy as np
om = torch.argmax(out.squeeze(), dim=0).detach().cpu().numpy()
print (om.shape)

(۲۲۴, ۲۲۴)

print (np.unique(om))

[۰ ۳]

همانطور که می توان پس از پردازش مشاهده کرد، اکنون یک تصویر دو بعدی داریم که هر پیکسل آن مربوط به یک کلاس است. آخرین کاری که باید انجام شود این است که این تصویر دو بعدی را به یک طرح ناحیه بندی تبدیل کنید که در آن هر برچسب کلاس به یک رنگ RGB تبدیل می شود، تا جلوه بصری  مطلوبی داشته باشیم.

۵-۲-۳ رمزگشایی خروجی

ما برای تبدیل این تصویر دو بعدی به یک تصویر RGB که هر برچسب به رنگ مربوطه خود نگاشت می شود، از تابع زیر استفاده خواهیم کرد.

# Define the helper function
def decode_segmap(image, nc=21):
  label_colors = np.array([(0, 0, 0),  # 0=background
               # ۱=aeroplane, 2=bicycle, 3=bird, 4=boat, 5=bottle
               (۱۲۸, ۰, ۰), (۰, ۱۲۸, ۰), (۱۲۸, ۱۲۸, ۰), (۰, ۰, ۱۲۸), (۱۲۸, ۰, ۱۲۸),
               # ۶=bus, 7=car, 8=cat, 9=chair, 10=cow
               (۰, ۱۲۸, ۱۲۸), (۱۲۸, ۱۲۸, ۱۲۸), (۶۴, ۰, ۰), (۱۹۲, ۰, ۰), (۶۴, ۱۲۸, ۰),
               # ۱۱=dining table, 12=dog, 13=horse, 14=motorbike, 15=person
               (۱۹۲, ۱۲۸, ۰), (۶۴, ۰, ۱۲۸), (۱۹۲, ۰, ۱۲۸), (۶۴, ۱۲۸, ۱۲۸), (۱۹۲, ۱۲۸, ۱۲۸),
               # ۱۶=potted plant, 17=sheep, 18=sofa, 19=train, 20=tv/monitor
               (۰, ۶۴, ۰), (۱۲۸, ۶۴, ۰), (۰, ۱۹۲, ۰), (۱۲۸, ۱۹۲, ۰), (۰, ۶۴, ۱۲۸)])
  r = np.zeros_like(image).astype(np.uint8)
  g = np.zeros_like(image).astype(np.uint8)
  b = np.zeros_like(image).astype(np.uint8)
  for l in range(0, nc):
    idx = image == l
    r[idx] = label_colors[l, 0]
    g[idx] = label_colors[l, 1]
    b[idx] = label_colors[l, 2]
  rgb = np.stack([r, g, b], axis=2)
  return rgb

بیایید نگاهی به درون این تابع بیندازیم.

ابتدا متغیر label_colors رنگ های مربوط به هر کلاس را بر اساس شاخص ذخیره می کند. بنابراین، رنگ مربوط به کلاس اول که پس زمینه است، در شاخص ۰ از لیست label_colors ذخیره می شود. کلاس دوم که هواپیما است، در شاخص ۱ و بقیه به همین ترتیب ذخیره می شود.

حالا باید از تصویر دو بعدی که داریم یک تصویر RGB بسازیم. بنابراین، کاری که انجام می دهیم این است که برای هر یک از ۳ کانال، یک ماتریس دو بعدی خالی ایجاد می کنیم.

بنابراین r ، g و b آرایه هایی هستند که کانال های RGB را برای تصویر نهایی تشکیل می دهند. هر یک از این آرایه ها به ابعاد[H x W]  است (که همان شکل دو بعدی تصویر است).

اکنون ، ما روی هر رنگ کلاس که در label_colors ذخیره کرده ایم حلقه ایجاد می کنیم و شاخص های مربوطه در تصویر که در آن برچسب کلاس وجود دارد را به دست می آوریم. سپس برای هر کانال، رنگ مربوط به آن را به آن پیکسل هایی که آن برچسب کلاس را دارند، اختصاص می دهیم.

در نهایت، ۳ کانال جداگانه را روی هم قرار می دهیم تا یک تصویر RGB بدست آوریم.

حال قصد داریم از این تابع برای دیدن خروجی ناحیه بندی شده نهایی استفاده کنیم.

rgb = decode_segmap(om)
plt.imshow(rgb); plt.show()

ناحیه بندی تصویر پرنده در PyTorch

بدین ترتیب، ما خروجی تصویر را ناحیه بندیکردیم. که یک پرنده است!

توجه: تصویر بعد از ناحیه بندی ، کوچکتر از تصویر اصلی است، زیرا تصویر در مرحله پیش پردازش تغییر اندازه داده و برش داده می شود.

۶-۲-۳ نتیجه نهایی

حالا بیایید همه این ها را به یک تابع مجزا منتقل کنیم و با چند تصویر دیگر اجرا کنیم :

def segment(net, path):
  img = Image.open(path)
  plt.imshow(img); plt.axis('off'); plt.show()
  # Comment the Resize and CenterCrop for better inference results
  trf = T.Compose([T.Resize(256), 
                   T.CenterCrop(224), 
                   T.ToTensor(), 
                   T.Normalize(mean = [0.485, 0.456, 0.406], 
                               std = [0.229, 0.224, 0.225])])
  inp = trf(img).unsqueeze(0)
  out = net(inp)['out']
  om = torch.argmax(out.squeeze(), dim=0).detach().cpu().numpy()
  rgb = decode_segmap(om)
  plt.imshow(rgb); plt.axis('off'); plt.show()

بیایید یک تصویر جدید بگیریم !

!wget -nv https://learnopencv.com/wp-content/uploads/2021/01/horse-segmentation.jpeg -O horse.png
segment(fcn, './horse.png')

تصویر اسب برای ناحیه بندی
تصویر ناحیه بندی شده اسب در PyTourch

چطور بود؟ حالا بیایید به سراغ یکی از معماری های پیشرفته در ناحیه بندی معنایی یعنی DeepLab برویم.

۳-۳ ناحیه بندی معنایی با استفاده از DeepLab

DeepLab یک معماری ناحیه بندی معنایی است که از Google Brain نشأت می گیرد. بیایید ببینیم چگونه می توانیم از آن استفاده کنیم.

dlab = models.segmentation.deeplabv3_resnet101(pretrained=1).eval()

چگونه می توان با استفاده از این مدل، ناحیه بندی معنایی را بر روی همان تصویر انجام داد؟ ما از همان تابعی که در بالا تعریف کردیم استفاده می کنیم.

segment(dlab, './horse.png')

تصویر اسب برای ناحیه بندی
تصویر ناحیه بندی شده اسب در PyTourch-2

بدین ترتیب، ملاحظه می کنید که ، مدل DeepLab تقریباً بطور کامل اسب را ناحیه بندی کرده است.

۴-۳ ناحیه بندی چند موردی

وقتی یک تصویر پیچیده تر با اشیا زیاد داشته باشیم، می توانیم نتایج متفاوتی با استفاده از هر دو مدل مشاهده کنیم.

می خواهیم آن را امتحان کنیم!

!wget -nv "https://learnopencv.com/wp-content/uploads/2021/01/person-segmentation.jpeg" -O dog-park.png
img = Image.open('./dog-park.png')
plt.imshow(img); plt.show()
print ('Segmenatation Image on FCN')
segment(fcn, path='./dog-park.png', show_orig=False)
print ('Segmenatation Image on DeepLabv3')
segment(dlab, path='./dog-park.png', show_orig=False)

تصویر مورد نظر برای ناحیه بندی

تصویر اصلی

ناحیه بندی تصویر با FCN

نتیجه ناحیه بندی معنایی با استفاده از FCN

ناحیه بندی تصویر با DeepLab

نتیجه ناحیه بندی معنایی با استفاده از DeepLab

همانطور که ملاحظه می کنید، هر دو مدل عملکرد کاملاً قابل قبولی دارند. با این حال ، متاسفانه مواردی وجود دارد که مدل نتیجه موفقی نخواهد داشت.

۴- مقایسه

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

  • زمان استنباط در CPU و GPU
  • اندازه مدل
  • حافظه مورد استفاده GPU هنگام استنباط

۱-۴ زمان استنباط

ما برای اجرای کد و دستیابی به این نتایج از Google Colab استفاده کردیم. می توانید کد مربوطه را بررسی کنید.

زمان استنباط FCN و DeepLab روی CPU
زمان استنباط FCN و DeepLab روی GPU

ملاحظه می کنید که مدل DeepLab کمی کندتر از FCN است.

۲-۴ اندازه مدل

اندازه مدل یعنی اندازه فایل وزن های مدل است. مدل DeepLab کمی بزرگتر از FCN است.

مقایسه اندازه ی FCN و DeepLab

۳-۴ الزامات حافظه GPU

ما برای این منظور از GPU NVIDIA GTX 1080 Ti استفاده کردیم و متوجه شدیم که هر دو مدل برای یک تصویر با ابعاد ۲۲۴ × ۲۲۴ حدود ۱٫۲ گیگابایت نیاز دارند.

اگر از این مقاله خوشتان آمده می توانید برای دریافت یک راهنمای مرجع بینایی رایانه ای رایگان اینجا کلیک کنید. در پست های بعدی، به حل سایر مسائل بینایی رایانه ای به کمک PyTorch و Torchvision خواهیم پرداخت. گوش به زنگ باشید!

بیشتر بخوانید :

درباره‌ی امیر اقتدائی

همچنین ببینید

آموزش PyTorch استنباط با ONNX و Caffe2

آموزش PyTorch برای مبتدیان – استنباط مدل با ONNX و Caffe2

PyTorch پس از عرضه در اکتبر ۲۰۱۶ توسط فیس بوک ، به دلیل کاربرپسند بودن …

دیدگاهتان را بنویسید

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