Thursday, December 4, 2014

การอ่านภาพขนาดใหญ่ทีละส่วน ด้วย Python และ PIL ตอนที่ 2

จากตอนที่แล้ว เรารู้ระบบการอ่านค่าของ PIL แล้ว

ต่อไปเราจะลองอ่านค่าส่วนหนึ่งของรูปขึ้นมา สมมติว่ารูปของเราคือ Lenna-gray-raw.tif ซึ่งเมื่อใช้คำสั่งในการอ่านรูปพบว่า
img.size ให้ค่า (512,512) ซึ่งเป็น ความกว้าง x ความสูง หรือ จำนวนคอลัมน์ x จำนวนแถว
และ
img.tile แล้วพบว่า มันเป็นแบบ ('raw', (0, 0, 512, 64), 8, ('L', 0, 1))

ซึ่งหมายเหตุตรงนี้นิดนึงว่า
1. ในการอ่านค่ารูปด้วย PIL แม้ว่ารูปเดียวกัน แต่คนละระบบหรือคนละเครื่อง มันอาจจะแสดงการอ่านจำนวนแถวครั้งละไม่เท่ากัน เช่นตอนนี้เป็น 64 แถว แต่เครื่องอื่นที่ผมทดลองใช้มันอ่านทีละ 16 หรือ 32 แถวก็มี ทั้งนี้เดาว่าแล้วแต่หน่วยความจำที่มี OS ที่ใช้และขนาดของรูป แต่ไม่มีผลอะไรกับสิ่งที่เราจะทำในตอนนี้ครับ
2. L ในที่นี้คือ 8-bit pixels, black and white แต่รูปแบบ tif อาจจะเก็บแบบ P คือ 8-bit pixels, mapped to any other mode using a colour palette ก็ได้ ตรวจสอบให้ดีก่อนจะเขียนโปรแกรมต่อไปนะครับ

เมื่อทราบข้อมูลข้างต้นแล้ว สมมติว่าผมจะลองอ่านค่าเฉพาะ 64 แถวแรกของรูปแบบ raw tif ขนาด 512x512 พิกเซลขึ้นมาแสดงผลให้ได้ดังนี้


ก็จะเขียนโค้ดดังนี้ครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.tif")

#try to read first 64 lines
img.size = (512,64)
#we will skip for 8 bytes (image header)
img.tile = [('raw', (0, 0, 512, 64), 8, ('L', 0, 1))]
#load part of image according to new size and tile's properties
#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(img)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()

และถ้าอยากประมวลผลรูปส่วนที่โหลดขึ้นมา ก็ทำได้ตามปกติครับ เช่นจะกลับหัวรูปก็แค่เพิ่มคำสั่ง
#try to flip image
img = img.transpose(Image.FLIP_TOP_BOTTOM)

หลังคำสั่ง img.load() ก็น่าจะได้ผลประมาณนี้ครับ


ถ้าเราต้องการเข้าถึงพิกเซลก็ทำได้ครับ โดยเปลี่ยนคำสั่ง img.load() ให้เป็น pix = img.load() เพื่ออ่านค่าพิกเซลมาเก็บไว้ในโครงสร้างของ PIL และสามารถอ่านค่าแต่ละพิกเซลโดยใช้คำสั่ง pix[c,r] เมื่อ c และ r คือคอลัมน์และแถวของภาพตามลำดับ อย่างไรก็ตาม ผมพบว่าการอ่านส่วนของภาพแบบนี้ จะทำให้โครงสร้างที่เก็บพิกเซลนี้เป็นแบบ read only คือแก้ไขไม่ได้ เพราะฉะนั้นจำเป็นต้องสร้างรูปใหม่ขึ้นมาอีกหนึ่งรูปเพื่อเก็บผลลัพธ์จากการประมวลผลภาพ

ลองดูตัวอย่างต่อไปนี้ ที่เราทำการอ่านส่วนของภาพแล้วประมวลผลทีละพิกเซล แล้วแสดงผลเป็นรูปใหม่ การประมวลผลจะเป็นแบบง่ายๆคือทำ inverse ของรูปครับ


โค้ดครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.tif")

#try to read first 64 lines
img.size = (512,64)
#we will skip for 8 bytes (image header)
img.tile = [('raw', (0, 0, 512, 64), 8, ('L', 0, 1))]
#load part of image according to new size and tile's properties to pixel object
pix = img.load()
#create new output image
out = Image.new(img.mode, img.size, None)
pix2 = out.load()
#loop for each row and column
w,h = img.size
for r in range(h):
    for c in range(w):
        pix2[c,r] = 255-pix[c,r]        

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()
ในตอนต่อไปเราจะลองใช้ลูปเพื่อวนอ่านค่ารูปทีละส่วนมาประมวลผลดูครับ

No comments:

Post a Comment