پردازش تصویر در OpenCV
هرمهای تصویری (Image Pyramids)
هدف#
در این فصل،
- با هرمهای تصویری (Image Pyramids) آشنا خواهیم شد
- از هرمهای تصویری برای ساخت یک میوه جدید به نام «Orapple» استفاده خواهیم کرد
- توابع زیر را خواهیم دید: cv.pyrUp(), cv.pyrDown()
تئوری#
معمولاً با تصویری با اندازه ثابت کار میکنیم. اما در مواردی نیاز داریم با همان تصویر در رزولوشنهای (Resolution) مختلف کار کنیم. برای مثال، هنگام جستجو برای چیزی در تصویر، مانند چهره، مطمئن نیستیم که شیء مورد نظر در چه اندازهای در تصویر حضور دارد. در این حالت، باید مجموعهای از همان تصویر با رزولوشنهای مختلف بسازیم و شیء را در تمام آنها جستجو کنیم. این مجموعه تصاویر با رزولوشنهای مختلف هرمهای تصویری (Image Pyramids) نامیده میشوند (زیرا وقتی به صورت پشتهای قرار میگیرند که تصویر با بالاترین رزولوشن در پایین و تصویر با کمترین رزولوشن در بالا قرار دارد، شبیه یک هرم به نظر میرسد).
دو نوع هرم تصویری وجود دارد. ۱) هرم گاوسی (Gaussian Pyramid) و ۲) هرم لاپلاسی (Laplacian Pyramids)
سطوح بالاتر (رزولوشن پایینتر) در هرم گاوسی با حذف ردیفها و ستونهای متوالی از تصویر سطح پایینتر (رزولوشن بالاتر) تشکیل میشوند. سپس هر پیکسل در سطح بالاتر با مشارکت ۵ پیکسل از سطح زیرین با وزنهای گاوسی شکل میگیرد. با انجام این کار، یک تصویر به تصویر تبدیل میشود. بنابراین مساحت به یکچهارم مساحت اصلی کاهش مییابد. به این عمل یک Octave گفته میشود. همین الگو با بالا رفتن در هرم ادامه مییابد (یعنی رزولوشن کاهش مییابد). به همین ترتیب در هنگام گسترش، مساحت در هر سطح ۴ برابر میشود. میتوانیم هرمهای گاوسی را با استفاده از توابع cv.pyrDown() و cv.pyrUp() بیابیم.
img = cv.imread('messi5.jpg')
assert img is not None, "file could not be read, check with os.path.exists()"
lower_reso = cv.pyrDown(higher_reso)
تصویر زیر ۴ سطح از یک هرم تصویری را نشان میدهد.

اکنون میتوانید با تابع cv.pyrUp() از هرم تصویری پایین بیایید.
higher_reso2 = cv.pyrUp(lower_reso)
توجه داشته باشید که higher_reso2 با higher_reso برابر نیست، زیرا وقتی رزولوشن را کاهش میدهید، اطلاعات را از دست میدهید. تصویر زیر ۳ سطح پایینتر از هرم است که از کوچکترین تصویر در حالت قبلی ایجاد شده است. آن را با تصویر اصلی مقایسه کنید:

هرمهای لاپلاسی از هرمهای گاوسی تشکیل میشوند. تابع اختصاصی برای آن وجود ندارد. تصاویر هرم لاپلاسی شبیه تصاویر لبه هستند. بیشتر عناصر آنها صفر است. از آنها در فشردهسازی تصویر استفاده میشود. یک سطح در هرم لاپلاسی با تفاوت بین آن سطح در هرم گاوسی و نسخه گسترشیافته سطح بالاتر آن در هرم گاوسی تشکیل میشود. سه سطح از یک هرم لاپلاسی به شکل زیر خواهند بود (کنتراست برای بهبود محتوا تنظیم شده است):

ترکیب تصویر با استفاده از هرمها (Image Blending using Pyramids)#
یکی از کاربردهای هرمها، ترکیب تصویر (Image Blending) است. برای مثال، در دوخت تصویر (Image Stitching)، باید دو تصویر را به هم متصل کنید، اما ممکن است به دلیل ناپیوستگیهای بین تصاویر، نتیجه خوب به نظر نرسد. در این حالت، ترکیب تصویر با هرمها یک ترکیب یکپارچه (Seamless Blending) بدون از دست دادن دادههای زیاد در تصاویر به شما میدهد. یک مثال کلاسیک از این روش، ترکیب دو میوه، پرتقال و سیب است. اکنون نتیجه را ببینید تا منظور را درک کنید:

لطفاً اولین مرجع در منابع اضافی را بررسی کنید؛ این مرجع جزئیات دیاگرامی کاملی درباره ترکیب تصویر، هرمهای لاپلاسی و غیره دارد. به طور خلاصه این کار به صورت زیر انجام میشود:
- دو تصویر سیب و پرتقال را بارگذاری کنید
- هرمهای گاوسی برای سیب و پرتقال بیابید (در این مثال خاص، تعداد سطوح ۶ است)
- از هرمهای گاوسی، هرمهای لاپلاسی آنها را بیابید
- اکنون نیمه چپ سیب و نیمه راست پرتقال را در هر سطح از هرمهای لاپلاسی به هم بپیوندید
- در نهایت از این هرمهای تصویری به هم پیوسته، تصویر اصلی را بازسازی کنید.
در زیر کد کامل آمده است. (برای سادگی، هر مرحله به طور جداگانه انجام شده که ممکن است حافظه بیشتری مصرف کند. اگر بخواهید میتوانید آن را بهینه کنید.)
import cv2 as cv
import numpy as np,sys
A = cv.imread('apple.jpg')
B = cv.imread('orange.jpg')
assert A is not None, "file could not be read, check with os.path.exists()"
assert B is not None, "file could not be read, check with os.path.exists()"
# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in range(6):
G = cv.pyrDown(G)
gpA.append(G)
# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in range(6):
G = cv.pyrDown(G)
gpB.append(G)
# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in range(5,0,-1):
GE = cv.pyrUp(gpA[i])
L = cv.subtract(gpA[i-1],GE)
lpA.append(L)
# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in range(5,0,-1):
GE = cv.pyrUp(gpB[i])
L = cv.subtract(gpB[i-1],GE)
lpB.append(L)
# Now add left and right halves of images in each level
LS = []
for la,lb in zip(lpA,lpB):
rows,cols,dpt = la.shape
ls = np.hstack((la[:,0:cols//2], lb[:,cols//2:]))
LS.append(ls)
# now reconstruct
ls_ = LS[0]
for i in range(1,6):
ls_ = cv.pyrUp(ls_)
ls_ = cv.add(ls_, LS[i])
# image with direct connecting each half
real = np.hstack((A[:,:cols//2],B[:,cols//2:]))
cv.imwrite('Pyramid_blending2.jpg',ls_)
cv.imwrite('Direct_blending.jpg',real)