วันจันทร์ที่ 17 ธันวาคม พ.ศ. 2561

การหาข้อมูลการทำ Feature เพิ่มเติมของ Smart Mirror

ได้ทำการหาข้อมูลเพิ่มเติมเกี่ยวกับ Feature ที่น่าสนใจสำหรับการทำ Smart Mirror โดยตัวอย่างด้านล่างจะเป็นการใช้คำสั่งเสียงและตรวจจับการเคลื่อนไหวโดยใช้ Ultrasonic sensor ในการควบคุมการทำงาน โดยมี Feature ต่างๆเช่น ดูรูปภาพ, เปิดเพลง, รับคำสั่งเสียงโดยใช้การปรบมือเป็น Wake Command ก่อนพูด หรือแม้กระทั่งใช้การเลื่อนมือเพื่อเปลี่ยน content ที่ดูขณะนั้นหรือใช้เล่นเกมได้ โดยทั้งหมดทำงานอยู่บนบอร์ด Raspberry Pi 3




สิ่งที่น่าสนใจในการเพิ่มคือ 
  • ใช้ทั้งคำสั่งเสียงและท่าทางได้
  • สามารถค้นหาคลิปได้โดยไม่ใช่การฝังคลิป
  • รองรับการทำงานหรือสั่งการผ่าน Smart Phone
อุปกรณ์ที่ต้องใช้เพิ่มเติม
  • ลำโพง
  • ไมค์โครโฟน
Url ข้อมูลอ้างอิง https://imgur.com/gallery/j0BFU

วันอังคารที่ 13 พฤศจิกายน พ.ศ. 2561

ทดสอบการทำ Hand Detection จากการใช้ contourArea

จากปัญหาที่ว่าพบ Convexity Defects 4 จุดภายใน Convex Hull ที่มีขนาดใหญ่ที่ไม่ใช่มือแต่ถูกตรวจจับว่าเป็นมือจากเงื่อนไขที่กำหนด จึงได้เพิ่มเติมเงื่อนไขเพื่อทดสอบการตรวจจับ


จากรูปจะเป็นการใส่เงื่อนไขการดักโดยใช้ค่า ContourArea หรือพื้นที่ของ Contour เพื่อกันกรณีที่มีภาพขนาดใหญ่เข้ามาและถูกนับว่าเป็นมือจาก Convexity Defects โดยขนาดพื้นที่ของ Contour ที่เป็นมือจะมีขนาดประมาณ 1400-1800 โดยจะดักไว้ที่ 2000 โดยได้ผลลัพธ์ดังนี้






เพิ่มเติมส่วนที่แสดงจุดของ Centroid ที่เป็นศูนย์กลางของวัตถุ

วันอาทิตย์ที่ 4 พฤศจิกายน พ.ศ. 2561

ทดสอบการทำ Convexity Defects เพื่อนับช่องว่างระหว่างนิ้ว

หลังจากทดสอบการทำงานในส่วนของการหาส่วนบกพร่องการนูนหรือ Convexity Defects โดยผลที่ได้มีลักษณะดังนี้


จากภาพจะเห็นได้ว่า ส่วนพื้นที่เว้าใน Convex Hull ที่มีมุมมากกว่า 90 องศาจะไม่นับ จึงเหลือเพียงบริเวณร่องนิ้วทั้ง 4 ที่สามารถตรวจพบได้


จากภาพจะพบว่าการตรวจจับจะการตรวจจับเฉพาะจุดที่เกิด Convex Hull เท่านั้น

จะเห็นได้ว่าการทดสอบยืนหน้ากล้องและการกางแขนดูให้อยู่ใน Convex Hull ทำให้พบว่าหากภายในมีจุดที่มีมุมน้อยกว่า 90 ครบ 4 จุดก็จะตรวจพบเป็นมือด้วย จึงคิดว่าจะทำการตรวจสอบขนาดของ Convex Hull ที่ใหญ่กว่าผิดปกติเพื่อตัดออกไม่ให้นับเป็นมือ

วันจันทร์ที่ 29 ตุลาคม พ.ศ. 2561

ข้อมูลอุปกรณ์ : Two way mirror

Mirror

    
mirror size

Two way mirror

    เป็นกระจกชนิดพิเศษ ที่จะเห็นใช้กันในห้องชี้ตัวผู้ต้องหา คือ เมื่อมองจากด้านหน้ามันจะเหมือนกระจกเงาธรรมดา แต่ถ้ามองจากด้านหลังจะสามารถมองทะลุไปเห็นอีกด้านได้

วิธีตรวจสอบ

Professional Plastics : เว็บที่ขาย Two way Mirror

   

ราคาของ two way mirror ขนาด 32 Inch x 18 Inch


ถ้าแปลงเป็นเงินบาทจะได้
98 $ x 33 = 3234 ฿

วันอังคารที่ 23 ตุลาคม พ.ศ. 2561

ทดสอบความเร็วการทำงานจากการอ่านค่า FPS

ทำการทดสอบว่าในการรับภาพจากกล้องนั้น มีความเร็วในการตรวจจับสูงหรือต่ำต่างกันแค่ไหน โดยทดสอบจากการจับเวลาก่อนดึงข้อมูลจากกล้องจนถึงช่วงที่ทำการคืนค่าเพื่อนำมาใช้แสดงผล และคำนวณโดยใช้สูตร 1/(เวลาล่าสุด - เวลาเริ่มต้น) โดยมีผลดังนี้



ค่า FPS ที่ได้จาก Raspberry Pi จะเห็นได้ว่า มีช่วงที่ FPS ตกไปบ้างเป็นบางช่วง และค่า FPS ที่ได้จะอยู่ในช่วงประมาณ 10-30 FPS




ค่า FPS ที่ได้จากการทำงานบน Window จะพบว่ามีค่า FPS จะอยู่ในช่วงที่ค่อนข้างสูง และค่า FPS ที่ได้จะอยู่ในช่วงประมาณ 60-90 FPS




สุดท้ายจะเป็นค่า FPS ที่ได้จากการทดลองการทำงานบน Ubuntu จะพบว่ามีค่า FPS ที่ค่อนข้างไล่เลี่ยกัน คือในช่วง 40 FPS
ซึ่งผลของการทำงานที่ได้มานั้นหลักๆจะขึ้นอยู่กับความเร็วในการทำงานของ Hardware ด้วยซึ่งเครื่องที่ลง Ubuntu ใช้ Intel Core i7-3632QM (2.20 - 3.20 GHz) RAM 8 GB และเครื่องที่เป็น Window ใช้ Intel Core i7-7700HQ (2.80 – 3.80 GHz) RAM 8 GB

วันเสาร์ที่ 13 ตุลาคม พ.ศ. 2561

หลักการทำงานของโค้ด Smart Mirror & Hand Detection ( Part 3 การติดตามมือ)


blobs_buffer[n] == [] case
1.จะเช็ค array ที่ถูกเก็บอยู่ใน blobs_buffer ทุกตัว คือตั้งแต่ blobs_buffer[1], blobs_buffer[2], blobs_buffer[3]
2.เมื่อเช็คจนครบทุกตัวแล้ว n + 1 > buffer_size จากนั้น จะ set id ให้กับ blob และ นำ [blob.centroid, blob.centroid] ไปเก็บใส่ไว้ใน blobs_movement ที่ตำแหน่ง i ( ตำแหน่งของตัวใน array จะเท่ากับ id ของ blob)


(น่าจะเป็นช่วงที่ มีเฟรม 2 ผ่านเข้ามา และต้องเอาไปใช้เทียบกับเฟรม 1)
blobs_buffer[n] != [] case ( blobs_buffer[n] มีค่า)
1.วนลูปเช็คหาว่า blob ที่ได้มากับ blobs_buffer[n][j] เป็น blob อันเดียวกันมั้ย //เช็คว่า blob ของ frame ก่อนหน้า กับ frame ปัจจุบัน ว่าเป็นอันเดียวกันหรือไม่
2.ถ้าเป็น blob เดียวกัน ให้เอา id จาก blobs_buffer[n][j] มาเก็บใน new_id และตั้งตัวแปร exist = false
3.วนลูปเช็คว่า id ของ blob ใน blobs มีค่าซ้ำกับ new_id หรือไม่ ถ้ามันซ้ำกัน จะเปลี่ยนค่า exist เป็น True
4.ถ้า exist = true
4.1 ถ้า n + 1 < buffer_size จะให้ทำ blobs_track(blob, i, n+1)
4.2 ถ้า n + 1 > buffer_size จะให้
  • new_id = 1
  • วนลูปหา id ที่มากที่สุดจาก old_id + 1 ที่มากกว่า new_id และเปลี่ยนค่า new_id เป็น max(old_id) + 1
  • set id ให้ blob เป็น new_id
  • ใส่ new_id ไปใน old_id[0]
  • นำ [blob.centroid, blob.centroid] ไปเก็บใส่ไว้ใน blobs_movement ที่ตำแหน่ง new_id
5.ถ้า exist = False
5.1 set id ให้กับ blob เป็น new_id
5.2 เช็คว่า blobs_buffer[n][j] มีลักษณะเป็นมือหรือไม่ ถ้าเป็น  ให้ set blob เป็นมือ
5.3 ใส่ blob.centroid ลงใน blobs_movement ที่ตำแหน่ง new_id
5.4 ให้ blobs_movement ที่ตำแหน่ง new_id เป็น blobs_movement 20 ตำแหน่งท้ายสุด
6. ถ้าเจอ blob ที่ไม่ใช่ id เป็น -1 ให้ออกจากลูป

7. ถ้า id ของ blob เป็น -1
7.1 ถ้า n + 1  < buffer_size ให้ทำ blobs_track(blob, i, n + 1) // หา id ของ blob ที่ไม่เท่ากับ -1
7.2 ถ้า n + 1  >= buffer_size ให้ทำ

  • new_id = 1
  • วนลูปหา id ที่มากที่สุดจาก old_id + 1 ที่มากกว่า new_id และเปลี่ยนค่า new_id เป็น max(old_id) + 1
  • set id ให้ blob เป็น new_id
  • ใส่ new_id ไปใน old_id[0]
  • นำ [blob.centroid, blob.centroid] ไปเก็บใส่ไว้ใน blobs_movement ที่ตำแหน่ง new_id

วันพฤหัสบดีที่ 11 ตุลาคม พ.ศ. 2561

หลักการทำงานของโค้ด Smart Mirror & Hand Detection ( Part 2 ส่วนเค่าและตรวจสอบว่าเป็นมือหรือไม่ )

ต่อจาก Part ที่แล้วในส่วนถัดไปจะเป็นการทำงานของ BlobAnalysis โดยมีการทำงานดังนี้

  • ทำการวน Loop ตามความยาวของ array cs
  • ส่งค่า array cs ชุดที่ i ไปที่ class BlobAnalysis เพื่อเตรียมการคำนวณ


  • ในที่นี้ cs[i] ที่ส่งเข้ามาจะถูกเก็บในตัวแปรชื่อ contour ซึ่ง self.contour ก็คือการเก็บค่า contour ไว้ใช้ใน class 
  • self.contour_s คือตัวแปรที่เก็บค่า contour ซึ่งถูกปรับการเรียงเป็นแนวตั้ง และ squeeze() คือการลบ array นอกสุดออก 1 มิติ เช่น หากข้อมูลมีลักษณะเป็น [ [1,2] ] array ตัวนอกจะถูกลบออก
    1 มิติ กลายเป็น [ 1,2 ] แทน
  • self.contour_point จะไปเรียกฟังค์ชันซึ่งทำการนำ array self.contour_s มาแปลงค่าเป็น numpy array เป็น python list เพื่อให้สามารถทำการคำนวณใน python ได้ง่ายขึ้น
  • self.centroid จะไปเรียกฟังค์ชัน get_centroid() เพื่อนำค่า self.contour ไปคำนวณ Moment เพื่อนำมาหาจุดศูนย์กลางของข้อมูล ผลที่ได้จะเป็นลักษณะคู่อันดับ (x,y)
  • self.convex_hull จะเรียกฟังค์ชัน get_convex_hull โดยจะนำ self.contour มาทำการหา hull หรือส่วนโค้งเว้าใน contour นำมาคำนวณเพื่อเก็บใส่ตัวแปร epsilon และนำ hull กับ epsilon ไปเพื่อปรับค่า contour ในคำสั่ง cv2.approxPolyDP(convexHull,epsilon,True) และปรับการเรียงเป็นแนวตั้งพร้อมลบ array นอกสุดออก 1 มิติ และแปลงค่าจาก numpy array เป็น python list ก่อนทำการเก็บค่า
  • self.approx_hull_count จะทำการเก็บค่าความยาว self.convex_hull ไว้
  • self.id กำหนดให้มีค่าเป็น -1
  • self.area ทำการเก็บค่าพื้นที่ของ contour ด้วยคำสั่ง cv2.contourArea(self.contour)
  • self.deflect_count_90 จะเรียกฟังค์ชัน get_deflect_count(90) โดยค่าที่ส่งไปคือมุมที่เราต้องการทำไปคิด และนำ self.contour มาหา convex_hull จากนั้นนำ contour กับ hull มาทำการหา deflect ของภาพ จากนั้นจะนำ deflect มาทำการคำนวณหามุม และเข้าเงื่อนไขเพื่อเช็คว่าค่ามุมที่หามาได้น้อยกว่าหรือเท่ากับ 90 หรือไม่ ซึ่งก็คือการหามุมระหว่างร่องมือ หากไม่ถึง 90 จะเพิ่มค่า count 1 ค่า คนครบทุกตัว และเก็บค่า count ไป
  • self.isHand จะเรียกคำสั่ง self.check_isHand() ซึ่งจะนำค่า self.deflect_count_90 มาเข้าเงื่อนไขว่ามีค่า == 4 หรือไม่ ซึ่งเป็นการเปรียบเทียบว่า มือเรานั้นจะมีร่องมือเพียง 4 ร่องเท่านั้น หากมีค่าเท่ากับ 4 พอดีแสดงว่า blob ที่ได้มานั้นเป็นรูปมือจริงๆ จะทำการเก็บค่าเป็น True หากมีค่ามากกว่า 4 หรือน้อยกว่า 4 แสดงว่าไม่ใช่มือ เป็นอันเสร็จสิ้นการทำงานส่วนเก็บค่าตัวแปร
แหล่งอ้างอิง

squeeze :
https://www.tutorialspoint.com/numpy/numpy_squeeze.htm

np.vstack :
https://www.tutorialspoint.com/numpy/numpy_vstack.htm

np.array().tolist()
https://stackoverflow.com/questions/1966207/converting-numpy-array-into-python-list-structure

moment, epsilon, convexHull :
https://docs.opencv.org/3.1.0/dd/d49/tutorial_py_contour_features.html
https://www.learnopencv.com/find-center-of-blob-centroid-using-opencv-cpp-python/

โค้ดส่วนที่ทำงาน

class BlobAnalysis:
    def __init__(self,contour):
        self.contour = contour
        self.contour_s = np.vstack(contour).squeeze()
        self.contour_point = self.get_contour_point()
        self.centroid = self.get_centroid()
        self.convex_hull = self.get_convex_hull()
        self.approx_hull_count = self.get_approx_hull_count()
        self.id = -1
        self.area = cv2.contourArea(self.contour)
        self.deflect_count_90 = self.get_deflect_count(90)
        self.isHand = self.check_isHand()

    def set_id(self,i):
        self.id = i

    def get_contour_point(self):
        return np.array(self.contour_s).tolist()

    def get_centroid(self):
        m = cv2.moments(self.contour)
        cX = int(m['m10'] / m['m00'])
        cY = int(m['m01'] / m['m00'])
        return (cX, cY)

    def get_convex_hull(self):
        convexHull = cv2.convexHull(self.contour)
        epsilon = 0.015*cv2.arcLength(convexHull,True)
        approx = cv2.approxPolyDP(convexHull,epsilon,True)
        approx = np.vstack(approx).squeeze()
        return np.array(approx).tolist()

    def get_approx_hull_count(self):
        approx = self.convex_hull
        return len(approx)

    def get_deflect_count(self,max_angle):
        count = 0
        hull = cv2.convexHull(self.contour,returnPoints = False)
        defects = cv2.convexityDefects(self.contour, hull)
        for i in range(defects.shape[0]):
            s,e,f,d = defects[i,0]
            start = self.contour_s[s]
            end = self.contour_s[e]
            far = self.contour_s[f]
            a = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2)
            b = math.sqrt((far[0] - start[0])**2 + (far[1] - start[1])**2)
            c = math.sqrt((end[0] - far[0])**2 + (end[1] - far[1])**2)
            angle = math.acos((b**2 + c**2 - a**2)/(2*b*c)) * 57
            if angle <= max_angle:
                count += 1
        return count

    def check_isHand(self):
        if self.deflect_count_90 == 4 :
            return True
        else:
            return False

หลักการทำงานของโค้ด Smart Mirror & Hand Detection ( Part 1 ส่วนการเปิดหน้าเว็บและการรับค่าจากกล้อง Kinect )

ไฟล์การทำงานจะถูกแบ่งออกเป็น 3 ส่วนคือ
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 :
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)

วันจันทร์ที่ 8 ตุลาคม พ.ศ. 2561

สิ่งที่ได้ทดลอง ( 30 กันยายน - 6 ตุลาคม 2561)



ทดสอบทุก case ที่คาดว่าทำให้เกิด Error

1.การเดินผ่านในระยะใกล้ตัวกล้อง

        ทดสอบโดยการเดินผ่านหน้ากล้องโดยไม่แบมือหรือเก็บมือไว้ด้านหลังในระยะตั้งแต่ 10-20 cm โดยประมาณ
        ผลลัพธ์ : ในระยะที่ใกล้กล้องมากๆ การรับผลจะเริ่มเพี้ยนและส่งผลให้ไปสั่งการได้ในบางรอบ

2.การเดินผ่านในระยะใกล้ตัวกล้องโดยที่กางมือไปด้วย

        ทดสอบโดยการเดินผ่านหน้ากล้องโดยแบมือและแกว่งมือไปมาในระยะตั้งแต่ 10-20 cm โดยประมาณ
        ผลลัพธ์ : หากกล้องตรวจจับมือได้ควบคู่กับการเดิน อาจส่งผลให้การเดินผ่านในครั้งต่อไปจะไปสั่งการ Kinect ได้

3.การโบกมือในระยะที่ใกล้ตัวกล้อง

        การโบกมือในระยะที่ใกล้ตัวกล้อง : ทำการกางมือและโบกซ้ายขวาในระยะตั้งแต่ 10-20 cm โดยประมาณ
        ผลลัพธ์ : อาจส่งผลให้การการตรวจจับรวนและไปสั่งการ Kinect ได้ในบางครั้ง

4.การกางมือบริเวณด้านหน้าตัวโดยให้ห่างจากตัวพอประมาณ

        การกางมือบริเวณด้านหน้าตัวโดยให้ห่างจากตัวพอประมาณ : ทำการกางมือให้ห่างจากลำตัวเล็กน้อย และโยกตัวไปมาพร้อมกับมือ
        ผลลัพธ์ : จะมีลักษณะคล้ายกับการเดินแบบกางมือ คือ เมื่อกล้องตรวจจับไปมือได้แล้ว หากเราเก็บมือและโยกตัวหรือเอวไปทางซ้ายหรือขวาก็มีโอกาสที่จะไปสั่งการ kinect ได้

5.การเดินผ่านในระยะใกล้ตัวกล้องโดยที่กางมือไปด้วย

        การเดินผ่านในระยะใกล้ตัวกล้องโดยที่กางมือไปด้วย : ทดสอบโดยการเดินผ่านหน้ากล้องโดยแบมือและแกว่งมือไปมาในระยะตั้งแต่ 10-20 cm โดยประมาณ        ผลลัพธ์คือ หากกล้องตรวจจับมือได้ควบคู่กับการเดิน อาจส่งผลให้การเดินผ่านในครั้งต่อไปจะไปสั่งการ Kinect ได้

6.การกางมือบริเวณด้านหน้าตัวโดยให้มือแนบกับตัว

        ทำการกางมือนำไปแนบกับลำตัวและโยกตัวไปมาพร้อมกับมือ
        ผลลัพธ์ : มีโอกาสน้อยมากที่จะไปสั่งการ Kinect ให้เลื่อนซ้ายหรือขวาได้

คาดถึงสาเหตุของ Error ที่เกิดขึ้น

จากการทดสอบใน case ที่กล่าวมา พอจะจำกัดได้ว่า Error เกิดขึ้นได้ ดังนี้

  • ระยะที่ใกล้มากเกินไป
  • Frame หรือ Blob ที่ตรวจจับว่าเป็นมือยังค้างอยู่
  • ความสูงในการติดตั้ง เนื่องจากที่ทดสอบตั้งที่ความสูงจากพื้นประมาณ 75-85 cm

ศึกษาการทำงานทั้งหมดของโค้ดเพื่อหาทางแก้ไข

        ได้ทำการศึกษาการทำงานของโค้ดโดยลึกเพื่อตรวจสอบว่าการทำงานจริงๆนั้นเป็นอย่างไร รวมไปถึงตรวจดูลำดับการทำงานว่าลำดับการทำงานเป็นอย่างไร เพื่อหาวิธีจัดการเรื่อง Frame หรือ Blob ที่ยังติดสถานะว่าเป็นมือค้างอยู่ทำให้การตรวจจับผิดพลาด
        จากการที่ Uncomment Code บางส่วนที่รุ่นพี่ปิดไว้เพื่อลดภาระการทำงานของ Raspberry Pi นั้นช่วยให้การตรวจจับเสถียรขึ้นแต่ยังพบกับปัญหาตามที่กล่าวไว้ข้างต้น
        หากสามารถนำข้อมูลที่เป็นภาพตรวจจับจากมือมาแสดงเพิ่มเติมได้จะนำมาเสนอในครั้งถัดไปเพื่อเช็คความถูกต้อง


ปัญหาที่พบของ Kinect เกี่ยวกับ Hotplug



        จากการที่ทดลองหาปัญหาหลายๆอย่างขณะทดลองก็ได้พบว่า สาเหตุที่ทดลอง Run Code แล้วไม่สามารถตรวจจับตัว Kinect ได้นั้น นอกจากจะเป็นปัญหาเรื่องพลังงานแล้ว ยังมาจากอีกสาเหตุคือ ในตอนที่ปิดการทำงานของโค้ดนั้น ระบบตรวจจับของ Kinect ยังไม่ถูกปิดไปด้วย เมื่อทดลองปิดโค้ดและ Run Code ใหม่อีกครั้งจึงตรวจหาอุปกรณ์ไม่พบ เพราะ Kinect ยังทำงานอยู่ จึงต้องหาวิธีสั่งการเพื่อปิดการทำงานทั้งโค้ดและกล้องในเวลาเดียวกัน

วันศุกร์ที่ 28 กันยายน พ.ศ. 2561

ปัญหา และวิธีการแก้ (23 - 29 กันยายน 2561)

Problem & Solutoion
    1. ในตอน boot มีคำเตือนว่า 2.0716401 Under-voltage detected! ทำให้ใช้ rasberry pi และตัว kinnect ไม่ได้เนื่องจากไฟไม่พอ


    Solution : เปลื่ยนตัว adapter ที่สามารถจ่ายไฟ 2 A ได้ เพื่อจะได้เพียงพอกับตัว rasberry pi

    Result : สามารถกลับมาใช้ได้ปกติ, การทำงานของโค้ดนั้นรวดเร็วมากขึ้น, ส่วนของ Video Youtube ที่เคยทำให้ Framerate ตกลงนั้นลดลง และ การสั่งการและตรวจจับการเคลื่อนไหวตอบสนองได้เร็วขึ้น

วันพฤหัสบดีที่ 27 กันยายน พ.ศ. 2561

การเปลี่ยนการแสดงผลหน้าจอของ Rasberry Pi ให้เป็นแนวนอน

โดยใช้คำสั่งดังนี้

    1. ใช้คำสั่ง sudo nano /boot/config.txt
    2. ใส่ค่า display_rotate = 1 (display_rotate = 0 จะเป็นการแสดงแบบปกติ)
    3. กด Crtl + x เพื่อ save และพิมพ์คำสั่ง reboot
    4. กด Enter เพื่อยืนยัน
    5. ใช้คำสั่ง reboot



ผลที่ได้

วันเสาร์ที่ 15 กันยายน พ.ศ. 2561

ทดสอบโค้ดตรวจจับมือผ่านกล้องของ Notebook

เนื่องจากอุปกรณ์ Kinect ที่ใช้ยังพบปัญหาอยู่จึงได้ทดลองหาโค้ดอื่นที่พอใช้ได้มาทดสอบเกี่ยวกับการทำ Hand Detection ซึ่งในที่นี้จะใช้โค้ดของภาษา Python + OpenCV2 ร่วมกับการใช้กล้องของตัว Notebook เอง และได้ผลดังนี้

 

จากภาพจะพบว่าการทำงานจะเป็นการตรวจสอบเฉพาะบริเวณกรอบสี่เหลี่ยมที่เรากำหนดไว้ โดยภาพที่ได้มาก็จะมีลักษณะตามกรอบด้านซ้ายซึ่งเห็นเป็นรูปมือได้ค่อนข้างชัดเจน

ปัญหาที่พบคือ เนื่องจากตัวกล้องนั้นไม่ใช่ Depth Camera การกำหนดระยะ Threshold ในโค้ดจึงช่วยให้การตรวจจับยังไม่ดีเท่าที่ควร อีกทั้งหากพื้นหลังมีแสงรบกวนมากหรือไม่ใช่พื้นที่ราบเรียบ การตรวจจับจะเพี้ยนไปดังรูปด้านบน

ตัวอย่างโค้ดที่นำมาทดลอง

# organize imports import cv2 import imutils import numpy as np # global variables bg = None #------------------------------------------------------------------------------- # Function - To find the running average over the background #------------------------------------------------------------------------------- def run_avg(image, aWeight): global bg # initialize the background if bg is None: bg = image.copy().astype("float") return # compute weighted average, accumulate it and update the background cv2.accumulateWeighted(image, bg, aWeight) #------------------------------------------------------------------------------- # Function - To segment the region of hand in the image #------------------------------------------------------------------------------- def segment(image, threshold=25): global bg # find the absolute difference between background and current frame diff = cv2.absdiff(bg.astype("uint8"), image) # threshold the diff image so that we get the foreground thresholded = cv2.threshold(diff, threshold, 255, cv2.THRESH_BINARY)[1] # get the contours in the thresholded image (_, cnts, _) = cv2.findContours(thresholded.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # return None, if no contours detected if len(cnts) == 0: return else: # based on contour area, get the maximum contour which is the hand segmented = max(cnts, key=cv2.contourArea) return (thresholded, segmented) #------------------------------------------------------------------------------- # Main function #------------------------------------------------------------------------------- if __name__ == "__main__": # initialize weight for running average aWeight = 0.5 # get the reference to the webcam camera = cv2.VideoCapture(0) # region of interest (ROI) coordinates top, right, bottom, left = 10, 350, 225, 590 # initialize num of frames num_frames = 0 # keep looping, until interrupted while(True): # get the current frame (grabbed, frame) = camera.read() # resize the frame frame = imutils.resize(frame, width=700) # flip the frame so that it is not the mirror view frame = cv2.flip(frame, 1) # clone the frame clone = frame.copy() # get the height and width of the frame (height, width) = frame.shape[:2] # get the ROI roi = frame[top:bottom, right:left] # convert the roi to grayscale and blur it gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (7, 7), 0) # to get the background, keep looking till a threshold is reached # so that our running average model gets calibrated if num_frames < 30: run_avg(gray, aWeight) else: # segment the hand region hand = segment(gray) # check whether hand region is segmented if hand is not None: # if yes, unpack the thresholded image and # segmented region (thresholded, segmented) = hand # draw the segmented region and display the frame cv2.drawContours(clone, [segmented + (right, top)], -1, (0, 0, 255)) cv2.imshow("Thesholded", thresholded) # draw the segmented hand cv2.rectangle(clone, (left, top), (right, bottom), (0,255,0), 2) # increment the number of frames num_frames += 1 # display the frame with segmented hand cv2.imshow("Video Feed", clone) # observe the keypress by the user keypress = cv2.waitKey(1) & 0xFF # if the user pressed "q", then stop looping if keypress == ord("q"): break # free up memory camera.release() cv2.destroyAllWindows()

วันศุกร์ที่ 7 กันยายน พ.ศ. 2561

การทดลองใช้โค้ดจากงานเก่า part 1

ก่อนที่เราจะสามารถใช้ฟังก์ชัน keymap ได้ เราจะต้องทำขั้นตอนดังนี้
   1. sudo apt-get install freenect
   2. sudo apt-cache search freenect
   3. sudo apt-get install python-freenect
   4. pip install pynput




และจากนั้นก็เหมือนจะมีปัญหาในการเรียกเว็บหน้า browser 

                           

เราจึงเปลี่ยนคำสั่งที่ใช้
     จาก
os.system("chromium-browser --app=file:///home/pi/Desktop/kinect_hand_detection_and_tracking/src/index.html")
       เป็น
webbrowser.open('file://' + os.path.realpath("/home/pi/Desktop/kinect_hand_detection_and_tracking/src/index.html"))


วันอังคารที่ 4 กันยายน พ.ศ. 2561

การทดลองใช้ Kinect Xbox 360 บน Window ผ่าน Toolkit

สิ่งที่ต้องเตรียมในการทดสอบ

  • กล้อง Kinect Xbox 360
  • สาย usb adapter
  • Kinect for Windows SDK v1.7
  • Kinect for Windows Developer Toolkit v1.7
  • KinectSDK-v1.0-beta2-x64
  • ที่ต้องใช้ตัว SDK beta และ Kinect for Windows v1.7 เนื่องจากตัว Kinect Xbox 360 รองรับเฉพาะเวอร์ชั่นดังกล่าว

ขั้นตอนการทดสอบ

  1. ทำการดาวน์โหลด SDK ของ Kinect เพื่อติดตั้งตัว Module ต่างๆของ Kinect จากเว็บ
    https://www.microsoft.com/en-ca/download/details.aspx?id=27876


  2. ทำการดาวน์โหลด Kinect for Windows SDK เพื่อใช้ร่วมกับ Developer Toolkit จากเว็บ
    https://www.microsoft.com/en-us/download/details.aspx?id=36996


  3. ทำการดาวน์โหลด Kinect for Windows Developer Toolkit จากเว็บ


  4. เมื่อโหลดครบทั้ง3ตัวแล้ว ให้ทำการติดตั้งโปรแกรมทั้ง 3 ลงเครื่องและทำการ Restart คอมพิวเตอร์เพื่อให้การติดตั้ง Driver ของ Kinect ให้สมบูรณ์ซึ่งสามารถตรวจสอบได้จาก
    Device Manager


  5. เปิดตัว Developer Toolkit ขึ้นมาจะได้หน้าจอดังนี้ ซึ่งเราสามารถทดสอบระบบที่เราต้องการจากกล้องได้


  6. ทำการทดสอบการจับภาพปกติจากโหมด Color Basics-D2D


  7. ทำการทดสอบการจับภาพความลึกจากโหมด Depth Basics-D2D แต่พบว่าภาพที่ได้มีความเพี้ยนค่อนข้างมากอาจเป็นผลจากตำแหน่งการตั้งกล้องและแสงภายในห้องทำให้ผลที่ได้มีลักษณะผิดแปลกไป



  8. ทำการทดสอบการจับภาพทั้งโหมดปกติและโหมดความลึกในเวลาเดียวกันจากโหมด Kinect Explorer-D2D ซึ่งสามารถตรวจจับเสียงและสร้างภาพโครงร่างหรือ Skeleton ได้ ส่วนตัวเลขด้านบนขวาของจอคือ FPS ของการตรวจจับ

วันอังคารที่ 28 สิงหาคม พ.ศ. 2561

ทดสอบการใช้งาน Amazon Voice Service บน Window 10

จากการหาข้อมูลในการทดลองใช้ Amazon Voice Service หรือ Alexa บน Window 10 พบว่าตัว ทาง Microsoft ได้มีการร่วมมือกับ Amazon เพื่อรวมการทำงาน โดยสามารถสั่งการได้ผ่าน Cortana ซึ่งเป็น Voice Assistant ของ Microsoft โดยสามารถทำได้ดังนี้

  1. สั่ง Cortana ผ่านคำสั่งเสียง หรือพิมพ์ในช่อง Search Box ว่า open alexa ซึ่งหลังจากพิมพ์ไปแล้ว Cortana จะตอบกลับมาให้เราสั่งการไปด้วยเสียง หากเข้าใช้ครั้งแรก Cortana จะให้เราทำการ Sign In เพื่อเข้าใช้ Alexa


  2. หลังจาก Sign In เรียบร้อยแล้ว ทาง Amazon จะทำการขออนุญาตว่าเรายอมรับเงื่อนไขของทาง Amazon ที่จะใช้ Alexa หรือไม่ ให้เราทำการกด Allow


  3. ต่อมาจะเป็นการเลือกตำแหน่งที่ตั้งที่เราอยู่ เมื่อเลือกเรียบร้อยแล้วให้เรากด Next เพื่อไปต่อ


  4. เมื่อเลือกแล้ว จะมีตัวเลือกว่าเราต้องการให้ Window บันทึกข้อมูลการ Sign In ของเราไว้หรือไม่ เพื่อให้สะดวกต่อการใช้งานร่วมกับ Apps อื่นๆที่เกี่ยวข้อง ในที่นี้จะเลือก Yes


  5. หลังเลือกแล้ว ระบบจะให้เราทำการ Sign In อีกครั้ง


  6. จะมีการขออนุญาตในการแชร์ข้อมูลระหว่าง Microsoft และ Amazon Alexa ให้กด Yes เพื่อเริ่มเข้าสู่การใช้ Alexa
  7. เมื่อเสร็จสิ้นทุกขั้นตอน เราจะสามารถใช้ Alexa ในการสั่งการแทน Cortana ได้ทันที ขั้นต่อไปจะเป็นการหาหนทางในการใช้งานร่วมกับ Raspberry Pi

วันพฤหัสบดีที่ 23 สิงหาคม พ.ศ. 2561

ที่มาของข้อมูลเกี่ยวกับ IOT


     จากที่ได้ไปศึกษาการทำ IOT เราได้เจอ  blog ที่เขียนเกี่ยวกับ การทำ IOT ซึ่งเขาได้ทำเกี่ยวกับการทำ smart home ซึ่งในบทความนี้ เป็นการสั่ง เปิด-ปิดไฟ โดยใช้ระบบ IOT ในลิ้งต่อไปนี้
http://naringroupxrt.blogspot.com/2016/03/iot-smart-home-nodemcu-v2-part-1.html


    โดยผู้เขียนใช้เป็นชื่อ user ว่า เซเรฟ คนที่โลกไม่ต้องการ เป็น นักวิศวกร ซึ่งนอกจากการทำ
smart home ก็ยังมีบทความอื่นๆอีก ดังนี้