ComBioLaw.De » Blog » เขียนโปรแกรม » Play PIL (Python Image Library) with NumPy

Play PIL (Python Image Library) with NumPy

image พอดีใน Blognone มีคนเข้ามาถามเกี่ยวกับ Image Processing ด้วย Python โดยการใช้โมดูลของ Python ที่มีชื่อว่า PIL (Python Image Library) ทีแรกผมคิดว่าจะตอบในฟอรั่มโดยตรง แต่ดูท่าจะยาว เลยเก็บมาเขียนเป็นบล็อกแทน

PIL ถือเป็นโมดูลสำหรับ Image Processing ที่ทรงพลัง ใช้งานง่าย และสนับสนุนไฟล์ภาพหลายรูปแบบด้วยกัน (ดูฟอร์แมตภาพที่ PIL สนับสนุนได้ที่  PIL Handbook)  ความสามารถของ PIL ครอบคลุมงานเกี่ยวกับ Image Processing ที่ใช้งานปกติได้เกือบทั้งหมด แต่ทั้งนี้ทั้งนั้นยังมีงานบางประเภทที่เราต้องออกแรงเขียนโปรแกรมเอง ตัวอย่างการเขียนโปรแกรมด้วย PIL สามารถดูได้ที่ เอ็นโปรเทค ซึ่งจากตัวอย่างจะเห็นได้ว่า เราสามารถเขียนโปรแกรมร่วมกับ PIL ได้โดยใช้ Python List ด้วยวิธีการดังกล่าว เราต้องวนลูปหลาย ๆ รอบ เพื่อรับค่าแต่ละ Pixel จากรูปภาพ แล้วนำไปประมวลผลอีกครั้งหนึ่ง ผลที่ได้คือ การเขียนโปรแกรมที่ซับซ้อน และทำงานช้า

ด้วยความที่ผมทำงานกับ NumPy ทุกวี่วัน ก็เลยอดไม่ได้ที่จะนำ PIL มาทำงานร่วมกับ NumPy เพื่อผนึกกำลังสองความสามารถให้เป็นหนึ่ง นำการคำนวนแบบ numerical ของ NumPy มาใช้ในงาน Image Processing ทำให้การคำนวน และประมวลผลต่าง ๆ ง่ายขึ้น มีประสิทธิภาพมากขึ้น และสามารถนำฟังก์ชั่นทางคณิตศาสตร์อื่น ๆ ของ NumPy และ SciPy มาใช้งานร่วมกับ PIL ได้

การแปลงรูปภาพให้อยู่ในรูปของอะเรย์สำหรับ NumPy นั้น สามารถทำได้ง่าย ๆ ด้วยคำสั่ง ...

เขียนโปรแกรม เขียนโปรแกรม

bow_der_kleine bow_der_kleine

import Image, numpy
 
# Read image from file
im = Image.open('image.png')
# Covert image to array
im_array = numpy.asarray(im)
plain code

ผลที่ได้คืออะเรย์ 3 มิติ ขนาดเท่ากับ "ความยาวของรูปภาพ x ความกว้างของรูปภาพ x 3" ค่า x 3 คือค่า RGB หรือค่าสีแดง สีเขียว และสีน้ำเงินของรูปภาพซึ่งมีค่าระหว่าง 0-255 เราสามารถนำอะเรย์ดังกล่าวมาคำนวนแบบ numerical ได้ทันที เพื่อให้เห็นภาพผมขอยกตัวอย่าง การเปลี่ยนภาพสีในระบบ RGB ให้เป็นภาพแบบ negative หรือภาพที่มีสีแบบฟิล์มเนกาทีฟ ที่เราใช้ถ่ายรูป (มีใครยังใช้กล้องฟีล์มถ่ายรูป รายงานตัวด่วน)

from Image import open, fromarray
from numpy import asarray
 
# Read image from file
im = open('child.jpg')
# Convert image to array
im_array = asarray(im)
# Convert image to negative
result = 255-im_array
# Save result to file
fromarray(result).save('child_negative.jpg')
plain code

จากตัวอย่างการคำนวนหาค่า negative ของรูป มีเพียงบรรทัดเดียวเท่านั้นคือ result = 255-im_array ที่เหลือเป็นการอ่านไฟล์และเซพไฟล์ภาพ ข้อควรระวังคือ im_array ที่ได้จากการเปลี่ยนข้อมูลจากรูปภาพเป็นอะเรย์ เป็นอะเรย์แบบ read only คือ อ่านได้อย่างเดียวเปลี่ยนแปลงแก้ไขไม่ได้ ข้างล่างคือรูปที่ได้จากโปรแกรม

image

ภาพเริ่มต้น

image

ภาพที่ได้จากการคำนวนแบบ negative

เมื่อเปรียบเทียบกับ Image Processing แบบไม่ใช้ NumPy ตามตัวอย่างข้างล่าง จะเห็นความทรงพลังของการคำนวนแบบ numerical และข้อแตกต่างในการเขียนโปรแกรมที่ชัดเจน (ผลลัพธ์ออกมาเหมือนกันทุกประการ)

from Image import open, fromarray
from numpy import asarray
 
# Read image from file
im = open('child.jpg')
 
# Convert image to negative pixel to pixel
negat = lambda x : 255-x
for i in range(im.size[0]) :
	for j in range(im.size[1]) :
		im.putpixel((i,j), tuple(map(negat, im.getpixel((i,j)))))
# Save result to file
im.save('child_negative2.jpg')
plain code

ตัวอย่างต่อไป เป็นการย่อขนาดภาพ ซึ่งใน PIL มีเมโธท resize ให้ใช้อยู่แล้ว แต่ตัวอย่างนี้ ต้องการแสดงให้เห็นถึงการเข้าถึงข้อมูลของอะเรย์ที่ได้จากภาพด้วย array slicing

import Image
from numpy import *
 
# Read image from file
im = Image.open('child.jpg')
# Resize image with PIL-method
im.resize((im.size[0]/2, im.size[1]/2)).save('child_pil_resize.jpg')
# Resize image with NumPy
Image.fromarray(asarray(im)[::2,::2,:]).save('child_num_resize.jpg')
plain code

ในตัวอย่างการย่อขนาดภาพอยู่ที่ประโยค asarray(im)[::2,::2,:] ซึ่งมีความหมายว่า เปลี่ยนข้อมูลภาพให้เป็นอะเรย์ และเลือก ข้อมูล pixel เว้น pixel ทั้งในแนวตั้ง และแนวนอน และเลือก ทุกสี อาจจะงง ๆ แต่สามารถอ่านข้อมูลเกี่ยวกับ array slicing ได้ที่ Numericla Programming สำหรับผลการเปลี่ยนขนาดภาพเหมือนกันคือ

image

ตัวอย่างต่อไปเป็นการแปะภาพหนึ่งลงบนอีกภาพหนึ่ง ซึ่งใน PIL มีเมโธท paste ให้ใช้เหมือนกัน ทั้งวิธีแบบ numerical และ paste ของ PIL ไม่แตกต่างกันมากนัก ทั้งในเรื่องความยากง่ายในการเขียนโปรแกรม ความเร็วของโปรแกรม และผลที่ได้รับ แต่ในกรณีการแปะภาพที่เป็น transparency วิธีแบบ numerical ค่อนข้างง่ายกว่าวิธีที่ไม่ใช้การคำนวนแบบ numerical พอสมควร เพราะการแปะภาพที่เป็น transparency ต้องมีการสร้าง layer ขึ้นมาอีกหนึ่งชั้น จากนั้นก็แปะภาพที่มีขนาดเล็กกว่าลงบน layer ดังกล่าว แล้วนำภาพต้นฉบับมารวมกับ layer ด้วยฟังก์ชั่น composite

import Image
from numpy import *
 
# Read source
child = asarray(Image.open('child.jpg'))
text = asarray(Image.open('text.png'))
 
# Find vertical position of text
v_pos = child.shape[0] - text.shape[0] - 20
# Find horizontal position of text
h_pos = (child.shape[1] - text.shape[1])/2
 
# Add text to image
result = child.copy()
result[v_pos:v_pos+text.shape[0], h_pos:h_pos+text.shape[1]] = text[:,:,:3]
 
# Convert result to image and save to a file
Image.fromarray(result).save('child_add_text.jpg')
 
# Add transparency text to image
# Read source
text_trans = asarray(Image.open('text_transparent.png'))
 
# Create transparency layer
layer = zeros((child.shape[0],child.shape[1],4), dtype=int8)
layer[v_pos:v_pos+text_trans.shape[0], h_pos:h_pos+text_trans.shape[1]] = text_trans
layer = Image.fromarray(layer, 'RGBA')
 
# Compose image with transparency text
result = Image.composite(layer, Image.fromarray(child).convert('RGBA'), layer)
 
# Save result to a file
result.save('child_add_text_transparent.png', 'PNG')
plain code

image

ผลการแปะภาพแบบปกติ

image

ผลการแปะภาพแบบ transparency

จากการแปะภาพแบบ transparency จะเห็นได้ว่าภาพแบบ transparency ไม่ได้มีโหมดการทำงานแบบ RGB แต่เป็นโหมดการทำงานแบบ RBGA (Reg Green Blue Alpha) ขนาดของอะเรย์จึงเป็น "ความยาวของรูปภาพ x ความกว้างของรูปภาพ x 4" แทนที่จะเป็น "ความยาวของรูปภาพ x ความกว้างของรูปภาพ x 3" ค่า Alpha คือค่า transparency ค่า Alpha ยิ่งน้อยลงเท่าใด ภาพก็จะโปร่งใสมากขึ้นเท่านั้น และที่สำคัญ ไฟล์ภาพแบบ JPEG ไม่สามารถทำบนโหมด RGBA ได้ การใช้โหมด RBGA ควรใช้ไฟล์แบบ PNG แทน

ตัวอย่างต่อมาไม่เกี่ยวกับการใช้ NumPy เท่าไร แต่เป็นการนำตัวอย่างข้างบนมาปรับใช้ในการใส่วันที่ลงไปในภาพ ซึ่งลำพังความสามารถของ PIL และ NumPy คงไม่เพียงพอ ต้องนำความสามารถของ Inkscape เข้ามาช่วยด้วย โดยการใช้ Inkscape ในการสร้างภาพ SVG เนื่องจากไฟล์ SVG เป็น textfile ที่ใช้อธิบาย Vector Graphic เราจึงสามารถใช้ Python แก้ไขเนื้อหาภายในภาพได้ โดยไม่ต้องใช้โมดูลอื่น ๆ เพิ่มเติม

ในไฟล์ SVG ที่ถูกสร้างด้วย Inkscape จะมีประโยค 00.00.0000 เป็นเทมเพลทอยู่ เราสามารถแทนค่าประโยคดังกล่าวด้วยวันที่ปัจจุบัน จากนั้นก็บันทึกข้อมูลที่ได้ไว้ในไฟล์อีกไฟล์หนึ่ง ปัญหาคือว่า เราไม่สามารถทำงานร่วมกับไฟล์ SVG ด้วย PIL หรือ NumPy ได้ ตรงนี้เราต้องการ Inkscape อีกครั้งหนึ่ง ในการคอนเวิร์ทไฟล์ SVG เป็นไฟล์ PNG จากนั้นก็ใช้วิธีรวมไฟล์ transparent ในตัวอย่างก่อนหน้านี้ ในการรวมไฟล์ภาพเริ่มต้น กับไฟล์วันที่ที่ได้จาก Inksacpe

import Image
import os
from numpy import *
from time import *
 
# Generate date image
# Read SVG template
date_svg = open('date.svg').read()
# Modify SVG image
today = gmtime()
if today[1] >= 10 : today_str = '%2d.%d.%d'%(today[2], today[1], today[0])
else : today_str = '%2d.0%d.%d'%(today[2], today[1], today[0])
date_svg = date_svg.replace('00.00.0000', today_str)
# Geneate PNG image from SVG image
open('today.svg','w').write(date_svg)
os.system('inkscape today.svg -e today.png')
 
# Add date to image
# Read source
child = asarray(Image.open('child.jpg'))
today_png = asarray(Image.open('today.png'))
 
# Find vertical position of text
v_pos = child.shape[0] - today_png.shape[0] - 20
# Find horizontal position of text
h_pos = (child.shape[1] - today_png.shape[1])/2
 
# Create transparency layer
layer = zeros((child.shape[0],child.shape[1],4), dtype=int8)
layer[v_pos:v_pos+today_png.shape[0], h_pos:h_pos+today_png.shape[1]] = today_png
layer = Image.fromarray(layer, 'RGBA')
 
# Compose image with transparency text
result = Image.composite(layer, Image.fromarray(child).convert('RGBA'), layer)
 
# Save result to a file
result.save('child_add_today.png', 'PNG')
plain code

image

ภาพที่ได้จากการเพิ่มวันที่ในภาพ

ตัวอย่างสุดท้าย เป็นตัวอย่างที่นำทฤษฎีเกี่ยวกับ Digital Signal Processing มาใช้ใน Image Processing ด้วยการใช้ IIR-Filter กรองสัญญาณความถี่สูงในภาพออก (?) (สัญญาณความถี่สูงในภาพคือ การเปลี่ยนแปลงสีในรูปภาพอย่างรวดเร็ว ผลที่ได้จากการกรองสัญญาณความถี่สูงออกจากภาพ คือ Blur-Filter) ซึ่งในตัวอย่างนี้ความทรงพลังของการนำ PIL มาทำงานร่วมกับ NumPy ชัดเจนยิ่งขึ้น เพราะเป็นการเปิดประตูไปสู่โมดูลทางคณิตศาสตร์อื่น ๆ อีกมากมายใน SciPy

import Image
from numpy import *
from scipy.signal import *
 
# Read source
child = asarray(Image.open('child.jpg'))
 
# Calcurate 2rd order IIR-Filter tabs
b, a = bessel(2, 0.09*pi)
 
# Filtering the image
result = zeros_like(child)
for i in range(3) :result[:,:,i] = lfilter(b, a, child[:,:,i])
 
# Save result to file
Image.fromarray(result).save('chuild_filtered.jpg')
plain code

image

ภาพที่ได้จาก IIR-Filter

สำหรับคนที่ต้องการทำงานเกี่ยวกับ Image Processing เป็นเรื่องเป็นราว ผมคิดว่าการใช้ PIL ร่วมกับ NumPy เป็นทางเรื่องที่น่าสนใจทางเลือกหนึ่ง เพราะความทรงพลังของทั้ง PIL และ NumPy การใช้งานที่ง่าย Productivity ที่สูง Performance ที่ดี มีโมดูลทางคณิตศาสตร์อื่น ๆ รองรับอีกมากมาย คุณสมบัติที่ผมโม้มาทั้งหมด ทุกคนสามารถนำมาใช้งานได้โดยไม่เสียเงินแม้แต่สลึงเดียว งานนี้ต้องขอขอบคุณ Open Source ครับ

27 Jul 08 | by | tags เขียนโปรแกรม Python PIL NumPy Image Processing

read 3452

<<Virus on Linux || ชื่อแซ่>>

house

มันเทพจริงๆนะ ผมเอามาทำ digital signature

เสียแต่ถ้าขาด numPy นี่มันช้าไปแบบเห็นชัดๆเลย(ที่เคยไปโพสต์ให้คุณโบว์ช่วยไง)

29 Jul 08

plynoi

ไม่รู้เรื่องเกี่ยวกับ Image Processing เลย Cry

แต่อ่านดูแล้วเจ๋ง น่าสนุกดีแฮะ

29 Jul 08

dek_en

อยากใช้ gaussian filter ของ scipy ครับ แต่หยิบมาใช้ไม่เป็นเลย ช่วยด้วยครับ...ขอบคุณมากครับ

16 Dec 08

nazt

numpy ทำแบบ ไม่วนลูป ได้ไหมครับ ตอนนี้มองไม่ค่อยออกเลยครับ

 

pixel = resized.load()

manual_gray=Image.new('L',resized.size)

# print mtuple

for i in range(width):

for j in range(height):

weight=(pixel[i,j][0]+pixel[i,j][1]+pixel[i,j][2])/3

manual_gray.putpixel((i,j),(weight))

gray.show()

10 Dec 09

ความคิดเห็น (click here to comment)

Search

Navigation

รวมลิงก์น่าสนใจ

ความเคลื่อนไหว

Login

name password

ลืมรหัสผ่าน