پردازش تصویر در OpenCV
آستانهگذاری تصویر (Image Thresholding)
هدف#
- در این آموزش، آستانهگذاری ساده (Simple Thresholding)، آستانهگذاری تطبیقی (Adaptive Thresholding) و آستانهگذاری اتسو (Otsu's Thresholding) را خواهید آموخت.
- توابع cv.threshold و cv.adaptiveThreshold را خواهید آموخت.
آستانهگذاری ساده (Simple Thresholding)#
در اینجا موضوع ساده است. برای هر پیکسل، همان مقدار آستانه اعمال میشود. اگر مقدار پیکسل کوچکتر یا مساوی آستانه باشد، به ۰ تنظیم میشود، در غیر این صورت به حداکثر مقدار تنظیم میشود. از تابع cv.threshold برای اعمال آستانهگذاری استفاده میشود. آرگومان اول تصویر منبع است که باید یک تصویر در مقیاس خاکستری باشد. آرگومان دوم مقدار آستانه است که برای طبقهبندی مقادیر پیکسل استفاده میشود. آرگومان سوم حداکثر مقداری است که به پیکسلهایی که از آستانه تجاوز میکنند تخصیص مییابد. OpenCV انواع مختلفی از آستانهگذاری را فراهم میکند که توسط پارامتر چهارم تابع مشخص میشود. آستانهگذاری پایهای که در بالا توضیح داده شد با استفاده از نوع cv.THRESH_BINARY انجام میشود. تمام انواع آستانهگذاری ساده عبارتند از:
- cv.THRESH_BINARY
- cv.THRESH_BINARY_INV
- cv.THRESH_TRUNC
- cv.THRESH_TOZERO
- cv.THRESH_TOZERO_INV
برای تفاوتها به مستندات انواع مراجعه کنید.
این متد دو خروجی برمیگرداند. اول آستانهای است که استفاده شده و خروجی دوم تصویر آستانهگذاریشده است.
این کد انواع مختلف آستانهگذاری ساده را مقایسه میکند:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('gradient.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
این کد نتیجه زیر را میدهد:

آستانهگذاری تطبیقی (Adaptive Thresholding)#
در بخش قبل، از یک مقدار سراسری به عنوان آستانه استفاده کردیم. اما این ممکن است در همه موارد مناسب نباشد، مثلاً اگر تصویری در نواحی مختلف شرایط نوری متفاوتی داشته باشد. در این صورت، آستانهگذاری تطبیقی میتواند کمک کند. در اینجا، الگوریتم آستانه را برای یک پیکسل بر اساس یک ناحیه کوچک اطراف آن تعیین میکند. بنابراین برای نواحی مختلف یک تصویر آستانههای متفاوتی به دست میآوریم که نتایج بهتری برای تصاویر با روشنایی متغیر میدهد.
علاوه بر پارامترهای توضیح دادهشده در بالا، متد cv.adaptiveThreshold سه پارامتر ورودی دریافت میکند:
adaptiveMethod تعیین میکند چگونه مقدار آستانه محاسبه میشود:
- cv.ADAPTIVE_THRESH_MEAN_C: مقدار آستانه برابر میانگین ناحیه همسایگی منهای ثابت C است.
- cv.ADAPTIVE_THRESH_GAUSSIAN_C: مقدار آستانه برابر مجموع وزندار گاوسی مقادیر همسایگی منهای ثابت C است.
blockSize اندازه ناحیه همسایگی را تعیین میکند و C ثابتی است که از میانگین یا مجموع وزندار پیکسلهای همسایه کم میشود.
کد زیر آستانهگذاری سراسری و آستانهگذاری تطبیقی را برای یک تصویر با روشنایی متغیر مقایسه میکند:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('sudoku.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
img = cv.medianBlur(img,5)
ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv.THRESH_BINARY,11,2)
titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]
for i in range(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
نتیجه:

دوتاییسازی اتسو (Otsu's Binarization)#
در آستانهگذاری سراسری، یک مقدار دلخواه به عنوان آستانه انتخاب کردیم. در مقابل، روش اتسو از نیاز به انتخاب یک مقدار اجتناب میکند و آن را به طور خودکار تعیین میکند.
یک تصویر را با تنها دو مقدار تصویری متمایز (تصویر دوحالته یا bimodal) در نظر بگیرید، جایی که هیستوگرام تنها از دو قله تشکیل میشود. یک آستانه مناسب در وسط این دو مقدار خواهد بود. به طور مشابه، روش اتسو یک مقدار آستانه سراسری بهینه را از هیستوگرام تصویر تعیین میکند.
برای انجام این کار، از تابع cv.threshold() استفاده میشود که cv.THRESH_OTSU به عنوان یک فلگ اضافی ارسال میشود. مقدار آستانه میتواند به صورت دلخواه انتخاب شود. سپس الگوریتم مقدار آستانه بهینه را پیدا میکند که به عنوان اولین خروجی برگردانده میشود.
مثال زیر را ببینید. تصویر ورودی یک تصویر پر از نویز است. در حالت اول، آستانهگذاری سراسری با مقدار ۱۲۷ اعمال میشود. در حالت دوم، آستانهگذاری اتسو مستقیماً اعمال میشود. در حالت سوم، ابتدا تصویر با یک هسته گاوسی ۵x۵ فیلتر میشود تا نویز حذف شود، سپس آستانهگذاری اتسو اعمال میشود. ببینید چگونه فیلتر کردن نویز نتیجه را بهبود میبخشد.
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
# global thresholding
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu's thresholding
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# plot all the images and their histograms
images = [img, 0, th1,
img, 0, th2,
blur, 0, th3]
titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
'Original Noisy Image','Histogram',"Otsu's Thresholding",
'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
for i in range(3):
plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()
نتیجه:

روش دوتاییسازی اتسو چگونه کار میکند؟#
این بخش یک پیادهسازی Python از دوتاییسازی اتسو را برای نشان دادن نحوه کارکرد واقعی آن ارائه میدهد. اگر به این موضوع علاقهای ندارید، میتوانید از آن بگذرید.
از آنجا که با تصاویر دوحالته کار میکنیم، الگوریتم اتسو سعی میکند یک مقدار آستانه (t) پیدا کند که واریانس درونکلاسی وزندار (Weighted Within-Class Variance) را که توسط رابطه زیر داده میشود، کمینه کند:
که در آن
در واقع مقداری از t را پیدا میکند که بین دو قله قرار میگیرد به طوری که واریانسها برای هر دو کلاس کمینه باشند. این میتواند به سادگی در Python به صورت زیر پیادهسازی شود:
img = cv.imread('noisy2.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
blur = cv.GaussianBlur(img,(5,5),0)
# find normalized_histogram, and its cumulative distribution function
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.sum()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in range(1,256):
p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
if q1 < 1.e-6 or q2 < 1.e-6:
continue
b1,b2 = np.hsplit(bins,[i]) # weights
# finding means and variances
m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2
# calculates the minimization function
fn = v1*q1 + v2*q2
if fn < fn_min:
fn_min = fn
thresh = i
# find otsu's threshold value with OpenCV function
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print( "{} {}".format(thresh,ret) )
منابع اضافی#
- پردازش تصویر دیجیتال (Digital Image Processing)، Rafael C. Gonzalez
تمرینها#
- بهینهسازیهایی برای دوتاییسازی اتسو وجود دارد. میتوانید آنها را جستجو کرده و پیادهسازی کنید.