matplotlib และ matplotlib นั้นนอกจากจะใช้วาดกราฟและแผนภาพแล้วก็ยังใช้ในการจัดการภาพทั่วๆไปได้ด้วย
เรื่องของการจัดการรูปภาพนั้นมีรายละเอียดมากมาย สามารถใช้ทำอะไรได้มากมาย แต่ในที่นี้จะพูดถึงแค่เบื้องต้นเท่านั้น
การใช้ imshow เพื่อแสดงภาพจากค่าสี ฟังก์ชันที่ใช้แสดงผลภาพใน matplotlib คือ plt.imshow ฟังก์ชันนี้ดูเผินๆจะคล้าย plt.pcolor และ plt.pcolormesh ซึ่งเคยกล่าวถึงไปแล้วใน
บทที่ ๒๔ นั่นคือทั้ง pcolor, pcolormesh และ imshow จะนำอาเรย์สองมิติมาแสดงผลเป็นสีๆ แต่มีข้อแตกต่างกันอยู่หลายอย่างซึ่งสามารถเห็นได้ชัด
ขอเริ่มยกตัวอย่างการใช้โดยเปรียบเทียบ imshow กับ pcolor โดยสร้างอาเรย์ที่มีค่าตัวเลขไล่เรียงกัน ๙ ตัวแล้วนำมาขึ้นรูปใหม่เป็นอาเรย์ 3x3 จากนั้นนำมาวาดแผนภาพไล่สีด้วย pcolor พร้อมกับ imshow
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(9).reshape(3,3)
plt.figure(figsize=[7,7])
plt.subplot(211,title='imshow')
plt.imshow(x,cmap='gray')
plt.subplot(212,title='pcolor')
plt.pcolor(x,cmap='gray')
plt.show()
จากตัวอย่างนี้จะทำให้เห็นข้อแตกต่างได้หลายข้อ เช่น
- imshow จะแสดงแกนตั้งโดยเริ่มจาก 0 ที่ด้านบนสุด แต่ pcolor จะเริ่มจากด้านล่างสุด
- เมื่อใช้ imshow สัดส่วนระหว่างแกน x และ y จะถูกตั้งให้เท่ากันเสมอ แต่ pcolor จะถูกปรับตามความเหมาะสม
- แต่ละช่องใน imshow จะมีใจกลางอยู่ที่ตำแหน่งตามค่า แต่ pcolor จะวางอยู่ระหว่างค่านั้นกับค่าถัดไป
นอกจากนี้ยังมีข้อแตกต่างอื่นๆที่จะเห็นได้ต่อไปอีก เพราะ imshow มีคีย์เวิร์ดที่ใส่เพิ่มเติมลงไปเพื่อปรับแต่งอะไรได้มากกว่าที่ pcolor มี
หากจะปรับแต่ง imshow ให้ดูคล้ายกับ pcolor ก็อาจทำได้โดยตั้งคีย์เวิร์ด aspect และ origin
aspect คือสัดส่วนขนาด y ต่อแกน x ซึ่งโดยทั่วไป imshow จะทำการปรับสัดส่วนแกนกราฟให้เป็น 1 คือ x y เท่ากัน เพื่อให้เป็นภาพตามสัดส่วนจริง แต่หากต้องการให้ปรับแผ่กว้างตามพื้นที่ที่มีโดยอัตโนมัติ auto
ส่วน origin จะกำหนดว่าจะเริ่มตำแหน่ง 0 ที่บนหรือล่าง ถ้า upper คือบน ถ้า lower คือล่าง โดยทั่วไปจะถูกตั้งเป็น upper แต่เราสามารถตั้งเป็น lower ได้
ลองปรับตามนี้จะได้ผลคล้ายกับ pcolor คือเริ่ม 0 จากด้านล่าง แล้วก็ไม่มีการปรับสัดส่วนของภาพแนวตั้งและนอนให้เท่ากัน
x = np.arange(81).reshape(9,9)
plt.imshow(x,cmap='bone',aspect='auto',origin='lower')
plt.show()
แต่ก็จะยังต่างกันอยู่เพราะตำแหน่งของช่องแต่ละช่องจะวางโดยมีใจกลางอยู่ที่ค่าตัวเลข ในขณะที่ pcolor จะวางช่องไว้ระหว่างค่า
นอกจากนี้ imshow ยังทำให้มีการประมาณค่าระหว่างช่วงได้ แทนที่จะแสดงเป็นช่องๆ รูปแบบการประมาณค่าในช่วงกำหนดโดยคีย์เวิร์ด interpolation สามารถเลือกได้หลากหลายรูปแบบมาก เช่นหากตั้งเป็น none ก็จะไม่มีการประมาณค่าในช่วงและจะออกมาเหมือน pcolor
ตัวอย่าง
x = np.sin(np.arange(400).reshape(20,20)/2.)
plt.figure(figsize=[6,12])
plt.subplot(211)
plt.imshow(x,cmap='rainbow',interpolation='none')
plt.subplot(212)
plt.imshow(x,cmap='rainbow',interpolation='hanning')
plt.show()
ภาพด้านบนไม่มีการประมาณค่าในช่วง ส่วนภาพด้านล่างมีการประมาณค่าในช่วงแบบ hanning
สำหรับแบบอื่นๆดูตัวอย่างได้ใน
http://matplotlib.org/examples/images_contours_and_fields/interpolation_methods.html และ imshow ไม่สามารถจะใส่ค่าพิกัด x,y ของแต่ละจุดได้ แต่หากต้องการปรับตำแหน่งหรือขนาดของภาพจะใช้คีย์เวิร์ด extent แทน
รูปแบบการใส่ค่าคือ extent=(ซ้าย,ขวา,บน,ล่าง)
เช่นปรับตามนี้จะได้ภาพอยู่ในตำแหน่งแนวนอนจาก 20 ถึง 35, แนวตั้งจาก 5 ถึง 15
x = np.cos(np.arange(600).reshape(20,30)/2.)
plt.imshow(x,cmap='jet',extent=(20,35,5,15),interpolation='spline16')
plt.show()
จะเห็นว่าเราสามารถปรับภาพให้ยืดขยายตามที่ต้องการได้ง่ายดาย เพียงแต่ว่าตำแหน่งแต่ละจุดต้องเรียงห่างเท่ากันเสมอ จะกำหนดจุดให้อยู่ตรงไหนห่างกันเท่าไหร่ก็ได้เหมือนอย่าง pcolor ไม่ได้
การใช้แม่สีสามสีใน imshow imshow นอกจากจะใช้สีเดียวคู่กับคัลเลอร์แม็ปแล้วยังสามารถใช้แม่สีสามสีเป็นตัว กำหนดสีได้ด้วย ซึ่งในกรณีนี้จะต้องใช้อาเรย์ในรูปแบบสามมิติโดยที่มิติที่หนึ่งมีขนาดเท่า ความสูง มิติที่สองมีขนาดเท่าความกว้าง และมิติที่สามมีขนาดเป็น 3 หรือ 4 โดยถ้ามี 3 ก็คือบรรจุค่าของแม่สีทั้งสาม แดง, เขียว และน้ำเงิน แต่หากมี 4 ก็คือบรรจุค่าของแม่สีทั้งสามและเพิ่มค่าความโปร่งใสมาเป็นตัวสุดท้าย
ค่าสีอาจใส่ในรูปแบบของจำนวนจริงตั้งแต่ 0 ถึง 1 หรืออาจแสดงเป็นตัวเลข 0 ถึง 255 ก็ได้
ในกรณีที่จะใช้ค่าสี 0 ถึง 255 จะต้องใช้อาเรย์ที่มีชนิดข้อมูลเป็น uint8 ซึ่งอาจทำได้โดยการใช้เมธอดแปลง astype('uint8')
ตัวอย่าง ตั้งค่าสีให้มีส่วนของสีเขียวเพิ่มขึ้นเรื่อยๆตามแนวนอน และสีน้ำเงินเพิ่มตามแนวตั้ง ส่วนสีแดงเป็น 0 ทั้งหมด
a,b = np.meshgrid(np.arange(0,125),np.arange(0,100))
x1 = np.zeros([100,125])
x2 = a/125.
x3 = b/100.
x = np.stack((x1,x2,x3),axis=2)
plt.imshow(x)
plt.show()
คราวนี้ลองเขียนให้อยู่ในรูปของค่า 0 ถึง 255 เปลี่ยนให้สีแดงเพิ่มตามแนวนอน สีเขียวเพิ่มตามแนวตั้ง ส่วนสีน้ำเงินคงที่
a,b = np.meshgrid(np.arange(0,125),np.arange(0,100))
x1 = a*255/125
x2 = b*255/100
x3 = np.zeros([100,125])+127
x = np.stack((x1,x2,x3),axis=2).astype('uint8')
plt.imshow(x)
plt.show()
ทีนี้ก็จะเห็นแล้วว่า imshow สามารถนำค่าแม่สีทั้งสามมาแสดงผลได้ทำให้ได้ภาพที่มีสีสันตามที่ต้องการ
ถ้าหากข้อมูลในอาเรย์นั้นคือค่าสีของจุดแต่ละพิกเซลของรูปภาพ เราก็สามารถแสดงผลรูปภาพลงภายในนี้ได้
การเปิดภาพขึ้นมาจากไฟล์ เราสามารถโหลดข้อมูลสีจากไฟล์ภาพทั่วไปขึ้นมาเพื่อใส่ในอาเรย์ได้โดยฟังก์ชัน imread ในมอดูลย่อย image ของ matplotlib มอดูลนี้มีไว้สำหรับจัดการภาพโดยเฉพาะ
โดยทั่วไป matplotlib.image นั้นตอน import มักนิยมย่อเป็น mpimg ในที่นี้ก็จะใช้ตามนั้น
import matplotlib.image as mpimg
ในส่วนนี้ขอยกภาพจากอนิเมะเรื่อง gochuumon wa usagi desu ka? มาใช้เป็นตัวอย่าง
ที่มาของภาพทั้งหมดมาจาก
ภาพที่โหลดมาจะอยู่ในรูปอาเรย์สามมิติซึ่งแทนตำแหน่งในแนวตั้ง, ตำแหน่งในแนวนอน และค่าของแม่สีทั้งสาม
ค่าภายในนั้นอาจมีทั้งที่เป็นชนิด uint8 ที่มีค่าตั้งแต่ 0 ถึง 255 หรือชนิด float32 ที่มีค่าตั้งแต่ 0 ถึง 1 แล้วแต่ชนิดไฟล์ จะแบบไหนก็สามารถแสดงสีได้ไม่ต่างกัน
ลองเปิดดูเป็นตัวอย่าง
import matplotlib.image as mpimg
chino = mpimg.imread('chino01.jpg')
print(chino)
print(chino.shape)
ได้
[[[246 237 228]
[246 237 228]
[246 237 228]
...,
...,
[245 236 229]
[255 249 241]
[250 243 235]]]
(700, 501, 3)
สามารถนำมาใช้ใน imgshow เพื่อแสดงภาพได้ทันที
plt.figure(figsize=[6,7])
plt.imshow(chino)
plt.show()
เราสามารถลองเอาภาพมาวิเคราะห์เพื่อแยกดูส่วนประกอบในแต่ละสีได้
chino = mpimg.imread('chino02.jpg')
plt.subplot(221)
plt.imshow(chino)
plt.subplot(222)
plt.imshow(chino*np.array([1,0,0],dtype='uint8'))
plt.subplot(223)
plt.imshow(chino*np.array([0,1,0],dtype='uint8'))
plt.subplot(224)
plt.imshow(chino*np.array([0,0,1],dtype='uint8'))
plt.subplots_adjust(0.05,0.05,0.98,0.98,0.1,0.1)
plt.show()
การปรับแต่งภาพ เมื่อเราได้เห็นแล้วว่าภาพสวยๆที่เห็นนั้นตัวจริงประกอบไปด้วยตัวเลขต่างๆในแถ วอาเรย์ หากเปลี่ยนแปลงแก้ไขตัวเลขภายในอาเรย์ก็จะสามารถทำให้ภาพเปลี่ยนแปลงไปได้ ในที่นี้เราจะมาลองจัดการภาพอย่างง่ายๆกัน
เริ่มแรกลองมาทำภาพโทนขาวดำ ซึ่งสามารถทำได้ง่ายๆโดยนำสามสีมาเฉลี่ยกัน
chino = mpimg.imread('chino03.jpg')
chino = (chino.sum(axis=2)[:,:,None]/3*np.array([1,1,1])).astype('uint8')
plt.figure(figsize=[6,7])
plt.axes([0.05,0.05,0.93,0.93])
plt.imshow(chino)
plt.show()
หรืออาจลองเปลี่ยนสีให้เป็นสีตรงข้ามโดยเอา 255 มาลบ
chino = mpimg.imread('chino04.jpg')
chino = 255-chino
plt.axes([0.05,0.01,0.92,0.99])
plt.imshow(chino)
plt.show()
ทำให้ภาพเข้มขึ้นหรืออ่อนลงได้
chino = mpimg.imread('chino05.jpg')
plt.figure(figsize=[9,6])
plt.subplot(121)
plt.imshow((chino*0.5).astype('uint8'))
plt.subplot(122)
plt.imshow((128+chino*0.5).astype('uint8'))
plt.subplots_adjust(0.05,0.05,0.98,0.98,0.1,0.1)
plt.show()
ตัดภาพมาบางส่วน
chino = mpimg.imread('chino06.jpg')
plt.subplot(121)
plt.imshow(chino[30:380,100:280])
plt.subplot(122)
plt.imshow(chino[0:330,290:480])
plt.subplots_adjust(0.05,0.05,0.98,0.98,0.1,0.1)
plt.show()
เราอาจเข้าถึงแล้วเปลี่ยนข้อมูลบางส่วนให้เป็นสีขาว สร้างภาพที่ขาดไปเป็นริ้วๆได้
chino = mpimg.imread('chino07.jpg')+0
chino[:,::6] = 255
plt.figure(figsize=[6,7])
plt.axes([0,0.05,1,0.9])
plt.imshow(chino)
plt.show()
ลองทำให้สว่างมืดสลับกันไปเป็นลูกคลื่น
chino = mpimg.imread('chino08.jpg')
x = np.arange(chino.shape[1])
chino = ((0.7+0.3*np.sin(x/20.))[:,None]*chino).astype('uint8')
plt.figure(figsize=[6,7])
plt.axes([0,0.05,1,0.9])
plt.imshow(chino)
plt.show()
ใช้ tile เพื่อเปลี่ยนอาเรย์ให้เป็นภาพเรียงซ้ำกัน
chino = mpimg.imread('chino09.jpg')
plt.figure(figsize=[7,7])
plt.axes([0.05,0.1,0.94,0.8])
plt.imshow(np.tile(chino,[3,4,1]))
plt.show()
สำหรับคำสั่งต่างๆที่ใช้จัดการภาพโดยเฉพาะซึ่งจะทำให้ปรับแต่งอะไรได้อย่างสะดวก หลากหลายนั้นอาจลองใช้มอดูลอื่นๆเสริมเพิ่มเติม เช่น PIL, skimage, opencv, ฯลฯ
การบันทึกภาพ สามารถบันทึกภาพได้โดยใช้ฟังก์ชัน imsave
ตัวอย่าง
x,y = np.meshgrid(np.arange(300),np.arange(300))
z1 = np.sin(((x-150)**2+(y-150)**2)/1100.)**2
z2 = 0.6+np.cos(np.sqrt((x-150)**2+(y*2-150)**2)/4)*0.4
z3 = np.random.rand(300,300)
z = np.stack([z1,z2,z3],axis=2)
mpimg.imsave('c40a15.jpg',z)
เท่านี้ก็จะได้ภาพออกมาตามที่ต้องการ
การนำภาพมาใส่เป็นฉากหลัง บางคนทำงานอยู่กับกราฟตลอด อาจรู้สึกเบื่อกับฉากหลังขาวๆธรรมดา ต้องการใส่รูปสวยๆเพื่อให้ดูมีสีสันขึ้นมา เราสามารถใช้ imshow เพื่อเพิ่มรูปลงไปได้
จุดสำคัญก็คือภาพที่ใส่ลงไปจะต้องไม่ไปกระทบ กับกราฟที่เป็นข้อมูลที่เราต้องการแสดงจริงๆ เพียงแค่ถูกใส่เข้าไปด้านหลังอย่างเงียบๆเท่านั้น
หนทางอาจมีหลายวิธี เช่นรับค่าขอบเขตของกราฟเดิมมาด้วยเมธอด ใส่คีย์เวิร์ด get_xlim และ get_ylim จากนั้นก็ปรับขอบเขตให้เป็นไปตามค่าที่ได้โดยใส่ในคีย์เวิร์ด extent แล้วตั้งค่า zorder ให้น้อยติดลบไว้เพื่อให้อยู่ด้านหลังเสมอ
อาจลองทำเป็นฟังก์ชันขึ้นมาเพื่อนำมาใส่ตอนท้ายได้
ลองทำดูเป็นตัวอย่าง
def chinobg(ax):
chino = mpimg.imread('chino10.jpg')
ext = ax.get_xlim()+ax.get_ylim()
ax.imshow(chino,extent=ext,zorder=-1,interpolation='gaussian')
x = np.linspace(0,1000,101)
plt.figure(figsize=[6,7])
ax = plt.axes([0.05,0.05,0.93,0.92])
for i in range(30):
y = np.random.normal(10*np.random.rand(),10+5*np.random.rand(),101).cumsum()
pl = plt.plot(x,y,c=np.random.rand(3))
chinobg(ax)
plt.show()
เท่านี้ก็ได้กราฟที่มีฉากหลังสวยๆแล้ว
อ้างอิง