Tuesday, February 26, 2013

การหาขอบ (Edge detection)

การหาขอบของภาพ ถูกนำมาใช้เพื่อหลายวัตถุประสงค์ เช่น เพื่อ segment ภาพนั้นออกเป็นส่วนๆ หรือ เพื่อเป็นกระบวนการเบื้องต้นสำหรับการหาเส้นตรง หรือวงกลมในภาพ ฯลฯ ในที่นี้เราจะลองใช้คำสั่งสำเร็จรูปของ OpenCV เพื่อหาขอบภาพ ด้วยสามวิธี ได้แก่



Canny

import cv2
import numpy as np

img = cv2.imread("lena.jpg",cv2.CV_LOAD_IMAGE_GRAYSCALE)
#Appy Gaussian blur to remove some noises
inoise = cv2.GaussianBlur(img,(3,3),sigmaX=0)

#Canny edge detection
lowThresh = 50
upThresh = 100
out = cv2.Canny(inoise,lowThresh,upThresh)

cv2.imshow("Origin",img)
cv2.imshow("Edge Detection",out)
cv2.waitKey()
cv2.destroyAllWindows()


Sobel
#Gradient X
gradX = cv2.Sobel(inoise,cv2.CV_16S,dx=1,dy=0)
#Gradient Y
gradY = cv2.Sobel(inoise,cv2.CV_16S,dx=0,dy=1)
#Convert Gradients to 8 bits
gradX = cv2.convertScaleAbs(gradX)
gradY = cv2.convertScaleAbs(gradY)
#Total Gradient (approximate)
#grad = abs(gradX) + abs(gradY)
out = cv2.addWeighted(gradX,1,gradY,1,0)


Laplacian
out = cv2.Laplacian(inoise,ddepth=cv2.CV_16S,ksize=3)
#Need to set depth to 16-bit signed integer because Laplacian can give negative intensity.
# Convert it to 8-bit image with absolute value.
out = cv2.convertScaleAbs(out)

Monday, February 25, 2013

Filtering / Convolution

การทำ Filtering / Convolution ก็คือการเอา kernel / template / filter (หน้าต่างขนาดเล็กว่ารูป มักมีขนาดเป็นเลขคี่ เช่น 3x3, 5x5, 7x7 ฯลฯ) มาคูณกับพื้นที่ของรูปที่ kernel นั้นซ้อนทับอยู่ แล้วหาผลรวม จากนั้นก็นำไปแทนที่พิกเซลที่อยู่ตรงกับตำแหน่งกลางของ kernel นั้น

เราลองมาดูการทำ filtering ด้วย average filter เพื่อเบลอรูปกันครับ


import cv2
import numpy as np

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

ksize = 5   #kernel size
#create average kernel
kernel = np.ones((ksize,ksize))/(ksize*ksize)
ddepth = -1     #depth of output image, -1 is same as input image
out = cv2.filter2D(img,-1,kernel)
#same as: out = cv2.blur(img,(ksize,ksize))

cv2.imshow("Origin",img)
cv2.imshow("Result",out)
cv2.waitKey()
cv2.destroyAllWindows()

ถ้าต้องการเขียนโค้ดเอง สมมติว่าเป็นรูปสีเทา และคำนวณเฉพาะพิกเซลที่ตำแหน่งของ kernel ยังอยู่ในรูป
import cv2
import numpy as np

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

ksize = 5   #kernel size, odd value
#create average kernel
kernel = np.ones((ksize,ksize))/(ksize*ksize)

hksize = ksize%2    #half of kernel size
row,column = img.shape[:2]
out = img.copy()
for r in range(row-ksize+1):
    for c in range(column-ksize+1):
        temp = kernel*img[r:r+ksize,c:c+ksize]
        pix = temp.sum()
        out[r+hksize,c+hksize] = np.uint8(pix)

cv2.imshow("Origin",img)
cv2.imshow("Result",out)
cv2.waitKey()
cv2.destroyAllWindows()

ต้องระวังบรรทัด out[r+hksize,c+hksize] = np.uint8(pix) ให้ดีครับ เพราะถ้าไม่ใช่ average filter ในบรรทัดนี้อาจจะต้องทำการ clip/normalize ค่าพิกเซลให้อยู่ในช่วง 0-255 ด้วย

ถ้าเป็นรูปสี BGR ก็ต้องเพิ่มอีก 1 loop สำหรับ channel

hksize = ksize%2    #half of kernel size
row,column,channel = img.shape
out = img.copy()
for k in range(channel):
    for r in range(row-ksize+1):
        for c in range(column-ksize+1):
            temp = kernel*img[r:r+ksize,c:c+ksize,k]
            pix = temp.sum()
            out[r+hksize,c+hksize,k] = np.uint8(pix)

วิธีนี้จะใช้เวลาค่อนข้างมากครับ แต่ทำให้เราสามารถประยุกต์ใช้ kernel ตามที่เราต้องการได้

Thresholding การเปลี่ยนรูปเทาให้เป็นขาวดำ

ทำได้หลายวิธีครับ ในที่นี้เราจะใช้วิธีง่ายๆโดยการกำหนดค่า threshold ซึ่งถ้าค่าพิกเซลมากกว่าหรือเท่ากับค่านี้ก็จะเปลี่ยนให้เป็นสีขาว (255) มิฉะนั้นก็เปลี่ยนเป็นสีดำ (0)

import cv2

img = cv2.imread("lena.jpg",cv2.CV_LOAD_IMAGE_GRAYSCALE)
thres = 128
ret,bw = cv2.threshold(img,thres,255,cv2.THRESH_BINARY)

cv2.imshow("Original",img)
cv2.imshow("Binary",bw)

cv2.waitKey()
cv2.destroyAllWindows()

หรือจะใช้เทคนิคของ NumPy โดยเปลี่ยนโค้ดข้างต้นเป็น
import cv2

img = cv2.imread("lena.jpg",cv2.CV_LOAD_IMAGE_GRAYSCALE)
thres = 128
bw = 255*np.ones_like(img)
bw[img < thres] = 0

cv2.imshow("Original",img)
cv2.imshow("Binary",bw)

cv2.waitKey()
cv2.destroyAllWindows()

จะได้ผลลัพธ์เหมือนกันครับ

การแยกแต่ละ channel ของรูป

ภาพที่อ่านจาก OpenCV ด้วยคำสั่่ง cv2.imread() จะมีลักษณะตามปกติเป็น
  • NumPy Array
  • มี 1,2,3,4 channels
  • มีค่าพิกเซลเป็น uint8
  • รูปสีจะเป็น BGR
ในการเข้าถึงแต่ละพิกเซล ถ้าเป็นรูปขาวดำ หรือสีเทา (channel=1) เราทำได้โดย
img[row,column]

ถ้าเป็นรูปสี BGR (channel = 3) เราจะใช้
img[row,column,channel]

ซึ่งแตกต่างกับ array 3 มิติเล็กน้อยที่จะใช้ img[channel,row,column]

ถ้าเราต้องการเฉพาะ channel ใดๆ ก็สามารถแยกออกมาได้ เช่น
blue = img[...,0]            #same as img[:,:,0]
ตัวแปร blue ก็จะอ้างถึง channel สีน้ำเงินในรูป
หากเราเปลี่ยนค่าตัวแปร blue รูปต้นฉบับจะเปลี่ยนตาม

หากไม่ต้องการให้รูปต้นฉบับเปลี่ยน ต้องทำสำเนาไป เช่น
blue = img[...,0].copy()

ลองดูตัวอย่างครับ

import cv2
import numpy as np

img = cv2.imread('lena.jpg')

blue = img[...,0].copy()
green = img[...,1].copy()
red = img[...,2].copy()

cv2.imshow("Blue",blue)
cv2.imshow("Red",red)
cv2.imshow("Green",green)
cv2.waitKey()
cv2.destroyAllWindows()

หากต้องการให้เห็นแต่ละสีในสีนั้นๆ

ก็ปรับโค้ดเล็กน้อยครับ
import cv2
import numpy as np

img = cv2.imread('lena.jpg')

blue = np.zeros_like(img)
blue[...,0] = img[...,0].copy()
green = np.zeros_like(img)
green[...,1] = img[...,1].copy()
red = np.zeros_like(img)
red[...,2] = img[...,2].copy()

cv2.imshow("Blue",blue)
cv2.imshow("Red",red)
cv2.imshow("Green",green)
cv2.waitKey()
cv2.destroyAllWindows()

หรือ
import cv2
import numpy as np

img = cv2.imread('lena.jpg')

blue = img.copy()
blue[...,1:3] = 0
green = img.copy()
green[...,0:3:2] = 0
red = img.copy()
red[...,0:2] = 0

cv2.imshow("Blue",blue)
cv2.imshow("Red",red)
cv2.imshow("Green",green)
cv2.waitKey()
cv2.destroyAllWindows()

OpenCV ก็มีคำสั่งสำเร็จรูปสำหรับการแยก channel คือ คำสั่ง split() ลองดูตัวอย่างการใช้งานครับ
import cv2
import numpy as np

img = cv2.imread("lena.jpg")
out = cv2.split(img)

cv2.imshow("Blue",out[0])
cv2.imshow("Green",out[1])
cv2.imshow("Red",out[2])
cv2.waitKey()
cv2.destroyAllWindows()

ก็จะได้ผลลัพธ์เหมือนตัวอย่างแรกครับ

Brightness และ Contrast

เราลองมาปรับค่า brightness โดยการบวกเพิ่มค่าพิกเซล และค่า contrast โดยการคูณเพิ่มค่าพิกเซล ของรูปสีเทา ให้ได้ผลดังนี้ครับ


เราจะใช้คำสั่ง cv2.add() และ cv2.multiply() ซึ่งคำสั่งทั้งสองจะ clamp หรือ saturate พิกเซล (ถ้าค่าต่ำกว่า 0 ให้เป็น 0 และ ถ้าค่าสูงกว่า 255 ให้เป็น 255) โดยอัตโนมัติครับ

โค้ดประมาณนี้ครับ
import cv2

#read image as gray
img = cv2.imread("lena.jpg",cv2.CV_LOAD_IMAGE_GRAYSCALE)

#increase brightness
bri = cv2.add(img,100)
#increase contrast
con = cv2.multiply(img,1.5)

cv2.imshow("Original",img)
cv2.imshow("Brightness",bri)
cv2.imshow("Contrast",con)

cv2.waitKey()
cv2.destroyAllWindows()

ในกรณีที่เป็นรูปสี ที่มีหลาย channel ผมลองโค้ดเดิมแล้วผลลัพธ์ไม่ถูกต้องครับ มันบวกหรือคูณแค่ channel เดียว เลยพยามเปลี่ยนโค้ดให้เป็นเมตริกซ์บวกหรือคูณกัน (ซึ่งอาจจะมีวิธีที่ดีกว่า) ดังนี้ครับ
import cv2
import numpy as np

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

#brightness
a = 100*np.ones_like(img)
bri = cv2.add(img,a)

#contrast

b = np.zeros_like(img)
con = cv2.scaleAdd(img,1.5,b)   #1.5*img + 0


cv2.imshow("Original",img)
cv2.imshow("Brightness",bri)
cv2.imshow("Contrast",con)

cv2.waitKey()
cv2.destroyAllWindows()

ผลลัพธ์

อย่างไรก็ตาม เราสามารถใช้เทคนิคของ NumPy Array มาทำงานนี้ได้ ดังตัวอย่างต่อไปนี้ครับ ซึ่งใช้ได้กับทั้งรูปสีเทาและสีปกติ
import cv2
import numpy as np

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

#increase brightness
bri = img+100.0
#clip in range 0-255
bri = np.clip(bri,0,255)
#convert to uint8
bri = np.uint8(bri)

#increase contrast
con = img*1.5
con = np.clip(con,0,255)
con = np.uint8(con)

cv2.imshow("Original",img)
cv2.imshow("Brightness",bri)
cv2.imshow("Contrast",con)

cv2.waitKey()
cv2.destroyAllWindows()

สังเกตว่า bri = img+100.0 ตัวเลขที่เอาไปบวกมีทศนิยมด้วย เพื่อเปลี่ยนให้ค่า Array จาก uint8 เป็น float เนื่องจาก ถ้าใช้แค่ bri = img+100 ค่าพิกเซลที่เกิน 255 จะถูกทอนค่าลง เช่น 256 ก็จะกลายเป็น 1 (256%255) ทำให้ได้ค่าที่ไม่ถูกต้องครับ

NumPy Array เปรียบเทียบคำสั่งกับ Matlab

อ้างอิงจาก https://docs.scipy.org/doc/numpy-dev/user/numpy-for-matlab-users.html

Matlab --> Python: NumPy
help func --> help ("func")

ndims(a) --> ndim(a) or a.ndim

size(a) --> shape(a) or a.shape

size(a,n) --> a.shape[n-1]

[ 1 2 3; 4 5 6 ] --> array([[1.,2.,3.], [4.,5.,6.]])

[ 1 2 3; 4 5 6 ] --> vstack(([1,2,3],[4,5,6]))

a(end) --> a[-1]

a.' --> a.T or a.transpose()

a.*b --> a*b

a*b --> dot(a,b)

find(a>0.5) --> nonzero(a>0.5)

y=x --> y=x.copy()

y=x(2,:)--> y=x(2,:)

zeros(3,4)--> zeros((3,4))

ones(3,4)--> ones((3,4))

rand(3,4)--> random.rand(3,4)

repmat(a, m, n) --> tile(a, (m, n))

[a b] --> concatenate((a,b),1) or hstack((a,b)) or column_stack((a,b)) or c_[a,b]

[a; b] --> concatenate((a,b)) or vstack((a,b)) or r_[a,b]

Sunday, February 24, 2013

NumPy Array ตอนที่ 5 การเพิ่มสมาชิกและซ้อน Array

สามารถใช้คำสั่ง append() , concatenate(), vstack(), hstack(), dstack() เช่น

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

เรายังสามารถเลือกที่จะเพิ่มแถว หรือ คอลัมน์ได้ แต่ข้อมูลที่จะเพิ่มต้องเท่ากับจำนวนแถวหรือคอลัมน์นั้น

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

>>> b = np.append(a,[[5,6]],axis=0)
>>> b
array([[1, 2],
       [3, 4],
       [5, 6]])

>>> c = np.append(b,[[0],[0],[0]],axis=1)
>>> c
array([[1, 2, 0],
       [3, 4, 0],
       [5, 6, 0]])

คำสั่ง b = np.append(a,[[5,6]],axis=0) สามารถแทนด้วย
b = np.concatenate((a,[[5,6]])) หรือ
b = np.vstack((a,[[5,6]]))

สำหรับรูป มี แถว คอลัมน์ และ channel ถ้าจะให้ง่ายก็คือ
vstack เพิ่มแถว
hstack เพิ่มคอลัมน์
dstack เพิ่มความลึก

NumPy Array ตอนที่ 4 การเปลี่ยนขนาดของ Array

การเปลี่ยนขนาดของ Array
  • ทำให้เหลือ 1 มิติ
สามารถใช้ฟังก์ชัน ravel() เช่น

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

>>> b = a.ravel()
>>> b
array([1, 2, 3, 4])
  • เปลี่ยนเป็น 2 มิติ ขนาดใดๆ
ใช้คำสั่ง array.shape(row,column) เช่น
>>> b.shape = (2,2)
>>> b
array([[1, 2],
       [3, 4]])
หมายเหตุ ถ้าอยากละมิติใดๆไว้ให้ถูกคำนวณเอง ให้ใส่ค่า -1 เช่น
b.shape = (2,-1)
หรือ ใช้คำสั่ง reshape(row,column) เช่น
c = b.reshape(2,2)
หรือ ใช้คำสั่ง resize(row,column) แต่จะเป็นการเปลี่ยน array นั้นแทน เช่น
b.resize(2,2)

ถ้าต้องการเข้าถึงแต่ละสมาชิกของ array ขนาดใดๆ ในลักษณะของ 1 มิติ สามารถใช้คุณสมบัติ array.flat ได้ เช่น
>>> b
array([[1, 2],
       [3, 4]])
>>> for i in b.flat:
...     print i
...     
1
2
3
4

Friday, February 22, 2013

NumPy Array ตอนที่ 3 Index และการเรียกใช้สมาชิก

Indexing
การเข้าถึงสมาชิกของ Array จะทำผ่าน index โดยจะมีค่าเริ่มต้นเป็น 0 1 2 ไปตามลำดับ และสามารถอ้างอิงจากตัวสุดท้ายมาตัวแรก โดยใช้เลข -1 -2 -3 ไป

import numpy as np 
 
a = np.array([7, 9, 4])

a[0]
a[-1]

ผลลัพธ์
7
4

นอกจากนี้ ยังสามารถใช้เครื่องหมาย : เพื่อระบุช่วงของ index ได้ในรูปแบบ
array[start:end:step] โดยผลลัพธ์จะไม่นับค่า end นะครับ เช่น

a[0:2:1]

ผลลัพธ์ 
[7, 9]

ถ้าใส่แค่ค่า end ค่า start จะเป็น 0 และค่า step จะเป็น 1 เช่น

a[:2]

ผลลัพธ์  
[7, 9]

ถ้าไม่ใส่ค่า end ก็จะทำถึงตัวสุดท้าย เช่น

a[0::2]ผลลัพธ์ 
[7, 4]

นั่นคือ
a[:]
ผลลัพธ์ 
[7, 9, 4]

เราสามารถเปลี่ยนค่าสมาชิกหลายๆตัวพร้อมกันได้ โดยใช้หลักการนี้ เช่น

a[1:3] = 0
จะได้
[7, 0, 0]

และสามารถแสดงผลสมาชิกของ array ด้วยคำสั่ง for และ in

for i in a:
      print i**2

จะได้  
49
0
0

สำหรับ array ตั้งแต่สองมิติเป็นต้นไป index ในแต่ละมิติก็จะแยกกันด้วยเครื่องหมาย , เช่น

b = np.array([ ["00","01"], ["10","11"], ["20","21"] ])

[['00', '01'],
['10', '11'],
['20', '21']]

b.shape
(3, 2)

ถ้าต้องการแถวใดๆ

b[1,:]                          # same as b[1]
['10', '11']

ถ้าต้องการหลักใดๆ

b[:,1]
['01', '11', '21']
กรณีที่เป็น array สามมิติ (ซึ่งก็คือ array ของ array สองมิติ)
c = np.array([ [["000","001"],["010","011"],["020","021"]] , [["100","101"],["110","111"],["120","121"]]])

[ [['000', '001'],
    ['010', '011'],
    ['020', '021']],

   [['100', '101'],
    ['110', '111'],
    ['120', '121']] ]

จะกลายเป็นว่า มิติที่ 3 กลายเป็นหลักแรกสุด ตามมาด้วยแถวและหลัก นั่นคือ
c[0,0,1] จะได้ค่า 001
และ

c[0, :, :]
[['000', '001'],
 ['010', '011'],
 ['020', '021']]

นอกจากนี้แล้ว ยังสามารถใช้เครื่องหมาย ... แทนทุกมิติที่ไม่ได้ระบุไว้ เช่น
c[0,...] ก็จะได้ผลลัพธ์เหมือน c[0,:,:]

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

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

มาดูความง่ายของ Python+OpenCV ในการการอ่านภาพและแสดงผลภาพกันครับ


สมมติว่าเรามีไฟล์รูป lena.jpg ที่เดียวกับโค้ดของเราครับ

ก่อนอื่น ถ้าเราใช้ PyScripter เพื่อเขียนโค้ด Python เราอาจจะกำหนดให้ตัว IDE นี้รู้จักโค้ดของ OpenCV โดยกำหนดดังนี้

เลือกเมนู Tools / Options / IDE Options ... แล้วพิมพ์เพิ่มตรง Special packages ว่า ",cv2" ดังรูป

จากนั้นก็เขียนโค้ดแบบนี้ได้เลยครับ
#import OpenCV
import cv2

#Read image file
img = cv2.imread("lena.jpg",cv2.CV_LOAD_IMAGE_UNCHANGED)

#Check if image is loaded
if img==None:
    print "Image is not loaded."
else:
    #create window for output
    cv2.namedWindow("Image Output")
    #show image in this window
    cv2.imshow("Image Output",img)
    #wait for user to press any key
    cv2.waitKey();
    #close all windows
    cv2.destroyAllWindows();

หมายเหตุ
คำสั่ง cv2.imread สามารถกำหนดรูปแบบการอ่านรูปได้ 3 แบบคือ

  • CV_LOAD_IMAGE_UNCHANGED (น้อยกว่า 0) อ่านรูปตามต้นฉบับ รวมถึง alpha channel ด้วยถ้ามี
  • CV_LOAD_IMAGE_GRAYSCALE (0) อ่านรูปให้เป็นสีเทา
  • CV_LOAD_IMAGE_COLOR (มากกว่า 0) อ่านรูปให้เป็น RGB format

OpenCV กับ Python

เทคโนโลยีเปลี่ยนไปเร็วมากครับ OpenCV ก็พัฒนาไปมากเช่นกัน จนขณะนี้กำลังจะก้าวเข้าสู่รุ่น 2.4.4 แล้วครับ ตอนนี้ OpenCV ก็สนับสนุนหลากหลายแพลตฟอร์ม และหลายภาษา ทั้ง C/C++/Java/Python ในบทความนี้เราจะลองมาใช้ OpenCV กับ Python ซึ่งเป็นภาษาที่กำลังได้รับความนิยมสูง เนื่องจากความง่ายในการใช้งานและประสิทธิภาพ โดยลองใช้งานบนวินโดวส์ครับ

วิธีการติดตั้ง
  1. ดาวน์โหลด Python (แนะนำว่าลองเวอร์ชัน 2.7) จาก http://www.python.org หรือ http://portablepython.com/wiki/PortablePython2.7.3.2 (ตัวนี้เป็น portable ไม่ต้องติดตั้งและมี library มาให้มากมาย แนะนำให้ใช้ครับ)
  2. ติดตั้งหรือแตกไฟล์ Python ให้เรียบร้อย สมมติว่าเป็นที่ c:\python หรือ c:\Portable Python 2.7.3.1
  3. ดาวน์โหลด OpenCV จาก http://opencv.org/ สำหรับ windows ก็จะเป็นไฟล์เช่น OpenCV-2.4.4-beta.exe
  4. ใช้โปรแกรมบีบอัดไฟล์เช่น 7-zip เปิดไฟล์ที่ดาวน์โหลดมา
  5. แตกไฟล์ที่อยู่ในโฟลเดอร์ \opencv\build\python\2.7\ ไฟล์ชื่อ cv2.pyd (อาจแตกโฟลเดอร์ตัวอย่าง ชื่อ \opencv\samples\python2\ ด้วย)
  6. สำเนาไฟล์ cv2.pyd ดังกล่าวไปไว้ที่ c:\python\Lib\site-packages หรือ c:\Portable Python 2.7.3.1\App\Lib\site-packages
  7. เสร็จแล้วครับ ง่ายมากมาย
วิธีการใช้งาน
ถ้าเป็นเวอร์ชัน portable สามารถใช้งานโดยเรียกใช้ไฟล์ PyScripter-Portable.exe ซึ่งเป็น IDE สำหรับเขียนโค้ด Python ที่มีมาให้แล้ว


ในบทความถัดไปเราจะลองเริ่มเขียนโค้ดกันครับ

เอกสารอ้างอิงหลัก (สำหรับหัวข้อนี้และถัดไป)

  • opencv.org
  • opencvpython.blogspot.com