φυβλαςのβλογ
phyblas的博客



opencv-python เบื้องต้น บทที่ ๑๑: การวิเคราะห์ฮิสโทแกรมและปรับสมดุลสีภาพ
เขียนเมื่อ 2020/06/28 19:01
แก้ไขล่าสุด 2024/02/22 10:24

ต่อจาก บทที่ ๑๐

บทนี้จะเป็นเรื่องของการวิเคราะห์สีภายในรูปภาพด้วยฮิสโทแกรม และการปรับแปลงสีในรูปภาพโดยพิจารณาจากฮิสโทแกรม รวมถึงการปรับสีโดยการปรับแก้แกมมา




การสร้างฮิสโทแกรมแจกแจงความสว่างของภาพ

ฮิสโทแกรมคือแผนภูมิแท่งที่เอาไว้แจกแจงว่าภายในข้อมูลกลุ่มหนึ่งมีค่าอะไรอยู่ในขอบเขตไหนแค่ไหน

ในด้านการจัดการรูปภาพ ข้อมูลที่ต้องการวิเคราะห์ก็คือค่าความสว่างของภาพ สามารถวาดฮิสโทแกรมเพื่อแจกแจงดูได้ว่ามีค่าความสว่างของสีไหนอยู่ในช่วงไหนมากแค่ไหน

สำหรับภาพสีอาจวาดฮิสโทแกรมแยกดูความสว่างของ ๓ สี หรืออาจเปลี่ยนเป็นภาพขาวดำแล้วดูแค่ความเข้ม

ก่อนอื่นจะเขียนถึงการใช้ฮิสโทแกรมกับภาพขาวดำก่อน จากนั้นช่วงท้ายจึงค่อยมาดูแยกสี

ฟังก์ชันสำหรับสร้างฮิสโทแกรมนั้นมีอยู่ทั้งใน 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()





อ่านบทถัดไป >> บทที่ ๑๒



-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> opencv
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> numpy
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> matplotlib

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ