1. keymap.py ที่จะทำหน้าที่เปิดหน้าเว็บ และรับข้อมูล input จาก kinect มา map กับปุ่มของ keyboard เพื่อสั่งการ
2. kinect_hand.py ทำหน้าที่รับข้อมูลจากกล้อง kinect มาทำการประมวลผลและตรวจสอบว่าแต่ละ blob เป็นมือหรือไม่ และมีการเคลื่อนไหวอย่างไร เพื่อส่งผลที่ได้ไปยัง keymap
3. index.html เป็นไฟล์หน้าเว็บที่เอาไว้แสดงบน smart mirror
โดยการทำงานเริ่มแรกนั้นจะเริ่มจาก keymap.py
- ขั้นแรกจะทำการ import library สำคัญๆ ทั้ง os, pynput.keyboard, time, threading รวมไปถึง kinect_hand.py เพื่อใช้ฟังค์ชันภายในไฟล์
- เป็นการทำงานโดยแบ่ง Thread ออกเป็น 2 ตัว คือ Thread ของเว็บ และ Thread ของการทำ keyboard mapping
- Thread ของเว็บจะเริ่มทำงานก่อนโดยทำการเปิดหน้าเว็บจากไฟล์ index.html โดยใช้ฟังค์ชัน webbrowser.open
- Thread ของการทำ keyboard mapping จะเริ่มทำงานต่อจากการเปิดเว็บ
- สร้างตัวแปรชื่อ keyboard ทำหน้าที่เป็น controller เพื่อสั่งการเหมือนการกดปุ่มบน keyboard
- ใช้ os.system("sleep 5") เพื่อให้รอ 5 วินาทีก่อนทำงานขั้นถัดไป
- keyboard.press(Key.f11) และ keyboard.release(Key.f11) ใช้แทนการกดและปล่อยปุ่ม f11 เพื่อให้หน้าจอเข้าสู่โหมด fullscreen
- กำหนดค่า fps เริ่มต้นเป็น 0
- ให้ last gesture หรือท่าการเคลื่อนไหวสุดท้ายเป็น "undefined action" หรือการเคลื่อนไหวที่ระบุไม่ได้
- เก็บค่า t0 เป็นเวลาเริ่มต้นจาก time.clock() จากนั้นจึงเข้าสู่ Loop
- ตัวแปร input จะทำหน้าที่เสมือนกับไฟล์ kinect_hand โดยในที่นี้จะเรียกฟังค์ชัน get_input() เพื่อทำการเก็บข้อมูลจากกล้อง Kinect
ขั้นต่อมาจะอยู่ในส่วนการทำงานของ kinect_hand.py
- ก่อนจะทำงานในส่วนของฟังค์ชัน get_input() ในไฟล์จะทำการ import library สำหรับทำการคำนวณและจัดการเกี่ยวกับ image processing โดยเฉพาะเช่น numpy, openCV2, math, pygame, time และ freenect สำหรับรับข้อมูลจากกล้อง Kinect
- ทำการประกาศตัวแปรหลักๆคือ xsize, ysize, blobs สำหรับเก็บข้อมูล, buffer_size = 3, blobs_movement ที่เก็บค่าในรูปของ dict หรือ {} ส่วน blob_buffer และ old_id จะมีลักษณะเป็น
[[]] * buffer_size ซึ่งจะได้ array 1 ตัว ที่ภายในบรรจุ array ไว้ค่าของ buffer_size ซึ่งในที่นี้คือ 3 โดยมีหน้าตาดังนี้ - ในฟังค์ชัน get_input() จะทำการเรียกใช้ xsize และ ysize แบบ pass by reference และส่งค่าที่เรียกมาไปฟังค์ชัน pygame_refresh(xsize ,ysize)
- ในฟังค์ชัน pygame_refresh() จะทำการเรียกใช้ตัวแปรอื่นๆแบบ pass by reference คือ blobs, blob_buffer, old_id และ blobs_movement โดยจะทำการล้างค่าใน blobs ทุกครั้งเพื่อเตรียมรับค่าใหม่ ( blobs = [] )
- ต่อมาจะทำการเรียกฟังค์ชัน update_old_id() โดยเรียกใช้ old_id มาและนำเข้า Loop แรกซึ่งวนตามจำนวน buffer_size คือ 3 และทำการล้างค่าใน array old_id ที่ i ก่อนเข้า Loop ที่ 2 ที่วนตามความกว้างของ blobs_buffer ตัวที่ i จากนั้นทำการนำ id ของ blobs_buffer ชุดที่ i ตัวที่ j
( blobs_buffer[i][j].id ) ไปใส่ใน old_id ตัวที่ i โดยการทำงานคร่าวๆจะมีลักษณะและผลคล้ายรูปดังนี้ - ต่อไปจะเรียกใช้ฟังค์ชัน get_contour เพื่อเก็บค่าไว้ในตัวแปร cs โดยส่ง xsize และ ysize ไปด้วย
- ใน get_contour จะทำงานเริ่มจากไปนำข้อมูลจากกล้อง Kinect จากคำสั่ง sync_get_depth() มาเก็บไว้ในตัวแปร depth
- แปลงข้อมูลให้เป็น float32 จากคำสั่ง astype(np.float32)
- กลับภาพตามแนวแกน y โดยใช้คำสั่ง cv2.flip(depth, 1) ถ้าเปลี่ยนจาก 1 เป็น 0 จะเป็นหมุนตามแนวแกน x ถ้าเป็น -1 จะหมุนทั้ง 2 แกน
- ทำการปรับขนาดของภาพตามค่า xsize และ ysize จาก cv2.resize(depth,(xsize,ysize))
- ทำการเบอลภาพและลบ gaussian noise ในภาพโดยใช้คำสั่ง cv2.GaussianBlur(depth, (5,5), 0) โดย (5,5) คือความกว้างและความสูงของ Kernel size ตามลำดับ และ 0 คือค่าเบี่ยงเบนมาตรฐานของ gaussian kernel ซึ่งจะมีทั้งของแกน X และ Y หากให้ Y เป็น 0 ค่าของ X จะเป็น 0 เช่นกัน และคำนวณโดยใช้ Kernel size เป็นเกณฑ์แทน โดยภาพซ้ายและขวาจะเป็นภาพก่อนและหลังทำ gaussian blur ตามลำดับ
- ทำการตัดขอบของวัตถุในภาพเพื่อลบ Noise โดยใช้คำสั่ง cv2.erode(depth, None, iterations=1) โดย None คือไม่ใส่ kernel ในการคำนวณและทำซ้ำเพียง 1 รอบ ( iterations=1 )
- ทำการเพิ่มขนาดของวัตถุที่ถูกตัดขอบไปและเพิ่มจุดที่เสียหายจาก Noise โดยใช้คำสั่ง cv2.dilate(depth, None, iterations=1) ซึ่งภาพที่ได้จะมีลักษณะดังนี้ ตั้งแต่ก่อนทำ erode หลังทำ erode และทำ dilate ตามลำดับ
- หาค่าความลึกของมือที่ต่ำที่สุด ( min_hand_depth ) โดยใช้คำสั่ง np.amin โดยจะดึงค่าที่น้อยที่สุดใน array ออกมาลบด้วย 10
- หาค่า max_hand_depth โดยเอา min_hand_depth มาบวกกับค่า hand_depth ซึ่งกำหนดไว้ที่ 80
- จากนั้นจะทำการเช็คเงื่อนไข หาก max_hand_depth มีค่ามากกว่า 700 จะกำหนดให้ค่า max_hand_depth = 700
- จากนั้นจะทำการแปลงข้อมูลโดยใช้คำสั่ง cv2.threshold(depth, max_hand_depth, min_hand_depth, cv2.THRESH_BINARY_INV) เพื่อเปลี่ยนให้ค่าที่น้อยกว่า max_hand_depth หรือ Threshold value ให้กลายเป็นค่าสีที่เรากำหนดหรือค่า min_hand_depth ซึ่งใช้ THRESH_BINARY_INV
การทำ THRESH_BINARY_INV คือการ invert ข้อมูลโดยเปลี่ยนให้ค่าที่มากกว่า Threshold value ให้กลายเป็น 0 หรือสีดำทั้งหมด ส่วนค่าที่น้อยกว่า Threshold value จะเปลี่ยนเป็นค่าสีที่เรากำหนดซึ่งอยู่ในช่วง [ 0 - 255 ] สามารถดูเปรียนเทียบกับแบบปกติได้ดังนี้ ( ในรูปให้ค่า Threshold อยู่กึ่งกลางพอดี )
- ทำการแปลงค่าที่ได้หลังจากทำ Threshold ให้อยู่ในช่วงข้อมูล 8-bit [ 0 - 255] โดยใช้คำสั่ง
cv2.convertScaleAbs(BW) - นำค่า Threshold ไปทำการหาเส้นขอบของภาพหรือ Contour ของภาพจากคำสั่ง
cv2.findContours(BW,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) โดย RETR_TREE เป็น mode ที่ทำการเก็บข้อมูลทั้งหมดมาทำเป็น contour และ CHAIN_APPROX_SIMPLE คือ method ที่จะเอาเฉพาะจุด End Point จากข้อมูล contour ทั้งหมดเช่น มุมของภาพดังรูป
- ข้อมูลที่ทำ contour จะเก็บไว้ในตัวแปร cs และสร้าง array cs_f เพื่อเตรียมขั้นตอนสุดท้าย
- นำข้อมูล cs เข้า Loop โดยวนตามจำนวนข้อมูลใน cs และเช็คเงื่อนไขว่า ข้อมูล cs ตัวที่ i ตัวใดมีค่า contour area มากกว่า 500 ให้บรรจุเข้าไปใน array cs_f จากโค้ดด้านล่าง
- ลบข้อมูลใน array depth และ BW ทั้งหมดจากคำสั่ง depth,BW และ return array cs_f ไปยังตัวแปร cs ที่เรียกในฟังค์ชัน pygame_refresh
>>> Continue Part 2
แหล่งอ้างอิง
contour : https://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html
https://docs.opencv.org/3.3.1/d4/d73/tutorial_py_contours_begin.html
convertScaleAbs : https://docs.opencv.org/2.4/modules/core/doc/operations_on_arrays.html
Threshold :
https://medium.com/@a5730051/threshold-opencv-%E0%B8%84%E0%B8%B7%E0%B8%AD%E0%B8%AD%E0%B8%B0%E0%B9%84%E0%B8%A3-768214f155eb
https://skconann.blogspot.com/2018/02/image-simple-thresholding.html
Erosion & Dilation :
https://wiki.morange.co.th/Morphological_Transformations
GaussianBlur :
contour : https://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html
https://docs.opencv.org/3.3.1/d4/d73/tutorial_py_contours_begin.html
convertScaleAbs : https://docs.opencv.org/2.4/modules/core/doc/operations_on_arrays.html
Threshold :
https://medium.com/@a5730051/threshold-opencv-%E0%B8%84%E0%B8%B7%E0%B8%AD%E0%B8%AD%E0%B8%B0%E0%B9%84%E0%B8%A3-768214f155eb
https://skconann.blogspot.com/2018/02/image-simple-thresholding.html
Erosion & Dilation :
https://wiki.morange.co.th/Morphological_Transformations
GaussianBlur :
https://docs.opencv.org/3.1.0/d4/d13/tutorial_py_filtering.html
Flip :
https://docs.opencv.org/3.4.2/d2/de8/group__core__array.html#gaca7be533e3dac7feb70fc60635adf441
โค้ดส่วนที่ใช้งาน
kinect_hand.py
def get_contours(xsize,ysize):
(depth,_) = get_depth()
depth = depth.astype(np.float32)
depth = cv2.flip(depth, 1)
depth = cv2.resize(depth,(xsize,ysize))
depth = cv2.GaussianBlur(depth, (5,5), 0)
depth = cv2.erode(depth, None, iterations=1)
depth = cv2.dilate(depth, None, iterations=1)
min_hand_depth = np.amin(depth)-10
hand_depth = 80
max_hand_depth = min_hand_depth + hand_depth
if max_hand_depth > 700 :
max_hand_depth = 700
(_,BW) = cv2.threshold(depth, max_hand_depth, min_hand_depth, cv2.THRESH_BINARY_INV)
BW = cv2.convertScaleAbs(BW)
#BW = cv2.resize(BW,(xsize,ysize))
cs,_ = cv2.findContours(BW,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cs_f = []
for i in range(len(cs)):
if cv2.contourArea(cs[i]) > 500:
cs_f.append(cs[i])
del depth,BW
return cs_f
blobs = []
buffer_size = 3
blobs_buffer = [[]] * buffer_size
old_id = [[]] * buffer_size
blobs_movement = {}
def update_old_id():
global old_id
for i in range(buffer_size):
old_id[i] = []
for j in range(len(blobs_buffer[i])):
old_id[i].append(blobs_buffer[i][j].id)
def pygame_refresh(xsize,ysize):
global blobs
blobs = []
global blobs_buffer
global old_id
global blobs_movement
update_old_id()
cs = get_contours(xsize,ysize)
for i in range(len(cs)):
blob = BlobAnalysis(cs[i])
blob = blobs_track(blob,i,0)
blobs.append(blob)
for i in range(buffer_size):
if i == 0:
blobs_buffer[i] = blobs
else:
blobs_buffer[i] = blobs_buffer[i-1]
return 1
xsize,ysize = 280,210
def get_input():
global xsize,ysize
pygame_refresh(xsize,ysize)
Flip :
https://docs.opencv.org/3.4.2/d2/de8/group__core__array.html#gaca7be533e3dac7feb70fc60635adf441
โค้ดส่วนที่ใช้งาน
kinect_hand.py
def get_contours(xsize,ysize):
(depth,_) = get_depth()
depth = depth.astype(np.float32)
depth = cv2.flip(depth, 1)
depth = cv2.resize(depth,(xsize,ysize))
depth = cv2.GaussianBlur(depth, (5,5), 0)
depth = cv2.erode(depth, None, iterations=1)
depth = cv2.dilate(depth, None, iterations=1)
min_hand_depth = np.amin(depth)-10
hand_depth = 80
max_hand_depth = min_hand_depth + hand_depth
if max_hand_depth > 700 :
max_hand_depth = 700
(_,BW) = cv2.threshold(depth, max_hand_depth, min_hand_depth, cv2.THRESH_BINARY_INV)
BW = cv2.convertScaleAbs(BW)
#BW = cv2.resize(BW,(xsize,ysize))
cs,_ = cv2.findContours(BW,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cs_f = []
for i in range(len(cs)):
if cv2.contourArea(cs[i]) > 500:
cs_f.append(cs[i])
del depth,BW
return cs_f
blobs = []
buffer_size = 3
blobs_buffer = [[]] * buffer_size
old_id = [[]] * buffer_size
blobs_movement = {}
def update_old_id():
global old_id
for i in range(buffer_size):
old_id[i] = []
for j in range(len(blobs_buffer[i])):
old_id[i].append(blobs_buffer[i][j].id)
def pygame_refresh(xsize,ysize):
global blobs
blobs = []
global blobs_buffer
global old_id
global blobs_movement
update_old_id()
cs = get_contours(xsize,ysize)
for i in range(len(cs)):
blob = BlobAnalysis(cs[i])
blob = blobs_track(blob,i,0)
blobs.append(blob)
for i in range(buffer_size):
if i == 0:
blobs_buffer[i] = blobs
else:
blobs_buffer[i] = blobs_buffer[i-1]
return 1
xsize,ysize = 280,210
def get_input():
global xsize,ysize
pygame_refresh(xsize,ysize)
ไม่มีความคิดเห็น:
แสดงความคิดเห็น