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



opencv-python เบื้องต้น บทที่ ๔: การจัดการสี
เขียนเมื่อ 2020/06/28 18:38
แก้ไขล่าสุด 2024/02/22 10:29

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

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




การแปลงระบบสี

ในการแสดงผสมและแสดงสีในคอมพิวเตอร์นั้นโดยทั่วไปเราจะคุ้นเคยกับระบบ RGB คือ แดง, เขียว, น้ำเงิน ในขณะที่ opencv จะสลับลำดับของสีแดงกับน้ำเงิน กลายเป็น BGR ซึ่งก็คือ น้ำเงิน, เขียว, แดง

นอกจากนี้ยังมีระบบ HSB ซึ่งนิยมใช้ เกี่ยวกับระบบนี้จะกล่าวถึงโดยละเอียดในหัวข้อถัดไป

ใน opencv มีฟังก์ชันที่ช่วยในการแปลงสีระหว่างระบบต่างๆ นั่นคือ cv2.cvtColor()

วิธีใช้

ลำดับ ชื่อ สิ่งที่ต้องใส่ ชนิดข้อมูล
1 src อาเรย์รูปภาพ np.array
2 code รหัสแปลงสี flag (int)

ในส่วนของรหัสแปลงสีนี้ค่าที่ใส่คือแฟล็กที่ cv2 เตรียมไว้ให้ ซึ่งจะมีชื่อขึ้นต้นด้วย COLOR_ และตามด้วยระบบที่แปลงโดยมีเลข 2 คั่น แบบนี้
cv2.COLOR_ระบบเดิม2ระบบที่จะเปลี่ยน

เช่นถ้าแปลงจาก RGB เป็น HSV ก็จะใช้ cv2.COLOR_RGB2HSV ถ้ากลับกันก็จะเป็น cv2.COLOR_HSV2RGB

ระบบสีที่แปลงไปมาได้มีอะไรบ้างอาจลองมาดูรายชื่อแฟล็กทั้งหมดได้ดังนี้
print('\n'.join(['%s: %d'%(x,getattr(cv2,x)) for x in dir(cv2) if x[:6]=='COLOR_']))

เวลาที่จะแปลงจากระบบ BGR ใน opencv ให้เป็น RGB เช่นเพื่อใช้ใน matplotlib อาจใช้ cv2.cvtColor โดยใส่ cv2.COLOR_BGR2RGB
rup = cv2.cvtColor(rup,cv2.COLOR_BGR2RGB)

การแปลง BGR>RGB แบบนี้อาจใช้ rup[:,:,::-1] ดังที่ได้กล่าวในบทที่ ๒ ไปแล้วแทนได้ รวมทั้งแปลงกลับ

แต่ถ้าเป็นระบบที่มีค่าความโปร่งใสอยู่ด้วยก็จะยุ่งยากเข้ามาหน่อย และชื่อโหมดเวลาแปลงโหมดที่มีความโปร่งใสจะเพิ่มตัว A เข้ามา เช่นถ้าแปลง BGR ที่มีความโปร่งใสด้วยเป็น RGB ก็ใช้cv2.COLOR_BGRA2RGBA

ตัวอย่างเช่นลองเอาภาพนี้ซึ่งมีฉากหลังโปร่งมาแปลงเป็น RGBA เพื่อมาแสดงใน matplotlib

miku04c01.png

import cv2
import numpy as np
import matplotlib.pyplot as plt

miku = cv2.imread('miku04c01.png')
miku = cv2.cvtColor(miku,cv2.COLOR_BGRA2RGBA)
plt.imshow(miku)
plt.show()




การแปลงสีเป็นขาวดำ

สีขาวดำก็ถือเป็นสีระบบหนึ่งเช่นกัน ในนี้ใช้คำว่า GRAY

ยกตัวอย่างการแปลงจากสี BGR เป็นสีขาวดำ ใช้ cv2.COLOR_BGR2GRAY

rin04c01.jpg

rin = cv2.imread('rin04c01.jpg')
cv2.imwrite('rin04c02.jpg',cv2.cvtColor(rin,cv2.COLOR_BGR2GRAY))
rin04c02.jpg


อย่างไรก็ตาม ถึงไม่แปลงด้วยวิธีนี้ ก็สามารถเปิดในโหมดขาวดำแล้วเอามาบันทึกใหม่ทันทีก็ได้ไฟล์ภาพขาวดำเช่นกัน
rin = cv2.imread('rin04c01.jpg',cv2.IMREAD_GRAYSCALE)
cv2.imwrite('rin04c02.jpg',rin)

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

เวลาแสดงภาพขาวดำด้วย imshow ของ matplotlib อาจใช้ cv2.COLOR_GRAY2RGB แปลง




ระบบสี HSV

ในขณะที่ระบบ RGB หรือ BGR ใช้เลข ๓ ตัวแทน ๓ สีที่เป็นส่วนผสม ระบบสี HSV เองก็ประกอบไปด้วยค่า ๓ ค่า แต่ไม่ได้แทนค่าสีต่างๆ แต่ ๓ ค่านั้นมีความหมายดังนี้

  • H (hue) ค่าเฉดสี มีค่าตั้งแต่ 0-179 เป็นค่าที่บอกว่าสีอยู่ในช่วงไหน สีแดงเป็น 0 สีเขียวเป็น 60 สีน้ำเงินเป็น 120
  • S (saturation) ค่าความอิ่มตัว หรือความสดของสี มีค่าตั้งแต่ 0-255 ยิ่งค่าสูงสียิ่งสด
  • V (value) ค่าความสว่าง มีค่าตั้งแต่ 0-255 ยิ่งสูงยิ่งสว่าง

เพื่อให้เห็นภาพชัดว่าตัวแปร H S V อันไหนส่งผลต่อสีอย่างไรบ้าง ลองวาดออกมาเป็นแผนภาพไล่สี โดยแบ่งเป็น ๓ อัน

ต่อไปลองทำเป็นตัวอย่างโดยสร้างค่า H S V ต่างๆจากนั้นแปลงสีเป็น RGB เพื่อแสดงด้วย matplotlib (ในที่นี้ใช้ np.meshgrid เพื่อสร้างโครงข่าย รายละเอียดอ่านได้ใน numpy & matplotlib บทที่ ๒๔)

ลองให้ h คงที่อยู่ที่ 135 แล้วไล่ค่า s กับ v
hsv = np.zeros([256,256,3],dtype=np.uint8)
s,v = np.meshgrid(np.arange(256),np.arange(256))
hsv[:,:,0] = 135
hsv[:,:,1] = s
hsv[:,:,2] = v
rup = cv2.cvtColor(hsv,cv2.COLOR_HSV2RGB)
plt.axes(title='h = 135',xlabel='s',ylabel='v')
plt.imshow(rup)
plt.show()


ต่อมาลองให้ s คงที่อยู่ที่ 200 แล้วไล่ค่า h กับ v
hsv = np.zeros([180,256,3],dtype=np.uint8)
v,h = np.meshgrid(np.arange(256),np.arange(180))
hsv[:,:,0] = h
hsv[:,:,1] = 200
hsv[:,:,2] = v
rup = cv2.cvtColor(hsv,cv2.COLOR_HSV2RGB)
plt.axes(title='s = 200',xlabel='v',ylabel='h')
plt.imshow(rup)
plt.show()


สุดท้ายลองให้ v คงที่อยู่ที่ 200 แล้วไล่ค่า h กับ s
hsv = np.zeros([180,256,3],dtype=np.uint8)
s,h = np.meshgrid(np.arange(256),np.arange(180))
hsv[:,:,0] = h
hsv[:,:,1] = s
hsv[:,:,2] = 200
rup = cv2.cvtColor(hsv,cv2.COLOR_HSV2RGB)
plt.axes(title='v = 200',xlabel='s',ylabel='h')
plt.imshow(rup)
plt.title('v = 200')
plt.show()


อย่างไรก็ตาม ระบบ HSV อาจมีความแตกต่างกันไปขึ้นอยู่กับว่าใช้ที่ไหน ขอบเขตของ H อาจเป็นค่ามุมซึ่งสูงสุดที่ 360 แทน ส่วน S กับ V อาจมีค่าเป็นเปอร์เซ็นต์เต็ม 100 และชื่อเรียกบางทีก็อาจเรียกว่า HSL หรือ HSB

ดังนั้นค่าที่แสดงในตัวอย่างนี้เป็นค่าในระบบของ opencv เอง ถ้าไปใช้ที่อื่นก็ต้องแปลงค่าให้เข้าตามนั้น




การคัดกรองเฉพาะสีในขอบเขตที่ต้องการ

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

ฟังก์ชันนี้จะให้ค่าออกมาเป็นอาเรย์ ๒ มิติ ซึ่งมีขนาดเท่ากับขนาดภาพ และมีค่าเป็น 255 หากค่าจุดนั้นอยู่ในขอบเขตช่วงที่กำหนด นอกนั้นจะเป็น 0

cv2.inRange() จะใช้กับระบบสี RGB, BGR หรือ HSV ก็ได้

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

rin04c03.jpg


เริ่มจากลองใช้กับระบบสี BGR ดู เพื่อคัดเอาเฉพาะส่วนที่มีสีน้ำเงินมากเหลือไว้
rin = cv2.imread('rin04c03.jpg')

inr = cv2.inRange(rin,np.array([127,0,0]),np.array([255,255,255]))
mask = cv2.cvtColor(inr,cv2.COLOR_GRAY2BGR)!=0
cv2.imwrite('rin04c04.jpg',rin*mask)
rin04c04.jpg

ผลที่ได้คือแถวที่มีส่วนประกอบของสีน้ำเงินอยู่ต่ำเกินก็จะถูกกรองออกไป แต่ว่าสีอื่นที่มีส่วนประกอบของสีน้ำเงินก็ยังอยู่ เช่นสีขาวและสีที่สว่างมาก

ต่อมาลองเปลี่ยนเป็นกรองสีเหลือง ซึ่งหมายถึงมีทั้งสีแดงและเขียวอยู่มากพร้อมกัน
inr = cv2.inRange(rin,np.array([0,127,127]),np.array([255,255,255]))
mask = cv2.cvtColor(inr,cv2.COLOR_GRAY2BGR)!=0
cv2.imwrite('rin04c05.jpg',rin*mask)
rin04c05.jpg

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

ดังนั้นการกรองในระบบ BGR นั้นไม่เหมาะที่จะคัดกรองเอาเฉดสีที่ต้องการ กรณีนี้ถ้าแปลงเป็นระบบ HSV จะเหมาะกว่า

ตัวอย่างต่อไปใช้ภาพเดิม แต่คราวนี้ลองกรองในระบบ HSV หาเฉดสีน้ำเงินดู
rin_hsv = cv2.cvtColor(rin,cv2.COLOR_BGR2HSV)

inr = cv2.inRange(rin_hsv,np.array([110,50,0]),np.array([130,255,255]))
mask = cv2.cvtColor(inr,cv2.COLOR_GRAY2BGR)!=0
cv2.imwrite('rin04c06.jpg',rin*mask)
rin04c06.jpg

คราวนี้ลองดูเฉดสีเหลือง
inr = cv2.inRange(rin_hsv,np.array([20,50,0]),np.array([50,255,255]))
mask = cv2.cvtColor(inr,cv2.COLOR_GRAY2BGR)!=0
cv2.imwrite('rin04c07.jpg',rin*mask)
rin04c07.jpg

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

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

ดังนั้นจะเห็นได้ถึงประโยชน์ของการแปลงเป็นระบบ HSV ระบบนี้อาจเหมาะสมในการใช้ในงานบางจำพวกมากกว่า BGR

การแปลงระบบสีไปมาเพื่อให้เหมาะสมกับการใช้งานจึงเป็นเทคนิคหนึ่งที่สำคัญ




การกลับสี

ฟังก์ชัน cv2.bitwise_not() ใช้เพื่อกลับค่าสีในระบบ BGR จากมืดเป็นสว่าง สว่างเป็นมืด

ตัวอย่าง

gumi04c01.jpg

gumi = cv2.imread('gumi04c01.jpg')
cv2.imwrite('gumi04c02.jpg',cv2.bitwise_not(gumi))
gumi04c02.jpg





การแยกและรวมสี

cv2.split() เอาไว้แยกสีของภาพออกเป็นลิสต์ของค่าแต่ละสี ส่วน cv2.merge() ใช้รวมลิสต์ของค่าแต่ละสีเข้าเป็นอาเรย์ของภาพสี

ลองยกตัวอย่างด้วยการแยกสีภาพออกเป็น ๓ สี แล้วก็เอามารวมใหม่ให้อีก ๒ สีเป็นศูนย์ ก็จะได้ภาพที่มีองค์ประกอบของ ๓ สีแยกกัน

miku04c03.jpg

miku = cv2.imread('miku04c03.jpg')
b,g,r = cv2.split(miku)
z = np.zeros_like(b)
b = cv2.merge([b,z,z])
g = cv2.merge([z,g,z])
r = cv2.merge([z,z,r])

plt.figure(figsize=[6,12])
for i,c in zip([0,1,2],[b,g,r]):
    plt.subplot(311+i)
    plt.imshow(c,cmap='gray')
plt.tight_layout()
plt.show()




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



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

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

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

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> 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月

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

ไทย

日本語

中文