ต่อจาก
บทที่ ๒
ในบทนี้จะพูดถึงการนำรูปภาพ ๒ รูปขึ้นไปมาทำอะไรกัน โดยพื้นฐานแล้วก็คือเป็นการบวกลบคูณหารอาเรย์ numpy
โดยมีการใช้ฟังก์ชันของ opencv เข้าร่วมในการคำนวณด้วยซึ่งเหมาะสมสำหรับใช้ในการคำนวณความสว่างของภาพ
โดยเฉพาะที่อยู่ในรูปแบบ uint8
การบวกลบรูปภาพ
ข้อมูลรูปภาพเป็นอาเรย์ของ numpy ดังนั้นหากมีภาพที่ขนาดเท่ากัน ๒ ภาพก็สามารถนำมาบวกลบกันได้ด้วยเครื่องหมาย + - เลย
อย่างง่าย
แต่ปัญหาก็คือ ข้อมูลรูปภาพซึ่งส่วนใหญ่จะใช้เป็นข้อมูลชนิด uint8 นั้นมีค่าเป็นได้แค่จำนวนเต็มตั้งแต่ 0 ถึง 255 เท่านั้น
ถ้าหากเมื่อบวกกันแล้วเกิดค่าต่ำกว่า
0 หรือเกิน 255 ขึ้นมาก็จะทำให้เกิดปัญหาได้
หากเอาอาเรย์มาบวกลบกันธรรมดาแล้วค่าเกิน 255 ค่านั้นก็จะถูกหารปัดเศษ
ตัวอย่างเช่น สร้าง ๒ ภาพขาวดำที่มีการไล่สีขาวดำแนวตั้งและแนวนอน
import cv2
import numpy as np
rup1 = np.tile(np.arange(256),(256,1))
rup2 = rup1.T
cv2.imwrite('imw03c1.png',rup1)
cv2.imwrite('imw03c2.png',rup2)
imw03c01.png
imw03c02.png
ลองเอามาบวกกัน
cv2.imwrite('imw03c3.png',rup1+rup2)
imw03c03.png
ผลที่ได้จะออกมาแปลกๆอย่างที่เห็น นั่นคือส่วนที่ค่าเกิน 255 ไปจะกลับมาเป็น 0 นั่นคือค่าจะกลายเป็นเหมือนถูกหารด้วย 256
แล้วเอาเศษ (x = x%256)
หรือถ้าเอามาลบกัน ส่วนที่ต่ำกว่า 0 ก็จะกลับมาเป็น 255 อีก จึงออกมาแปลกๆแบบนี้
cv2.imwrite('imw03c3.png',rup1+rup2)
imw03c04.png
ซึ่งโดยทั่วไปเราคงจะไม่ได้ต้องการแบบนี้ ดังนั้นการนำอาเรย์รูปภาพมาบวกกันโดยตรงจึงไม่ควรนัก
แทนที่จะทำแบบนั้น เพื่อความปลอดภัยอาจใช้ฟังก์ชัน cv2.add() เวลาบวกกัน และ cv2.subtract() เวลาลบกันแทน
ข้อแตกต่างก็คือถ้าใช้ฟังก์ชันนี้บวกหรือลบแล้วค่าเกิน 255 ก็จะกลายเป็น 255 ไม่เกินนั้น ถ้าต่ำกว่า 0 ก็จะเป็น 0
cv2.imwrite('imw03c5.png',cv2.add(rup1,rup2))
cv2.imwrite('imw03c6.png',cv2.subtract(rup1,rup2))
imw03c05.png
imw03c06.png
คราวนี้ส่วนที่เกินจึงกลายเป็นขาวและดำไป
ภาพสีก็นำมาบวกลบได้เช่นเดียวกัน โดยสีเดียวกันก็จะบวกกันไป
ลองสร้างภาพที่มีเสียงไล่เรียงไปในแนวตั้งและแนวนอนคนละสีแล้วเอามาผสมกันดู
rup3 = np.ones([256,256,3])*255
rup3[:,:,0] = 0
rup3[:,:,1] = np.arange(256)
cv2.imwrite('imw03c07.png',rup3) # ไล่ซ้ายขวา แดงไปเหลือง
rup4 = np.zeros([256,256,3])
rup4[:,:,0] = 255
rup4[:,:,1] = np.arange(256).reshape(256,1)
cv2.imwrite('imw03c08.png',rup4) # ไล่บนล่าง น้ำเงินไปฟ้า
cv2.imwrite('imw03c09.png',cv2.add(rup3,rup4))
cv2.imwrite('imw03c10.png',cv2.subtract(rup3,rup4))
cv2.imwrite('imw03c11.png',cv2.subtract(rup4,rup3))
imw03c07.png
imw03c08.png
imw03c09.png
imw03c10.png
imw03c11.png
ผลลัพธ์มีหลายส่วนที่มีสีเป็นค่าเกิน 255 หรือต่ำกว่า 0 ส่วนที่เกินขอบเขตไปก็ถูกตัดเป็น 255 หรือ 0 ไป
ข้อแตกต่างจากการบวกลบอาเรย์ธรรมดาอีกอย่างก็คือ จะต้องทำกับข้อมูลชนิดเดียวกันเท่านั้น ถ้ามี uint8 จะไปบวกลบกับ float32
ก็ต้องแปลงตัวใดตัวหนึ่งเป็นอีกตัวก่อน ต่างจากเมื่อบวกกันธรรมดา
ลองเปรียบเทียบกันดู
arr1 = np.array([1],dtype=np.float32)
arr2 = np.array([1],dtype=np.uint8)
print(arr1+arr2) # ได้ [2.]
print(cv2.add(arr1,arr2)) # ได้ error: (-5:Bad argument) When the input arrays in add/subtract/multiply/divide functions have different types, the output array type must be explicitly specified in function 'arithm_op'
อาเรย์ใน numpy ที่บวกลบกันธรรมดานั้นถ้าจำนวนเต็มบวกกับจำนวนจริงก็จะได้ผลเป็นจำนวนจริง
การหาค่าส่วนต่างระหว่างรูปภาพ
นอกจากบวกลบกันธรรมดาแล้ว บางครั้งอาจต้องการเอา ๒ ภาพมาเทียบแล้วหักลบกันโดยหาส่วนต่าง ซึ่งทำได้โดยฟังก์ชัน
cv2.absdiff()
ตัวอย่างเทียบระหว่างหักลบด้วย cv2.absdiff() กับใช้ c2.subtract()
ใช้กับภาพขาวดำ
miku03c01.jpg
miku03c02.jpg
import cv2
miku1 = cv2.imread('miku03c01.jpg',cv2.IMREAD_GRAYSCALE)
miku2 = cv2.imread('miku03c02.jpg',cv2.IMREAD_GRAYSCALE)
miku3 = cv2.absdiff(miku2,miku1)
cv2.imwrite('miku03c03.jpg',miku3)
miku4 = cv2.subtract(miku2,miku1)
cv2.imwrite('miku03c04.jpg',miku4)
miku03c03.jpg
miku03c04.jpg
จะเห็นว่าเมื่อใช้ cv2.absdiff() ส่วนต่างไม่ว่าจะเกินมาจากภาพไหนก็จะได้ค่าออกมาเป็นค่าบวก
ต่อไปลองใช้กับภาพสี
miku03c05.jpg
miku03c06.jpg
miku5 = cv2.imread('miku03c05.jpg')
miku6 = cv2.imread('miku03c06.jpg')
miku6 = cv2.absdiff(miku6,miku5)
cv2.imwrite('miku03c07.jpg',miku6)
miku7 = cv2.subtract(miku6,miku5)
cv2.imwrite('miku03c08.jpg',miku7)
miku03c07.jpg
miku03c08.jpg
การคูณหารรูปภาพ
เมื่อมีบวกลบก็ย่อมมีคูณหารด้วย ฟังก์ชันคูณใน cv2 คือ cv2.multiply() ส่วนฟังก์ชันหารคือ cv2.divide()
หลักการก็เหมือนกับตอนบวกและลบ คือค่าจะไม่พ้นไปจากในขอบเขต 0 ถึง 255
ประโยชน์การใช้งานเช่นอาจสร้างตัวคูณที่จะทำให้เกิดการไล่สีต่างกันไปในแต่ละบริเวณ
ตัวอย่างเช่น มีภาพนี้อยู่
teto03c01.jpg
และลองสร้างภาพไล่สีแบบนี้ขึ้นมาเป็นตัวคูณ
arrx = np.ones([450,600,3])*255
arrx[:,:,1] -= np.arange(600)/5
arrx[:,:,0] = np.arange(600)/5
cv2.imwrite('a03c01.jpg',arrx)
a03c01.jpg
สามารถเอาภาพแรกมาคูณกับตัวไล่สีเพื่อจะให้ได้ภาพที่มีลักษณะไล่สีไปตามที่คูณได้
เพียงแต่ว่ามีความยุ่งยากอยู่เล็กน้อย เนื่องจากข้อมูลภาพมีค่าอยู่ในช่วง 0 ถึง 255 แบบนี้จึงไม่สามารถเอามาคูณกันได้เลย
ควรปรับขนาดตัวคูณให้เป็นค่า 0 ถึง 1
และการบวกลบคูณหารอาเรย์จะต้องทำโดยอาเรย์ที่มี dtype ชนิดข้อมูลเดียวกัน
ดังนั้นจึงต้องเปลี่ยนชนิดข้อมูลภาพให้เป็นชนิดจำนวนจริงไปด้วย
ในที่นี้ทำให้เป็น float32 เหมือนกันไปด้วย แล้วลองนำมาคูณกันดู
arrx = arrx.astype(np.float32)/255
teto = cv2.imread('teto03c01.jpg').astype(np.float32)
teto2 = cv2.multiply(teto,arrx)
cv2.imwrite('teto03c02.jpg',teto2)
teto03c02.jpg
เวลาที่จะนำผลที่ได้มาใช้ต่ออีกนั้น บางกรณีอาจต้องเปลี่ยนกลับเป็น uint8 ขึ้นอยู่กับว่าจะไปใช้กับฟังก์ชันอะไรต่อ แต่สำหรับ cv2.imwrite()
ซึ่งใช้บันทึกภาพลงไฟล์นั้นจะใช้ข้อมูลชนิดใดก็ได้จึงไม่จำเป็นต้องเปลี่ยนกลับก็ได้
ต่อมาลองเปลี่ยนมาเป็นหารดู
teto3 = cv2.divide(teto,arrx)
cv2.imwrite('teto03c03.jpg',teto3)
teto03c03.jpg
นอกจากนี้มีฟังก์ชัน cv2.pow() ไว้คำนวณยกกำลัง cv2.max() กับ cv2.min() เอาไว้ใช้คัดเอาค่าสูงสุดหรือต่ำสุด (คล้าย
np.maximum และ np.minimum ของ numpy แต่ต่างจาก np.max หรือ np.min)
การผสมรูปภาพโดยถ่วงน้ำหนัก
ฟังก์ชัน cv2.addWeighted() ใช้นำภาพ ๒ ภาพมารวมกันโดยถ่วงน้ำหนักตามที่กำหนด
วิธีใช้
cv2.addWeighted(x1, α, x2, β, γ)
ภาพใหม่ที่ได้จากการผสมจะมีค่าเป็น αx
1+βx
2+γ
โดย
- x1 คือ ภาพที่ ๑
- α คือ น้ำหนักของภาพที่ ๑
- x2 คือ ภาพที่ ๒
- β คือ น้ำหนักของภาพที่ ๒
- γ คือ ค่าที่บวกเพิ่มเติม
α+β จะเป็นตัวกำหนดว่าจะผสมเอาภาพไหนด้วยอัตราส่วนน้ำหนักเท่าไหร่ โดยทั่วไปจะให้ α+β=1
เพื่อให้ได้ความสว่างออกมาค่าอยู่ในช่วงระหว่างค่าของ ๒ ตัว
ลองใช้ ๒ ภาพนี้เป็นตัวอย่าง
teto03c04.jpg
teto03c05.jpg
เอามาผสมกันด้วยน้ำหนักที่ต่างกันไป
teto1 = cv2.imread('teto03c04.jpg')
teto2 = cv2.imread('teto03c05.jpg')
# ผสม 2:8
teto3 = cv2.addWeighted(teto1,0.2,teto2,0.8,1)
cv2.imwrite('teto03c06.jpg',teto3)
# ผสม 5:5
teto4 = cv2.addWeighted(teto1,0.5,teto2,0.5,1)
cv2.imwrite('teto03c07.jpg',teto4)
# ผสม 8:2
teto5 = cv2.addWeighted(teto1,0.8,teto2,0.2,1)
cv2.imwrite('teto03c08.jpg',teto5)
teto03c06.jpg
teto03c07.jpg
teto03c08.jpg
ด้วยการใช้น้ำหนักที่ต่างกันไปเรื่อยๆแบบนี้ทำขึ้นมาหลายภาพก็จะได้ภาพที่แสดงขั้นตอนการเปลี่ยนไปทีละขั้นแบบนี้ได้
อ่านบทถัดไป >>
บทที่ ๔