Tuesday, December 9, 2014

การอ่านค่าจาก Checkbox ด้วย jQuery

ลองมาอ่านค่าจาก checkbox กันสองอย่างคือ

1. มี checkbox กี่อันถูกเลือก
2. ค่าใน checkbox ที่ถูกเลือกทั้งหมดเป็นเท่าใด

แบบนี้ครับ
โค้ด
<!doctype html>
<html>
 <head>
  <meta charset="utf-8">
  <script src="jquery-2.1.1.min.js"></script>
  <script>
   $(document).ready(function(){  
    $("button").on("click",function(){
     //number of checked checkbox
     var num = $("input[type=checkbox]:checked").length;
     var value = "";
     //loop for each checked checkbox
     $("input[type=checkbox]:checked").each(function() {
      value = value + $(this).val() + " ";
     });
     alert("Number of selected checkbox = "+num+"\n"+"Value = "+value);
    });
   });  
  </script>
 </head>

<body>
 <input type="checkbox" name="fruit" value="Apple">Apple<br/>
 <input type="checkbox" name="fruit" value="Papaya">Papaya<br/>
 <input type="checkbox" name="fruit" value="Banana">Banana<br/>
 <button>OK</button>
</body>
</html>

อ้างอิง
http://api.jquery.com/checked-selector/
http://stackoverflow.com/questions/14679916/jquery-get-multiple-checked-values-from-checkbox

Thursday, December 4, 2014

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

จากตอนที่แล้ว เรารู้ระบบการอ่านค่าของ PIL แล้ว

ต่อไปเราจะลองอ่านค่าส่วนหนึ่งของรูปขึ้นมา สมมติว่ารูปของเราคือ Lenna-gray-raw.tif ซึ่งเมื่อใช้คำสั่งในการอ่านรูปพบว่า
img.size ให้ค่า (512,512) ซึ่งเป็น ความกว้าง x ความสูง หรือ จำนวนคอลัมน์ x จำนวนแถว
และ
img.tile แล้วพบว่า มันเป็นแบบ ('raw', (0, 0, 512, 64), 8, ('L', 0, 1))

ซึ่งหมายเหตุตรงนี้นิดนึงว่า
1. ในการอ่านค่ารูปด้วย PIL แม้ว่ารูปเดียวกัน แต่คนละระบบหรือคนละเครื่อง มันอาจจะแสดงการอ่านจำนวนแถวครั้งละไม่เท่ากัน เช่นตอนนี้เป็น 64 แถว แต่เครื่องอื่นที่ผมทดลองใช้มันอ่านทีละ 16 หรือ 32 แถวก็มี ทั้งนี้เดาว่าแล้วแต่หน่วยความจำที่มี OS ที่ใช้และขนาดของรูป แต่ไม่มีผลอะไรกับสิ่งที่เราจะทำในตอนนี้ครับ
2. L ในที่นี้คือ 8-bit pixels, black and white แต่รูปแบบ tif อาจจะเก็บแบบ P คือ 8-bit pixels, mapped to any other mode using a colour palette ก็ได้ ตรวจสอบให้ดีก่อนจะเขียนโปรแกรมต่อไปนะครับ

เมื่อทราบข้อมูลข้างต้นแล้ว สมมติว่าผมจะลองอ่านค่าเฉพาะ 64 แถวแรกของรูปแบบ raw tif ขนาด 512x512 พิกเซลขึ้นมาแสดงผลให้ได้ดังนี้


ก็จะเขียนโค้ดดังนี้ครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.tif")

#try to read first 64 lines
img.size = (512,64)
#we will skip for 8 bytes (image header)
img.tile = [('raw', (0, 0, 512, 64), 8, ('L', 0, 1))]
#load part of image according to new size and tile's properties
#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(img)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()

และถ้าอยากประมวลผลรูปส่วนที่โหลดขึ้นมา ก็ทำได้ตามปกติครับ เช่นจะกลับหัวรูปก็แค่เพิ่มคำสั่ง
#try to flip image
img = img.transpose(Image.FLIP_TOP_BOTTOM)

หลังคำสั่ง img.load() ก็น่าจะได้ผลประมาณนี้ครับ


ถ้าเราต้องการเข้าถึงพิกเซลก็ทำได้ครับ โดยเปลี่ยนคำสั่ง img.load() ให้เป็น pix = img.load() เพื่ออ่านค่าพิกเซลมาเก็บไว้ในโครงสร้างของ PIL และสามารถอ่านค่าแต่ละพิกเซลโดยใช้คำสั่ง pix[c,r] เมื่อ c และ r คือคอลัมน์และแถวของภาพตามลำดับ อย่างไรก็ตาม ผมพบว่าการอ่านส่วนของภาพแบบนี้ จะทำให้โครงสร้างที่เก็บพิกเซลนี้เป็นแบบ read only คือแก้ไขไม่ได้ เพราะฉะนั้นจำเป็นต้องสร้างรูปใหม่ขึ้นมาอีกหนึ่งรูปเพื่อเก็บผลลัพธ์จากการประมวลผลภาพ

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


โค้ดครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.tif")

#try to read first 64 lines
img.size = (512,64)
#we will skip for 8 bytes (image header)
img.tile = [('raw', (0, 0, 512, 64), 8, ('L', 0, 1))]
#load part of image according to new size and tile's properties to pixel object
pix = img.load()
#create new output image
out = Image.new(img.mode, img.size, None)
pix2 = out.load()
#loop for each row and column
w,h = img.size
for r in range(h):
    for c in range(w):
        pix2[c,r] = 255-pix[c,r]        

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()
ในตอนต่อไปเราจะลองใช้ลูปเพื่อวนอ่านค่ารูปทีละส่วนมาประมวลผลดูครับ

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

เรื่องนี้ค้างคามานานแล้ว คงถึงเวลาที่ต้องทำให้สำเร็จซะที 

มีโจทย์อยู่ว่าต้องการอ่านภาพขนาดใหญ่มาก เช่น ภาพพวก Remote Sensing แบบ Multi-Spectral ซึ่งปกติต้องใช้เครื่องคอมที่มีแรมเยอะๆ เพราะต้องอ่านภาพทั้งหมดไปไว้ในหน่วยความจำก่อนการประมวลผล และถ้าต้องประมวลผลแบบพวก Convolution ก็จะต้องใช้หน่วยความจำมากขึ้นไปอีก 

ทีนี้ก็เลยมาคิดว่า ถ้าเครื่องมีแรมไม่พอจะทำได้ไหม ถ้าโหลดภาพกันตรงๆก็คงเจอปัญหา Out of Memory แน่ๆ 

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

 ต่อไปก็หาเทคนิคที่จะใช้ ไปค้นดูปรากฎว่า Java ก็ทำได้ ลองเขียนโค้ดไปพักใหญ่ๆ 

ต่อมา ลองค้นหากับ Python บ้าง เพื่อว่าจะมีวิธีเหมือนกัน ก็ไปพบที่เว็บนี้ Using PIL On Large Images
เค้าบอกว่า ถ้าใช้ PIL (Python Imaging Library) ในการอ่านค่ารูป มันสามารถแบ่งส่วนได้

เมื่อเราใช้คำสั่งในการอ่านรูป คือ
im = Image.open("Lenna.png")

จริงๆแล้วรูปยังไม่ถูกอ่านและโหลดเข้าหน่วยความจำทั้งหมด คำสั่งนี้เพียงแค่โหลดส่วนต้นของไฟล์ภาพ ที่เกี่ยวข้องกับข้อมูลภาพ เช่น การเข้ารหัส ขนาดของภาพ ชนิดของภาพ (ภาพสีเทา ภาพสี) ฯลฯ กระบวนการนี้ถูกเรียกว่า Lazy operation

ซึ่งหลังจากคำสั่งนี้ เราสามารถใช้คำสั่งอื่นเพื่อดูข้อมูลนี้ได้ เช่น
im.size ก็จะให้ค่าเป็น (width, height) ออกมา

ถ้าใช้คำสั่ง im.tile จะได้โครงสร้างรูปแบบการอ่านค่าแบบบล็อกออกมา (แสดงว่าจริงๆแล้ว PIL ก็อ่านค่ารูปแบบแบ่งส่วนอยู่แล้ว ไม่ได้อ่านทีเดียวทั้งหมด เพียงแต่กระบวนการนี้ต่อเนื่องกันทั้งรูป)

ตัวอย่างเช่น ถ้ามีรูป Lenna.png ที่ดาวน์โหลดมาจากที่นี่


เมื่อเราอ่านรูปโดย
im = Image.open("Lenna.png")
และดูขนาด
im.size
(512, 512)
หมายเหตุว่าเป็น (width, height) ครับ

และดูการแบ่งส่วนในการอ่านรูป
im.tile
[('zip', (0, 0, 512, 512), 54L, 'RGB')]

ก็จะเห็นว่ามีค่าอยู่สี่ชุด ได้แก่
1. การเข้ารหัส ในที่นี้คือ zip ถ้าไม่เข้ารหัสเลยก็จะเป็น raw
2. ขนาดกรอบสี่เหลี่ยมในการอ่านรูป เริ่มตั้งแต่มุมซ้ายบน (x1,y1) จนถึงขวาล่างสุด (x2,y2) ของรูป
3. ค่า offset ในการอ่านรูปแต่ละครั้ง ในที่นี้คือ 54L ซึ่งในที่นี้ไม่มีการแบ่งส่วนของรูป จึงเป็นค่าขนาดของ header ของไฟล์รูปนี้ (ไฟล์รูปจะมีส่วนหัวที่บอกถึงการเข้ารหัส ชนิดของรูป ขนาด ฯลฯ) ค่า offset นี้จะเป็นตัวบอกว่าเราจะเริ่มอ่านไฟล์เนื้อหาของรูปจริงๆที่ตรงไหน เช่น 54L ก็คือเริ่มอ่านหลังจาก 54 คูณ ขนาดของ long (ไม่รู้เท่าไหร่เหมือนกันครับ 555) ไบต์เป็นต้นไป
4. โหมดของรูป ในที่นี้คือ RGB

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

ถ้าลองเปลี่ยนมาเป็นรูปสีเทา โดยในที่นี้จะเป็นรูปเดิมที่ถูกแปลงเป็นไฟล์ tif โดยไม่เข้ารหัส (ไฟล์ raw)


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

จะเห็นว่าคราวนี้ ข้อมูลชุดแรกของค่า tile (แถบแรก) คือ
('raw', (0, 0, 512, 64), 8, ('L', 0, 1))
บอกว่าไม่มีการเข้ารหัส (raw) และ มีการอ่านค่าทุกๆ 64 บรรทัด โดยครั้งแรกที่อ่านจะต้องเลื่อนไป 8 bytes ก่อน (offset) ซึ่งเป็น header ของไฟล์รูปนี้ รูปจริงๆก็จะอยู่ต่อจาก byte ที่ 8 นั่นเอง

ข้อมูลชุดถัดมา (แถบที่สอง) คือ
('raw', (0, 64, 512, 128), 32776, ('L', 0, 1))
บอกว่าให้อ่านอีก 64 แถวต่อไป (ทุกคอลัมน์) ค่า Offset คราวนี้ก็จะเท่ากับ 64 แถว * 512 คอลัมน์ + 8 ไบต์แรก = 32776 ไบต์

เหมือนกับรูปประมาณนี้ครับ


ในตอนต่อไปเราจะลองเปลี่ยนค่า size และ tile ของรูป เพื่อแบ่งส่วนในการอ่านรูปดูครับ

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

Wednesday, December 3, 2014

[OpenCV กับการแสดงผลใน Tkinter] และ [PIL กับการแสดงผลด้วย OpenCV]

ตั้งใจจะเขียนเรื่องนี้นานแล้ว พึ่งจำได้ครับ

ตอนเราประมวลผลรูปด้วย OpenCV มันก็จะมีหน้าต่างแสดงผลมาให้ ด้วยคำสั่ง เช่น imshow

อย่างไรก็ตาม ถ้าเราใช้ Python ทางเลือกหนึ่งในการแสดงผลก็คือ การใช้ Tkinter ซึ่งเป็น UI ที่มีมากับ Python เลย

สิ่งที่ต้องทำคือ
1. รูปแบบการเก็บข้อมูลภาพของ OpenCV ต่างกับแบบอื่น คือเป็น BGR จำเป็นต้องเปลี่ยนให้เป็น RGB ก่อน
2. OpenCV เก็บข้อมูลภาพเป็น NumPy Array การอ่านไปแสดงผลใน Tkinter จำเป็นต้องใช้คำสั่งการอ่านจาก Array แทน

โค้ดก็จะเป็นประมาณนี้ครับ คืออ่านค่าภาพ (และประมวลผล) ด้วย OpenCV จากนั้นสร้าง UI ด้วย Tkinter และแสดงผล
import cv2
from Tkinter import Tk, Label
from PIL import Image, ImageTk

#load image in grayscale, output is numpy array
img = cv2.imread("lena.jpg", cv2.IMREAD_COLOR)

if img==None:
    print "Image not loaded"
else:
    #convert BGR to RGB
    imgRGB = img[:,:,::-1]
    #convert to PIL format
    imgtk = Image.fromarray(imgRGB)
    #create main window
    root = Tk()
    root.title("Image Display")
    #convert to Tkinter format
    imgTk = ImageTk.PhotoImage(imgtk)
    #create a label to store and display image
    lbl = Label(root, image=imgTk)
    #pack a label to fit image
    lbl.pack()
    #lbl.image = imgTk
    root.mainloop()

ผลลัพธ์ที่ได้ก็คือ


ในทางกลับกันถ้าเราต้องการโหลดรูปด้วย PIL แต่ไปแสดงผลแบบ OpenCV กระบวนการก็จะกลับกันคือ
1. แปลงรูปให้เป็น numpy array ก่อน
2. เปลี่ยนจาก RGB ให้เป็น BGR

ลองดูโค้ดตามนี้ครับ
import cv2
from PIL import Image
import numpy as np

#load image by PIL
img = Image.open("lena.jpg")

#convert PIL image to numpy array
img = np.array(img)

#convert RGB to BGR
imgBGR = img[:,:,::-1]

#display in opencv
cv2.namedWindow("Output")
cv2.imshow("Output",imgBGR)
cv2.waitKey()
cv2.destroyAllWindows()

ผลลัพธ์ที่ได้น่าจะประมาณนี้


อ้างอิง
http://stackoverflow.com/questions/384759/pil-and-numpy

Tuesday, December 2, 2014

ลองติดตั้ง VirtualBox บน LXLE

พอติดตั้ง LXLE ที่เดิมทีตั้งใจจะให้เป็นการใช้งานทั่วไปเบาๆ ก็อยากลองอะไรที่มันมากขึ้น

อยากลองติดตั้ง VirtualBox แล้วรัน Android Emulator ดู ถ้าเซ็ต memory ต่ำๆ ซัก 216 MB น่าจะไหวอยู่

ว่าแล้วก็ติดตั้งเลย เลือก Software Center ของ lubuntu ติดตั้ง VirtualBox เวอร์ชันที่มีให้ก็ประมาณ 4.1 ทำนองนี้

ติดตั้งเสร็จ เพิ่มตัว emu (ในที่ใช้ OVA ของ AndroVM) รันไม่ได้ซะงั้น มีข้อความผิดพลาดบอกว่า

Kernel driver not installed (rc=-1908)

The VirtualBox Linux kernel driver (vboxdrv) is either not loaded or there is a permission problem with /dev/vboxdrv. Please reinstall the kernel module by executing

'/etc/init.d/vboxdrv setup'

as root. If it is available in your distribution, you should install the DKMS package first. This package keeps track of Linux kernel changes and recompiles the vboxdrv kernel module if necessary.


แย่ละ ไปต่อไม่ถูกเลย

ไปลองค้นหาดู หลายๆที่บอกว่าเพราะไม่มีพวก kernel drivers + build tools เลยลองทำตามเค้า เช่นที่เว็บ http://www.binarytides.com/fix-vbox-kernel-driver-error/

ก็ยังไม่สำเร็จในขั้นสุดท้าย

ไปหาดูอีก บางคนบอกว่านอกจากไม่มีพวกไฟล์ข้างต้นแล้ว เวอร์ชันของ VirtualBox ใน Software Center มันเก่าไป ไม่สนับสนุนกัน

สุดท้าย วิธีที่มั่วแล้วใช้ได้คือ
1. ติดตั้ง kernel drivers + build tools ก่อน โดยใช้คำสั่ง

$ sudo apt-get install build-essential module-assistant 
$ sudo m-a prepare

2. ดาวน์โหลด VirtualBox มาใหม่จาก https://www.virtualbox.org/wiki/Linux_Downloads เลือกเวอร์ชันให้ตรงกับ OS ของเรา อย่างเช่น ผมใช้ LXLE 12.04 ที่มีพื้นฐานมาจาก Ubuntu 12.04 LTS ก็โหลดเวอร์ชันนี้

3. ติดตั้งโดยดับเบิลคลิกทีไฟล์ที่โหลดมาเลย มันจะเรียก GDebi Package Installer มาติดตั้งไฟล์ .deb ที่โหลดมา รวมถึงคอมไพล์ไฟล์ที่เกี่ยวข้องให้ใหม่เอง

เย้ ใช้การได้แล้วครับ