วันศุกร์ที่ 22 กุมภาพันธ์ พ.ศ. 2562

การทดสอบประสิทธิภาพการทำงานของ Serial, Threading และ Multiprocessing

การทดสอบในครั้งนี้จะมีลักษณะคือ การเรียกใช้ฟังค์ชันแบบ Serial, Thread และ Multiprocessing ตั้งแต่ 1 ครั้ง, 4 ครั้ง และ 8 ครั้ง และในแต่ละครั้งจะแบ่งเป็น การเรียกใช้ฟังค์ชันเพียงครั้งเดียว และ การเรียกใช้ฟังค์ชันที่ภายในมี loop การทำงานมากๆ โดยฟังค์ชันที่ใช้จะเป็นฟังค์ชัน Image Processing ต่างๆเพื่อทดสอบการทำงานของโปรแกรมหลักไปในตัว



ตัวอย่างการทำงานจากการใช้ Thread

โดยมีผลลัพธ์ที่ได้คือ การเรียกใช้แบบ Serial จะใช้ CPU Core 1 ตัวเป็นหลักเสมอ อาจมีการสับเปลี่ยน Core เพื่อลดภาระการทำงานของ Core ตัวก่อนหน้าโดย OS บ้าง แต่จะมีเพียง 1 Core ที่ทำงานสูงสุดเสมอ ไม่ว่าจะเรียกฟังค์ชันกี่ครั้งก็ตาม


ผลลัพธ์ของ Threading และ Multiprocessing การทำงานของ CPU Core จะมีลักษณะคล้ายๆกันคือ Core ที่ทำงานนั้นจะขึ้นอยู่กับจำนวนของ Thread หรือ Process ที่ถูกสร้างเพื่อเรียกใช้ฟังค์ชัน



4 Threads



4 Processes


8 Threads


8 Processes

ซึ่งเวลาในการทำงานของแต่ละวิธีนั้นจะแสดงดังตารางด้านล่าง




จากตารางจะพบว่าหากการทำงานเป็นแบบเรียกใช้เพียงครั้งเดียว การทำงานแบบ Serial จะมีประสิทธิภาพมากกว่า ในอีกด้านนั้น ยิ่งจำนวน Loop ในฟังค์ชันนั้นมากขึ้น การทำงานแบบ Multiprocessing นั้นจะให้ประสิทธิภาพการทำงานที่สูงกว่าการทำงานแบบ Serial และ Thread เนื่องจาก Thread ใน Python ไม่ใช่การทำงานแบบขนาน แต่เป็นการสลับการทำงานระหว่าง Thread แทน

และเนื่องจากในการทำงานจริงนั้นจะใช้งานบนบอร์ด Raspberry Pi 3 Model B+ จึงได้นำโค้ดทดลองไปทดสอบบนบอร์ด โดยเรียกใช้ฟังค์ชันการทำงานสูงสุดตามจำนวน Core สูงสุดของบอร์ดคือ 4 Cores
( Notebook ที่ใช้ทดสอบผลด้านบนคือ Lenovo Z580 8 Cores )



ซึ่งผลลัพธ์ที่ได้นั้นก็ยังคงสอดคล้องกับผลที่ทดลองบน Lenovo อยู่

ผลสรุปคร่าวๆจากการทดลองทั้งหมดคือ

  • ในกรณีที่เรียกใช้ฟังค์ชันที่มีการทำงานเพียงครั้งเดียวนั้น การเรียกใช้แบบปกติหรือแบบ Serial นั้น ใช้เวลาน้อยกว่าทั้ง 2 กรณี
  • ในกรณีที่ฟังค์ชันนั้นมี loop หรือการทำซ้ำอยู่ภายในเป็นจำนวนมากๆ เวลาในการทำงานนั้นจะขึ้นอยู่กับ จำนวนของการเรียกใช้ฟังค์ชัน 
  • หากมีการเรียกใช้น้อย เวลาการทำงานของทั้ง 3 วิธีจะไล่เลี่ยกัน 
  • หากมีการเรียกใช้มากๆ เวลาการทำงานของ Multiprocessing จะเร็วกว่า ตามมาด้วย Serial และ Thread ตามลำดับ



จากรูปภาพด้านบนจะสังเกตได้ว่าในระหว่างการทดสอบนั้นทำให้อุณหภูมิของ CPU สูงขึ้นด้วย ต้องใช้งานอย่างระมัดระวัง

วันศุกร์ที่ 15 กุมภาพันธ์ พ.ศ. 2562

ทดสอบการตรวจจับท่าทางการกำมือ

ได้ทำการทดลองเพิ่มการตรวจจับการกำมือ โดยเช็คจากอัตราส่วนของ Contour เทียบกับอัตราส่วนของขนาด Convex Hull



และเพิ่มการตรวจจับท่าทางจากการเคลื่อนไหวขณะกำมือ


การเลื่อนขึ้น


การเลื่อนลง


การเลื่อนซ้าย


การเลื่อนขวา

วันศุกร์ที่ 8 กุมภาพันธ์ พ.ศ. 2562

การทำงานโดยรวมของระบบ Hand Detection และ Hand Gesture

ส่วนแรกการรับ Input และ Image Processing
  • ในช่วงเริ่มต้นของโปรแกรมจะทำการสร้างตัวแปรสำหรับใช้ในการตรวจจับคือ
    • blobs สำหรับใช้เก็บ object ของ class ในการคำนวณหาผลลัพธ์
    • blobs_buffer สำหรับเก็บ blobs เป็น Array ขนาด [ [] , [] , [] ]
    • old_id สำหรับเก็บ id ของข้อมูลที่เข้ามาเป็น Array ขนาด [ [] , [] , [] ]
    • โดยทุกครั้งที่มีการเรียกฟังค์ชัน blobs จะถูก reset ให้เป็น Array [] เสมอ
  • ทำการ Update old id ทุกครั้งที่ โดยจะทำการ reset array ก่อนนำ id ใน blobs_buffer มาใส่ค่าแทนที่ลงไป
  • จากนั้นจะทำการไปดึงข้อมูล contour ของภาพมาผ่านฟังค์ชัน get_contours โดยส่งค่าขนาดความจอในแนวแกน x และ y ไปเพื่อทำ Image Processing โดยผลที่ได้กลับมาจะเป็น Array ของข้อมูล contour ที่ตัด threshold และอื่นๆเรียบร้อย ในลักษณะ
    Array[ [ [[ x y ]] [[ x y ]] [[ x y ]] .. [[ x y ]] ] ] โดย x และ y array คือ ตำแหน่งของ contour แต่ละจุดรวมๆกัน
ส่วนที่ 2 การทำ BlobAnalysis และ blob_track
  • จากนั้นจะทำการเรียก class BlobAnalysis มาเก็บไว้ในตัวแปร blob โดยจะส่งข้อมูล contour ไปทำการปรับรูปแบบของข้อมูลและคำนวณหาจุดศูนย์กลางของ contour หรือ centroid รวมไปถึงส่วนบกพร่องการนูนหรือ convexity deflect เพื่อใช้ตรวจสอบร่องมือและตรวจจับว่าเป็นมือหรือไม่
  • จากนั้นจะนำ blob มาทำ blobs_track โดยการทำงานคร่าวๆคือ จะทำการตรวจสอบก่อนว่า blob ที่ได้มาใหม่นั้นเหมือนกับ blob จาก blobs_buffer เก่าหรือไม่ จากนั้นจะทำการบันทึกค่า id ใหม่ให้กับ blob ที่เป็น class object , ตรวจสอบว่า blob นั้นเป็นมือหรือไม่จากข้อมูลใน object และทำการบันทึกค่า centroid 2 ชุด ลงใน blobs_movement จากนั้นจึง return blob ไปเก็บในตัวเอง
  • เพิ่ม blob ลงไปใน array blobs และนำ blobs ไปบันทึกลงใน array blobs_buffer ซึ่งข้อมูลใน array ทั้ง 3 ตัวจะมีค่าเหมือนกันทั้งหมด
ส่วนที่ 3 การ check_gesture
  • ทำการรับค่า fps และดึงค่า blobs และ blobs_movement มาใช้ด้วย และนำไปเช็คว่า blobs นั้นเป็นมือหรือไม่ หากใช่จะทำการนำ fps มากำหนด frame สำหรับสร้างตัวแปร vector ที่ใช้เก็บข้อมูล centroid ทั้งหมด 20 จุดจาก blobs_movement 
  • นำ vector มาคำนวณหาองศาการเคลื่อนที่และระยะห่างของ centroid ทีละ 2 จุด หากระยะห่างของ centroid ทั้ง 2 มากกว่า 8 เซนติเมตร จะทำการนำองศาที่คำนวณไปเข้าเงื่อนไขหาว่าเคลื่อนที่ไปทางไหนแหละให้ weight แก่ทิศทางนั้นๆ 
  • เมื่อครบจะนำ weight ของทิศที่มีค่าสูงสุดไปตรวจสอบว่ามีค่ามากกว่า 70% ของการเคลื่อนที่ทั้งหมดใน frame หรือไม่ หากใช่จะทิศการเคลื่อนที่นั้นไปสำหรับ mapping กับ keyboard
จากนั้นจะทำการวน loop ไปเรื่อยๆเพื่อตรวจสอบท่าทาง

วันอังคารที่ 5 กุมภาพันธ์ พ.ศ. 2562

การทำ Contour เพื่อดูข้อมูลก่อนถูกส่งไปทำ Gesture

ข้อมูลของ Contour ที่ได้มาจะเป็นลักษณะ Array ที่เก็บพิกัด ( x,y ) ของจุดรอบรูปที่เราทำการป้อนเข้าไป
                                                                         

เมื่อนำไปแต่ละจุดไปทำการ plot จะได้ลักษณะดังภาพคือ จุดสีตามตำแหน่งของภาพที่ถูกนำไปทำ Contour

         

ทำการตรวจสอบข้อมูลส่วนของ Loop และเงื่อนไขว่าข้อมูลที่ได้ก่อนส่งไปทำ Blob Tracking เป็นอย่างไร เพื่อนำไปสู่การตรวจสอบการตรวจจับนิ้วในขั้นถัดๆไป

ผลจากการทำ Multithreaded และ Multiprocessing โดยใช้โค้ดหลัก แต่ต่างอุปกรณ์

ผลทดสอบจาก Raspberry Pi 3 Model B+

   
                            
การใช้ Threading การใช้ Multiprocessing
ผลทดสอบจาก Lenovo Z580

การใช้ Threading การใช้ Multiprocessing

ผลทดสอบจากการวัดค่า FPS เทียบกับเวลา
( Raspberry Pi 3 Model B+ )
  

   การใช้ Threading (Mean FPS = 24) การใช้ Multiprocessing (Mean FPS = 23)

ผลทดสอบจากการวัดค่า FPS เทียบกับเวลา
( Lenovo Z580 )
   การใช้ Threading (Mean FPS = 32 ) การใช้ Multiprocessing (Mean FPS = 32)

ผลสรุป
  • ผลของค่า FPS เฉลี่ยจากทั้ง 2 วิธีนั้นไม่ต่างกันเลยการทดลองบน Lenovo Z580 ทำให้พบว่า Error บางส่วนอย่าง การออก Action เกินกว่าที่เราสั่งไป เช่น เลื่อนมือไปทางขวาครั้งเดียวแต่คำสั่งกลับเลื่อนมากกว่า 1 ครั้ง พบได้น้อยมาก อาจมีสาเหตุมาจากการประมวลผลที่เร็วกว่าทำให้ได้ผลที่แม่นยำกว่าการทำงานของระบบแบบ Fullscreen ของ Lenovo Z580 ยังให้ผลลัพธ์ที่เร็วกว่า Rpi 3 ในการรันโปรแกรมเดียวกันเนื่องจากการทดลองด้าน Thread และ Process ให้ผลลัพธ์ที่ไม่ดีตามที่ตั้งไว้ จึงตัดสินใจที่จะไปทำในส่วนของการตรวจจับและนับนิ้วแทนเพื่อไม่ให้เสียเวลาไปมากกว่านี้