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")

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

No comments:

Post a Comment