الدرس العشرون : GrabCut

Lesson_20 slides

اشتقاق الامامية التفاعلي باستخدام خوارزمية Grabcut:

الهدف:

سنتعلم بهذا الفصل:

  • خوارزمية grabcut لاشتقاق الاجسام من الصور.

  • سننشئ تطبيق تفاعلي باستخدامها

النظرية:

الورقة الاصلية خلف هذه الخوارزمية كانت بعنوان : " اشتقاق الامامية التفاعلي باستخدام graph cuts التفاعلية" حيث كان الهدف ان تتم الخوارزمية باقل تفاعل من المستخدم.

وطريقة عملها من وجهة نظر المستخدم , هي ان يرسم المستخدم مستطيلاً حول الجسم (الامامية) بحيث يحتوي كامل الجسم .ومن ثم يتكرر تطبيق الخوارزمية حتى الوصول لافضل نتيجة , لكن احياناً قد لا نحصل على النتيجة المرغوبة بسبب الاخطاء الحاصلة عند تعليم الخلفية كامامية او العكس مثلاً..

سنحتاج في هذه الحالة لاعادة تعليم للنتيجة مثلاً التحديد يدوياً بضربات تحدد اماكن الاخطاء بالناتج , وبعدها نحدث لنحصل على نتائج ادق .

حيث كما المثال ادناه , نرسم اولاً مستطيل حول الجسم ومن ثم هناك لمسات اخيرة باللون الابيض لتحديد الامامية والاسود لتحديد الخلفية ومن ثم نحصل على نتيجة جيدة.

In [1]:
from IPython.display import Image
Image("messi.jpg")
Out[1]:

ولكن ما الذي حل بالخلفية ؟

  • اولا يرسم المستخدم مستطيل وكل شيء خارجه يعد خلفية اكيدة , ولذلك وجب تضمين الجسم كاملاً ضمنه , واي ادخال كهذا من قبل المستخدم يبقى كذلك بالنتيجة دون تغيير .

  • ويقوم الحاسوب بعدها بتحديد اولي حيث الخلفية والامامية اعتماداً على مدخلاتنا.

  • يستخدم الان نموذج خلائطي غاوسي GMM لنمذجة الخلفية والامامية.

  • تتعلم ال GMM وتنشأ توزعاً جديداً للبكسلات بالاعتماد على البيانات المقدمة , حيث تصنف البكسلات غير المحددة اما كامامية اوخلفية محتملتين , بالاعتماد على علاقاتها مع البكسلات المحددة بدلالة احصاءات الالوان (مثل التجميع بعناقيد)

  • ويتم بناء ال graph من توزيع البكسلات هذا وتتمثل العقد بالبكسلات مع اضافة عقدة المصدر وترتبط بها بكسلات الامامية وعقدة المصرف وترتبط بها بكسلات الخلفية .

  • اوزان الحواف التي تصل البكسلات بعقدة المنبع/المصرف متناسبة مع احتمالية كون البكسل ينتمي للامامية / الخلفية . تحدد الاوزان بين البكسلات بمعلومات الحافة او تشابه البكسل , فاذا كان هناك فرق كبير بالوان البكسلات المترابطة فالحافة بينهم ستكتسب وزناً اخف.

  • وعندها تطبق خوارزمية mincut لتقطيع ال graph . حيث تقطعه لعقدتي منبع ومصرف بتابع كلفة اصغري. وتابع الكلفة هو مجموع كل اوزان الحواف المقطوعة , وبعد القطع تقسم كل البكسلات اما لامامية او لخلفية حسب ارتباطها.

  • تستمر العملية حتى تقارب التصنيف .

يتم توضيح العملية بالصورة التالية

مثال:

ان تطبيق خوارزمية Grab cut في OpenCV يتم عن طريق التابع cv2.grabcut حيث متغيراته:

  • img : الصورة الاصل

  • mask : القناع للصورة والذي يحدد المناطق الامامية والخلفية أو احتماليتها , وهذا يتم بالاعلام التالية : cv2.GC_BGD , cv2.GC_FGD , cv2.GC_PR_BGD ,cv2.GC_PR_FGD. او نمرر 0,1,2,3 على الترتيب ببساطة .

  • rect: وهي احداثيات المستطيل المحيط بالامامية بصيغة (x,y,w,h).

  • fgdModel , bgdModel : وهي مصفوفات تستخدمها الخوارزمية داخلياً , يمكنك فقط تمرير مصفوفات صفرية بصيغة np.float64 وبحجم (1,65).

  • iter_count : عدد المرات التي يجب تكرار الخوارزمية فيها.

  • mode : يمكن ان تكون cv2.GC_INIT_WITH_RECT , cv2.GC_INIT_WITH_MASK او وضعاً مركباً والذي يحدد ما اذا كان سيتم رسم مستطيل او لمسات اخيرة معدلة.

أولا سنبدأ بوضع المستطيل حيث : نحمل الصورة , وننشئ قناعاً مماثلاً وكذلك ننشأ fgdModel , bgdModel , ويعطي بارامترات المستطيل وهذا كله مباشر , لنفرض اننا سنشغل الخوارزمية ل 5 مرات .

وال Mode يجب ان يكون cv2.GC_INIT_WITH_RECT بما اننا نبدأ بمستطيل ومن ثم نشغل ال grabcut.

وفي الصورة الجديدة المعدلة للقناع ستكون البكسلات معدلة ب 4 أعلام , محددة اما للخلفية أو الامامية كما أعلاه.

وبعدها نعدله بحيث ان البكسلات بأرقام 0 أو 2 تكون خلفية اكيدة , اما البكسلات بارقام 1 و 3 تكون امامية اكيدة , وبعد جهوز القناع نضربه بالصورة الاصلية لنحصل على نتيجة التقطيع المرغوية كالكود التالي:

In [5]:
import numpy as np
import cv2
from matplotlib import pyplot as plt
%matplotlib inline

img = cv2.imread('wt.jpg')
mask = np.zeros(img.shape[:2],np.uint8)

bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)

rect = (150,60,600,566)
cv2.grabCut(img,mask,rect,
            bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)

mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]

plt.imshow(img),plt.colorbar(),plt.xticks([])
plt.yticks([])
plt.show()
None

لنلحظ النتيجة , نرى بانها جيدة ولكن هناك بعض المشاكل مثلاً دائرة على اليسار وايضاً الاسهم بالاسفل , اذا اردنا تحسينها علينا استخدام الطريقة الثانيةوتكون بتحديد القناع اولاً , عبر اداة خاصة حيث نحدد الاجسام الامامية الاكيدة باللون الابيض والخلفية الاكيدة بالاسود ونكتب مايلي:

In [13]:
# newmask is the mask image I manually labelled
newmask = cv2.imread('wtmask.jpg',0)

# whereever it is marked white (sure foreground), change mask=1
# whereever it is marked black (sure background), change mask=0
mask[newmask == 0] = 0
mask[newmask == 255] = 1

cv2.grabCut(img,mask,None,
            bgdModel,fgdModel,
            5,cv2.GC_INIT_WITH_MASK)

mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask[:,:,np.newaxis]

plt.figure(figsize=(18,9))

plt.subplot(121)
plt.imshow(mask),plt.xticks([]),
plt.yticks([])

plt.subplot(122)
plt.imshow(img),plt.xticks([]),
plt.yticks([])

plt.show()
None

ونلاحظ ان النتيجة رائعة , وكل مافعلناه اننا استخدمنا صورة قناع مع تحديد مناطق الخلفية والجسم يدوياً بدون تحديد المستطيل , ومن ثم طبقنا خوارزمية grabcut مباشرة.

مراجع اضافية:

تمارين:

  1. تتضمن امثلة OpenCV مثالاً تفاعلياً لاستخدام الخوارزمية السابقة ,ولذلك يمكنك تعلمها منه أو مشاهدة مقطع اليوتوب عنه .

  2. وهنا ايضاً يمكنك انشاء تطبيق يمكّنك من رسم مستطيل تفاعلياً , ويسمح لك ايضاً برسم خطوط يدوياً ولعرض نتحكم به هن طريق Trackbar .

المثال كما التالي:

In [ ]:
#!/usr/bin/env python
'''
===============================================================================
Interactive Image Segmentation using GrabCut algorithm.

This sample shows interactive image segmentation using grabcut algorithm.

USAGE :
    python grabcut.py <filename>

README FIRST:
    Two windows will show up, one for input and one for output.

    At first, in input window, draw a rectangle around the object using
mouse right button. Then press 'n' to segment the object (once or a few times)
For any finer touch-ups, you can press any of the keys below and draw lines on
the areas you want. Then again press 'n' for updating the output.

Key '0' - To select areas of sure background
Key '1' - To select areas of sure foreground
Key '2' - To select areas of probable background
Key '3' - To select areas of probable foreground

Key 'n' - To update the segmentation
Key 'r' - To reset the setup
Key 's' - To save the results
===============================================================================
'''

import numpy as np
import cv2
import sys

BLUE = [255,0,0]        # rectangle color
RED = [0,0,255]         # PR BG
GREEN = [0,255,0]       # PR FG
BLACK = [0,0,0]         # sure BG
WHITE = [255,255,255]   # sure FG

DRAW_BG = {'color' : BLACK, 'val' : 0}
DRAW_FG = {'color' : WHITE, 'val' : 1}
DRAW_PR_FG = {'color' : GREEN, 'val' : 3}
DRAW_PR_BG = {'color' : RED, 'val' : 2}

# setting up flags
rect = (0,0,1,1)
drawing = False         # flag for drawing curves
rectangle = False       # flag for drawing rect
rect_over = False       # flag to check if rect drawn
rect_or_mask = 100      # flag for selecting rect or mask mode
value = DRAW_FG         # drawing initialized to FG
thickness = 3           # brush thickness

def onmouse(event,x,y,flags,param):
    global img,img2,drawing,value,mask,rectangle,rect,rect_or_mask,ix,iy,rect_over

    # Draw Rectangle
    if event == cv2.EVENT_RBUTTONDOWN:
        rectangle = True
        ix,iy = x,y

    elif event == cv2.EVENT_MOUSEMOVE:
        if rectangle == True:
            img = img2.copy()
            cv2.rectangle(img,(ix,iy),(x,y),BLUE,2)
            rect = (ix,iy,abs(ix-x),abs(iy-y))
            rect_or_mask = 0

    elif event == cv2.EVENT_RBUTTONUP:
        rectangle = False
        rect_over = True
        cv2.rectangle(img,(ix,iy),(x,y),BLUE,2)
        rect = (ix,iy,abs(ix-x),abs(iy-y))
        rect_or_mask = 0
        print " Now press the key 'n' a few times until no further change \n"

    # draw touchup curves

    if event == cv2.EVENT_LBUTTONDOWN:
        if rect_over == False:
            print "first draw rectangle \n"
        else:
            drawing = True
            cv2.circle(img,(x,y),thickness,value['color'],-1)
            cv2.circle(mask,(x,y),thickness,value['val'],-1)

    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing == True:
            cv2.circle(img,(x,y),thickness,value['color'],-1)
            cv2.circle(mask,(x,y),thickness,value['val'],-1)

    elif event == cv2.EVENT_LBUTTONUP:
        if drawing == True:
            drawing = False
            cv2.circle(img,(x,y),thickness,value['color'],-1)
            cv2.circle(mask,(x,y),thickness,value['val'],-1)

# print documentation
print __doc__

# Loading images
if len(sys.argv) == 2:
    filename = sys.argv[1] # for drawing purposes
else:
    print "No input image given, so loading default image, lena.jpg \n"
    print "Correct Usage : python grabcut.py <filename> \n"
    filename = '../cpp/lena.jpg'

img = cv2.imread(filename)
img2 = img.copy()                               # a copy of original image
mask = np.zeros(img.shape[:2],dtype = np.uint8) # mask initialized to PR_BG
output = np.zeros(img.shape,np.uint8)           # output image to be shown

# input and output windows
cv2.namedWindow('output')
cv2.namedWindow('input')
cv2.setMouseCallback('input',onmouse)
cv2.moveWindow('input',img.shape[1]+10,90)

print " Instructions : \n"
print " Draw a rectangle around the object using right mouse button \n"

while(1):

    cv2.imshow('output',output)
    cv2.imshow('input',img)
    k = 0xFF & cv2.waitKey(1)

    # key bindings
    if k == 27:         # esc to exit
        break
    elif k == ord('0'): # BG drawing
        print " mark background regions with left mouse button \n"
        value = DRAW_BG
    elif k == ord('1'): # FG drawing
        print " mark foreground regions with left mouse button \n"
        value = DRAW_FG
    elif k == ord('2'): # PR_BG drawing
        value = DRAW_PR_BG
    elif k == ord('3'): # PR_FG drawing
        value = DRAW_PR_FG
    elif k == ord('s'): # save image
        bar = np.zeros((img.shape[0],5,3),np.uint8)
        res = np.hstack((img2,bar,img,bar,output))
        cv2.imwrite('grabcut_output.png',res)
        print " Result saved as image \n"
    elif k == ord('r'): # reset everything
        print "resetting \n"
        rect = (0,0,1,1)
        drawing = False
        rectangle = False
        rect_or_mask = 100
        rect_over = False
        value = DRAW_FG
        img = img2.copy()
        mask = np.zeros(img.shape[:2],dtype = np.uint8) # mask initialized to PR_BG
        output = np.zeros(img.shape,np.uint8)           # output image to be shown
    elif k == ord('n'): # segment the image
        print """ For finer touchups, mark foreground and background after pressing keys 0-3
        and again press 'n' \n"""
        if (rect_or_mask == 0):         # grabcut with rect
            bgdmodel = np.zeros((1,65),np.float64)
            fgdmodel = np.zeros((1,65),np.float64)
            cv2.grabCut(img2,mask,rect,bgdmodel,fgdmodel,1,cv2.GC_INIT_WITH_RECT)
            rect_or_mask = 1
        elif rect_or_mask == 1:         # grabcut with mask
            bgdmodel = np.zeros((1,65),np.float64)
            fgdmodel = np.zeros((1,65),np.float64)
            cv2.grabCut(img2,mask,rect,bgdmodel,fgdmodel,1,cv2.GC_INIT_WITH_MASK)

    mask2 = np.where((mask==1) + (mask==3),255,0).astype('uint8')
    output = cv2.bitwise_and(img2,img2,mask=mask2)

cv2.destroyAllWindows()

ليست هناك تعليقات:

إرسال تعليق