ต่อจาก 
บทที่ ๑๐
  
  บทนี้จะเป็นเรื่องของการวิเคราะห์สีภายในรูปภาพด้วยฮิสโทแกรม และการปรับแปลงสีในรูปภาพโดยพิจารณาจากฮิสโทแกรม
  รวมถึงการปรับสีโดยการปรับแก้แกมมา
  
  
  
  
  
การสร้างฮิสโทแกรมแจกแจงความสว่างของภาพ
  
  ฮิสโทแกรมคือแผนภูมิแท่งที่เอาไว้แจกแจงว่าภายในข้อมูลกลุ่มหนึ่งมีค่าอะไรอยู่ในขอบเขตไหนแค่ไหน
  
  ในด้านการจัดการรูปภาพ ข้อมูลที่ต้องการวิเคราะห์ก็คือค่าความสว่างของภาพ
  สามารถวาดฮิสโทแกรมเพื่อแจกแจงดูได้ว่ามีค่าความสว่างของสีไหนอยู่ในช่วงไหนมากแค่ไหน
  
  สำหรับภาพสีอาจวาดฮิสโทแกรมแยกดูความสว่างของ ๓ สี หรืออาจเปลี่ยนเป็นภาพขาวดำแล้วดูแค่ความเข้ม
  
  ก่อนอื่นจะเขียนถึงการใช้ฮิสโทแกรมกับภาพขาวดำก่อน จากนั้นช่วงท้ายจึงค่อยมาดูแยกสี
  
  ฟังก์ชันสำหรับสร้างฮิสโทแกรมนั้นมีอยู่ทั้งใน numpy และ matplotlib แต่ในที่นี้จะแนะนำฟังก์ชันสร้างฮิสโทแกรมของใน opencv
  ซึ่งเหมาะสำหรับใช้งานกับข้อมูลรูปภาพโดยเฉพาะ ฟังก์ชันนั้นคือ cv2.calcHist()
  
  ค่าที่ต้องใส่ในฟังก์ชันมีดังนี้
  
  
    
      | ลำดับที่ | 
      ชื่อ | 
      ค่าที่ต้องใส่ | 
      ชนิดข้อมูล | 
    
    
      | 1 | 
      images | 
      อาเรย์รูปภาพ | 
      list ของ np.array | 
    
    
      | 2 | 
      channels | 
      ช่องสีที่จะพิจารณา | 
      list ของ int | 
    
    
      | 3 | 
      mask | 
      อาเรย์ตัวกรอง เมื่อต้องการเฉพาะบางส่วน | 
      list ของ np.array | 
    
    
      | 4 | 
      histSize | 
      จำนวนช่อง | 
      list ของ int | 
    
    
      | 5 | 
      ranges | 
      ขอบเขตค่าที่ต้องการ | 
      list ของ int | 
    
  
  
  ค่าทั้งหมดที่ต้องใส่ล้วนใส่ในรูปของลิสต์ แม้ว่าอาเรย์รูปภาพที่จะใช้มีเพียงรูปเดียวก็ให้ใส่ไว้ในลิสต์
  
  สำหรับช่องสีที่จะพิจารณานั้น ในกรณีภาพขาวดำให้ใส่ [0] ไป เพราะไม่มีสีอยู่แล้ว
  
  ตัวกรองมีไว้ใช้เมื่อต้องการพิจารณาแค่บางส่วน ถ้าไม่ใช้ให้ใส่ None ไปได้
  
  จำนวนช่อง ปกติจะใส่ [256] และขอบเขตใส่เป็น [0,256] เนื่องจากภาพมี ๒๕๖ สี
  
  ขอยกตัวอย่างโดยโดยวิเคราะห์ฮิสโทแกรมของภาพนี้ในโหมดขาวดำ
  
  
  miku11c01.jpg
  import cv2
import numpy as np
import matplotlib.pyplot as plt
miku = cv2.imread('miku11c01.jpg',0) # เปิดภาพมาเป็นขาวดำ
plt.figure(figsize=[6,8])
plt.subplot(211) # วาดภาพเดิมแบบขาวดำ
plt.imshow(miku,cmap='gray')
plt.colorbar(pad=0.01)
plt.subplot(212) # วาดฮิสโทแกรม
hist = cv2.calcHist([miku],[0],None,[256],[0,256])
plt.bar(np.arange(256),hist[:,0],1,color='m')
plt.tight_layout()
plt.show()
  
  
  ได้ฮิสโทแกรมที่แสดงการแจกแจงความสว่างของภาพออกมาดังนี้
  จะเห็นว่าในภาพนี้ส่วนใหญ่ค่าความสว่างของภาพจะกองกันอยู่ตรงกลางๆ
  
  ลองคำนวณผลบวกสะสมในอาเรย์ของฮิสโทแกรมโดยใช้ .cumsum()
  
plt.plot(hist[:,0].cumsum(),'r')
plt.show()
  
  
  กราฟแสดงค่าผลบวกสะสมนี้จะเป็นตัวพิจารณาสำคัญเพื่อนำมาใช้ในกระบวนการปรับภาพ
  เช่นพิจารณาปรับค่าผลบวกสะสมให้ขึ้นต่อเนื่องอย่างสม่ำเสมอเป็นเส้นตรง
  
  
  
  
  
การนำฮิสโทแกรมมาปรับให้เรียบ
  
  ใน opencv มีฟังก์ชัน cv2.equalizeHist() ซึ่งเอาไว้ปรับรูปภาพให้มีฮิสโทแกรมสะสมในลักษณะที่ราบเรียบ
  
  ฟังก์ชันนี้ใส่แค่ตัวอาเรย์รูปภาพที่ต้องการปรับ แล้วก็จะคืนค่าอาเรย์ผลลัพธ์หลังการปรับมาให้เรียบร้อย
  
  ลองเอาภาพจากตัวอย่างที่แล้วมาปรับเรียบ แล้ววาดภาพที่ได้ใหม่ พร้อมวาดฮิสโทแกรม และค่าผลบวกสะสมดู
  
miku = cv2.imread('miku11c01.jpg',0)
miku_eql = cv2.equalizeHist(miku)
plt.figure(figsize=[6,12])
plt.subplot(311) # ภาพหลังการปรับ
plt.imshow(miku_eql,cmap='gray')
plt.colorbar(pad=0.01)
plt.subplot(312) # ฮิสโทแกรม
hist_eql = cv2.calcHist([miku_eql],[0],None,[256],[0,256])
plt.bar(np.arange(256),hist_eql[:,0],1,color='m')
plt.subplot(313) # ผลบวกสะสมของฮิสโทแกรม
plt.plot(hist_eql[:,0].cumsum(),'r')
plt.tight_layout()
plt.show()
  
  
  จะเห็นว่าภาพที่ได้จากการปรับจะมีการแจกแจงของฮิสโทแกรมเปลี่ยนไปจากเดิม
  โดยจะแจกแจงให้ผลบวกสะสมเรียงกันเป็นค่าเพิ่มสม่ำเสมอ
  
  ผลที่ได้คือภาพที่มีการแจกแจงสีมากกว่าเดิม โดยเห็นตั้งแต่ขาวไปจนดำ
  ซึ่งการทำแบบนี้มีประโยชน์ตรงที่ทำให้แยกแยะรายละเอียดภายในภาพได้ชัดขึ้น เห็นลวดลายต่างๆเด่นชัด
  
  อย่างไรก็ตาม การที่แค่ปรับเรียบแบบนี้อาจไม่ได้ให้ผลออกมาดีนักเสมอไปในกรณีทั่วไป
  โดยเฉพาะสำหรับภาพที่มีแสงต่างกันไปมากในแต่ละบริเวณ
  
  ขอยกตัวอย่างเช่นภาพนี้
  
  
  gumi11c01.jpg
  
  ลองวิเคราะห์ดูฮิสโทแกรม
  
gumi = cv2.imread('gumi11c01.jpg',0)
plt.figure(figsize=[6,12])
plt.subplot(311) # ภาพเดิม
plt.imshow(gumi,cmap='gray')
plt.colorbar(pad=0.01)
plt.subplot(312) # ฮิสโทแกรม
hist = cv2.calcHist([gumi],[0],None,[256],[0,256])
plt.bar(np.arange(256),hist[:,0],1,color='m')
plt.subplot(313) # ผลบวกสะสม
plt.plot(hist[:,0].cumsum(),'r')
plt.tight_layout()
plt.show()
  
  
  จะเห็นว่าแจกแจงกระจายไม่สม่ำเสมอยิ่งกว่าภาพแรกอีก โดยมีค่าหนึ่งที่มากเป็นพิเศษ
  
  ลองเอามาปรับเรียบด้วยวิธีเดิม
  
gumi = cv2.imread('gumi11c01.jpg',0)
gumi_eql = cv2.equalizeHist(gumi)
plt.figure(figsize=[6,12])
plt.subplot(311) # ภาพหลังการปรับ
plt.imshow(gumi_eql,cmap='gray')
plt.colorbar(pad=0.01)
plt.subplot(312) # ฮิสโทแกรม
hist_eql = cv2.calcHist([gumi_eql],[0],None,[256],[0,256])
plt.bar(np.arange(256),hist_eql[:,0],1,color='m')
plt.subplot(313) # ผลบวกสะสม
plt.plot(hist_eql[:,0].cumsum(),'r')
plt.tight_layout()
plt.show()
  
  
  จะเห็นว่าแปลงแล้วกลับออกมาแปลกๆ ส่วนที่สว่างอยู่แล้วโดนปรับจนสว่างจ้าเกินไป กลับดูเสียสมดุล
  
  และถ้าดูฮิสโทแกรมแล้วจะเห็นว่าแม้จะพยายามปรับให้ผลบวกฮิสโทแกรมดูเรียบ
  แต่ตรงค่าส่วนที่สูงเด่นโดดๆนั้นก็ทำเสียสมดุลไป
  
  ดังนั้นจึงมีการคิดวิธีการ CLAHE ขึ้นมา ซึ่งใช้งานได้ดีกว่าวิธีเดิมเมื่อเจอกรณีแบบนี้
  
  
  
  
  
วิธีการ CLAHE
  
  CLAHE ย่อมาจาก Contrast Limited Adaptive Histogram Equalization คือเป็นวิธีการปรับฮิสโทแกรมในแบบที่ปรับตัวเองได้
  
  จากเดิมปัญหาของวิธี cv2.equalizeHist() ก็คือนำทั้งภาพมาคิดพร้อมๆกัน
  แบบนั้นเมื่อเจอภาพที่มีความสว่างต่างกันมากในแต่ละบริเวณก็จะดึงถ่วงกันจนเสียสมดุลได้
  
  ดังนั้นควรแยกพิจารณาเป็นบริเวณไปจึงช่วยรักษาสมดุลของภาพหลังแปลงได้ นี่คือหลักการของ CLAHE
  
  วิธีการใช้เริ่มจากสร้างออบเจ็กต์ CLAHE ขึ้นมาโดยฟังก์ชัน cv2.createCLAHE() จากนั้นใช้เมธอด .apply()
  ที่ตัวรูปภาพที่ต้องการแปลง
  
  ตัวอย่าง ลองเอาภาพเดิมจากตัวอย่างที่แล้วมาปรับด้วยวิธี CLAHE
  
gumi = cv2.imread('gumi11c01.jpg',0)
clahe = cv2.createCLAHE() # สร้างออบเจ็กต์ CLAHE
gumi_clahe = clahe.apply(gumi) # เริ่มทำการแปลง
plt.figure(figsize=[6,12])
plt.subplot(311)
plt.imshow(gumi_clahe,cmap='gray')
plt.colorbar(pad=0.01)
plt.subplot(312)
hist_clahe = cv2.calcHist([gumi_clahe],[0],None,[256],[0,256])
plt.bar(np.arange(256),hist_clahe[:,0],1,color='m')
plt.subplot(313)
plt.plot(hist_clahe[:,0].cumsum(),'r')
plt.tight_layout()
plt.show()
  
  
  จะได้ภาพออกมาดูดีกว่าวิธีเดิม ส่วนฮิสโทแกรมก็มีการแจกแจงกระจายดีขึ้น ผลบวกสะสมแม้จะไม่ได้เรียบตรงเสียทีเดียว
  แต่ก็ทำให้ค่อนข้างเรียบ ในขณะที่รักษาสมดุลสีในแต่ละบริเวณไปด้วย
  
  
  
  
  
การใช้ฮิสโทแกรมกับภาพสี
  
  ตัวอย่างที่ผ่านมาเป็นการพิจารณาฮิสโทแกรมของภาพขาวดำโดยดูแค่ความสว่างโดยรวม
  
  สำหรับในส่วนสุดท้ายนี้จะเขียนถึงการพิจารณาแยกสี
  
  ฟังก์ชัน cv2.calcHist() นั้นเมื่อใช้กับภาพขาวดำจะใส่ค่าช่องสี (channels) เป็น [0] เพราะมีสีเดียว
  แต่ถ้าใช้กับภาพสีอาจใส่ตัวเลข 0,1,2 แล้วแต่ว่าจะวิเคราะห์ช่องไหน
  
  ลองดูตัวอย่าง ใช้วิเคราะห์ภาพสีโดยแยกวาดฮิสโทแกรมของแต่ละสี แต่ใส่ในกราฟเดียวกัน
  
  
  rin11c01.jpg
  rin = cv2.imread('rin11c01.jpg')
plt.figure(figsize=[6,7])
ax1 = plt.subplot(211)
ax2 = plt.subplot(212)
for i,c in enumerate('bgr'):
    hist = cv2.calcHist([rin],[i],None,[256],[0,256])
    ax1.plot(hist,c) # ฮิสโทแกรม
    ax2.plot(hist.cumsum(),c) # ผลบวกสะสม
plt.tight_layout()
plt.show()
  
  
  ผลที่ได้จะเห็นได้ว่ามีการแจกแจงของสีไหนอยู่ในช่วงไหนมาก
  
  ต่อมาลองวิเคราะห์พร้อมกันทั้ง ๓ สีดู เพื่อจะได้เห็นความสัมพันธ์ระหว่างแต่ละสี โดยค่าจำนวนช่องก็ต้องใส่ 256 ไป ๓ ครั้ง
  ส่วนช่วงขอบเขตก็ใส่ 0,256 ไป ๓
  ครั้งเรียงต่อกัน
  
  ผลลัพธ์ที่ได้จะเป็นอาเรย์ที่มี ๓ มิติ เพราะมีค่าของสีทั้ง ๓ แต่การแสดงผลของทั้ง ๓ สีพร้อมกันทีเดียวนั้นทำได้ยาก
  จึงได้แต่ทำแยกทีละ ๒ สี เช่นแบบนี้
  
rin = cv2.imread('rin11c01.jpg')
hist = cv2.calcHist([rin],[0,1,2],None,[256,256,256],[0,256,0,256,0,256])
plt.figure(figsize=[6,15])
# น้ำเงิน-เขียว
plt.subplot(311,xlabel='b',ylabel='g')
plt.imshow(hist.sum(2),cmap='coolwarm')
plt.colorbar(pad=0.01)
# น้ำเงิน-แดง
plt.subplot(312,xlabel='b',ylabel='r')
plt.imshow(hist.sum(1),cmap='coolwarm')
plt.colorbar(pad=0.01)
# เขียว-แดง
plt.subplot(313,xlabel='g',ylabel='r')
plt.imshow(hist.sum(0),cmap='coolwarm')
plt.colorbar(pad=0.01)
plt.tight_layout()
plt.show()
  
  
  ผลที่ได้ดูแล้วจะเห็นว่าคู่สีไหนมีสีอยู่ในช่วงไหนมากเป็นพิเศษ จะมีค่าสูงเด่นขึ้นมา
  
  แต่ค่าต่างกันมากแบบนี้ทำให้ดูค่าส่วนสีอื่นที่มีบ้างเล็กน้อยได้ยาก
  เพื่อให้เห็นภาพชัดลองวาดใหม่โดนเปลี่ยนเป็นปรับสัดส่วนเป็นลอการิธึม
  
import matplotlib as mpl
lognorm = mpl.colors.LogNorm()
plt.figure(figsize=[6,15])
for i,(xl,yl) in enumerate(['bg','br','gr']):
    plt.subplot(311+i,xlabel=xl,ylabel=yl)
    plt.imshow(hist.sum(2-i),cmap='coolwarm',norm=lognorm)
    plt.colorbar(pad=0.01)
plt.tight_layout()
plt.show()
  
  
  จากนั้นลองมาวิเคราะห์ฮิสโทแกรมของสีในระบบ HSV ดูบ้าง
  
  เราสามารถแปลงภาพเป็น HSV แล้วนำมาวิเคราะห์ได้ในลักษณะเดียวกัน เพียงแต่ส่วนค่า H จะมีค่าแค่ 180
  จึงต้องใส่ในฟังก์ชันตามนั้นด้วย
  
rin = cv2.imread('rin11c01.jpg')
rin_hsv = cv2.cvtColor(rin,cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([rin_hsv],[0,1,2],None,[180,256,256],[0,180,0,256,0,256])
plt.figure(figsize=[6.5,4.5])
plt.subplot(221,title='h-s')
plt.imshow(hist.sum(2),cmap='coolwarm',norm=lognorm)
plt.colorbar(pad=0.01)
plt.subplot(222,title='h')
plt.plot(hist.sum(2).sum(1))
plt.subplot(223,title='s')
plt.plot(hist.sum(2).sum(0))
plt.subplot(224,title='v')
plt.plot(hist.sum(1).sum(0))
plt.tight_layout()
plt.show()
  
  
  
  
  
  การปรับแก้แกมมา
  
  อีกวิธีหนึ่งที่อาจใช้เพื่อปรับโทนสีโดยรวมของภาพก็คือวิธีการที่เรียกว่า
การปรับแก้แกมมา (gamma
    correction)
  
  วิธีการก็คือเปลี่ยนค่าความสว่างโดนดูจากค่าความสว่างเดิม ตามสมการนี้
  
  
  
  โดยค่า γ (แกมมา) เป็นจำนวนบวก ลองวาดกราฟเทียบระหว่างค่าเก่ากับใหม่ที่ค่าแกมมาต่างๆกันดูได้ดังนี้
  
x = np.arange(256)
gamma = [1/5,1/3,0.5,1,2,3,5]
plt.figure(figsize=[5,5])
plt.axes(aspect=1)
for i,g in enumerate(gamma):
    x_gamma = 255*(x/255)**(1/g)
    plt.plot(x,x_gamma,label='$\gamma=%.2f$'%g,color=plt.get_cmap('rainbow')(i/6))
plt.legend()
plt.show()
  
  
  กราฟนี้จะเป็นตัวแสดงบอกว่าถ้าเดิมมีความสว่างเท่าไหร่จะกลายเป็นเท่าไหร่ไป
  
  ซึ่งจะเห็นได้ว่าถ้าค่า γ สูงกว่า 1 ภาพโดยรวมจะสว่างขึ้น ถ้า γ=1 ก็จะเท่ากับภาพเดิมไม่มีอะไรเปลี่ยนแปลง ถ้าค่า γ
  น้อยกว่า 1
  ก็จะมืดลง แต่ไม่ว่าจะเปลี่ยนไปยังไง ความสว่างโดยรวมก็จะยังคงอยู่ในช่วง 0 ถึง 255
  
  ในการแปลงแกมมาในรูปภาพเราอาจเอาค่า γ คำนวณแล้วสร้างตารางเทียบเตรียมเอาไว้ก่อน แล้วใช้ฟังก์ชัน cv2.LUT()
  เพื่อแปลงค่าสีในภาพนั้นตามตาราง
  
  ค่าที่ต้องใส่ในฟังก์ชัน cv2.LUT() คือ
  
  
    
      | ลำดับที่ | 
      ชื่อ | 
      ค่าที่ต้องใส่ | 
      ชนิดข้อมูล | 
    
    
      | 1 | 
      src | 
      อาเรย์รูปภาพ | 
      np.array | 
    
    
      | 2 | 
      lut | 
      ตัวเทียบแปลง | 
      np.array | 
    
  
  
  ตารางสำหรับแปลงที่ต้องใช้คืออาเรย์ขนาด 256 ซึ่งแทนค่าที่ต้องการแปลงไปตั้งแต่ 0 ถึง 255 ไว้
  
  ลองดูตัวอย่างการนำวิธีการปรับแก้แกมมาเอามาใช้กับรูปภาพ
  
  
  teto11c01.jpg
  teto = cv2.imread('teto11c01.jpg')
gamma = [1/2,2/3,1,1.5,2,3] # ค่าแกมมาที่จะลองใช้
plt.figure(figsize=[6,7])
ar0255 = np.arange(0,256)/255
for i,g in enumerate(gamma):
    plt.subplot(321+i,title='$\gamma=%.2f$'%g,xticks=[],yticks=[])
    # สร้างตัวเทียบแปลงตามค่าแกมมา
    lut = (255*ar0255**(1/g)).astype(np.uint8)
    # แปลงสีตามตัวเทียบแปลงที่เตรียมไว้
    teto_gamma = cv2.LUT(teto,lut)
    plt.imshow(teto_gamma[:,:,::-1])
plt.tight_layout(0)
plt.show()
  
  
  สุดท้ายลองเทียบฮิสโทแกรมของภาพที่ได้จากการปรับดู
  
plt.figure(figsize=[6,7])
for i,g in enumerate(gamma):
    teto_gamma = 255*(teto/255)**(1/g)
    plt.subplot(321+i,title='$\gamma=%.2f$'%g)
    for j,c in enumerate('bgr'):
        hist = cv2.calcHist([teto_gamma],[j],None,[256],[0,256])
        plt.plot(hist,c) # ฮิสโทแกรม
plt.tight_layout(0)
plt.show()
  
  
  
  
  
  อ่านบทถัดไป >> 
บทที่ ๑๒