Saturday, December 1, 2012

การ crop และ paste รูป

ในบทความนี้ เราจะลองใช้หลักการของการตัดและแปะ เพื่อสร้าง effect แบบง่ายๆให้กับรูป คือการเคลื่อนรูปไปบางส่วน ให้ได้ผลลัพธ์ดังนี้



โดยกำหนดว่าให้มีการเคลื่อนของรูปไปประมาณ 30% ของความกว้างของรูป

ก่อนอื่น ต้องเพิ่มเมนูเช่น
processMenu.add_command(label="Roll", command=self.imgRoll)

แล้วก็สร้าง method คือ imgRoll ดังนี้
def imgRoll(self):
    #assume rolling horizontally by 30% of width
    w,h = self.im.size
    roll = int(0.30*w)
    #crop 2 parts
    part1 = self.im.crop((0,0,roll,h)) #(left, upper, right, lower)
    part2 = self.im.crop((roll,0,w,h))
    #create new image
    imr = Image.new("RGB",(w,h))
    #swap and paste crop images
    imr.paste(part2,(0,0,w-roll,h))
    imr.paste(part1,(w-roll,0,w,h))
    #set image to rolled image
    self.im = imr.copy()
    self.showImage()

การประมวลผลแต่ละพิกเซลของรูป

จากบทความที่แล้ว เราสามารถเข้าถึงพิกเซลของรูปและแก้ไขได้ ซึ่งเป็นกระบวนการที่สำคัญและสามารถประมวลผลภาพแบบซับซ้อนได้ อย่างไรก็ตาม หากเราต้องการเฉพาะการประมวลผลภาพแต่ละพิกเซล (Point Operation) ก็สามารถใช้คำสั่งที่อาจง่ายขึ้นได้ ได้แก่

out = im.point(function)

ตัวอย่างเช่น หากเราต้องการปรับค่า contrast ของรูป ซึ่งทำได้โดยการคูณแต่ละพิกเซลด้วยค่าคงที่ค่าหนึ่ง เช่น 1.5 ก็จะใช้คำสั่ง

out = im.point(lambda i: i*1.5)

(หมายเหตุ lambda เป็นเทคนิคการเขียนฟังก์ชันเฉพาะกิจของ python เรียกว่า anonymous functions เพื่อที่เราจะไม่ต้องเขียนฟังก์ชันใหม่แยกออกมา)

ตัวอย่างผลลัพธ์ที่เราต้องการ





โค้ดของโปรแกรมก็เพิ่มเติมจากเดิมดังนี้
เมนู
        processMenu.add_command(label="Contrast",command=self.imgContrast)

method
    def imgContrast(self):
        self.im = self.im.point(lambda i: i*1.5)
        self.showImage()

หมายเหตุ
1. คำสั่ง Image.point มีผู้ระบุไว้ว่าเร็วกว่าคำสั่ง Image.load ในการประมวลผลพิกเซล หากมีโอกาสจะได้ทดลองวัดความเร็วเปรียบเทียบกันครับ
2. หากต้องการแยกเป็นฟังก์ชันออกมาต่างหาก ก็ทำได้โดย


    def myContrast(self,i):
        return i*1.5

    def imgContrast(self):
        self.im = self.im.point(self.myContrast)
        self.showImage()

การเข้าถึงพิกเซลของรูป

แม้ว่า Python Imaging Library (PIL) จะมีคำสั่งในการประมวลผลภาพหลากหลาย แต่ในบางครั้งเราอาจจะต้องการที่จะประมวลผลแต่ละพิกเซลเอง ก็จะจำเป็นที่จะต้องทราบการอ่านและแก้ไขพิกเซลนั้น

การอ่านพิกเซล จะทำได้โดย
1. อ่านพิกเซลของรูปทั้งหมด แล้วนำไปเก็บไว้ในโครงสร้างข้อมูลที่เรียกว่า pixel access object ที่มีลักษณะคล้ายกับตัวแปรชุดสองมิติ (2D array) โดยใช้คำสั่ง load ดังนี้
pix = im.load()
2. สามารถอ่านค่าของแต่ละพิกเซล โดยใช้คำสั่ง
pix[x,y]
เมื่อ x และ y เป็นพิกัด เริ่มต้นที่มุมซ้ายบน (0,0)

การแก้ไขค่าพิกเซล หลังจากอ่านค่าพิกเซลทั้งหมดแล้ว ทำได้โดย
pix[x,y] = value
ถ้าเป็นรูป grayscale ค่า value จะเป็นเลขโดดๆ ตั้งแต่ 0 ถึง 255
ถ้าเป็นรูป RGB ค่า value จะเป็นเลขชุด เช่น (100,100,100)

ลองดูการเพิ่มเมนูเพื่อทำการกลับสีรูป (inverse) เพื่อให้ได้ผลลัพธ์ดังนี้



โค้ดเฉพาะส่วนของฟังก์ชันนี้ คือ
def imgInverse(self):
    #load an image reference to a special structure like a list
    pix = self.im.load()
    #print pix[0,0]
    #get image size
    w,h = self.im.size
    #if grayscale image
    if self.im.mode=="L":
        #loop for each row and column
        for r in range(h):
            for c in range(w):
                pix[c,r] = 255 - pix[c,r]
                #Note that we can use getpixel(x,y) and putpixel(x,y)
                #but this method is faster
    elif self.im.mode=="RGB":
    #loop for each row and column
        for r in range(h):
            for c in range(w):
                p = pix[c,r]
                pix[c,r] = (255-p[0],255-p[1],255-p[2])
    self.showImage()

ลองเอาไปเชื่อมต่อกับโค้ดก่อนหน้านี้ได้ครับ
อ้างอิง
http://effbot.org/zone/pil-pixel-access.htm

Friday, November 30, 2012

เมนูบันทึกไฟล์รูป

เราลองมาเพิ่มเมนูสำหรับการบันทึกไฟล์รูปหลังจากประมวลผลกันครับ



หลักการก็คือต้องสร้างเมนู Save as... จากนั้นก็เพิ่มโค้ดในส่วนของการบันทึกรูป โค้ดทั้งหมดก็จะเป็นตามนี้ครับ

from Tkinter import Tk,Menu,Label
from PIL import Image,ImageTk
import tkFileDialog

class MainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")
        self.lbl = Label(self.mainwindow)

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        self.mainwindow.config(menu=menubar)
        #FILE menu
        fileMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=fileMenu)
        fileMenu.add_command(label="Open...",command=self.openFile)
        fileMenu.add_command(label="Save as...",command=self.saveFile)
        fileMenu.add_separator()
        fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)
        #EDIT menu
        editMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Edit", menu=editMenu)
        editMenu.add_command(label="Restore Image", command=self.imgRestore)
        #PROCESS menu
        processMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Process", menu=processMenu)
        processMenu.add_command(label="Rotate", command=self.imgRotate)

    def openFile(self):
        #file type filter
        ftypes = [('Image files', '*.jpg *.png *.gif'),
        ('All files', '*')]
        filename = tkFileDialog.askopenfilename(parent=self.mainwindow,title='Choose a file',filetypes=ftypes)
        if filename:
           try:
                #open image
                self.im = Image.open(filename)
                #keep its copy for restoring later
                self.imOrigin = self.im.copy()
                self.showImage()
           except: pass

    def saveFile(self):
        ftypes = [('JPEG', '*.jpg')]
        filename = tkFileDialog.asksaveasfilename(parent=self.mainwindow,title='Save image as',filetypes=ftypes)
        if len(filename)>0:
            try:
                #in Linux, comment this line, it is not useful
                filename = filename+".jpg"
                #save image
                self.im.save(filename,"JPEG")
            except IOError:
                tkMessageBox.showerror("Error","Cannot save image!")

    def showImage(self):
        self.imTk = ImageTk.PhotoImage(self.im)
        self.lbl.destroy()
        self.lbl = Label(self.mainwindow, image=self.imTk)
        self.lbl.pack()

    def imgRestore(self):
        self.im = self.imOrigin.copy()
        self.showImage()

    def imgRotate(self):
        self.im = self.im.rotate(45)
        self.showImage()

#---Main app starts here---
root = Tk()
app = MainApp(root)
#create menu
app.createMenu()
root.mainloop()

Thursday, November 29, 2012

เมนูเปิดไฟล์รูป

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





ลองดูตามโค้ดด้านล่างนี้ได้ครับ
from Tkinter import Tk,Menu,Label
from PIL import Image,ImageTk
import tkFileDialog

class MainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")
        self.lbl = Label(self.mainwindow)

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        self.mainwindow.config(menu=menubar)
        #FILE menu
        fileMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=fileMenu)
        fileMenu.add_command(label="Open...",command=self.openFile)
        fileMenu.add_separator()
        fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)
        #EDIT menu
        editMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Edit", menu=editMenu)
        editMenu.add_command(label="Restore Image", command=self.imgRestore)
        #PROCESS menu
        processMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Process", menu=processMenu)
        processMenu.add_command(label="Rotate", command=self.imgRotate)

    def openFile(self):
        #file type filter
        ftypes = [('Image files', '*.jpg *.png *.gif'),
        ('All files', '*')]
        filename = tkFileDialog.askopenfilename(parent=self.mainwindow,title='Choose a file',filetypes=ftypes)
        if filename:
           try:
                #open image
                self.im = Image.open(filename)
                #keep its copy for restoring later
                self.imOrigin = self.im.copy()
                self.showImage()
           except: pass

    def showImage(self):
        self.imTk = ImageTk.PhotoImage(self.im)
        self.lbl.destroy()
        self.lbl = Label(self.mainwindow, image=self.imTk)
        self.lbl.pack()

    def imgRestore(self):
        self.im = self.imOrigin.copy()
        self.showImage()

    def imgRotate(self):
        self.im = self.im.rotate(45)
        self.showImage()

#---Main app starts here---
root = Tk()
app = MainApp(root)
#create menu
app.createMenu()
root.mainloop()

กลับไปสู่ภาพต้นฉบับ

ในบทความนี้เราจะลองสร้างเมนูใหม่ คือ Edit และมีคำสั่ง Restore Image เพื่อให้เราสามารถย้อนกลับไปสู่ภาพต้นฉบับ ในกรณีที่ทำการประมวลผลไปแล้ว




หลักการก็ตรงไปตรงมาครับ สำเนารูปต้นฉบับไว้ก่อน ด้วยคำสั่ง
Image.copy()

ตัวอย่างเช่น
imOrigin = im.copy()

จากนั้นเมื่อต้องการกลับไปสู่ต้นฉบับ ก็สำเนากลับ โดยใช้คำสั่ง
im = imOrigin.copy()

ลองมาดูโค้ดเต็มๆที่เพิ่มเมนูนี้ครับ
from Tkinter import Tk,Menu,Label
from PIL import Image,ImageTk

class MainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")
        self.lbl = Label(self.mainwindow)

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        self.mainwindow.config(menu=menubar)
        #FILE menu
        fileMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=fileMenu)
        fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)
        #EDIT menu
        editMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Edit", menu=editMenu)
        editMenu.add_command(label="Restore Image", command=self.imgRestore)
        #PROCESS menu
        processMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Process", menu=processMenu)
        processMenu.add_command(label="Rotate", command=self.imgRotate)

    def loadImage(self):
        self.im = Image.open("lena.jpg")
        self.imOrigin = self.im.copy()
        self.showImage()

    def showImage(self):
        self.imTk = ImageTk.PhotoImage(self.im)
        self.lbl.destroy()
        self.lbl = Label(self.mainwindow, image=self.imTk)
        self.lbl.pack()

    def imgRestore(self):
        self.im = self.imOrigin.copy()
        self.showImage()

    def imgRotate(self):
        self.im = self.im.rotate(45)
        self.showImage()

#---Main app starts here---
root = Tk()
app = MainApp(root)
#create menu
app.createMenu()
app.loadImage()
root.mainloop()

Tuesday, November 27, 2012

เมนู+รูป+การประมวลผล 2

ต่อเนื่องจากคราวที่แล้วครับ แก้ปัญหา Label เก่าซ้อนกับอันใหม่ เพื่อให้ได้ผลลัพธ์ตามนี้


วิธีการแก้ปัญหาคือ ต้องทำลาย Label อันเก่าที่ค้างอยู่บนหน้าต่างก่อนที่จะแสดง Label ใหม่ นั่นคือ
1. ให้ตัวแปร Label เป็นตัวแปรของคลาส และ
2. ใช้คำสั่ง destroy เพื่อทำลาย Label อันเก่า

โคัดทั้งหมดหลังจากการแก้ไขก็จะเป็นดังนี้ครับ
from Tkinter import Tk,Menu,Label
from PIL import Image,ImageTk

class MainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")
        self.lbl = Label(self.mainwindow)

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        self.mainwindow.config(menu=menubar)
        #FILE menu
        fileMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=fileMenu)
        fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)
        #PROCESS menu
        processMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Process", menu=processMenu)
        processMenu.add_command(label="Rotate",command=self.imgRotate)

    def loadImage(self):
        self.im = Image.open("lena.jpg")
        self.showImage()

    def showImage(self):
        self.imTk = ImageTk.PhotoImage(self.im)
        self.lbl.destroy()
        self.lbl = Label(self.mainwindow, image=self.imTk)
        self.lbl.pack()

    def imgRotate(self):
        self.im = self.im.rotate(45)
        self.showImage()

#---Main app starts here---
root = Tk()
app = MainApp(root)
#create menu
app.createMenu()
app.loadImage()
root.mainloop()

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

เมนู+รูป+การประมวลผล

คราวนี้เราจะลองเอา เมนูในบทความที่แล้ว มาประกอบกับรูปในบทความก่อนหน้านั้นครับ


โคัดเมื่อรวมๆกันแล้วก็จะได้แบบนี้
from Tkinter import Tk,Menu,Label
from PIL import Image,ImageTk

class MainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        self.mainwindow.config(menu=menubar)
        #FILE menu
        fileMenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=fileMenu)
        fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)

    def loadImage(self):
        self.im = Image.open("lena.jpg")
        self.showImage()

    def showImage(self):
        self.imTk = ImageTk.PhotoImage(self.im)
        lbl = Label(self.mainwindow, image=self.imTk)
        lbl.pack()

#---Main app starts here---
root = Tk()
app = MainApp(root)
#create menu
app.createMenu()
app.loadImage()
root.mainloop()

จากนั้น เราจะลองเพิ่มเมนู Process สำหรับการประมวลผลภาพ เพื่อทำการหมุนรูปใหัได้ผลคือ


ลองเพิ่มโค้ดต่อไปนี้ในส่วนของ createMenu() ครับ
#PROCESS menu
processMenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="Process", menu=processMenu)
processMenu.add_command(label="Rotate",command=self.imgRotate) 

จะเห็นว่าเราได้เพิ่มคำสั่ง Rotate ในส่วนของเมนู Process ดังนั้น เราจำเป็นต้องเขียน method ใหม่ให้สอดคล้องกับคำสั่งดังกล่าว เพื่อหมุนรูปดังนี้
    def imgRotate(self):
        self.im = self.im.rotate(45)
        self.showImage()

เมื่อแก้ไขข้างต้นแล้ว หากรันโปรแกรมแ้ล้วเลือกเมนู Process / Rotate จะได้ผลลัพธ์คือ



อ้าว ทำไมเป็นแบบนี้ล่ะ มีส่วนว่างๆข้างบนมาได้อย่างไร?

เหตุผลก็เป็นเพราะว่า ใน showImage() มีการสร้าง Label ใหม่เรื่อยๆ ส่วน Label เก่าที่ถูกสร้างไว้ก็ยังเหลืออยู่ (ยกเว้นรูป เพราะถูก garbage collector ทำลายไปแล้ว) ทำให้เกิดพื้นที่ว่างที่เป็นของ Label เก่าค้างอยู่ในหน้าต่าง

เราจะมาลองแก้ปัญหานี้ในคราวถัดไปครับ

Monday, November 26, 2012

การสร้างเมนู

ตอนที่แล้วเรามีการประมวลผลภาพและแสดงผลในหน้าต่าง ซึ่งจะเรียกใช้งานคำสั่งผ่านโค้ดโดยตรง

ขั้นตอนดังกล่าวอาจจะไม่สะดวกและยืดหยุ่นพอ ในกรณีที่มีการประมวลผลมีหลายรูปแบบ ซึ่งผู้ใช้งานน่าจะเลือกได้ด้วยตนเอง

ดังนั้น ทางเลือกนึงคือการสร้างเมนู เพื่อให้จัดการการประมวลผลรูปได้ง่ายขึ้น

ในบทความนี้จึงนำเสนอการสร้างเมนูอย่างง่าย เพื่อให้ได้ผลลัพธ์ดังนี้


นั่นคือ การสร้างเมนูหนึ่งเมนูชื่อ File และมีคำสั่งย่อยคือ Exit เมื่อคลิกที่คำสั่งนี้ก็จะปิดหน้าต่างไป

เราลองมาสร้างเมนูก่อน ด้วยคำสั่งดังต่อไปนี้
from Tkinter import Tk,Menu

class mainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Menu")

    def createMenu(self):
        #create menubar
        menubar = Menu(self.mainwindow)
        #attach menubar to main window
        self.mainwindow.config(menu=menubar)
        #FILE menu, tearoff = user can drag the menu
        fileMenu = Menu(menubar, tearoff=0)
        #add FILE menu to menubar
        menubar.add_cascade(label="File", menu=fileMenu)
        #add EXIT command to FILE menu
        fileMenu.add_command(label="Exit")

#---Main app starts here---
root = Tk()
app = mainApp(root)
#create menu
app.createMenu()
root.mainloop()

ณ ตอนนี้ หากเราคลิกที่คำสั่ง Exit ก็จะยังไม่มีอะไรเกิดขึ้น เราต้องเชื่อมคำสั่งนี้กับ method ซึ่งในที่นี้ก็คือคำสั่ง root.destroy (ผมลองใช้คำสั่ง root.quit ตามตัวอย่างหลายๆแห่งแล้วมันค้างงงงง)

แก้ไขเฉพาะบรรทัดเดียวครับ คือบรรทัด
 fileMenu.add_command(label="Exit")
เปลี่ยนเป็น
fileMenu.add_command(label="Exit", command=self.mainwindow.destroy)

จากนั้นเมนูคำสั่ง Exit ก็จะใช้งานได้แล้วครับ

ตอนหน้าเราจะลองเอาเมนูไปผูกกับคำสั่งในการประมวลผลภาพต่อครับ

การประมวลผลรูปและแสดงผลในหน้าต่าง

จากบทความที่แล้ว เราได้ลองเขียนคลาสที่มี constructor สำหรับอ่านและแสดงผลรูป ซึ่งได้ผลลัพธ์ดังนี้


ก่อนที่จะประมวลผลรูป น่าจะเป็นการดีที่เราแยกหน้าที่เหล่านี้ออกจากกันโดยใช้ method ใหม่แทน นั่นคือ เราจะแยกเป็น 3 method

1. constructor ทำหน้าที่รับหน้าต่างหลักมา แล้วกำหนด title ของหน้าต่าง
2. loadImage ทำหน้าที่อ่านไฟล์รูปมาเก็บไว้ในตัวแปรของคลาส
3. showImage ทำหน้าที่แสดงผลรูปในหน้าต่าง

เพื่อที่จะให้ตัวแปรใช้ร่วมกันในแต่ละ method ได้ ใน python จึงต้องสร้างให้เป็นตัวแปรของคลาส คือ มีคำว่า self นำหน้า

โค้ดที่ปรับปรุงใหม่ แต่ให้ผลลัพธ์เหมือนบทความที่แล้วก็จะเป็นดังนี้
from Tkinter import Tk, Label
from PIL import Image, ImageTk

class mainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Image Label")

    def loadImage(self):
        #load image
        self.im = Image.open("lena.jpg")

    def showImage(self):
        #convert image to Tk format
        self.imTk = ImageTk.PhotoImage(self.im)
        #create a label on main window with image
        lbl = Label(self.mainwindow, image=self.imTk)
        #pack window to label and display
        lbl.pack()

#---Main app starts here---
root = Tk()
app = mainApp(root)
#load image
app.loadImage()
#show image
app.showImage()
root.mainloop()

สังเกตว่าคราวนี้เราอ่านรูปและแสดงผลด้วย method ใหม่ ซึ่งถูกเรียกใช้งานผ่าน object

ต่อไปเราจะลองเขียน method เพื่อประมวลผลรูป เช่น เปลี่ยนรูปสี ให้เป็นรูปสีเทา
หมายเหตุ ใน PIL รูปสีปกติจะเรียกว่า RGB และรูปสีเทาจะเรียกว่า L



โค้ดของโปรแกรมก็จะเพิ่ม method ใหม่สั้นๆแค่
 #----------------------------------------------
    #method to convert image to grayscale
    def imgGray(self):
        self.im = self.im.convert("L")
        self.showImage()
#----------------------------------------------

ถ้าเป็นโค้ดเต็มๆ ก็ตามนี้ครับ
from Tkinter import Tk, Label
from PIL import Image, ImageTk

class mainApp:
    #constructor
    def __init__(self,mainwindow):
        #assign class variable to input parameter
        self.mainwindow = mainwindow
        #set title
        self.mainwindow.title("Image Label")

    def loadImage(self):
        #load image
        self.im = Image.open("lena.jpg")

    def showImage(self):
        #convert image to Tk format
        self.imTk = ImageTk.PhotoImage(self.im)
        #create a label on main window with image
        lbl = Label(self.mainwindow, image=self.imTk)
        #pack window to label and display
        lbl.pack()

    #method to convert image to grayscale
    def imgGray(self):
        self.im = self.im.convert("L")
        self.showImage()

#---Main app starts here---
root = Tk()
app = mainApp(root)
#load image
app.loadImage()
#convert to grayscale image
app.imgGray()
root.mainloop()

Sunday, November 25, 2012

การอ่านรูปและแสดงผลบน UI

คราวที่แล้วเราลองสร้าง UI อย่างง่าย ซึ่งจะนำมาใช้ต่อในบทความนี้ครับ

ก่อนอื่น เราจะลองนำข้อความไปวางบนหน้าต่างของ UI ให้ได้ผลลัพธ์ตามนี้ครับ

โดยใช้โค้ดต่อไปนี้
from Tkinter import Tk, Label
from PIL import Image, ImageTk

class mainApp:
    #constructor
    def __init__(self,mainwindow):
        #set title
        mainwindow.title("Text Label")
        #create a label on main window with text
        lbl = Label(mainwindow, text="Hello world!")
        #pack window to label and display
        lbl.pack()

#Main app starts here
root = Tk()
app = mainApp(root)
root.mainloop()


ต่อไปเราจะลองอ่านรูป แล้วแสดงผลบนหน้าต่าง โดยใช้ Label เป็นตัวเก็บรูป (เนื่องจากหน้าต่างหลักเก็บรูปไม่ได้ครับ)

ผลลัพธ์ที่ต้องการ


แก้ไขโค้ดใหม่ดังนี้ครับ
from Tkinter import Tk, Label
from PIL import Image, ImageTk

class mainApp:
    #constructor
    def __init__(self,mainwindow):
        #set title
        mainwindow.title("Image Label")
        #load image
        im = Image.open("lena.jpg")
        #convert image to Tk format
        #self is used to set the image to class variable
        #to prevent garbage collector to destroy image
        self.imTk = ImageTk.PhotoImage(im)
        #create a label on main window with image
        lbl = Label(mainwindow, image=self.imTk)
        #pack window to label and display
        lbl.pack()

#Main app starts here
root = Tk()
app = mainApp(root)
root.mainloop() 

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

การสร้าง User Interface (UI) ขั้นต้น

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

UI ที่ใช้กับ python สามารถสร้างโดยใช้ส่วนเสริมหลายตัวครับ แต่ตัวที่เป็นพื้นฐานและมาตรฐานที่มาพร้อมกับภาษา (เฉพาะ windows ส่วน linux อาจต้องติดตั้งเพิ่ม) คือ Tkinter

การใช้งานไม่ยากครับ ตัวอย่างโค้ดเช่น
from Tkinter import *
#create a Tk root widget, a simple window
root = Tk()
#set window title
root.title("Hello Title")
#stay in the event loop until closing the window
root.mainloop()

ก็จะได้ผลลัพธ์เป็น


เราสามารถเขียนโค้ดดังกล่าว ให้อยู่ในรูปแบบของคลาส ได้ดังนี้
from Tkinter import *
class MainApp:
    #constructor
    def __init__(self,mainwindow):
        mainwindow.title("Hello Title")
    
#create a Tk root widget, a simple window
root = Tk()
#create an object from class App
app = MainApp(root)
#stay in the event loop until closing the window
root.mainloop()

ซึ่งก็จะได้ผลเช่นเดียวกันครับ


เอกสารอ้างอิง
An Introduction to Tkinter, http://www.pythonware.com/library/tkinter/introduction/

การอ่านไฟล์ภาพและแสดงผล

สมมติว่าเรามีรูปที่ต้องการอ่านและแสดงผลคือ "lena.jpg" ซึ่งอยู่ในโฟลเดอร์เดียวกับโปรแกรมที่เราจะเขียนนะครับ


วิธีการอ่านรูป ก็จะใช้โค้ดง่ายๆดังนี้ครับ
from PIL import Image
#open image
im = Image.open("lena.jpg")
#show image, loading temp image by default image viewer
im.show()

คำสั่ง im.show() จะบันทึกผลลัพธ์เป็นไฟล์ชั่วคราว และเรียกโปรแกรมดูภาพในเครื่องมาเปิดไฟล์รูปนี้ขึ้นมาแสดงครับ

เราลองทำการประมวลผลภาพอย่างง่าย เช่นการกลับรูปซ้ายขวา (Flip left-right) ด้วยโค้ด
from PIL import Image
#open image
im = Image.open("lena.jpg")
#flip image left-right
out = im.transpose(Image.FLIP_LEFT_RIGHT)
#show image, loading temp image by default image viewer
out.show() 

ผลลัพธ์ก็จะเป็น

บทความถัดไป เราจะลองสร้าง GUI เพื่อเตรียมไว้แสดงผลรูปครับ

 เอกสารอ้างอิง
Python Image Library Handbook, http://www.pythonware.com/library/pil/handbook/index.htm

Python กับการประมวลผลภาพเบื้องต้น

มีโอกาสได้ลองใช้ python ครับ เลยถือโอกาสศึกษาวิธีการใช้กับการประมวลผลภาพ พบว่า python เป็นภาษาที่มีทั้งข้อเด่นและข้อจำกัด แต่โดยรวมแล้วถือว่าเป็นภาษาที่มีประสิทธิภาพมาก โดยเฉพาะอย่างยิ่งมีส่วนเสริมที่สนับสนุนงานทางด้านการประมวลผลภาพได้ดี นอกจากนี้แล้วยังเป็น multi-platform ใช้งานได้บนหลายระบบปฏิบัติการ

สำหรับตัวอย่างที่เราจะลองสร้างกัน ต้องการซอฟต์แวร์ดังต่อไปนี้ครับ
1. python รุ่น 2.7
2. python-imaging (PIL) สำหรับการประมวลผลภาพ
3. python-tk สำหรับการสร้าง interface
4. python-imaging-tk สำหรับการเชื่อมต่อระหว่าง PIL และ python-tk

ซึ่งข้อ 1. ถึงข้อ 3. มักจะมีมากับ linux อยู่แล้ว ถ้าต้องการติดตั้งข้อ 4. เพิ่มก็สามารถทำได้ เช่น ใช้ Synaptic package manager แล้วเลือก python-imaging-tk

สำหรับ windows สามารถดาวน์โหลดข้อ 1. และ 2. มาติดตั้งก็น่าจะเพียงพอ หรือ จะใช้ portable python (http://www.portablepython.com/) ที่สามารถใช้งานผ่าน usb drive ได้เลย แต่ตอนแตกไฟล์ครั้งแรกให้เลือกส่วนเสริม PIL ด้วยครับ

ความต้องการอีกอย่างนึงคือ เราต้องเขียนโปรแกรมพื้นฐานด้วย python พอได้นะครับ

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

print "hello"

(ใช้ IDE ตัวไหนก็ได้ตามใจชอบครับ ถ้าบน linux mint ที่ผมใช้อยู่จะมี Geany ส่วนบน windows ที่ใช้ portable python ก็จะมี PyScripter ซึ่งดีมากๆเช่นกัน)

จากนั้นบันทึกเป็นไฟล์ชื่อใดๆ เช่น hello.py
และรันโค้ดนี้ด้วยเมนู run หรือ execute ใน IDE ที่ใช้ ซึ่งมักจะ link ไปยัง python ที่ติดตั้งไว้โดยอัตโนมัติ
หรือ รันด้วยคำสั่งจาก terminal หรือ command prompt คือ
python hello.py

ถ้าเกิดผลลัพธ์ที่เป็นข้อความว่า hello แสดงว่าเครื่องคอมพิวเตอร์ของเราพร้อมสำหรับ python แล้วครับ

Tuesday, September 4, 2012

สร้าง executable jar

เมื่อเขียนโค้ดจาวาเสร็จแล้ว อยากจะเผยแพร่ให้คนอื่นใช้ จะทำอย่างไรดี

สำหรับคนที่ใช้ Eclipse จะสามารถคลิกขวาที่โปรเจ็ค เลือก Export แล้วสร้าง executable jar ได้

แต่บางครั้งก็มีปัญหาครับ เพราะหากโค้ดของเรามีการใช้ไฟล์ jar จากภายนอกเพิ่มเติม บางที Eclipse ก็ไม่สามารถทำได้

ลองมาทำกันแบบ command line ดีกว่า

สมมติว่าเราติดตั้ง jdk ในเครื่องแล้ว กำหนด path ให้ถูกต้อง

1 เปิด command prompt
2 ไปที่โฟลเดอร์ที่เก็บผลลัพธ์จากการคอมไพล์ (ที่มีไฟล์ .class)
3 สร้างไฟล์ manifest.txt แนะนำให้ใช้ notepad ธรรมดา
4 เติมเนื้อหาดังเช่น

Main-Class: QRReader
Class-Path: dsj.jar JNative.jar


ความหมาย
Main-Class:(เคาะ)ชื่อของคลาสหลัก
Class-Path:(เคาะ)ชื่อของ external jar (ถ้ามี) ต้องเรียงลำดับตามการอ้างถึงในคลาสหลัก เช่นตามตัวอย่างคลาสใน dsj.jar จะถูกเรียกใช้ก่อน JNative.jar
(ขึ้นบรรทัดใหม่)

ต้องระวังให้ดีว่า บรรทัดสุดท้ายของไฟล์ manifest นี้ต้องเคาะให้เป็นบรรทัดว่างเสมอ

5 ใช้คำสั่ง jar -cfm test.jar manifest.txt *.class
6 จะได้ไฟล์ test.jar มา ซึ่งสามารถย้ายไปไว้ที่เดียวกับไฟล์อื่นๆที่จำเป็น เช่น ตามตัวอย่างคือ dsj.jar และ JNative.jar รวมถึงไฟล์อื่นๆอีกเช่นไฟล์ dll เป็นต้น
7 ลองใช้คำสั่ง java -jar test.jar หรือ javaw -jar test.jar ทดสอบรัน หรือสำหรับบางเครื่องสามารถดับเบิลคลิกไฟล์ jar ได้เลย
8 ถ้ามีปัญหาดับเบิลคลิกไฟล์ jar ไม่ได้ ก็เขียน batch file (นามสกุล .bat เช่น test.bat) แล้วแปะโค้ด java -jar test.jar หรือ javaw -jar test.jar ลงไป
9 ถ้าอยากใช้ java executable wrapper เพื่อสร้างไฟล์ exe ที่ไปเรียก jar ไฟล์อีกที แนะนำลองใช้ Lanuch4j ครับ สุดยอดมากๆ

Thursday, May 3, 2012

jQuery UI Autocomplete widget

เห็น widget อันนี้แล้วต้องการใช้ครับ เอาไปประยุกต์ในการพัฒนาระบบค้นหาข้อมูลได้

ใช้โค้ดหลักๆเหมือนตัวอย่างที่แล้วนะครับ แต่มาเปลี่ยนฟอร์มกับสคริปดังนี้
  • ฝั่ง html
    <div>
    <p>    Search <input type="text" id="search" /> </p>
    </div>

  • ฝั่ง javascript
 $(function() {
    var tags = ["love","like","bike"];
    $("#search").autocomplete({
        source: tags
    });
});

แค่นี้เองครับ แต่ที่ต้องทำต่อคือการไปดึงข้อมูลมาจากฐานข้อมูลแทน

เอกสารอ้างอิง
  • http://jqueryui.com/demos/autocomplete/
ปล. เท่าที่ทราบ ระบบของ google ไม่ได้เป็นแบบนี้ครับ แต่ใช้ ajax ทำการ sync ข้อมูลกับฐานข้อมูลตลอด

ลองเล่น jQuery UI

ข้อมูลจาก jqueryui.com ครับ

jQuery UI (UI = User Interface) ก็คือ widget หรือ interface ที่ออกแบบมาให้ใช้กับเว็บ ทำให้เว็บมีปฏิสัมพันธ์กับผู้ใช้ได้สะดวกและสวยงามขึ้น ซึ่ง jQuery UI เองก็ถูกพัฒนาโดยใช้ jQuery ซึ่งเป็น javascript library อย่างหนึ่ง (jquery.com)

ลองมาดูตัวอย่างการใช้งานครับ ในที่นี้เราจะลองใช้ datepicker widget หรือ calendar widget เพื่อป้อนข้อมูลวันที่ ดังนี้ครับ


การติดตั้งและขั้นตอนการใช้งาน ดูได้จาก http://jqueryui.com/docs/Getting_Started โดยสรุปสั้นๆว่า
  1. ไปดาวน์โหลด jQuery UI มาก่อน สามารถเลือก theme หรือเลือกเฉพาะ widget ก็ได้ ถ้าอยากได้ง่ายๆไม่ต้องทำอะไรมากก็ดาวน์โหลดตัวปกติได้ที่หน้าหลัก เช่น http://jqueryui.com/download/jquery-ui-1.8.20.custom.zip
  2. แตกไฟล์ออกมา จะมีไดเรกทอรี js (jQuery), css, development-bundle (เอกสาร, ตัวอย่าง, UI) และไฟล์ index.html
  3. ใช้ text editor เปิดไฟล์ index.html จะเห็นว่ามี 3 บรรทัดคือ 
< link type="text/css" href="css/smoothness/jquery-ui-1.8.19.custom.css" rel="stylesheet" />   
< script type="text/javascript" src="js/jquery-1.7.1.min.js" > < /script >
< script type="text/javascript" src="js/jquery-ui-1.8.19.custom.min.js" > < /script >

จากนั้นให้สร้างไฟล์ html ของเราที่จะใช้งาน เช่น testUI.html แล้วนำโค้ดข้างต้นมาเพิ่ม พร้อมกับเติม textbox ที่ต้องการป้อนค่าวันที่เข้าไป เช่น

<html>                                                                 
 <head>                                                                 
 <link type="text/css" href="css/smoothness/jquery-ui-1.8.19.custom.css" rel="stylesheet" />   
 <script type="text/javascript" src="js/jquery-1.7.1.min.js"></script>
 <script type="text/javascript" src="js/jquery-ui-1.8.19.custom.min.js"></script>        
 <script type="text/javascript" src="myScript.js"></script>                                                              
 </head>                                                                
 <body>                                                                 
    <div>
    <p>    Date <input type="text" name="date" id="date" /> </p> </div>
 </body>                                                                
 </html>

ต่อมาให้สร้างไฟล์ javascript เช่น myScript.js ให้สอดคล้องกับไฟล์ html ที่อ้างถึง แล้วเพิ่มรายละเอียด ได้แก่

$(function(){
    $('#date').datepicker();
});

บันทึกไฟล์ทั้งหมด แล้วลองเปิดไฟล์ html ก็จะได้ผลลัพธ์ตามต้องการครับ

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

$(function(){
    $('#date').datepicker({ dateFormat: "dd/mm/yy" });
});

เอกสารอ้างอิง
  • http://jqueryui.com/docs/Getting_Started
  • http://jqueryui.com/demos/datepicker/
  • http://jquery.com