φυβλαςのβλογ
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
モンゴル語
言語学
maya
確率論
日本での日記
中国での日記
-- 北京での日記
-- 香港での日記
-- 澳門での日記
台灣での日記
北欧での日記
他の国での日記
qiita
その他の記事

記事の類別



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

  記事を検索

  おすすめの記事

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

ไทย

日本語

中文