پردازش تصویر در OpenCV
هیستوگرام - 2: یکسانسازی هیستوگرام (Histogram Equalization)
هدف#
در این بخش،
- مفاهیم یکسانسازی هیستوگرام (Histogram Equalization) را فرا میگیریم و از آن برای بهبود کنتراست تصاویر استفاده میکنیم.
تئوری#
تصویری را در نظر بگیرید که مقادیر پیکسلهای آن فقط در یک بازه خاص قرار دارند. برای مثال، یک تصویر روشن تمام پیکسلهایش را در مقادیر بالا متمرکز خواهد داشت. اما یک تصویر خوب باید پیکسلهایی از تمام نواحی داشته باشد. پس باید این هیستوگرام را به هر دو انتها گسترش داد (همانطور که در تصویر زیر از ویکیپدیا نشان داده شده) و این همان کاری است که یکسانسازی هیستوگرام انجام میدهد (به زبان ساده). این کار معمولاً کنتراست تصویر را بهبود میبخشد.

توصیه میشود صفحه ویکیپدیا را در مورد Histogram Equalization مطالعه کنید. این صفحه توضیحات بسیار خوبی همراه با مثالهای عملی دارد که پس از خواندن آن، تقریباً همه چیز را درک خواهید کرد. در اینجا، پیادهسازی آن با Numpy را بررسی میکنیم و سپس تابع OpenCV را خواهیم دید.
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('wiki.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf = hist.cumsum()
cdf_normalized = cdf * float(hist.max()) / cdf.max()
plt.plot(cdf_normalized, color = 'b')
plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')
plt.show()

میتوانید ببینید که هیستوگرام در ناحیه روشنتر قرار دارد. ما به طیف کامل نیاز داریم. برای این کار، به یک تابع تبدیل نیاز داریم که پیکسلهای ورودی در ناحیه روشن را به پیکسلهای خروجی در کل طیف نگاشت کند. این همان کاری است که یکسانسازی هیستوگرام انجام میدهد.
اکنون کمینه مقدار هیستوگرام (به استثنای ۰) را پیدا میکنیم و معادله یکسانسازی هیستوگرام را همانطور که در صفحه ویکی آمده اعمال میکنیم. اما اینجا از مفهوم آرایههای masked در Numpy استفاده شده است. برای آرایه masked، تمام عملیات روی عناصر غیر masked انجام میشود. میتوانید در مستندات Numpy بیشتر درباره آرایههای masked بخوانید.
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
cdf = np.ma.filled(cdf_m,0).astype('uint8')
اکنون جدول جستجو (look-up table) داریم که به ما میگوید مقدار پیکسل خروجی برای هر مقدار پیکسل ورودی چیست. پس کافی است تبدیل را اعمال کنیم.
img2 = cdf[img]
اکنون هیستوگرام و cdf آن را مانند قبل محاسبه میکنیم (این کار را خودتان انجام دهید) و نتیجه به صورت زیر خواهد بود:

یکی دیگر از ویژگیهای مهم این است که حتی اگر تصویر تاریکتری داشتیم (به جای تصویر روشنی که استفاده کردیم)، پس از یکسانسازی تقریباً همان تصویر را به دست میآوریم. در نتیجه، از این روش به عنوان یک «ابزار مرجع» برای یکسانسازی شرایط نوری تمام تصاویر استفاده میشود. این در بسیاری از موارد مفید است. برای مثال، در تشخیص چهره (Face Recognition)، قبل از آموزش دادههای چهره، تصاویر چهره با یکسانسازی هیستوگرام پردازش میشوند تا همه آنها شرایط نوری یکسانی داشته باشند.
یکسانسازی هیستوگرام در OpenCV#
OpenCV تابعی برای این کار دارد: cv.equalizeHist(). ورودی آن فقط یک تصویر در مقیاس خاکستری است و خروجی تصویر یکسانسازیشده هیستوگرام میباشد.
در زیر یک قطعه کد ساده برای نشان دادن نحوه استفاده از آن برای همان تصویری که استفاده کردیم آمده است:
img = cv.imread('wiki.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
equ = cv.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv.imwrite('res.png',res)

بنابراین اکنون میتوانید تصاویر مختلفی با شرایط نوری متفاوت بگیرید، آنها را یکسانسازی کنید و نتایج را بررسی نمایید.
یکسانسازی هیستوگرام زمانی مناسب است که هیستوگرام تصویر در یک ناحیه خاص محدود باشد. در مکانهایی که تغییرات شدت روشنایی زیاد است و هیستوگرام ناحیه وسیعی را پوشش میدهد (یعنی هم پیکسلهای روشن و هم تاریک وجود دارند)، این روش خوب عمل نمیکند. لطفاً لینکهای SOF موجود در منابع اضافی را بررسی کنید.
CLAHE (یکسانسازی تطبیقی هیستوگرام با محدودیت کنتراست)#
اولین روش یکسانسازی هیستوگرامی که دیدیم، کنتراست کلی تصویر را در نظر میگیرد. در بسیاری از موارد، این ایده خوبی نیست. برای مثال، تصویر زیر یک تصویر ورودی و نتیجه آن پس از یکسانسازی هیستوگرام سراسری را نشان میدهد.

درست است که کنتراست پسزمینه پس از یکسانسازی هیستوگرام بهبود یافته است. اما چهره مجسمه را در هر دو تصویر مقایسه کنید. بیشتر اطلاعات آن به دلیل روشنایی بیش از حد از دست رفته است. این به این دلیل است که هیستوگرام آن در یک ناحیه خاص محدود نیست (هیستوگرام تصویر ورودی را رسم کنید تا بهتر درک کنید).
برای حل این مشکل، از یکسانسازی تطبیقی هیستوگرام (adaptive histogram equalization) استفاده میشود. در این روش، تصویر به بلوکهای کوچکی به نام «tile» تقسیم میشود (اندازه tileSize بهطور پیشفرض ۸×۸ در OpenCV است). سپس هر یک از این بلوکها بهطور معمول یکسانسازی هیستوگرام میشوند. بنابراین در یک ناحیه کوچک، هیستوگرام در یک ناحیه کوچک محدود میشود (مگر اینکه نویز وجود داشته باشد). اگر نویز وجود داشته باشد، تقویت میشود. برای جلوگیری از این، محدودیت کنتراست (contrast limiting) اعمال میشود. اگر هر bin هیستوگرام بالاتر از حد کنتراست مشخصشده باشد (بهطور پیشفرض ۴۰ در OpenCV)، آن پیکسلها برش داده شده و قبل از یکسانسازی هیستوگرام بهطور یکنواخت به binهای دیگر توزیع میشوند. پس از یکسانسازی، برای حذف آرتیفکتهای مرزی tile، درونیابی دوخطی (bilinear interpolation) اعمال میشود.
قطعه کد زیر نحوه اعمال CLAHE در OpenCV را نشان میدهد:
import numpy as np
import cv2 as cv
img = cv.imread('tsukuba_l.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
# create a CLAHE object (Arguments are optional).
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv.imwrite('clahe_2.jpg',cl1)
نتیجه زیر را ببینید و با نتایج بالا مقایسه کنید، بهویژه ناحیه مجسمه:

منابع اضافی#
- صفحه ویکیپدیا درباره Histogram Equalization
- Masked Arrays در Numpy
همچنین این سوالات SOF را درباره تنظیم کنتراست بررسی کنید: