پردازش تصویر در OpenCV
هیستوگرام - 4: بازتاب هیستوگرام (Histogram Backprojection)
هدف#
در این فصل، با بازتاب هیستوگرام (Histogram Backprojection) آشنا میشویم.
تئوری#
این روش توسط Michael J. Swain و Dana H. Ballard در مقالهای با عنوان Indexing via color histograms ارائه شده است.
به زبان ساده چیست؟ از آن برای تقسیمبندی تصویر (Image Segmentation) یا یافتن اشیاء مورد نظر در یک تصویر استفاده میشود. به زبان ساده، این روش یک تصویر به همان اندازه تصویر ورودی (اما تککاناله) ایجاد میکند که در آن هر پیکسل بیانگر احتمال تعلق آن پیکسل به شیء مورد نظر ما است. به زبانی سادهتر، تصویر خروجی شیء مورد نظر ما را با رنگ سفیدتر نسبت به بقیه نواحی نشان میدهد. البته این یک توضیح شهودی است. از بازتاب هیستوگرام در الگوریتمهایی مانند camshift استفاده میشود.
چطور این کار را انجام میدهیم؟ یک هیستوگرام از تصویری که شیء مورد نظر ما را شامل میشود محاسبه میکنیم (در مثال ما، زمین، نه بازیکن و سایر چیزها). برای نتایج بهتر، شیء باید تا حد ممکن تمام تصویر را پر کند. هیستوگرام رنگی نسبت به هیستوگرام در مقیاس خاکستری ارجحیت دارد، زیرا رنگ شیء روش بهتری برای تعریف آن نسبت به شدت روشنایی در مقیاس خاکستری است. سپس این هیستوگرام را روی تصویر آزمایشی که میخواهیم شیء را در آن بیابیم «بازتاب» میدهیم؛ یعنی احتمال تعلق هر پیکسل به زمین را محاسبه و نمایش میدهیم. خروجی پس از آستانهگذاری مناسب فقط زمین را به ما میدهد.
الگوریتم در Numpy#
- ابتدا باید هیستوگرام رنگی هر دو شیء مورد جستجو (آن را 'M' بنامیم) و تصویری که در آن جستجو میکنیم (آن را 'I' بنامیم) را محاسبه کنیم.
import numpy as np
import cv2 as cvfrom matplotlib import pyplot as plt
#roi is the object or region of object we need to find
roi = cv.imread('rose_red.png')
assert roi is not None, "file could not be read, check with os.path.exists()"
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
#target is the image we search in
target = cv.imread('rose.png')
assert target is not None, "file could not be read, check with os.path.exists()"
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
# Find the histograms using calcHist. Can be done with np.histogram2d also
M = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
I = cv.calcHist([hsvt],[0, 1], None, [180, 256], [0, 180, 0, 256] )
- نسبت را پیدا کنید. سپس R را بازتاب دهید، یعنی از R به عنوان پالت استفاده کنید و یک تصویر جدید بسازید که در آن هر پیکسل احتمال تعلق آن به هدف است. یعنی B(x,y) = R[h(x,y),s(x,y)] که در آن h رنگ (Hue) و s اشباع (Saturation) پیکسل در (x,y) است. پس از آن شرط را اعمال کنید.
h,s,v = cv.split(hsvt)
B = R[h.ravel(),s.ravel()]
B = np.minimum(B,1)
B = B.reshape(hsvt.shape[:2])
- اکنون یک پیچیدگی (convolution) با یک دیسک دایرهای اعمال کنید: ، که در آن D کرنل دیسک است.
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
cv.filter2D(B,-1,disc,B)
B = np.uint8(B)
cv.normalize(B,B,0,255,cv.NORM_MINMAX)
- اکنون مکان بیشینه شدت، مکان شیء را به ما میدهد. اگر انتظار داریم یک ناحیه در تصویر پیدا کنیم، آستانهگذاری با مقدار مناسب نتیجه خوبی میدهد.
ret,thresh = cv.threshold(B,50,255,0)
همین!
بازتاب در OpenCV#
OpenCV یک تابع توکار cv.calcBackProject() دارد. پارامترهای آن تقریباً مشابه تابع cv.calcHist() هستند. یکی از پارامترهای آن هیستوگرام شیء است که باید آن را پیدا کنیم. همچنین هیستوگرام شیء باید قبل از ارسال به تابع بازتاب، نرمالسازی شود. این تابع تصویر احتمال را برمیگرداند. سپس تصویر را با یک کرنل دیسک پیچیده میکنیم و آستانهگذاری را اعمال میکنیم. در زیر کد و خروجی من آمده است:
import numpy as np
import cv2 as cv
roi = cv.imread('rose_red.png')
assert roi is not None, "file could not be read, check with os.path.exists()"
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
target = cv.imread('rose.png')
assert target is not None, "file could not be read, check with os.path.exists()"
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
# calculating object histogram
roihist = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
# normalize histogram and apply backprojection
cv.normalize(roihist,roihist,0,255,cv.NORM_MINMAX)
dst = cv.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)
# Now convolute with circular disc
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
cv.filter2D(dst,-1,disc,dst)
# threshold and binary AND
ret,thresh = cv.threshold(dst,50,255,0)
thresh = cv.merge((thresh,thresh,thresh))
res = cv.bitwise_and(target,thresh)
res = np.vstack((target,thresh,res))
cv.imwrite('res.jpg',res)
در زیر یک مثال است که روی آن کار کردهام. از ناحیه داخل مستطیل آبی به عنوان شیء نمونه استفاده کردم و میخواستم کل زمین را استخراج کنم.

منابع اضافی#
- «Indexing via color histograms»، Swain، Michael J.، سومین کنفرانس بینالمللی بینایی کامپیوتر، ۱۹۹۰.