作者: Python程序员
  无人机现在真的非常的简单,小巧并且负担得起,甚至可以把它当玩具看待。你可以利用方便的应用编程接口(APIs)对其进行编程以定制化!Parrot AR无人机就提供了API接口,不仅可以控制它的移动,而且能控制摄像头获取的视频和图像。在这篇文章中,我将向你展示如何使用Python和node.js去创建一个可以自我移动的无人机。
  
  项目概述

  先声明我并不是一个无人机或机器视觉方面的专家,所以我会让事情尽量简单。在这个项目中,我将教会无人机去跟踪一个红色的物体。
  我知道这与T-800 Model 101(或者与此类似的东西)相去甚远,但是考虑到时间和预算,这是一个很好的开始!同时,请按我的方式发送信息给你最好的自主终端或无人机群,不要有其他压力。
  
这里没有神经网络处理器,只有node.js和python
  无人机

  当我在圣诞节的早晨打开我的无人机时,我对下一步做什么并没有确切的想法。但是有一件事是确定的:这个玩意真酷!AR Drone 2.0(我所知道的超级蹩脚的名字)是一个四轴飞行器。如果你想象着那些适合你手掌大小,单旋翼,RC线框的无人机,那你就走错了地方。我首先注意到(也是最让我惊讶的)AR Drone很大。当它“室内机壳”打开时,它足足有2英尺宽,2英尺长,6英寸高。同时它也有一些吵——换种更好的说法(就像恐吓你的狗的方式,并不像是恐吓小狗那样)。再加上2个摄像头——一个前置和一个后置,你才能将你的怪胎玩具发挥到极致。
  
  无人机编程

  在无人机的历史中,AR Drone算是比较老旧的——第一版发行于2012年。这看起来像是一件坏事情,但是由于我们将在它基础上编程,因而实际上这是一件好事。
  由于它经历了四年的成长期,因而有一些非常优秀的API和库,以及一些控制它的实际项目/代码示例(参考下面列出的资源)。所以大体上,已经有一些人克服了用字节码与无人机交互的困难,我所做的只是导入了node_module以及动身去drone races。
  无人机编程实质上很简单。我使用的是node.js中ar-drone模块,我发现它运行的非常好,虽然并没有超级积极地开发。开始之前,我将向你展示如何做飞行计划的热身工作。以下是要完成的编程工作:
  

  • 将无人机连上wifi
  • 告诉无人机起飞
  • 1秒后全速顺时针旋转
  • 再过1秒,停下来并且以50%推力向前移动
  • 再过1秒,停止且降落
  这是非常简单的小程序,虽然很直接,但是我仍然强烈推荐你有一个可以紧急降落的脚本。因为你只有真正用到时才会意识到你真的需要它:)
  
var arDrone = require("ar-drone"); var drone = arDrone.createClient(); drone.takeoff();  drone   .after(1000, function() {     drone.clockwise(1.0);   })   .after(1000, function() {     drone.stop();     drone.front(0.5);   })   .after(1000, function() {     drone.stop();     drone.land();   })  你也可以完成一些更漂亮的动作——你知道,这会给你的朋友留下深刻印象。我个人最喜欢的是后空翻。
  机器视觉

  现在将解答第二个迷惑:教我们的无人机如何去观察。为了完成这个目标,我们将使用OpenCV和python的cv2模块。OpenCV可能有些棘手,但是它真的可以完成一些令人惊叹的工作,甚至包含了一些机器学习的库。
  我们将使用OpenCV去做一些基础物体跟踪。告诉摄像机去跟踪任何出现在它视野中的红色物体。这有点像斗牛场中的公牛。
  
  就像这一样,但要用无人机替代掉牛,以及为Greg准备的红色斗篷!另外,我的裤子也没那么紧。
  有一个好消息就是cv2模块将使得这一切更简单。
  
import numpy as np import cv2 from skimage.color import rgb2gray from PIL import Image from StringIO import StringIO from scipy import ndimage import base64 import time  def get_coords(img64):     "Reads in a base64 encoded image, filters for red, and then calculates the center of the red"     # convert the base64 encoded image a numpy array     binaryimg = base64.decodestring(img64)     pilImage = Image.open(StringIO(binaryimg))     image = np.array(pilImage)      # create lower and upper bounds for red     red_lower = np.array([17, 15, 100], dtype="uint8")     red_upper = np.array([50, 56, 200], dtype="uint8")      # perform the filtering. mask is another word for filter     mask = cv2.inRange(image, red_lower, red_upper)     output = cv2.bitwise_and(image, image, mask=mask)     # convert the image to grayscale, then calculate the center of the red (only remaining color)     output_gray = rgb2gray(output)     y, x = ndimage.center_of_mass(output_gray)      data = {         "x": x,         "y": y,         "xmax": output_gray.shape[1],         "ymax": output_gray.shape[0],         "time": time.time()     }     return data  正如你上面看到的,我使用了一个颜色滤镜去过滤掉图片中的像素。这是简单而直接的方法,更重要的是它可以工作,如下所示:
  
原始图像  
  
过滤红色处理后
  也许我们并没有真正做到T-800 Model 101那样,但是至少这是个开端。
  
这个红点是巧合吗?再考虑一下…
  (这里有个图,太大了,微信公众平台限制了上传。不过大家可以到Python部落中查看:python.freelycode.com)
原始的摄像机图像

红色滤镜过滤过的图像
  拼接各块

  现在到了棘手的部分。我们已经有了一些操纵无人导航的node.js脚本,以及可以检测图片中红色部分的python代码,但问题是我们如何将它们结合起来?
  
  朋友们,我将使用Yhat自身的模块部署软件——SicenceOps。首先将Python代码部署到ScienceOps中,这样就能通过API访问到,然后再在node.js中从ScienceOps中调用自身的模块。总体思想就是将OpenCV红色过滤的模块部署到一个真正的简单的HTTP终端上。借用ScienceOps使得我童年无人机斗牛的梦想实现了,你可以使用它将R或者Python模块嵌入任何能够发送API请求的应用中,不止无人机也可以是其他的东西。
  我不需要再浪费时间进行跨平台的讨论,如果我需要加大马力(比如说同时控制多台无人机),我可以让ScienceOps自动进行扩容。如果你想了解更多的ScienceOps进行模块部署的信息,可以访问我们的网站或者观看Deom的演示。
  
from yhat import Yhat, YhatModel  class DroneModel(YhatModel):     REQUIREMENTS = [         "opencv"     ]     def execute(self, data):         return get_coords(data["image64"])  yh = Yhat(USERNAME, APIKEY, "https://sandbox.yhathq.com/") yh.deploy("DroneModel", DroneModel, globals(), True)  这段代码是什么意思?起码有一点,这使得我的node.js代码更简单了,甚至可以通过Yhat的node.js库去执行我的模块:
  
var fs = require("fs"); var img = fs.readFileSync("./example-image.png").toString("base64"); var yhat = require("yhat"); var cli = yhat.init("greg", "my-apikey-goes-here", "<a href="https://sandbox.yhathq.com/%27%29;" _src="https://sandbox.yhathq.com/");" style="font-size: 14px; text-decoration: underline;">https://sandbox.yhathq.com/");  cli.predict("DroneModel", base64edImage, function(err, data) {   console.log(JSON.stringify(data, null, 2));   // {   //   "result": {   //     "time": 1460067540.30213,   //     "total_red": 5.810973333333334,   //     "x": 425.0256166460453,   //     "xmax": 640,   //     "y": 220.03434178824077,   //     "ymax": 360   //   },   //   "version": 1,   //   "yhat_id": "529b84c9c4957008446a56faadc152a6",   //   "yhat_model": "DroneModel"   // }   var x = data.x / data.xmax - 0.5     , y = data.y / data.ymax - 0.5;    if (x > 0) {     drone.right(Math.abs(x));   } else {     drone.left(Math.abs(x));   }   if (y > 0) {     drone.up(Math.abs(y));   } else {     drone.down(Math.abs(y));   }});  非常棒!现在我可以把这个脚本放进导航脚本中了,所有需要做的事情就是告诉脚本我想如何处理响应。大概包含以下几步:
  

  • 调用部署在ScienceOps上的DroneModel
  • 如果没有报错,那么看看结果。结果会告诉我们红色在图像中的x, y坐标。
  • 给无人机调整线路,让红色成为无人机视区的中心
  这么简单,哪里可能做错呢?
  修补拼合

  就像策略规定的那样,如果第一次没有成功,会再次重试。从开始到最终真正地能工作花费了我很多的时间。结果证明,当将各个模块结合起来时,隐藏的错误也会越来越明显。
  但是不要担心,我的无人机经过了数次的颠簸和瘀伤,但是仍然坚强地活了下来——可以使用一个胶带修补无人机,但是要确保无人机每一边的数量都差不多,这样才能保证平衡!
  
  我所汲取的一些惨痛教训:
  


  • 创建一个帮助APP:经过数次的尝试,我创建了一个帮助APP来决定发生的是什么事情以及发生的原因。但是这不应该是第一步,能够看到程序执行的代码以及处理的图像是无价的。

  • 不要矫枉过正:举个简单的例子,如果你让无人机一次执行太多的事情,它会崩溃掉要么静止不动,要么开始在屋里闲逛。

  • 永远准备好紧急降落的脚本:之前提到过但是也不能过分强调。原因在于如果你的程序崩溃掉而你的无人机并没有降落,那么你将陷入深深的麻烦之中。你的无人机将在你告诉它降落之前一直保持飞机,如果你手边有emergency-landing.js脚本,将会节省你大量的时间。

  • 如果天气潮湿,不要起飞无人机:这真的是一个惨痛的教训…

  最终,在坚持与幸运中,我终于能够让无人机自动驾驶了。
  在野外

  最终在PAPIs Valencia的展示非常有趣(BTW PAPIs也很不错!对于预测分析有兴趣的人来说强烈推荐)。不幸的是,我的PAPIs样例进展的并不顺利,演讲厅的照明和办公室的并不同,因而获取红色的方式并不能正确工作。尽管对性能不太满意,但是它仍然是非常有趣的。
  资源

  是否想对你自己的无人机了解更多的编程技巧?下面是一些优秀的资源:
  


  • NodeCopter——无人机的JS社区,虽然没有之前活跃,但是仍然有一些优秀的新手指导。
  • Node-ar-drone——无人机编程的Node库
  • Parrot AR Drone 2.0
  • OpenCV——使用OpenCV的帮助
  • Scikit-image——更高层次的视觉处理库
  • Dronestream——Parrot AR 2.0的实时视频反馈
  当然,这里有本文中使用的代码。