ต่อจาก
บทที่ ๓
ในบทนี้จะว่าด้วยเรื่องของระบบของสีและการแปลงสีไปมาระหว่างระบบต่างๆ
รวมถึงการคัดกรองเอาเฉพาะบริเวณที่ต้องการโดยใช้สีเป็นเกณฑ์
การแปลงระบบสี
ในการแสดงผสมและแสดงสีในคอมพิวเตอร์นั้นโดยทั่วไปเราจะคุ้นเคยกับระบบ 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()
อ่านบทถัดไป >>
บทที่ ๕