ต่อจาก
บทที่ ๑๐
บทนี้จะเป็นเรื่องของการวิเคราะห์สีภายในรูปภาพด้วยฮิสโทแกรม และการปรับแปลงสีในรูปภาพโดยพิจารณาจากฮิสโทแกรม
รวมถึงการปรับสีโดยการปรับแก้แกมมา
การสร้างฮิสโทแกรมแจกแจงความสว่างของภาพ
ฮิสโทแกรมคือแผนภูมิแท่งที่เอาไว้แจกแจงว่าภายในข้อมูลกลุ่มหนึ่งมีค่าอะไรอยู่ในขอบเขตไหนแค่ไหน
ในด้านการจัดการรูปภาพ ข้อมูลที่ต้องการวิเคราะห์ก็คือค่าความสว่างของภาพ
สามารถวาดฮิสโทแกรมเพื่อแจกแจงดูได้ว่ามีค่าความสว่างของสีไหนอยู่ในช่วงไหนมากแค่ไหน
สำหรับภาพสีอาจวาดฮิสโทแกรมแยกดูความสว่างของ ๓ สี หรืออาจเปลี่ยนเป็นภาพขาวดำแล้วดูแค่ความเข้ม
ก่อนอื่นจะเขียนถึงการใช้ฮิสโทแกรมกับภาพขาวดำก่อน จากนั้นช่วงท้ายจึงค่อยมาดูแยกสี
ฟังก์ชันสำหรับสร้างฮิสโทแกรมนั้นมีอยู่ทั้งใน 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()
อ่านบทถัดไป >>
บทที่ ๑๒