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