Thursday, February 21, 2013

NumPy Array ตอนที่ 2 การสร้างและประมวลผล Array

มาว่ากันต่อกับการสร้างและใช้งาน NumPy Array ที่จะช่วยให้การประมวลผลสะดวกขึ้นอีกมากครับ

เราสามารถสร้าง Array ที่มีสมาชิกต่อเนื่องกันได้ โดยการใช้คำสั่ง arange(start,stop,step,dtype) ครับ เช่น

import numpy as np

a = np.arange(5)
b = np.arange(0,5,1, dtype=np.float64)
c = np.arange(0.5,0.1,-0.1)

print(a)
print(b)
print(c)

ผลลัพธ์
[0, 1, 2, 3, 4]
[ 0.,  1.,  2.,  3.,  4.]
[ 0.5,  0.4,  0.3,  0.2]
สังเกตว่าผลลัพธ์ไม่รวมค่า stop ด้วยนะครับ

หรือจะสร้าง Array โดยกำหนดจำนวนสมาชิกก็ได้ จากคำสั่ง linspace(start,stop,number) เช่น

import numpy as np

a = np.linspace(0, 3, 4)

จะได้ผลลัพธ์เป็น
[ 0.,  1.,  2.,  3.]
สังเกตว่ารวม stop ด้วยครับ

ถ้าเรามี Array อยู่แล้ว และต้องการสร้าง zero หรือ one matrix ที่มีขนาดเท่ากับ Array นั้น สามารถใช้คำสั่ง
zeros_like(array)
ones_like(array)
ได้ เช่น

import numpy as np

a = np.array([[11,12], [21,22]])
b = np.zeros_like(a)
c = np.ones_like(a)

ผลลัพธ์
[[11, 12],
  [21, 22]]

[[0, 0],
 [0, 0]]

[[1, 1],
 [1, 1]]

การประมวลผล Array
เราสามารถ +, -, *, /, **(ยกกำลัง) Array กับตัวเลข หรือ กับ Array ด้วยกันได้ ซึ่งจะเป็นการประมวลผลแบบทีละตำแหน่ง (element-wise) เช่น

import numpy as np

a = np.identity(2)
b = np.ones((2,2))
print(a)
print(b)

a = a*2
b = a+b
print(a)
print(b)

ก็จะได้ผลลัพธ์เป็น
[[ 1.,  0.],
[ 0.,  1.]]

[[ 1.,  1.],
[ 1.,  1.]]

[[ 2.,  0.],
[ 0.,  2.]]

[[ 3.,  1.],
[ 1.,  3.]]


แต่ถ้าต้องการหาผลคูณแบบเมตริกซ์ของ Array จะใช้คำสั่ง dot(matrix1,matrix2) แทน เช่น

import numpy as np

a = np.identity(2)
b = np.ones((2,2))
print(a)
print(b)

c = a*b
d = np.dot(a,b)
print(c)
print(d)

[[ 1.,  0.],
[ 0.,  1.]]

[[ 1.,  1.],
[ 1.,  1.]]
 
[[ 1.,  0.],
[ 0.,  1.]]

[[ 1.,  1.],
[ 1.,  1.]]

การคำนวณผลสรุปจาก Array
สามารถทำได้ด้วยคำสั่ง เช่น
  • array.max()
  • array.min()
  • array.sum()
ตัวอย่าง

import numpy as np

a = np.array([[2,1],[4,3]])

a.max()
a.min()
a.sum()

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

เราสามารถกำหนดแถวหรือคอลัมน์ที่ต้องการคำนวณโดยใช้ฟังก์ชันข้างต้นได้ ในรูปแบบ
array.max(axis)
เช่น
array.max(axis=0) หาค่าสูงสุดในแต่ละคอลัมน์
array.max(axis=1) หาค่าสูงสุดในแต่ละแถว

ตัวอย่าง

import numpy as np

a = np.array([[2,1],[4,3]])

a
a.max(axis=0)
a.max(axis=1)

ผลลัพธ์

[[2 1]
 [4 3]]

[4 3]       #[max คอลัมน์1 max คอลัมน์2]

[2 4]       #[max แถว1 max แถว2]

NumPy Array ตอนที่ 1 การสร้างและคุณสมบัติของ Array

เนื่องจากการเขียนโปรแกรมด้วย python ส่วนใหญ่จะมีการเก็บข้อมูลที่มีหลากหลายรูปแบบ  ข้อมูลบางอย่างเช่นรูปภาพ จะถูกเก็บไว้ในรูปแบบของ NumPy Array ดังนั้นเราจะมาทำความรู้จักมันก่อนครับ

ปกติแล้ว Array ของ Python จะเป็นแค่ 1 มิติ แต่ NumPy Array ถูกออกแบบมาให้รองรับ Array หลายมิติและมีฟังก์ชันการใช้งานที่หลากหลายกว่า

ถ้าต้องการสร้าง NumPy Array จะทำได้หลายวิธี แต่หลักๆแล้วจะใช้คำสั่ง
np.array() ตัวอย่างเช่น

import numpy as np

#1D array
a1 = np.array([1, 2, 3]) 

#2D array
a2 = np.array([ [11, 12], [21, 22] ])

#3D array
a3 = np.array([ [[111, 112], [121, 122]], [[211, 212], [221, 222]] ])

ผลลัพธ์

#a1
array([1, 2, 3])

#a2
array([[11, 12],
       [21, 22]])

#a3
array([[[111, 112],
        [121, 122]],

       [[211, 212],
        [221, 222]]])

ให้สังเกตจำนวนคู่ของ [ ] ที่ครอบทั้งหมดครับ มีกี่คู่ก็คือมิติของ array เป็นเท่านั้น

โดยปกติแล้ว ชนิดของข้อมูลใน NumPy Array จะเป็น int64 หรือ float64

นอกจากนี้แล้ว ในการสร้างเรายังสามารถระบุชนิดของข้อมูล ด้วย flag ชื่อ dtype เช่น
a1 = np.array([1,2,3], dtype=np.float16)

ก็จะได้ array ของ floating-point number (สังเกตเครื่องหมายจุดต่อท้ายตัวเลข)
[ 1.  2.  3.]

และเรายังสามารถใช้คำสั่งในการสร้าง Array พิเศษบางประเภท ที่จำเป็นต่อการประมวลผลภาพ เช่น
  • zeros((row,column,channel),dtype) สร้าง zero matrix
  • ones((row,column,channel),dtype) สร้าง unit matrix
  • identity(size,dtype) สร้าง identity matrix
  • eye(row,column,dtype) สร้าง 2D matrix ที่คล้ายกับ identity matrix แต่ไม่จำเป็นต้องเป็น square matrix
  • หมายเหตุ identity(size) จะเหมือนกับ eye(size)
  • random.rand(row,column) สร้าง uniform random matrix หรือ random.randn(row,column) สำหรับสร้าง normal/Gaussian random matrix
เช่น

import numpy as np

z = np.zeros((2,2), dtype=np.uint8)
o = np.ones((2,2), dtype=np.uint8)
i = np.identity(2, dtype=np.uint8)

print(z)
print(o)
print(i)

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

[[1 1]
 [1 1]]

[[1 0]
 [0 1]]

Numpy Array ที่สร้างขึ้นมา ก็จะมีคุณสมบัติ เช่น
  • ndarray.ndim มิติ
  • ndarray.shape (แถว, หลัก, จำนวนชั้น)
  • ndarray.size จำนวนสมาชิกของ Array ทั้งหมด
  • ndarray.dtype ชนิดของข้อมูลใน Array นั้น
ตัวอย่างเช่น

import numpy as np

z = np.zeros((3,4))

print(z.ndim)
print(z.shape)
print(z.size)
print(z.dtype)

จะได้ผลลัพธ์เป็น
2
(3, 4)
12
float64

ข้อมูลอ้างอิง
http://www.scipy.org/Tentative_NumPy_Tutorial
http://www.scipy.org/Numpy_Example_List

Tuesday, February 19, 2013

ทดสอบเวลาในการเข้าถึงพิกเซล

การประมวลผลแต่ละพิกเซลมักมีข้อจำกัดคือ ใช้เวลามากพอสมควร เราจะลองวัดเวลาที่ใช้ในแต่ละวิธีกัน โดยทดสอบกับการทำ image negative เหมือนกับตัวอย่างก่อนหน้านี้ บนรูปขนาด 640x480 พิกเซลครับ

วิธีที่ 1. เข้าถึงแต่ละพิกเซลของแต่ละ channel โดยตรง
#---------------------------------------------------------------------
import cv2
import numpy as np
import time

img = cv2.imread('01.JPG',cv2.CV_LOAD_IMAGE_COLOR)
rows,cols = img.shape[:2]

start = time.time()
#loop for every row, column and channel
for r in range(rows):
    for c in range(cols):
        for k in range(3):
            img[r,c,k] = 255-img[r,c,k] #Invert color

elapse = time.time()-start
print elapse
#---------------------------------------------------------------------
ใช้เวลา 4.236 วินาที โดยประมาณ

วิธีที่ 2. เข้าถึงแต่ละพิกเซลในทุก channel ทีเดียว
#---------------------------------------------------------------------
for r in range(rows):
    for c in range(cols):
        img[r,c] = 255-img[r,c]
#---------------------------------------------------------------------
ใช้เวลา 1.239 วินาที โดยประมาณ

วิธีที่ 3. ใช้ numpy ซึ่งเป็นโมดูลเกี่ยวกับการคำนวณของ Python (โดยปกติแล้ว OpenCV จะเก็บภาพอยู่ในรูปแบบของ numpy's array อยู่แล้ว) โดยเราสามารถใช้คำสั่ง img.item(r,c,channel) สำหรับอ่านค่า และคำสั่ง img.itemset(r,c,channel) สำหรับกำหนดค่าให้พิกเซล

#---------------------------------------------------------------------
for r in range(rows):
    for c in range(cols):
        for k in range(3):
            img.itemset((r,c,k),255-img.item(r,c,k))
#---------------------------------------------------------------------
ใช้เวลา 0.820 วินาที โดยประมาณ

วิธีที่ 4. ใช้คำสั่ง cv2.subtract() สำหรับการลบ
#---------------------------------------------------------------------
img = cv2.subtract(255*np.ones(img.shape, np.uint8),img)
#---------------------------------------------------------------------
ใช้เวลา 0.005 วินาที โดยประมาณ

วิธีที่ 5. ใช้ numpy ประมวลผลทั้งรูปโดยไม่ใช้ลูป
#---------------------------------------------------------------------
img = 255-img;
#---------------------------------------------------------------------
ใช้เวลา 0.002 วินาที โดยประมาณ

เวลาที่ใช้อาจแตกต่างกันในแต่ละเครื่องนะครับ และบางวิธีอาจจะใช้ได้เฉพาะงาน

แต่โดยสรุปแล้วถ้าเอาวิธีที่ 1 เป็นตัวเปรียบเทียบ นั่นคือ
ความเร็ววิธีที่ 1 = 1x
ความเร็ววิธีที่ 2 = 3.42x
ความเร็ววิธีที่ 3 = 5.16x
ความเร็ววิธีที่ 4 = 847x
ความเร็ววิธีที่ 5 = 2118x

สามารถศึกษาเพิ่มเติมได้ที่
  • http://opencvpython.blogspot.com/2012/06/fast-array-manipulation-in-numpy.html
  • http://www.bytefish.de/wiki/python/numpy/performance

การจัดการค่าพิกเซล และ กลับสีรูป (Image Negative)

การประมวลผลภาพระดับพิกเซลเป็นกระบวนการที่สำคัญมากอย่างหนึ่ง ในที่นี้เราจะลองทำการกลับสีภาพ (Image Negative) เพื่อให้ได้ผลลัพธ์ดังนี้


โค้ดเป็นแบบนี้ครับ
import cv2

#read image
img = cv2.imread('lena.jpg',cv2.CV_LOAD_IMAGE_UNCHANGED)
#get image's size
rows,cols = img.shape[:2]
cv2.imshow('Origin',img)

#loop for every row and column
for r in range(rows):
    for c in range(cols):
        img[r,c] = 255-img[r,c] #Invert color

cv2.imshow('Invert',img)
cv2.waitKey()
cv2.destroyAllWindows()

หมายเหตุ

  • เราสามารถเข้าถึงแต่ละพิกเซลโดยใช้คำสั่ง img[r,c] ซึ่งคือพิกเซลในทุก channel (BGR)
  • หากต้องการเข้าถึงพิกเซลใน channel ใดๆ เช่น สีน้ำเงิน สามารถใช้คำสั่ง img[r,c,0] ได้ หรือ img[r,c,1] สำหรับสีเขียว เป็นต้น
ดังนั้นส่วนของการประมวลผลลูปข้างต้น สามารถเขียนใหม่ได้ว่า
import cv2

#read image
img = cv2.imread('lena.jpg',cv2.CV_LOAD_IMAGE_UNCHANGED)
#get image's size
rows,cols = img.shape[:2]
cv2.imshow('Origin',img)

#loop for every row, column and channel
for r in range(rows):
    for c in range(cols):
        for k in range(3):
            img[r,c,k] = 255-img[r,c,k] #Invert color

cv2.imshow('Invert',img)
cv2.waitKey()
cv2.destroyAllWindows()

ซึ่งแน่นอนครับ จะประมวลผลช้าลงกว่าเดิมมาก

Monday, February 18, 2013

การแปลงสีภาพและบันทึกภาพ

ลองใช้คำสั่งง่ายๆในการแปลงภาพสี ให้เป็นสีเทาและบันทึกภาพครับ

import cv2
import sys

img = cv2.imread("lena.jpg",cv2.CV_LOAD_IMAGE_UNCHANGED)

#Check if image is loaded
if img==None:
    print "Image is not loaded."
    sys.exit()

#Convert image to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#Output
cv2.namedWindow("Original")
cv2.imshow("Original",img)
cv2.namedWindow("Gray")
cv2.imshow("Gray",gray)

#write output to disk
cv2.imwrite("gray.jpg",gray)
cv2.waitKey();
cv2.destroyAllWindows();