ต่อจาก
บทที่ ๑
ในบทนี้จะแนะนำการอ่านไฟล์ภาพเข้ามาเป็นอาเรย์ในไพธอนด้วย cv2.imread() และเขียนภาพลงไฟล์ด้วย cv2.imwrite()
รวมถึงเรื่องระบบสีและการนำภาพมาแสดงโดย matplotlib
การอ่านภาพจากไฟล์มาเป็นอาเรย์
ฟังก์ชันสำหรับอ่านไฟล์รูปภาพใน opencv คือ cv2.imread() ใช้อ่านภาพจากไฟล์เข้ามาเป็นอาเรย์ของ numpy
เกี่ยวกับการใช้งานอาเรย์ในรายละเอียดนั้นจะไม่เน้นอธิบายในนี้มาก อาจอ่านเพิ่มเติมได้ใน
เนื้อหา numpy & matplotlib
เบื้องต้น โดยเฉพาะใน
บทที่ ๔:
การตัดแต่งแก้ไขอาเรย์
ในที่นี้ขอยกตัวอย่างการอ่านไฟล์รูปภาพโดยใช้ภาพนี้เป็นตัวอย่าง เซฟไปใช้เพื่อลองทำตามได้
miku02c01.jpg
วิธีการใช้คือให้ใส่ชื่อไฟล์รูปภาพที่ต้องการอ่านลงไป ก็จะได้อาเรย์ของภาพมา
import cv2
# numpy กับ matplotlib เองก็ใช้ตลอดด้วย แนะนำให้ import ไปด้วยทุกครั้ง
import numpy as np
import matplotlib.pyplot as plt
rup = cv2.imread('miku02c01.jpg')
print(type(rup)) # ชนิดออบเจ็กต์ ได้ <class 'numpy.ndarray'>
print(rup.shape) # รูปร่างอาเรย์ ได้ (450, 600, 3)
print(rup.dtype) # ชนิดข้อมูล ได้ uint8
print(rup.min()) # ค่าต่ำสุด ได้ 0
print(rup.max()) # ค่าสูงสุด ได้ 255
ภาพที่อ่านขึ้นมาได้จะอยู่ในรูปของอาเรย์ ๓ มิติ ซึ่งมีความหมายดังนี้
- มิติที่ 0: ความสูง มีขนาดเท่ากับจำนวนพิกเซลแนวตั้ง
- มิติที่ 1: ความกว้าง มีขนาดเท่ากับจำนวนพิกเซลแนวนอน
- มิติที่ 2: มีขนาดเป็น 3 แสดงถึงสี ๓ สี (น้ำเงิน, เขียว, แดง)
โดย dtype (ชนิดของข้อมูลภายในอาเรย์) จะเป็น unit8 ซึ่งเป็นค่าจำนวนเต็มที่อยู่ได้แค่ในช่วง 0 ถึง 255 เท่านั้น
ข้อควรระวังคือ ๓ สีใน opencv นั้นจะเรียงค่อนข้างแปลกกว่าที่อื่น คือเป็น [น้ำเงิน, เขียว, แดง] (BGR)
ในขณะที่สากลส่วนใหญ่จะเรียงเป็น [แดง, เขียว, น้ำเงิน] (RGB)
และการที่ข้อมูลเป็นอาเรย์ ทำให้การเข้าถึงแนวตั้งมาก่อนแนวนอน คือมิติที่ 0 (มิติแรก) แทนแนวตั้ง คือตามแกน y
และมิติถัดมาจึงแสดงแนวนอน แกน x ดังนั้นจึงเป็น (y,x) ไม่ใช่ (x,y)
ตรงนี้เป็นเรื่องที่อาจทำให้สับสนเผลอเขียนสลับกันได้ง่าย ต้องทำความเคยชินให้ดี
ในตัวอย่างนี้ขนาดของอาเรย์เป็น (450, 600, 3) จึงหมายถึงสูง ๔๕๐ พิกเซล กว้าง ๖๐๐ พิกเซล มี ๓ สี
การแปลงชนิดของข้อมูล
ดังที่ได้กล่าวถึงไปในบทนำแล้วว่าเรื่องชนิดของข้อมูลเป็นสิ่งที่ต้องระวังเป็นพิเศษเวลาใช้ opencv
เวลาเปิดไฟล์ขึ้นมาโดยใช้ cv2.imread() จะได้ชนิดข้อมูลเป็น uint8 หากต้องการชนิดอื่นก็ค่อยนำมาแปลงอีกทีได้
วิธีการแปลงชนิดข้อมูลภายในอาเรย์ของ numpy มีอยู่หลายวิธี เช่นถ้าต้องการเปลี่ยนเป็น float32 อาจเขียนได้แบบนี้
rup = rup.astype(np.float32)
rup = np.float32(rup)
rup = np.array(rup,dtype=np.float32)
rup = rup.astype('float32')
rup = np.array(rup,dtype='float32')
ภาพสีและขาวดำ
ภาพที่อ่านขึ้นมาโดยคำสั่ง cv2.imread() นี้หากแค่ใส่ชื่อไฟล์ลงไปโดยไม่กำหนดอะไรเลยก็จะอ่านภาพมาเป็นอาเรย์ของสี ๓ สีเสมอ
แม้ว่าจะเป็นภาพขาวดำก็ตาม
หากต้องการอ่านภาพแบบอื่นให้กำหนดสิ่งที่เรียกว่า "แฟล็ก" (flag) โดยเติมเป็นพารามิเตอร์ตัวที่ ๒ ลงไปถัดจากชื่อไฟล์
แฟล็กที่ใส่ได้คือ cv2.IMREAD_GRAYSCALE สำหรับอ่านภาพขาวดำ และ cv2.IMREAD_COLOR สำหรับอ่านภาพเป็นภาพสี
ที่จริงแล้วค่าพวกนี้เป็นแค่ตัวเลขธรรมดา เช่น cv2.IMREAD_GRAYSCALE จริงๆคือ 0 ส่วน cv2.IMREAD_COLOR นั้นจริงๆคือ 1
print(cv2.IMREAD_GRAYSCALE) # ได้ 0
print(cv2.IMREAD_COLOR) # ได้ 1
หากไม่ได้ใส่ ค่าตั้งต้นคือ 1 คือ cv2.IMREAD_COLOR อยู่แล้ว จึงเป็นภาพสี ดังนั้นหากต้องการอ่านภาพขาวดำให้ใส่
cv2.IMREAD_GRAYSCALE หรือจะใส่เลข 0 ไปโดยตรงก็ได้
ตัวอย่าง
rup = cv2.imread('miku02c01.jpg',cv2.IMREAD_UNCHANGED)
print(rup.shape) # ได้ (450, 600, 3)
จะเขียน cv2.IMREAD_GRAYSCALE หรือ 0 ก็ให้ผลเหมือนกัน แต่การใส่แฟล็กในรูปตัวแปรแฟล็กแบบนี้แทนที่จะใส่เป็นตัวเลข 0
โดยตรงจะทำให้เห็นภาพชัดกว่า เพราะไม่ต้องมาจำว่าภาพขาวดำคือ 0 ภาพสีคือ 1 แบบนี้ ดังนั้นโดยทั่วไปแนะนำให้ใส่
cv2.IMREAD_GRAYSCALE มากกว่าที่จะใส่เลข 0 ไปเฉยๆ แม้จะเขียนยาวกว่าก็ตาม
นอกจากนี้ยังมี cv2.IMREAD_UNCHANGED (ค่าเป็น -1) ซึ่งจะดูว่าภาพนั้นเป็นภาพแบบไหน
ถ้าเป็นขาวดำก็จะอ่านเป็นขาวดำโดยอัตโนมัติ ถ้ามีสีก็จะอ่านเป็นสี
ตัวอย่างเช่น สมมุติว่ามีภาพนี้ ซึ่งเป็นภาพขาวดำ
miku02c02.jpg
ถ้าอ่านโดยไม่ใส่แฟล็กก็จะได้เป็นอาเรย์ ๓ มิติ แต่ถ้าใส่ cv2.IMREAD_UNCHANGED หรือ cv2.IMREAD_GRAYSCALE
จึงจะได้เป็นภาพขาวดำ
rup = cv2.imread('miku02c02.jpg')
print(rup.shape) # ได้ (450, 600, 3)
rup = cv2.imread('miku02c02.jpg',cv2.IMREAD_UNCHANGED)
print(rup.shape) # ได้ (450, 600)
แต่หากเป็นภาพสีอยู่แล้ว ใส่ cv2.IMREAD_UNCHANGED ก็จะเป็นภาพสีเหมือนกับใส่ cv2.IMREAD_COLOR หรือไม่ใส่อะไร
ดังนั้นหากไม่รู้ว่าเป็นภาพสีหรือภาพขาวดำ แต่ต้องการให้อ่านในแบบนั้นอย่างที่ควรเป็นก็ให้ใส่ cv2.IMREAD_UNCHANGED (หรือ
-1) ไป
แฟล็ก |
เลข |
ความหมาย |
cv2.IMREAD_UNCHANGED |
-1 |
เปิดขึ้นมาเป็นมีสีหรือขาวดำแล้วแต่ภาพ |
cv2.IMREAD_GRAYSCALE |
0 |
เปิดขึ้นมาเป็นภาพขาวดำ |
cv2.IMREAD_COLOR |
1 |
เปิดขึ้นมาเป็นภาพมีสี |
นอกจากแฟล็ก ๓ ตัวที่ยกมาแล้วยังมีแฟล็กอื่นๆอีกมาก ซึ่งในที่นี้จะยังไม่กล่าวถึง
อาจสามารถลองดูแฟล็กที่เหลือได้โดยพิมพ์
print('\n'.join(['%s: %d'%(x,getattr(cv2,x)) for x in dir(cv2) if 'IMREAD' in x]))
แสดงภาพด้วย matplotlib
opencv เองก็มีฟังก์ชันที่สร้างหน้าต่าง GUI ในการดูภาพ แต่โดยทั่วไปสามารถใช้ matplotlib แทนได้
ดังนั้นในที่นี้จะยังไม่กล่าวถึง
เพราะจุดประสงค์หลักคือจะมุ่งไปที่ความสามารถในการแก้ไขและวิเคราะห์ภาพโดยใช้โค้ด ส่วนการใช้ GUI
นั้นอาจเขียนถึงทีหลัง
matplotlib มีฟังก์ชัน plt.imshow() ซึ่งใช้แสดงภาพได้ง่ายกว่าที่จะใช้ฟ้งก์ชันแสดงภาพของ opencv เอง
อย่างไรก็ตาม มีข้อควรระวังดังที่ได้กล่าวไปแล้วคือระบบสีใน opencv จะเรียงเป็น [น้ำเงิน, เขียว, แดง]
ในขณะที่สากลปกติรวมถึงใน matplotlib จะเรียงแบบ [แดง, เขียว, น้ำเงิน]
ดังนั้นเมื่อจะนำภาพสีมาแสดงใน matplotlib จะต้องสลับลำดับให้ถูกต้องด้วย ซึ่งก็ทำง่ายๆโดยแค่เติม [:,:,::-1] ลงไป
ตัวอย่าง อ่านภาพนี้
miku02c03.jpg
rup = cv2.imread('miku02c03.jpg')
plt.imshow(rup[:,:,::-1])
plt.show()
จะได้ภาพแบบนี้
ถ้าไม่ได้กลับสีก็จะออกมาแปลกๆแบบนี้ คือสีน้ำเงินกับแดงถูกสลับกัน ดูแปลกๆไป
ส่วนภาพขาวดำจะเป็นอาเรย์ที่มีแค่ ๒ มิติ เมื่ออ่านใน imshow() จะได้สีที่เปลี่ยนไปตาม colormap โดยค่าตั้งต้นคือสี
viridis ซึ่งไล่ค่าจากต่ำไปสูงเป็น ม่วงเข้ม>เขียว>เหลือง
คราวนี้ลองอ่านภาพเดิมขึ้นมาในโหมดขาวดำแล้วใช้ imshow() จะได้แบบนี้
rup = cv2.imread('miku02c03.jpg',0)
plt.imshow(rup)
plt.colorbar(pad=0.01) # วาดแถบสีทางขวาเพื่อบอกว่าความเข้มแต่ละจุดที่เห็นเป็นเท่าไหร่ด้วย
plt.show()
เพื่อที่จะให้เห็นภาพขาวดำอย่างที่เป็นจริงๆ โดยทั่วไปควรกำหนด cmap เป็น gray
plt.imshow(rup,cmap='gray')
plt.colorbar(pad=0.01)
plt.show()
นอกจากนี้อาจใช้ Grays_r สีที่ได้จะต่างกันเล็กน้อย แต่ก็เป็นขาวดำเช่นกัน
plt.imshow(rup,cmap='Greys_r')
plt.show()
สามารถตัดมาแสดงเฉพาะบางส่วน
plt.imshow(rup[0:70,240:360],cmap='gray')
plt.show()
ข้อควรระวังอีกอย่างก็คือ เมื่อวาดแบบไร้สีใน matplotlib สีที่ปรากฏจะปรับสเกลอัตโนมัติให้เข้ากับค่าต่ำสุดและสูงสุด
ซึ่งอาจจะไม่ใช่ 0 ถึง 255 เช่นถ้าตัดมาเฉพาะบางส่วนแบบนี้
plt.imshow(rup[70:160,320:430],cmap='gray')
plt.colorbar(pad=0.01)
plt.show()
แบบนี้สีที่ได้ก็จะต่างไปจากที่ควรจะเป็นจริงๆ ตรงที่ค่าความสว่าง 100 กลับกลายเป็นสีดำไป
หากต้องการให้ปรับสเกลให้ไล่ตั้งแต่ 0 ถึง 255 จริงๆก็กำหนดค่า vmin กับ vmax ลงไปเองด้วย แบบนี้
plt.imshow(rup[70:160,320:430],vmin=0,vmax=255,cmap='gray')
plt.colorbar(pad=0.01)
plt.show()
ความโปร่งใส
ภาพบางสกุลเช่น .png นอกจากจะเป็นภาพสีที่มีค่าสี ๓ สีแล้ว ยังมีค่าความโปร่งใส ซึ่งทำให้จำนวนสีไม่ใช่ ๓ แต่เป็น ๔
หากเปิดด้วย cv2.imread() โดยไม่ได้ใส่แฟล็กอะไร แม้ว่าจะเป็นภาพที่มีค่าความโปร่งใสอยู่ก็จะไม่ได้ค่าความโปร่งใสติดมาด้วย
ขนาดของมิติที่ ๓ จึงยังคงเป็น ๓
ตัวอย่างเช่น อ่านภาพนี้ซึ่งมีฉากหลังโปร่งใส
teto02c01.png
rup = cv2.imread('teto02c1.png')
print(rup.shape) # ได้ (450, 600, 3)
plt.imshow(rup[:,:,::-1])
plt.show()
เพื่อที่จะให้ภาพที่มีค่าความโปร่งใสอยู่ถูกอ่านออกมาได้อย่างถูกต้องด้วย ตอนอ่านไฟล์ให้ใส่แฟล็ก
cv2.IMREAD_UNCHANGED
แต่พอมีค่าความโปร่งแสงขึ้นมาเป็นสีที่ ๔ แล้ว เวลาใช้ imshow() จะกลับแถวโดยใช้ [:,:,::-1] แบบนี้ไม่ได้เพราะถ้า ::-1
แบบนี้ลำดับจะกลายเป็น 3,2,1,0 ให้ใส่ลำดับไปโดยตรง [:,:,[2,1,0,3]] ให้สลับเฉพาะแค่ ๓ ตัวแรก
ตัวอย่าง
rup = cv2.imread('teto02c1.png',cv2.IMREAD_UNCHANGED)
print(rup.shape) # ได้ (450, 600, 4)
plt.imshow(rup[:,:,[2,1,0,3]])
plt.show()
การสร้างภาพขึ้นมาใหม่
เนื่องจากข้อมูลภาพเมื่ออยู่ในโปรแกรมไพธอนก็คือตัวอาเรย์ของ numpy นั่นเอง
หากเราจะสร้างอาเรย์ขึ้นมาเพื่อแทนภาพก็ทำได้
ตอนสร้างควรกำหนด dtype ของอาเรย์เป็น uint8 ไปด้วย เพื่อให้ทำงานได้ถูกต้องใน cv2
(แม้ว่าบางครั้งอาจจะไม่จำเป็นก็ตาม แต่มีหลายกรณีที่จำเป็น)
ตัวอย่างเช่น สร้างอาเรย์ที่มีแต่ค่า 0 แล้วค่อยๆเติมสีเข้าไปในแต่ละช่อง
arr = np.zeros([200,300,3],dtype=np.uint8)
arr[20:180,20:200,0] = 255 # วาดสีน้ำเงิน
arr[50:150,50:250,1] = 200 # วาดสีเขียว
arr[120:180,150:270,2] = 155 # วาดสีแดง
plt.imshow(arr[:,:,::-1])
plt.show()
เพียงแต่ต้องไม่ลืมว่าขณะแสดงผลด้วย imshow() มิติที่ ๓ จะเรียงเป็น [แดง, เขียว, น้ำเงิน] เมื่อนำมาใช้ใน cv2
จึงเปลี่ยนเป็น [น้ำเงิน, เขียว, แดง]
การบันทึกภาพลงไฟล์ด้วย cv2.imwrite
คำสั่งสำหรับนำข้อมูลภาพจากอาเรย์มาบันทึกลงในไฟล์คือ cv2.imwrite
ลำดับ |
ชื่อ |
สิ่งที่ต้องใส่ |
ชนิดข้อมูล |
1 |
filename |
ชื่อไฟล์ |
str |
2 |
img |
อาเรย์ของรูปภาพ |
np.array |
3 |
params |
พารามิเตอร์เพิ่มเติม |
tuple ของ flag (int) |
ชนิดของไฟล์ที่บันทึกจะขึ้นอยู่กับชื่อ โดยดูตรงสกุลไฟล์ เช่น .png, .jpg (หรือ .jpeg), .tif (หรือ .tiff), .bmp
จะได้เป็นชนิดนั้นโดยอัตโนมัติ
ตัวอย่าง ลองสร้างอาเรย์ง่ายๆขึ้นมาอันหนึ่ง แล้วบันทึกเป็นภาพ (สำหรับวิธีการสร้างนั้นใช้คำสั่งของ numpy
จะไม่อธิบายละเอียด อาจอ่านเพิ่มเติมได้ใน
numpy &
matplotlib บทที่ ๔)
rup = np.repeat(np.repeat((np.arange(0,20*25*3*100,100)%256).reshape(20,25,3),20,0),15,1)
cv2.imwrite('imw02c01.jpg',rup)
imw02c01.jpg
ในส่วนของพารามิเตอร์เพิ่มเติมนั้นเป็นแฟล็กที่ไว้ใช้กำหนดรายละเอียดรูปแบบของภาพที่จะได้ จะใส่หรือไม่ใส่ก็ได้
สำหรับการใส่จะมีวิธีใช้ที่ยุ่งเล็กน้อย
คือให้ใส่ในรูปของทูเพิลของคู่
(แฟล็กของพารามิเตอร์, ค่า) แบบนี้
แฟล็กที่ใช้ได้ใน imwrite นั้นมีหลายตัว อาจลองดูแฟล็กทั้งหมดที่มีได้โดยพิมพ์
print('\n'.join(['%s: %d'%(x,getattr(cv2,x)) for x in dir(cv2) if 'IMWRITE' in x]))
ในที่นี้ขอยกตัวอย่างการใช้ส่วนหนึ่ง เช่นกรณีบันทึกภาพ jpg สามารถใส่แฟล็ก cv2.IMWRITE_JPEG_QUALITY ลงไปเพื่อกำหนดคุณภาพ
ตั้งแต่ 0 ถึง 100 (หากไม่ใส่ ค่าตั้งต้นเป็น 95) ยิ่งคุณภาพต่ำขนาดยิ่งเล็ก แต่ภาพอาจจะออกมาดูไม่ดี
ลองเอาภาพเดิมมาบันทึกโดยปรับแฟล็กกำหนดคุณภาพของภาพ
cv2.imwrite('imw02c02.jpg',rup,(cv2.IMWRITE_JPEG_QUALITY,50))
cv2.imwrite('imw02c03.jpg',rup,(cv2.IMWRITE_JPEG_QUALITY,0))
ภาพคุณภาพ 50
imw02c02.jpg
ภาพคุณภาพ 0
imw02c03.jpg
จะเห็นได้ถึงความแตกต่าง ยิ่งคุณภาพต่ำภาพก็ยิ่งแตก
สำหรับภาพที่จะบันทึกเป็น .png นั้นสามารถใส่แฟล็ก cv2.IMWRITE_PNG_COMPRESSION เพื่อกำหนดระดับการบีบอัดไฟล์
ใส่เป็นเลขได้ตั้งแต่ 0 ถึง 9 โดยถ้าไม่ใส่ ค่าตั้งต้นคือ 3 ยิ่งใส่ก็จะยิ่งได้ไฟล์ที่กินเนื้อที่มาก
แต่ก็จะใช้เวลาเขียนนานขึ้น
rup = np.repeat(np.repeat((np.arange(0,20*20*3*150,150)%256).reshape(20,20,3),20,0),20,1)
cv2.imwrite('imw02c04.png',rup,(cv2.IMWRITE_PNG_COMPRESSION,9))
imw02c04.jpg
การบันทึกภาพลงไฟล์ด้วยมอดูลอื่น
นอกจากจะใช้ฟังก์ชันของ cv2 โดยตรงแล้ว ใน matplotlib เองก็มีคำสั่งสำหรับเขียนไฟล์ คือ imsave จะใช้แทนก็ได้เช่นกัน
เพียงแต่ถ้าใช้กับภาพสีที่อ่านโดย cv2 จะต้องไม่ลืมเรียงลำดับอาเรย์กลับเป็น [แดง, เขียว, น้ำเงิน]
import matplotlib.image as mpimg
mpimg.imsave('rupmpl.jpg',rup[:,:,::-1])
ถ้าเป็นภาพขาวดำก็ไม่มีปัญหาเรื่องลำดับสี แต่เวลาใช้ต้องกำหนด cmap ไม่เช่นนั้นจะออกมาเป็นสี viridis
เหลืองเขียวม่วงแทน
อย่างไรก็ตาม ถึงตะกำหนด cmap='gray' ผลที่ได้ออกมาก็จะเป็นภาพชนิดสี (แค่ค่าของ ๓ สีเท่ากัน) จึงไม่แนะนำให้ใช้
ยกเว้นต้องการเปลี่ยนภาพขาวดำให้เป็นภาพไล่สีตาม colormap
เช่นถ้าต้องการเอาภาพมาเปิดในโหมดขาวดำแล้วกำหนดเป็นสีโทนทองแดงแบบนี้ก็ทำได้ สวยไปอีกแบบ
rin02c01.jpg
rin = cv2.imread('rin02c01.jpg',cv2.IMREAD_GRAYSCALE)
mpimg.imsave('rin02c02.jpg',rin,cmap='copper')
rin02c02.jpg
นอกจากนี้หากใช้มอดูลอื่นๆเช่น imageio หรือ skimage ก็มีฟังก์ชัน imsave สำหรับบันทึกภาพเช่นกัน แต่ก็ต้องเปลี่ยนลำดับสี
เช่นเดียวกับเมื่อใช้ matplotlib
from skimage import io
io.imsave('rupskimage.jpg',rup[:,:,::-1])
import imageio
imageio.imsave('rupimageio.jpg',rup[:,:,::-1])
ดังนั้นหากอ่านอาเรย์มาด้วย cv2.imread() ก็เขียนกลับด้วย cv2.imwrite() จะสะดวกที่สุด เพราะไม่ต้องกลับลำดับสี
ทิ้งท้ายบท
ที่ได้เขียนถึงในบทนี้เป็นแค่พื้นฐานการอ่านและเขียนไฟล์รูปภาพด้วย cv2
ยังไม่ได้เข้าเรื่องการแก้ไขหรือวิเคราะห์ภาพมากนัก
ความจริงแล้วเมื่อได้ข้อมูลรูปภาพออกมาเป็นอาเรย์แบบนี้แล้วถ้าจะใช้ฟังก์ชันของ numpy
จัดการอะไรต่างๆก็สามารถแก้ไขหรือวิเคราะห์ภาพได้เช่นกัน โดยไม่ต้องใช้ฟังก์ชันใน cv2
แต่แบบนั้นก็จะไม่ได้ใช้ความสามารถของ opencv
ซึ่งมีฟังก์ชันที่ช่วยในการจัดการแก้ไขปรับแต่งรูปภาพอยู่มากมายซึ่งใช้งานได้สะดวก
ในบทจากนี้ไปก็จะค่อยๆแนะนำฟังก์ชันสำหรับจัดการภาพเหล่านั้นไปเรื่อยๆทีละนิดไปตามลำดับ
อ่านบทถัดไป >>
บทที่ ๓