2018年12月29日 星期六

Tello 普通版編程


聖誕假期間,看到 Facebook 內的 DJI 廣告,發現一台入門級航拍機只需要 HK$619,實在很吸引。在航拍機未興起前已經想組裝一台,但一直沒有行動。之前都見過有價錢在 HK$400-$500 間的航拍機,不過質素及外型都不太好。現在 DJI 這台完全能滿足這兩點要求,只花幾百就買得到,於是買了一台回來。


Tello 的操作簡單,用手機 App 便能控制。規格中指飛行距離可達 100 米,而我嘗試過用 iPhone XR 或 iPad Pro 都只能去到 30 米左右,向上飛只能達三層樓高左右。旗艦店職員說是因為手機內的 WiFi 間片細,訊號接收力弱所導致;要是用遙控器則會好多了。我希望用電腦加帶天線的外置 WiFi USB 來控制 Tello,於是研究了一下,發現了 TelloPy 這個 Python 模組。編寫了一個簡單的測試程式,讓 Tello 起飛、拍照、降落。
##----------------------------------------------------------------------------------------
##  Tello DEMO Program
##----------------------------------------------------------------------------------------
##  Platform: Python 3.6 + TelloPy
##  Written by Pacess HO
##  Copyright Pacess Studio, 2018.  All rights reserved
##----------------------------------------------------------------------------------------

from time import sleep
import tellopy

##  Global variable
_counter = 0

##----------------------------------------------------------------------------------------
def handler(event, sender, data, **args):
   global _counter
   drone = sender
   
   if event is drone.EVENT_FLIGHT_DATA:
      print(data)
   
   if event is drone.EVENT_FILE_RECEIVED:
      _counter = _counter+1
      path = "tello_%s.jpg" % str(_counter)
      with open(path, "wb") as file:
         file.write(data)

##----------------------------------------------------------------------------------------
def flyNow():
   drone = tellopy.Tello()
   try:
      drone.subscribe(drone.EVENT_FLIGHT_DATA, handler)
      drone.subscribe(drone.EVENT_FILE_RECEIVED, handler)

      drone.connect()
      drone.wait_for_connection(60.0)
      
      drone.takeoff()
      sleep(3)
      
      drone.take_picture()
      sleep(3)
      
      drone.land()
      sleep(3)
   
   except Exception as ex:
      print(ex)
   
   finally:
      drone.quit()

##----------------------------------------------------------------------------------------
if __name__ == '__main__':
   flyNow()

2018年12月25日 星期二

用 TensorFlow + PoseNet 偵測骨骼位置


最近女兒參加了跳舞比賽,心想怎樣能用影像判斷出骨骼位置,從而收集動作數據呢?在網上找到以 TensorFlow + PoseNet 可以做到。以下是用 Javascript 編寫了一個簡單的偵測程序:
<html>
   <head>
      <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
      <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/posenet"></script>
   </head>

   <body>
      <canvas id="canvas"></canvas>
      <img id="photo" src="./sport_01.jpg" style="display:none;" />

      <script>
         var image = document.getElementById("photo");
         var imageScaleFactor = 0.2;
         var flipHorizontal = false;
         var outputStride = 16;

         //----------------------------------------------------------------------------------------
         function drawConnection(context, keypoints, partA, partB)  {
            var radius = 8;
            var partAPoint = null;
            var partBPoint = null;
            for (var i=0; i<keypoints.length; i++)  {

               var element = keypoints[i];
               var part = element.part;
               if (part != partA && part != partB)  {continue;}

               //  Either matches part A or part B
               if (part == partA)  {partAPoint = element.position;}
               if (part == partB)  {partBPoint = element.position;}

               //  Continue if not both position have been set
               if (partAPoint == null || partBPoint == null)  {continue;}

               //  Both parts are ready, connect them
               context.beginPath();
               context.arc(partAPoint.x, partAPoint.y, radius, 0, 2*Math.PI, false);
               context.fillStyle = 'green';
               context.fill();

               context.beginPath();
               context.moveTo(partAPoint.x, partAPoint.y);
               context.lineTo(partBPoint.x, partBPoint.y);
               context.strokeStyle = 'green';
               context.stroke();

               context.beginPath();
               context.arc(partBPoint.x, partBPoint.y, radius, 0, 2*Math.PI, false);
               context.fillStyle = 'green';
               context.fill();
            }
         }

         //----------------------------------------------------------------------------------------
         function drawConnection12(context, keypoints, partA, partB, partC)  {
            var radius = 8;
            var partAPoint = null;
            var partBPoint = null;
            var partCPoint = null;
            for (var i=0; i<keypoints.length; i++)  {

               var element = keypoints[i];
               var part = element.part;
               if (part != partA && part != partB && part != partC)  {continue;}

               //  Either matches part A or part B or part C
               if (part == partA)  {partAPoint = element.position;}
               if (part == partB)  {partBPoint = element.position;}
               if (part == partC)  {partCPoint = element.position;}

               //  Continue if not both position have been set
               if (partAPoint == null || partBPoint == null || partCPoint == null)  {continue;}

               var pointX = (partBPoint.x+partCPoint.x)/2;
               var pointY = (partBPoint.y+partCPoint.y)/2;

               //  Both parts are ready, connect them
               context.beginPath();
               context.arc(partAPoint.x, partAPoint.y, radius, 0, 2*Math.PI, false);
               context.fillStyle = 'green';
               context.fill();

               context.beginPath();
               context.moveTo(partAPoint.x, partAPoint.y);
               context.lineTo(pointX, pointY);
               context.strokeStyle = 'green';
               context.stroke();
            }
         }

         //----------------------------------------------------------------------------------------
         function drawConnection22(context, keypoints, partA, partB, partC, partD)  {
            var radius = 8;
            var partAPoint = null;
            var partBPoint = null;
            var partCPoint = null;
            var partDPoint = null;
            for (var i=0; i<keypoints.length; i++)  {

               var element = keypoints[i];
               var part = element.part;
               if (part != partA && part != partB && part != partC && part != partD)  {continue;}

               //  Either matches part A or part B or part C or part D
               if (part == partA)  {partAPoint = element.position;}
               if (part == partB)  {partBPoint = element.position;}
               if (part == partC)  {partCPoint = element.position;}
               if (part == partD)  {partDPoint = element.position;}

               //  Continue if not both position have been set
               if (partAPoint == null || partBPoint == null || partCPoint == null || partDPoint == null)  {continue;}

               var pointX1 = (partAPoint.x+partBPoint.x)/2;
               var pointY1 = (partAPoint.y+partBPoint.y)/2;
               var pointX2 = (partCPoint.x+partDPoint.x)/2;
               var pointY2 = (partCPoint.y+partDPoint.y)/2;

               //  Both parts are ready, connect them
               context.beginPath();
               context.arc(pointX1, pointY1, radius, 0, 2*Math.PI, false);
               context.fillStyle = 'green';
               context.fill();

               context.beginPath();
               context.moveTo(pointX1, pointY1);
               context.lineTo(pointX2, pointY2);
               context.strokeStyle = 'green';
               context.stroke();
            }
         }

         //----------------------------------------------------------------------------------------
         posenet.load().then(function(net)  {
            return net.estimateSinglePose(image, imageScaleFactor, flipHorizontal, outputStride);
         }).then(function(pose)  {

            var width = image.width;
            var height = image.height;

            var canvas = document.getElementById("canvas");
            canvas.width = width;
            canvas.height = height;

            var context = canvas.getContext("2d");
            context.drawImage(image, 0, 0);

            var keypoints = pose.keypoints;
            drawConnection(context, keypoints, "leftEye", "rightEye");
            drawConnection(context, keypoints, "leftEye", "nose");
            drawConnection(context, keypoints, "rightEye", "nose");

            drawConnection(context, keypoints, "leftEar", "leftEar");
            drawConnection(context, keypoints, "rightEar", "rightEar");

            drawConnection(context, keypoints, "leftShoulder", "rightShoulder");
            drawConnection(context, keypoints, "leftShoulder", "leftElbow");
            drawConnection(context, keypoints, "rightShoulder", "rightElbow");
            drawConnection(context, keypoints, "leftElbow", "leftWrist");
            drawConnection(context, keypoints, "rightElbow", "rightWrist");

            drawConnection(context, keypoints, "leftHip", "rightHip");
            drawConnection(context, keypoints, "leftHip", "leftKnee");
            drawConnection(context, keypoints, "rightHip", "rightKnee");
            drawConnection(context, keypoints, "leftKnee", "leftAnkle");
            drawConnection(context, keypoints, "rightKnee", "rightAnkle");

            drawConnection12(context, keypoints, "nose", "leftShoulder", "rightShoulder");
            drawConnection22(context, keypoints, "leftShoulder", "rightShoulder", "leftHip", "rightHip");
         });
      </script>
   </body>
</html>