φυβλαςのβλογ
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
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



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

  查看日志

  推荐日志

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