Thursday, January 1, 2015

Processing กับเสียง

มีโครงงานอันนึงทำด้วย Processing ต้องการเพิ่มเสียงเข้าไป ทั้ง Background music และ Event sound ก็เลยต้องหาวิธีการเล่นไฟล์เสียง

ก่อนอื่นก็ต้องไปหาไฟล์เสียงก่อน เน้นของฟรี (Royalty-free) ซึ่งหาดูแล้วมีมากมายครับ เช่น http://www.playonloop.com/music-loops-category/videogame/

แต่ก่อนจะดาวน์โหลด ให้อ่านดูดีๆนะครับว่าเค้ามี license แบบไหน เช่น Creative Commons ก็สามารถนำไปใช้ได้ฟรีทั้งแจกจ่ายและแก้ไข รวมถึงเพื่อการค้า แต่ต้อง You must give appropriate credit, provide a link to the license, and indicate if changes were made.

เมื่อได้ไฟล์เสียงที่ต้องการแล้ว อาจจะเป็นไฟล์ wav หรือ mp3 ก็ได้ ก็ให้ย้ายไปยัง Sketch folder (เปิด Processing สร้างโปรเจคแล้วเซฟไว้ แล้วเลือกเมนู Sketch/Show Sketch folder หรือ Ctrl+K จากนั้นคัดลอกไฟล์ไปไว้ยังโฟลเดอร์ data ในโปรเจคที่เปิดอยู่) ในที่นี้สมมติว่าไฟล์เสียงของเราชื่อ POL-icy-town-short.wav ครับ

ขั้นตอนต่อไปคือเลือกใช้ sound library สำหรับ Processing แล้วจะมีหลายตัว ตัวที่เป็นตัวพื้นฐานของ Processing เองเลย คือ https://processing.org/reference/libraries/sound/index.html ซึ่งเท่าที่ลองอ่านดู จำเป็นต้องติดตั้งต่างหากเพิ่มเติม ซึ่งก็ไม่ยากอะไร เพียงเลือกเมนู Sketch/Import Library/Add Library แล้วเลือก Sound รอดาวน์โหลดแป้บนึงก็เรียบร้อย อย่างไรก็ตาม ณ ปัจจุบัน library ตัวนี้ยังสนับสนุนเฉพาะ OS แบบ 64 บิตเท่านั้น เครื่องผมเป็น 32 บิตเลยแห้วไป

เมื่อใช้ตัวปกติของ Processing ไม่ได้ ก็ต้องไปหา library ตัวใหม่ ผมพบว่าถ้าเป็น Processing 2 จะมี library ตัวนึงแถมมาให้เลย ชื่อ minim ไม่เข้าใจเหมือนกันว่าทำไม Processing 3 (ซึ่งปัจจุบันเป็นตัวเบต้าอยู่) ถอดออกไป และเลือกที่จะพัฒนาเอง

แต่ช่างมันเถอะครับ เลือกตัวที่เราใช้ได้ตอนนี้ดีกว่า ถ้าใครใช้ Processing 2 อยู่แล้วก็ไม่มีปัญหา ส่วนใครใช้ Processing 3 beta ก็ไปดาวน์โหลด minim ที่ http://code.compartmental.net/tools/minim/ จะได้ไฟล์ zip ออกมา แตกไฟล์ไปไว้ที่ sketchbook location เช่น C:\Users\user\Documents\Processing\libraries ได้เลย ข้างในโฟลเดอร์จะมีไฟล์มากมาย ซึ่งเราสามารถลบให้เหลือเฉพาะโฟลเดอร์ชื่อ library ได้

จากนั้นลองเริ่มเขียนโค้ดกันดู สมมติว่ามีไฟล์เสียงในโฟลเดอร์ data ของโปรเจคปัจจุบันแล้วนะครับ
import ddf.minim.*;

Minim minim;
AudioPlayer player;

void setup()
{
  size(200, 200);  
  minim = new Minim(this);
  //load music file
  player = minim.loadFile("POL-icy-town-short.wav");
  //play music
  player.play();
}

void draw(){
  background(0);
}

ลองรันดูครับ น่าจะได้ยินเสียงตามต้องการแล้ว

นอกจากนี้แล้ว ยังมีฟังก์ชันที่น่าสนใจอีกมากมาย เช่น
-ถ้าต้องการให้เล่นไฟล์แบบวนไปเรื่อยๆ ก็เปลี่ยนจาก player.play() เป็น player.loop() หรือถ้าต้องการให้วนกี่ครั้งก็ใช้ player.loop(num) เมื่อ num คือจำนวนครั้งที่ต้องการให้เล่นซ้ำครับ
-ถ้าต้องการหยุดชั่วคราว ใช้ฟังก์ชัน pause
-ถ้าต้องการปิด/เปิดเสียง ใช้ฟังก์ชัน mute/unmute

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

Tuesday, December 16, 2014

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

เมื่อเราพยายามแก้ปัญหาด้วยวิธีหนึ่ง เราอาจจะต้องยอมสูญเสียบางอย่างไปใช่ไหมครับ

เช่นเดียวกับปัญหาในการอ่านภาพขนาดใหญ่ วิธีที่ง่ายที่สุดคือเพิ่มประสิทธิภาพของคอมพิวเตอร์ เช่น เปลี่ยน CPU หรือเพิ่ม RAM ซึ่งสิ่งที่เสียไปคือค่าใช้จ่าย

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

เราลองมาทำการทดลองวัดเฉพาะเรื่องของเวลา ที่ใช้ในการอ่านและประมวลผลรูป ระหว่างการอ่านครั้งเดียว กับการอ่านทีละส่วนกันครับ

วิธีวัดเวลาใน python ก็ตรงไปตรงมาคือ
import time
start = time.time()
#our codes
elapse = time.time()-start

อย่างไรก็ตาม มีข้อสังเกตว่า python มีการสร้าง cache ของรูปที่เคยอ่านไว้ ทำให้การอ่านรูปซ้ำใช้เวลาน้อยกว่าการอ่านรูปครั้งแรก ดังนั้น ในการวัดเวลาครั้งนี้เราจะเอาเวลาโดยประมาณ ที่ไม่รวมการอ่านรูปในรอบแรก
ในที่นี้เราใช้รูปทดสอบขนาด 7076x7001 พิกเซล เป็นรูปแบบ RGB
กรณีที่ 1 อ่านทั้งรูป เวลาที่ใช้โดยประมาณ 0.65 วินาที
from PIL import Image
import time
#starting time
start = time.time()
img = Image.open("mfu2009(chs. 1,2,3).tif")
#load image
img.load()
#time used
elapse = time.time() - start
print elapse

กรณีที่ 2 อ่านทีละส่วนของรูป เวลาที่ใช้โดยประมาณ 1.10 วินาที
from PIL import Image
import time
#starting time
start = time.time()
img = Image.open("mfu2009(chs. 1,2,3).tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 128 rows
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead

#image header size, please check by img.tile, it is different for images
header = img.tile[0][2];
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("mfu2009(chs. 1,2,3).tif")
    #------- tile parameters --------
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3
    #if the last set of row is additional
    if rr==totalRead-1 and rowLeft!=0:
        rowRead = rowRead + rowLeft
    #set tile size
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('RGB', 0, 1))]
    #------- size parameters --------
    img.size = (oriW, rowRead)
    #load part of image
    img.load()
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(img,(0,rr*rowReadExtra))    


elapse = time.time() - start
print elapse

กรณีที่ 3 อ่านทั้งรูป แล้วประมวลผลแต่ละพิกเซล เวลาที่ใช้โดยประมาณ 1.24 วินาที
from PIL import Image
import time
#starting time
start = time.time()
img = Image.open("mfu2009(chs. 1,2,3).tif")
#load image and perform point processing
out = img.point(lambda i: 255-i)
#time used
elapse = time.time() - start
print elapse

กรณีที่ 4 อ่านทีละส่วนของรูป แล้วประมวลผลแต่ละพิกเซล เวลาที่ใช้โดยประมาณ 1.83 วินาที
โค้ดเหมือนกรณีที่ 2 เพียงแค่แก้ไขในส่วนสุดท้ายของลูปนิดหน่อย
from PIL import Image
import time
#starting time
start = time.time()
img = Image.open("mfu2009(chs. 1,2,3).tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 128 rows
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead

#image header size, please check by img.tile, it is different for images
header = img.tile[0][2];
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("mfu2009(chs. 1,2,3).tif")
    #------- tile parameters --------
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3
    #if the last set of row is additional
    if rr==totalRead-1 and rowLeft!=0:
        rowRead = rowRead + rowLeft
    #set tile size
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('RGB', 0, 1))]
    #------- size parameters --------
    img.size = (oriW, rowRead)
    #load image part and perform point processing
    img.load()
    temp = img.point(lambda i: 255-i)    
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(temp,(0,rr*rowReadExtra))  

elapse = time.time() - start
print elapse

สรุปว่า
กรณีที่ 2 ใช้เวลาเป็น 1.69 เท่าของวิธีที่ 1
กรณีที่ 4 ใช้เวลาเป็น 1.48 เท่าของวิธีที่ 3
นั่นคือ พอจะบอกได้ว่า การแบ่งส่วนอ่านและประมวลผลพิกเซลรูป จะใช้เวลาเพิ่มขึ้นอีกประมาณ 1.5-1.7 เท่าของวิธีปกติ แต่ความเป็นจริงแล้วค่านี้ยังขึ้นอยู่กับหลายปัจจัย ทั้งจำนวนแถวทีอ่านในแต่ละครั้ง อัลกอริธึมในการประมวลผลรูป ขนาดและชนิดของรูป ซึ่งถ้าต้องการได้ค่าที่ถูกต้อง คงต้องทดสอบอีกหลายๆครั้งในหลายๆกรณีครับ

Sunday, December 14, 2014

การอ่านภาพขนาดใหญ่ทีละส่วน ด้วย Python และ PIL ตอนที่ 4 (Color image + extra rows)

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

ในหัวข้อนี้เราจะพยายามปรับโค้ดของเราให้ยืดหยุ่น รองรับสองสถานการณ์นี้ครับ

กรณีแรก รูปสีแบบ RGB
อันนี้ค่อนข้างง่าย รูป RGB ก็จะมี offset เพิ่มขึ้นเป็น 3 เท่าของรูปเทา เนื่องจากมี 3 channels
เราก็จะใช้โค้ดประมาณนี้
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3

กรณีที่สอง มีเศษเหลือของแถวจากการอ่าน
ตัวอย่างเช่น รูปมีทั้งสิ้น 7001 แถว (ความสูง) เราต้องการอ่านทีละ 128 พิกเซล
ก็จะต้องวนอ่านเท่ากับ 7001/128 = 54.69 หรือ 55 รอบ ซึ่งรอบสุดท้ายจะอ่านแค่ 7001%128 = 89 แถว
โค้ดที่ใช้ก็จะคือ
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead
#if there are some rows left at the bottom, add one more row to process
if rowLeft!=0:
    totalRead = totalRead+1

ลองดูตัวอย่างเต็มได้ตามโค้ดนี้ครับ ในที่นี้เราจะวนอ่านรูปสี RGB ขนาด 7076x7001 พิกเซลและบันทึกไว้เป็นไฟล์ใหม่ชื่อ output.tif ซึ่งเหมือนรูปตั้งต้นทุกประการ
from PIL import Image
img = Image.open("mfu2009(chs. 1,2,3).tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 128 rows
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead
#if there are some rows left at the bottom, add one more row to process
if rowLeft!=0:
    totalRead = totalRead+1

#image header size, please check by img.tile, it is different for images
header = img.tile[0][2];
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("mfu2009(chs. 1,2,3).tif")
    #------- tile parameters --------
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3
    #if the last set of row is additional
    if rr==totalRead-1 and rowLeft!=0:
        rowRead = rowLeft
    #set tile size
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('RGB', 0, 1))]
    #------- size parameters --------
    img.size = (oriW, rowRead)
    #load part of image
    img.load()
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(img,(0,rr*rowReadExtra))    

#save output to file
out.save("output.tif","TIFF")

สำหรับกรณีที่สองนี้ เราสามารถคิดใหม่ก็ได้ว่า ให้อ่านแถวสุดท้ายเพิ่มขึ้น ซึ่งก็จะดีกว่าวิธีที่แล้ว เพราะลดขั้นตอนในการวนลูปลงหนึ่งขั้น ตัวอย่างเช่น
รูปมีทั้งสิ้น 7001 แถว (ความสูง) เราต้องการอ่านทีละ 128 พิกเซล
ก็จะต้องวนอ่านเท่ากับ 7001/128 = 54.69 หรือ 54 รอบ ซึ่งรอบสุดท้ายจะอ่านเพิ่มเป็น 128+(7001%128) = 128+89 = 217 แถว

โค้ดใหม่จะประมาณนี้ครับ ให้ผลลัพธ์เหมือนเดิม เร็วกว่าเดิมนิดนึง
from PIL import Image
img = Image.open("mfu2009(chs. 1,2,3).tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 128 rows
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead

#image header size, please check by img.tile, it is different for images
header = img.tile[0][2];
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("mfu2009(chs. 1,2,3).tif")
    #------- tile parameters --------
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3
    #if the last set of row is additional
    if rr==totalRead-1 and rowLeft!=0:
        rowRead = rowRead + rowLeft
    #set tile size
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('RGB', 0, 1))]
    #------- size parameters --------
    img.size = (oriW, rowRead)
    #load part of image
    img.load()
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(img,(0,rr*rowReadExtra))    

#save output to file
out.save("output.tif","TIFF")

ต่อไปเราคงต้องมาลองเทียบเวลาในการประมวลผลว่า การอ่านทั้งหมดและอ่านทีละส่วน เร็วหรือช้ากว่ากันเท่าใดครับ

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 เท่านั้นครับ

Tuesday, December 9, 2014

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

ต่อจากคราวที่แล้ว เราได้ลองอ่านส่วนหนึ่งของภาพขึ้นมาจำนวน 64 แถว

ตอนนี้เราจะลองใช้ลูปอ่านค่าจนครบรูป

ทบทวนจากเดิมที่เราทราบว่า เมื่ออ่านรูปขึ้นมาด้วย PIL บางทีจะอ่านเป็นส่วนอยู่แล้วเช่นรูปนี้


จะเกี่ยวข้องกับคำสั่งในการอ่านคือ size และ tile
i = Image.open("Lenna-gray-raw.tif")
i.size
(512, 512)
i.tile
[('raw', (0, 0, 512, 64), 8, ('L', 0, 1)), ('raw', (0, 64, 512, 128), 32776, ('L', 0, 1)), ('raw', (0, 128, 512, 192), 65544, ('L', 0, 1)), ('raw', (0, 192, 512, 256), 98312, ('L', 0, 1)), ('raw', (0, 256, 512, 320), 131080, ('L', 0, 1)), ('raw', (0, 320, 512, 384), 163848, ('L', 0, 1)), ('raw', (0, 384, 512, 448), 196616, ('L', 0, 1)), ('raw', (0, 448, 512, 512), 229384, ('L', 0, 1))]

หากเราจะใช้ลูปวนอ่าน ก็ต้องเปลี่ยนสองค่านี้ เช่น
ถ้าขนาดรูปเป็น 512 คอลัมน์ x 512 แถว
สมมติว่าอ่านทีละ 64 แถว ก็จะต้องอ่าน 512/64 = 8 ครั้ง

รอบที่ 0
size ก็จะเป็น (512,64)
tile ก็จะเป็น ('raw', (0, 0, 512, 64), 8, ('L', 0, 1))

รอบที่ 1
size ก็จะเป็น (512,64)
tile ก็จะเป็น ('raw', (0, 0, 512, 64), (8+512*64), ('L', 0, 1))

รอบที่ 2
size ก็จะเป็น (512,64)
tile ก็จะเป็น ('raw', (0, 0, 512, 64), (8+512*64+512*64), ('L', 0, 1))

ไปเรื่อยๆ โดยสังเกตว่าในที่นี้ขนาดของ tile เป็น (0, 0, 512, 64) ตลอด

ถ้าสมมติว่า rr เป็นตัวแปรเอาไว้บอกรอบ มีค่าตั้งแต่ 0-7 (8 รอบ) ดั้งนั้น
รอบที่ rr
size ก็จะเป็น (512, 64)
tile ก็จะเป็น ('raw', (0, 0, 512, 64), (8 + (rr*(512*64)), ('L', 0, 1))

สมมติว่าในตัวอย่างแรกนี้ เราจะลองอ่านรูปขึ้นมาทีละส่วน แล้วแปะลงไปในรูปใหม่ ให้ผลลัพธ์หน้าตาเหมือนรูปต้นฉบับเป๊ะ


โค้ดก็จะประมาณนี้ครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.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
#total read = 512/64 =8
totalRead = oriH/rowRead

#image header size, please check by img.tile, it is different for images
header = 8;
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("Lenna-gray-raw.tif")
    #tile
    offset = rr*(rowRead*oriW)
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('L', 0, 1))]
    #reading size eg. 512,64
    img.size = (oriW, rowRead)   
    #load part of image
    img.load()
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(img,(0,rr*rowRead))

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()
ต่อมา ถ้าเราต้องการประมวลผลแต่ละพิกเซลไปด้วย ในที่นี้เอาแบบง่ายๆคือทำ Image Negative เพื่อให้ได้ผลลัพธ์ดังนี้


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

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

#image header size, please check by img.tile, it is different for images
header = 8;
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("Lenna-gray-raw.tif")
    #tile
    offset = rr*(rowRead*oriW)
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('L', 0, 1))]
    #reading size eg. 512,64
    img.size = (oriW, rowRead)   
    #load part of image to pixel object
    pix = img.load()
    
    #try to perform pixel processing
    #loop for each row and column
    w,h = img.size
    for r in range(h):
        for c in range(w):
            pixOut[c,r+(rr*rowRead)] = 255-pix[c,r]

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