Lesson_15_Histogram_equal

  |   Source

تسوية الهستوغرام :

الهدف:

</div>

في هذا الفصل : سنتعلم مفهوم تسوية الهستوغرام ونستخدمه لتحسين تباين صورنا

النظرية:

</div>

فليكن لدينا صورة قيم بكسلاتها متجمعة في منطقة واحدة من الصورة . مثلاً الصور اللامعة قيم بكسلاتها متجمعة بالمناطق العالية القيم . ولكن الصور الجيدة تمتلك توزعاً منتظما للقيم , ولذلك نحتاج لنشر تلك القيم على مجال اوسع . وهذا بالتالي يحسن تباين الصورة
لتفاصيل اكثر , زر صفحة الموسوعة ويكيبيديا الخاصة بالموضوع . , حيث هناك شرح مفصل ( بالانكليزية) , اما هنا فسنعرض تطبيقOpenCV و Numpy بالتتالي , كما الكود التالي:
In [2]:
%matplotlib inline

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('wiki.jpg',0)

hist,bins = np.histogram(img.flatten(),256,[0,256])

cdf = hist.cumsum()
cdf_normalized = cdf * float(hist.max())/ cdf.max()
In [3]:
plt.style.use('fivethirtyeight')
plt.figure(figsize=(16,8))
plt.subplot(121)
plt.grid('off')
plt.imshow(img , cmap = 'gray')

plt.subplot(122)
plt.plot(cdf_normalized, color = 'b')
plt.hist(img.flatten(),256,[0,256], color = 'g')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')

plt.show()
ولذلك نحتاج لانشاء تابع تحويل من الصورة الاولى للثانية , يدعى تسوية الهستوغرام. والان علينا البحث عن اخفض قيمة بعد الصفر للشدات لتطبيق علاقة التسوية , ولكن يمكن ايضاً استخدام مفهوم المصفوفات المقنعة في Numpy حيث تجرى العمليات فقط على العناصر غير المقنعة كالتالي:
In [4]:
# hist op
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255.0/(cdf_m.max()-cdf_m.min())
cdf = np.ma.filled(cdf_m,0).astype('uint8')
وهنا اصبح لدينا الجدول البحثي للتحويل للصورة ويمكننا كنابة:
In [5]:
img2 = cdf[img]
cdf.shape
Out[5]:
(256,)
والان يمكننا حساب الهستوغرام والcdf كما السابق والنتيجة كما التالي:
In [5]:
plt.figure(figsize=(16,8))
plt.subplot(121)
plt.grid('off')

plt.imshow(img2, cmap = 'gray')
plt.xticks([])
plt.yticks([])

hist,bins = np.histogram(img2.flatten(),256,[0,256])

cdf = hist.cumsum()
cdf_normalized = cdf * float(hist.max())/ cdf.max()

plt.subplot(122)
plt.plot(cdf_normalized, color = 'b')
plt.hist(img2.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')

plt.show()
وهناك امر اخر , وهو انه حتى ولو كانت هذه الصور اغمق , فسنحصل في النهاية على نفس النتيجة , اي يمكن استخدام هذا كشروط اضاءة مرجعية , وهذا مفيد بعدة حالات , مثلاً حيث نريد اخذ عينات من صور الوجه للتعرف عليها فاننا نجعل كافة الصور ذات هستوغرام محسن اولاً..
التالي يبين أداة تفاعلية , \لن تعمل في الا مع تشغيل الدفتر\ لتعديل الهستوغرام وملاحظة تغير تباين الصورة أثناء ذلك :)
In [1]:
import ipywidgets as widgets
L_slide = widgets.Layout(width='80%',height='3em')
L_button = widgets.Layout(width='20%',height='3em')

import IPython

img = cv2.imread('wiki.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf0 = hist.cumsum()
cdf_normalized1 = cdf0 * float(hist.max())/ cdf.max()

def f1(x=10):
    # hist op
    IPython.display.clear_output()
    plt.clf()
    plt.figure(figsize=(9,9))
    cdf_m = (cdf0 )*255.0/(cdf0.max())
    cdf_m = np.ma.masked_outside(cdf_m,x.new[0],x.new[1])
    cdf_m = (cdf_m - cdf_m.min())*255.0/(cdf_m.max()-cdf_m.min())
    cdf = np.ma.filled(cdf_m,0).astype('uint8')
    img2 = cdf[img]
    hist2,bins = np.histogram(img2.flatten(),256,[0,255])

    cdf2 = hist.cumsum()
    cdf_normalized2 = cdf2 * float(hist.max())/ cdf2.max()
    plt.figure(1)
    plt.plot(cdf_normalized2, color = 'b')
    plt.hist(img2.flatten(),256,list(x.new), color = 'r')
    plt.xlim(list(x.new))
    plt.legend(('cdf','histogram'), loc = 'upper left')
    plt.show()

def f2(b):
    IPython.display.clear_output()
    plt.figure(figsize=(9,9))
    plt.grid('off')
    x = sld.value;
    cdf_m = (cdf0 )*255.0/(cdf0.max())
    cdf_m = np.ma.masked_outside(cdf_m,x[0],x[1])
    cdf_m = (cdf_m - cdf_m.min())*255.0/(cdf_m.max()-cdf_m.min())
    #print(cdf_m.shape)
    cdf = np.ma.filled(cdf_m,0).astype('uint8')
    img2 = cdf[img]
    plt.imshow(img2, cmap = 'gray')
    plt.xticks([])
    plt.yticks([])
    plt.show()


sld = widgets.IntRangeSlider(min=0,max=255,description='Expand range',continuous_update=False,
                        layout=L_slide);
sld.observe(f1,'value')
btn = widgets.Button(description='show image',layout=L_button,button_style='primary')
#(f1,'value')
inpts = [sld,
         btn]
btn.on_click(f2)
widgets.HBox(inpts)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-f090108db3a1> in <module>()
      5 import IPython
      6 
----> 7 img = cv2.imread('wiki.jpg',0)
      8 hist,bins = np.histogram(img.flatten(),256,[0,256])
      9 cdf0 = hist.cumsum()

NameError: name 'cv2' is not defined
In [8]:
btn.keys; 
cdf_m = np.ma.masked_inside(cdf0,10,100)
#f2()
#cdf_m
تسوية الهستوغرام في OpenCV:
لدى OpenCV التابع التالي الذي يحسب الصورة المعدلة من الصورة الرمادية في الدخل **cv2.equalizeHist** والكود التالي يبين استخدامها :
In [5]:
img = cv2.imread('wiki.jpg',0)
equ = cv2.equalizeHist(img)

res = np.hstack((img,equ)) #stacking images side-by-side

plt.figure(figsize=(8,4))
plt.imshow(res,cmap = 'gray')
plt.xticks([])
plt.yticks([])

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

CLAHE (تسوية الهستوغرام المتكيفة المحدودة التباين):

التعديل السابق للهستوغرام ياخذ بعين الاعتبار التباين الكلي للصورة , ولكن احياناً قد لا يصلح هذا , فالصورة التالية تظهر صورة الدخل وخرجها بعد تسوية الهستوغرام الشاملة:
الخلفية قد تكون تحسنت بالصورة السابقة ولكن ملامح الوجه تبدو زائلة بالمقارنة و هذا لان الهستوغرام غير مركز في منطقة واحدة كما سبق,
نحل هذه المشكلة باستخدام ** تسوية الهستوغرام المتكيفة** , وذلك يتم باخذ مكعبات (افتراضياً 8X8 )وتسوية الهستوغرام لكل منها على حدا , حيث نلاحظ ان هذا يأخذ بعين الاعتبار جزءاً من الصورة فقط وفي حالة وجود ضجيج يتم حذف اي قيمة لعمود من الهستوغرام الاصلي تتجاوز ال 40 افتراضياً ثم التسوية , واخيراً يتم اجراء استيفاء ثنائي الخطية على الحدود لتنعيم الانتقال ,
التالي هو كود كمثال لتطبيق هذه الطريقة في OpenCV:
In [10]:
img = cv2.imread('tsukuba_l.png',0)

# create a CLAHE object (Arguments are optional).
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)

# interactive 
#from mpld3 import disable_notebook
#disable_notebook()

res = np.hstack([img,cl1])
plt.figure(figsize=(8,4))
plt.imshow(res ,cmap = 'gray')
plt.xticks([])
plt.yticks([])
plt.show()
وكما نرى النتيجة , حيث تم تحاسين تفاصيل الوجه.

الهستوغرام ثنائي الابعاد:

الهدف:

سنتعلم هنا ايجاد ورسم الهستوغرام ثنائي الابعاد , وسيكون هذا جيداً للفصول القادمة .

مقدمة :

في السابق , اوجدنا الهستوغرام احادي البعد , اما حالياً فسنبحث عن خاصيتين لرسمهما بشكل ثلاثي الابعاد , وعادة تكونان ال Heu & Saturation لكل بكسل . هناك مثال يأتي مع تنصيب OpenCV يتعلق بالرسم هكذا وبالتالي سنشرح المفهوم والذي سيفيد بعمليات اخرى مثل الاسقاط الخلفي للهستوغرام.

الهستوغرام 2D في OpenCV:

هذا بسيط جداً , ويتم باستخدام نفس التابع , cv2.calcHist وللتحويل نحتاج لنقل الصورة لفضاء الالوان HSV ,(سابقاً نقلناها للرمادي) والبارامترات كالتالي:
  • [0,1]=channels: لاننا نحتاج قناتين S و H.
  • [180,256]=bins: حيث 180 للمستوي H و 256 للمستوي S.
  • [0,180,0,256]=range: قيمة ال Hue تستقر بين ال 0 وال 180 اما ال Saturation بين ال0 وال 256.
الان الى الكود:
In [7]:
img = cv2.imread('wt.jpg')
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)

hist = cv2.calcHist([hsv],
                    [0, 1], None, [180, 256], [0, 180, 0, 256])

print hist.shape
(180, 256)

الهستوغرام 2D في Numpy:

ولدى Numpy تابع يقوم ايضاً بالحساب وهو **np.histogram2d** كالتالي:
In [8]:
h,s,v = hsv[:,:,0],hsv[:,:,1],hsv[:,:,2]

hist, xbins, ybins = np.histogram2d(
    h.ravel(),s.ravel(),[180,256],[[0,180],[0,256]])
print hist.shape
(180, 256)
المتغير الاول هو المستويH , المتغير الثاني هو المستوي S , الثالث هو عدد ال bins والرابع مجالاتها..

رسم الهستوغرامات ال 2D:


  1. الطريقة الاولى : باستخدام cv2.imshow: بما ان النتيجة هي مصفوفة ثنائية فلذلك يمكن عرضها كصورة رمادية باستخدام التابع السابق , ولكن لن نحصل على فكرة كبيرة عن الالوان الا اذا كنا على علم بقيم ال Hue للالوان المختلفة:
  2. الطريقة الثانية : باستخدام Matplotlib : يمكننا استخدام الامر plt.imshow للرسم مع خرائط الوان مناسبوة وهذا سيعطينا فكرة افضل عن شدات البكسلات , ولكن يبقا أيضاً قاصراً عن العرض الكامل للمعلومات .

ملاحظة عند استخدام هذا التابع تذكر وضع العلم interpolation للقيمة nearest لنتائج افضل اعتبر الكود التالي:
In [9]:
img = cv2.imread('opencv_ios.png')
# img = np.array([img[:,:,2],img[:,:,1],img[:,:,0]])

img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
hist = cv2.calcHist( [hsv],
                    [0, 1], None, [180, 256], [0, 180, 0, 256] )

plt.figure(figsize = (8,4))
plt.subplot(121)
plt.imshow(img,cmap = 'gray')
plt.xticks([])
plt.