φυβλαςのβλογ
บล็อกของ phyblas



opencv-python เบื้องต้น บทที่ ๑๐: การแบ่งขาวดำโดยพิจารณาตามความสว่าง
เขียนเมื่อ 2020/06/28 18:59
แก้ไขล่าสุด 2024/02/22 10:25

ต่อจาก บทที่ ๙

ในบทนี้จะเป็นเรื่องของการใช้ค่าจุดเปลี่ยน (threshold) เพื่อแบ่งหรือคัดกรองส่วนของรูปภาพ โดยพิจารณาจากความสว่างในภาพ

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




การใช้ฟังก์ชัน threshold

ฟังก์ชัน cv2.threshold() ใช้คัดกรองภาพโดยพิจารณาความสว่าง เพื่อที่จะคัดเอาหรือปรับค่าบางส่วน

ค่าที่ต้องใส่ในฟังก์ชันเป็นดังนี้

ลำดับ ชื่อ สิ่งที่ต้องใส่ ชนิดข้อมูล
1 src อาเรย์รูปภาพ np.array
2 thresh ค่าจุดเปลี่ยน int
3 maxval ค่าสูงสุด int
4 type ชนิดวิธีการ flag (int)

ชนิดวิธีการให้ใส่เป็นแฟล็ก โดยมีดังนี้

แฟล็ก ค่า ความหมาย
cv2.THRESH_BINARY 0 ถ้าสูงกว่าค่าจุดเปลี่ยนจะเป็น 255
ที่เหลือเป็น 0
cv2.THRESH_BINARY_INV 1 ถ้าสูงกว่าค่าจุดเปลี่ยนจะเป็น 0
ที่เหลือเป็น 255
cv2.THRESH_TRUNC 2 ถ้าสูงกว่าค่าจุดเปลี่ยนจะกลายเป็นค่าจุดเปลี่ยน
ที่เหลือคงเดิม
cv2.THRESH_TOZERO 3 ถ้าต่ำกว่าค่าจุดเปลี่ยนจะเป็น 0
ที่เหลือคงเดิม
cv2.THRESH_TOZERO_INV 4 ถ้าสูงกว่าค่าจุดเปลี่ยนจะเป็น 0
ที่เหลือคงเดิม
cv2.THRESH_OTSU 8 ทำการคำนวณค่าจุดเปลี่ยนอัตโนมัติ ไม่ใช้ค่า thresh ที่ใส่เข้าไป

ผลที่ได้จากฟังก์ชันนี้จะได้ออกมาเป็น ๒ ตัว โดยค่าแรกคือค่าจุดเปลี่ยน ซึ่งปกติจะเท่ากับค่า thresh ที่ใส่เข้าไป ยกเว้นใส่แฟล็ก cv2.THRESH_OTSU ซึ่งจะทำให้ค่าจุดเปลี่ยนถูกคำนวณเองอัตโนมัติ

เกี่ยวกับ cv2.THRESH_OTSU จะอธิบายในหัวข้อถัดไป สำหรับตอนนี้ขอแสดงตัวอย่างเพื่อดูความแตกต่างของอีก ๕ ตัว

สร้างอาเรย์ที่มีค่าตั้งแต่ 0-255 ขึ้นมาแล้วลองกรองที่ค่า 127 แล้วดูผลของวิธีต่างๆ
import cv2
import numpy as np
import matplotlib.pyplot as plt

title_thresh = [('binary',cv2.THRESH_BINARY),
                ('binary inv',cv2.THRESH_BINARY_INV),
                ('trunc',cv2.THRESH_TRUNC),
                ('tozero',cv2.THRESH_TOZERO),
                ('tozero inv',cv2.THRESH_TOZERO_INV)]

rup = np.arange(256).reshape(16,16).astype(np.float32)
plt.figure(figsize=[6,9])
plt.subplot(321,xticks=[],yticks=[])
plt.title('ภาพเดิม',family='Tahoma')
plt.imshow(rup,cmap='gray')
for i,(title,thresh) in enumerate(title_thresh):
    plt.subplot(322+i,title=title,xticks=[],yticks=[])
    ret,rup_thr = cv2.threshold(rup,127,255,thresh)
    plt.imshow(rup_thr,cmap='gray',vmin=0,vmax=255)
    
plt.tight_layout()
plt.show()


คราวนี้ลองเอาภาพที่เตรียมไว้มาเปิดด้วยโหมดขาวดำแล้วใช้ cv2.threshold() เพื่อกรองค่าตามความสว่าง ลองเทียบระหว่างค่าจุดเปลี่ยนต่างๆกัน


rin10c01.jpg
rin = cv2.imread('rin10c01.jpg',0)
plt.figure(figsize=[6,7.5])
plt.subplot(321,xticks=[],yticks=[])
plt.title('ภาพเดิม',family='Tahoma')
plt.imshow(rin,cmap='gray')
for i in range(5):
    ret,rinth = cv2.threshold(rin,i*50+20,255,cv2.THRESH_BINARY)
    plt.subplot(322+i,title='%d'%ret,xticks=[],yticks=[])
    plt.imshow(rinth,cmap='gray')

plt.tight_layout()
plt.show()





การหาจุดเปลี่ยนด้วยวิธีของโอตสึ

ตรงค่าชนิดวิธีการนั้นเมื่อใส่ cv2.THRESH_OTSU เพิ่มลงไปก็จะกลายเป็นการให้หาค่าจุดเปลี่ยนโดยอัตโนมัติโดยใช้อัลกอริธึมของโอตสึ

วิธีการนี้คิดโดยอาจารย์โอตสึ โนบุยุกิ (大津おおつ 展之のぶゆき) แห่งมหาวิทยาลัยทสึกุบะ (筑波大学つくばだいがく) ที่จังหวัดอิบารากิของญี่ปุ่น

วิธีการนี้ทำโดยเอาค่าความสว่างมาแจกแจงเป็นฮิสโทแกรม แล้วพิจารณาหาจุดเปลี่ยนที่เหมาะสม (เกี่ยวกับเรื่องฮิสโทแกรมจะอธิบายละเอียดอีกทีในบทถัดไป)

ลองเอาภาพเดิมจากตัวอย่างที่แล้วมาหาจุดเปลี่ยนโดยใช้ cv2.THRESH_OTSU ค่าจุดเปลี่ยนที่ได้จากวิธีนี้จะได้มาเป็นค่าคืนกลับตัวแรกของฟังกัน cv2.threshold() ในที่นี้ลองเอาค่ามาดูด้วย
rin = cv2.imread('rin10c01.png',0)
ret,rinth = cv2.threshold(rin,0,255,cv2.THRESH_OTSU)
cv2.imwrite('rin10c03.png',rinth)
print(ret) # ได้ 118

rin10c03.png

cv2.THRESH_OTSU สามารถใส่พร้อมกับแฟล็กอื่นได้ โดยบวกกันไป ถ้าใส่แค่ cv2.THRESH_OTSU จะเป็นการใช้วิธี cv2.THRESH_BINARY (เพราะแฟล็กตัวนี้มีค่าเป็น 0)

ลองเอาภาพเดิมมาใส่แฟล็กแบบต่างๆร่วมกับแฟล็กขอโอตสึ เปรียบเทียบผลที่ได้
tt = [('binary inv',cv2.THRESH_BINARY_INV),
      ('trunc',cv2.THRESH_TRUNC),
      ('tezero',cv2.THRESH_TOZERO),
      ('tozero inv',cv2.THRESH_TOZERO_INV)]

rin = cv2.imread('rin10c01.jpg',0)
plt.figure(figsize=[6,5])
for i,(title,thresh) in enumerate(tt):
    plt.subplot(221+i,title=title,xticks=[],yticks=[])
    ret,rinth = cv2.threshold(rin,127,255,thresh+cv2.THRESH_OTSU)
    plt.imshow(rinth,cmap='gray')
    
plt.tight_layout()
plt.show()





การใช้จุดเปลี่ยนแบบปรับตัวเองได้

ฟังก์ชัน cv2.threshold() ที่ใช้ในตัวอย่างที่ผ่านมานั้นเป็นการกำหนดค่าจุดเปลี่ยนตายตัวค่าหนึ่งคงตัวตลอดทั้งภาพ

แต่วิธีนี้มีจุดอ่อนอยู่ก็คือเวลาใช้กับภาพที่มีความสว่างโดยรวมในแต่ละบริเวณไม่สม่ำเสมอ

เช่นในภาพ ๓ มิติซึ่งมีแสดงและเงาแบบในตัวอย่างที่แล้ว ทำให้มีบริเวณที่สว่างและมืดกว่าปกติอยู่ หากใช้ค่าจุดเปลี่ยนค่าเดียวในการพิจารณาแบบนั้นจะทำให้บริเวณที่มืดกับสว่างดูแล้วได้ผลต่างกันมาก

เพื่อแก้ปัญหานี้ จึงมีการใช้ค่าจุดเปลี่ยนแบบปรับตัวได้ (adaptive threshold) คือคิดค่าความสว่างบริเวณใกล้ๆแล้วพิจารณาว่าควรใช้จุดเปลี่ยนที่เท่าไหร่ ซึ่งในแต่ละจุดจะใช้ค่าไม่เท่ากัน

วิธีนี้ใน opencv ทำได้โดยฟังก์ชัน cv2.adaptiveThreshold()

ค่าที่ต้องใส่ในฟังก์ชันนี้มีอยู่ ๖ ตัว ดังนี้

ลำดับ ชื่อ สิ่งที่ต้องใส่ ชนิดข้อมูล
1 src อาเรย์รูปภาพ np.array
2 maxValue ค่าสูงสุด int
3 adaptiveMethod วิธีการคำนวณปรับตัว flag (int)
4 thresholdType วิธีพิจารณาค่าจุดเปลี่ยน flag (int)
5 blockSize ขนาดบล็อก int
6 C ค่า C float

ค่าคืนกลับที่ได้จาก cv2.adaptiveThreshold() จะมีตัวเดียว คือภาพผลลัพธ์ที่ได้เท่านั้น ไม่ได้คืนมา ๒ ตัวเหมือนอย่าง cv2.threshold()

ในส่วนของวิธีพิจารณาค่าจุดเปลี่ยนก็ใส่เป็นแฟล็ก ซึ่งมีอยู่ ๒​ วิธี คือ
แฟล็ก ค่าตัวเลข วิธีคำนวณค่าจุดเปลี่ยน
cv2.ADAPTIVE_THRESH_MEAN_C 0 คำนวณค่าเฉลี่ยรอบข้างขนาด blockSize×blockSize แล้วหักจากค่า C
cv2.ADAPTIVE_THRESH_GAUSSIAN_C 1 คำนวณค่าเฉลี่ยรอบข้างขนาด blockSize×blockSize โดยถ่วงน้ำหนักด้วยฟังก์ชันเกาส์ แล้วหักจากค่า C

ลองเทียบดูความแตกต่างระหว่าง ๒ วิธีการ ลองใช้กับภาพเดิมจากตัวอย่างก่อนๆ
rin = cv2.imread('rin10c01.jpg',0)
plt.figure(figsize=[3.5,7.5])
plt.subplot(311,xticks=[],yticks=[])
plt.title('ภาพเดิม',family='Tahoma')
plt.imshow(rin,cmap='gray')
rinth = cv2.adaptiveThreshold(rin,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,9,1)
plt.subplot(312,title='thresh mean',xticks=[],yticks=[])
plt.imshow(rinth,cmap='gray')
rinth = cv2.adaptiveThreshold(rin,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,9,1)
plt.subplot(313,title='thresh gauss',xticks=[],yticks=[])
plt.imshow(rinth,cmap='gray')

plt.tight_layout()
plt.show()


จะเห็นว่าผลออกมาดูสม่ำเสมอกว่าเมื่อเทียบกับการใช้ cv2.threshold() เพราะพิจารณาจุดเปลี่ยนต่างไปตามความสว่างของบริเวณรอบๆ ไม่ว่าจะบริเวณที่มืดหรือสว่างก็หาจุดที่สว่างหรือมืดเมื่อเทียบกับในบริเวณนั้นได้เหมือนกัน

ต่อมาลองพิจารณาค่า blockSize กับ C ซึ่งใช้ในการคำนวณ ค่าทั้ง ๒ ตัวนี้เมื่อเปลี่ยนแปลงไปก็จะให้ผลต่างกันออกไป

ตัวอย่างเปรียบเทียบเมื่อมี blockSize เป็นค่าต่างๆกันแต่ให้ค่า C คงที่


miku10c01.jpg
miku = cv2.imread('miku10c01.jpg',0)
plt.figure(figsize=[6,7.5])
plt.subplot(321,xticks=[],yticks=[])
plt.title('ภาพเดิม',family='Tahoma')
plt.imshow(miku,cmap='gray')
for i in range(5):
    bs = 1+2**(i+1)
    mikuth = cv2.adaptiveThreshold(miku,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,bs,1)
    plt.subplot(322+i,title='block size = %d'%bs,xticks=[],yticks=[])
    plt.imshow(mikuth,cmap='gray')

plt.suptitle('c = 1')
plt.tight_layout()
plt.show()


ต่อมาลองเปรียบเทียบเมื่อ blockSize คงที่แล้วเปลี่ยนค่า C ดู
miku = cv2.imread('miku10c01.jpg',0)
plt.figure(figsize=[6,7.5])
plt.subplot(321,xticks=[],yticks=[])
plt.title('ภาพเดิม',family='Tahoma')
plt.imshow(miku,cmap='gray')
for i in range(5):
    c = 2*i-3
    mikuth = cv2.adaptiveThreshold(miku,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,9,c)
    plt.subplot(322+i,title='c = %d'%c,xticks=[],yticks=[])
    plt.imshow(mikuth,cmap='gray')

plt.suptitle('block size = 9')
plt.tight_layout()
plt.show()





การใช้ค่าจุดเปลี่ยนเป็นตัวคัดกรองเพื่อเอาหรือตัดส่วนที่เหลือ

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

เพื่อที่จะทำแบบนี้ได้ก็มีอยู่หลายวิธี เช่นอาจใช้คู่กับ np.where() เพื่อแยกเงื่อนไขตามค่าที่ได้ให้เก็บบางส่วนหรือแปลงบางส่วน

ตัวอย่าง ลองทำภาพให้จัดการเปลี่ยนเฉพาะส่วนที่มืดหรือสว่าง


gumi10c01.jpg
gumi = cv2.imread('gumi10c01.jpg') # อ่านเป็นภาพสีตามปกติ
gumi_gray = cv2.cvtColor(gumi,cv2.COLOR_BGR2GRAY) # แปลงเป็นสีขาวดำ
# เอาภาพที่แปลงเป็นขาวดำแล้วมากรอง
gumi_thr = cv2.adaptiveThreshold(gumi_gray,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,9,1)

# ภาพนี้ให้แปลงส่วนสว่างให้เป็นสีขาวไป
cv2.imwrite('gumi10c02.jpg',np.where(gumi_thr[:,:,None]==255,gumi,255))
# ภาพนี้ให้แปลงส่วนมืดให้เป็นสีดำไป
cv2.imwrite('gumi10c03.jpg',np.where(gumi_thr[:,:,None]==0,gumi,0))

gumi10c02.jpg


gumi10c03.jpg

หรืออาจใช้ส่วนที่คัดกรองมาทำเป็นค่าความทึบแสงเพื่อลบฉากหลังที่มืดๆออกได้แล้วบันทึกเป็น .png ที่มีส่วนโปร่งมองเห็นพื้นหลังได้ เช่น


teto10c01.jpg
gumi = cv2.imread('teto10c01.jpg')
gumi_gray = cv2.cvtColor(gumi,cv2.COLOR_BGR2GRAY)
_,gumi_thr = cv2.threshold(gumi_gray,5,255,cv2.THRESH_BINARY)
gumi = cv2.cvtColor(gumi,cv2.COLOR_BGR2BGRA)
gumi[:,:,3] = gumi_thr
cv2.imwrite('teto10c02.png',gumi)

teto10c02.png



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



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

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

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

หมวดหมู่

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

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



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

  ค้นหาบทความ

  บทความแนะนำ

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

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文