Saturday, December 13, 2014

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

สมมติว่าเราต้องการทำ Convolution หรือ Filtering แบบ Sliding neighborhood operation กับรูปทีละส่วน จะทำได้อย่างไร

ถ้าเราใช้โค้ดของเดิมแล้ว filter เลย เช่น ใช้ average filter เพื่อเบลอรูป ซึ่งใน PIL จะมีคำสั่งให้เช่น
kernel = [1]*25
img.filter(ImageFilter.Kernel((5,5),kernel))

ผลลัพธ์จะได้ประมาณนี้ (ให้สังเกตรอยต่อในแนวนอนในภาพ)
หมายเหตุ ข้อนี้ได้ลดขนาดของรูปเหลือ 256x256 เพื่อให้เห็นผลลัพธ์ชัดขึ้น


ซึ่งมาจากโค้ดต่อไปนี้
from PIL import Image, ImageTk, ImageFilter
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw-256.tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 64 rows
rowRead = 64
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead

#image header size, please check by img.tile, it is different for images
header = 8;
#kernel size 5x5
ksize = 5

#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("Lenna-gray-raw-256.tif")
    #reading size eg. 256,64
    img.size = (oriW, rowRead)
    #set tile size
    offset = rr*(rowRead*oriW)
    img.tile = [('raw', (0, 0, oriW, rowRead), header + offset, ('L', 0, 1))]
    #load part of image
    img.load()
    #filter part of image
    #create an average kernel of 5x5 = 25 elements
    kernel = [1]*(ksize*ksize)
    temp = img.filter(ImageFilter.Kernel((ksize,ksize),kernel))
    #copy to new image
    #paste(source image, top-left corner)
    out.paste(temp,(0,rr*rowRead))

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()

จะเห็นว่ามีข้อผิดพลาดเกิดขึ้นเป็นแถบในแนวนอน ซึ่งก็คือรอยต่อระหว่างแต่ละรูปที่เราแบ่งส่วนในการโหลด

เพื่อที่จะแก้ปัญหาดังกล่าว อาจทำได้โดย อ่านภาพในแต่ละครั้ง ให้จำนวนแถวมากกว่าเดิมนิดนึง คือเพิ่มด้วยขนาดความสูงของ filter ที่จะใช้หารด้วยสอง เช่น



สมมติว่าแบ่งรูปออกเป็น 3 แถบ บน กลาง ล่าง
ถ้าอ่านทีละ 64 แถว และ filter มีขนาด 5x5
1. แถบบน จำนวนแถวที่อ่านใหม่ก็จะเป็น 64+(5-1)/2 = 64+2 = 66 แถว นั่นคือ เพิ่มล่างสองแถว
2. แถบกลาง จำนวนแถวที่อ่านใหม่ก็จะเป็น 64+4 = 68 แถว นั่นคือ เพิ่มบนสองแถว ล่างสองแถว
3. แถบล่าง 64 จำนวนแถวที่อ่านใหม่ก็จะเป็น 64+2 = 66 แถว นั่นคือ เพิ่มบนสองแถว
ซึ่งการกำหนดดังกล่างจะมีผลต่อค่า size และ tile ต้องกำหนดค่านี้ใหม่ T_T นอกจากนี้แล้ว ตอนประมวลผลเสร็จแต่ละแถบ ผลลัพธ์จะใช้เพียงแค่ 64 แถวเหมือนเดิม

ผลลัพธ์ใหม่คือ (สังเกตว่าไม่มีรอยต่อของแถบแล้ว)


โค้ดใหม่ก็จะประมาณนี้ครับ
from PIL import Image, ImageTk, ImageFilter
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw-256.tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 64 rows
#total read = 256/64 = 4
rowRead = 64
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead

#image header size, please check by img.tile, it is different for images
header = 8;
#kernel size 5x5
ksize = 5

#loop for each partial read
for rr in range(totalRead):   
    #additional row to read at top of bottom
    extraTopRow = (ksize-1)/2
    extraBottomRow = extraTopRow
    #if the first set of row
    if rr==0:
        extraTopRow = 0
    elif rr==totalRead-1:   #last set of row
        extraBottomRow = 0
    #reopen image, necessary due to partial loading
    img = Image.open("Lenna-gray-raw-256.tif") 
    #tile
    offset = rr*(rowRead*oriW)
    img.tile = [('raw', (0, 0 - extraTopRow, oriW, rowRead +extraTopRow +extraBottomRow), header + offset - (extraTopRow*oriW), ('L', 0, 1))]
    #set reading size
    img.size = (oriW, rowRead + extraTopRow + extraBottomRow)
    #load part of image
    img.load()
    #filter part of image
    #create an average kernel of 5x5 = 25 elements
    kernel = [1]*(ksize*ksize)
    temp = img.filter(ImageFilter.Kernel((ksize,ksize),kernel))
    #crop to normal size
    #img.crop((x1,y1,x2,y2)) --> top left to bottom right
    crop = temp.crop((0,extraTopRow, oriW,extraTopRow+rowRead))
    #copy to new image
    #paste(source image, top-left corner)
    out.paste(crop,(0,rr*rowRead))

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()
หมายเหตุ
คำสั่ง ImageFilter.Kernel มีข้อจำกัดคือทำได้เฉพาะ kernel ขนาด 3x3 หรือ 5x5 เท่านั้นครับ

No comments:

Post a Comment