Skip to content

Commit ad13f00

Browse files
author
ictar
committed
add three raw posts from pycoder weekly #213
1 parent f415b15 commit ad13f00

3 files changed

Lines changed: 589 additions & 0 deletions

File tree

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
原文:[Building a (semi) Autonomous Drone with Python](http://blog.yhat.com/posts/autonomous-droning-with-python.html)
2+
3+
---
4+
5+
They might not be delivering our mail ([or our burritos](http://tacocopter.com/)) yet, but drones
6+
are now simple, small, and affordable enough that they can be considered a toy. You can
7+
even customize and program some of them via handy dandy Application Programming Interfaces ([APIs](https://www.quora.com/What-is-an-API-4))!
8+
The Parrot AR Drone has an API that lets you control not only the drone's movement but also stream video and images from its camera.
9+
In this post, I'll show you how you can use Python and node.js to build a drone that
10+
moves all by itself.
11+
12+
![](http://blog.yhat.com/static/img/greg-flying-drone.png)
13+
14+
Hold onto your butts.
15+
16+
### The Project
17+
18+
So given that I'm not a drone, or a machine vision professional, I'm going to have
19+
to keep things simple. For this project, I'm going to **teach my drone how to follow
20+
a red object**.
21+
22+
I know, I know, it's a far cry from a T-800 Model 101 (or [even something like this](https://www.youtube.com/watch?v=YQIMGV5vtd4)), but
23+
given my time and budget constraints it's a good place to start! In the meantime, feel free to send your best autonomous terminators or drone swarms my way.
24+
25+
![](http://blog.yhat.com/static/img/terminator.jpg)
26+
27+
No neural net processors here, just node.js and Python.
28+
29+
### The Drone
30+
31+
When I opened my drone on Christmas morning I wasn't entirely sure what I was going
32+
to do with it, but one thing was for certain: This thing was cool. The [AR Drone 2.0](http://ardrone2.parrot.com/) (I know
33+
super lame name) is a quadcopter. If you're imagining those fit in the palm of your hand, single-rotor,
34+
RC gizmos, you're in the wrong ballpark. The first thing I noticed (and was most surprised by)
35+
was how big the AR Drone is. With its "indoor shell" on, it's about 2 feet wide, 2 feet long,
36+
and 6 inches high. It's also kind of loud--in a good way (like a terrify your dog kind of way, unlike [this down to drone pup](https://vimeo.com/95078536)).
37+
Combine that with 2 cameras--one front and one bottom, and you've got yourself the ultimate grown up geek toy.
38+
39+
![](http://blog.yhat.com/static/img/ar-drone.png)
40+
41+
### Programming your drone
42+
43+
What sets the AR Drone apart is that it's old (in drone years)--it was first released in 2012. This
44+
might seem like a bad thing BUT since we're trying to program this gizmo, it's actually
45+
a good thing.
46+
47+
Given that it's had 4 years to "mature", there are some really great APIs, helper
48+
libraries, and project/code samples for controlling/programming the drone (see list of resources below). So in essence, someone
49+
else has already done the hard part of figuring out how to communicate with the drone in bytecode, so all I have
50+
to do is import the _node_module_ and I'm off to the figurative [drone races](http://thedroneracingleague.com/).
51+
52+
Programming the drone is actually quite easy. I'm using the [`ar-drone`](https://github.com/felixge/node-ar-drone) node.js module. I've
53+
found that it works really well despite not being under _super_ active development. To start, let me
54+
show you how to do a pre-programmed flightplan. The following program is going to:
55+
56+
* connect to the drone over wifi
57+
* tell the drone to takeoff
58+
* after 1 second, spin clockwise at full throttle
59+
* 1 second after that, stop and then move forwards at 50% thrust
60+
* another 1 second after that, stop and land
61+
62+
Pretty simple little program. Now even though it's pretty straightforward, I will
63+
still **highly recommend having an _[emergency landing](https://gist.github.com/glamp/0b6f0ef87525e3cefcfb4f5bd146712c)_ script readily available**. Cause
64+
you never know you need one till you really need one ;)
65+
```python
66+
var arDrone = require('ar-drone');
67+
var drone = arDrone.createClient();
68+
drone.takeoff();
69+
70+
drone
71+
.after(1000, function() {
72+
drone.clockwise(1.0);
73+
})
74+
.after(1000, function() {
75+
drone.stop();
76+
drone.front(0.5);
77+
})
78+
.after(1000, function() {
79+
drone.stop();
80+
drone.land();
81+
})
82+
```
83+
<iframe width="420" height="315" src="https://www.youtube.com/embed/Edh98lNtFfo" frameborder="0" allowfullscreen=""></iframe>
84+
85+
86+
You can also pull off some fancier moves--you know, to impress your friends. My personal
87+
favorite is a backflip.
88+
89+
90+
<iframe width="420" height="315" src="https://www.youtube.com/embed/aF8V8p1n3P0" frameborder="0" allowfullscreen=""></iframe>
91+
92+
93+
### Le Machine Vision
94+
95+
Ok now for the second piece of the puzzle: teaching our drone how to see. To do this, we're going
96+
to be using [OpenCV](http://opencv.org/) and the Python module [cv2](http://opencv-python-tutroals.readthedocs.org/). OpenCV can
97+
be a little prickly to work with, but it can do some really impressive stuff and even has
98+
some machine learning libraries baked right into it.
99+
100+
We're going to be using OpenCV to do some basic object tracking. We're going to have the
101+
**camera track anything red** that appears
102+
in its field of vision. Sort of like a bull at a bullfight.
103+
104+
![](http://blog.yhat.com/static/img/bullfight.jpg)
105+
106+
Just like this, except substitute the bull for a drone, and the red cape (_muleta_) for a Greg ☹! Also, my pants aren't quite that tight.
107+
108+
Good news for us is that `cv2` makes this really easy to do.
109+
```python
110+
import numpy as np
111+
import cv2
112+
from skimage.color import rgb2gray
113+
from PIL import Image
114+
from StringIO import StringIO
115+
from scipy import ndimage
116+
import base64
117+
import time
118+
119+
def get_coords(img64):
120+
"Reads in a base64 encoded image, filters for red, and then calculates the center of the red"
121+
# convert the base64 encoded image a numpy array
122+
binaryimg = base64.decodestring(img64)
123+
pilImage = Image.open(StringIO(binaryimg))
124+
image = np.array(pilImage)
125+
126+
# create lower and upper bounds for red
127+
red_lower = np.array([17, 15, 100], dtype="uint8")
128+
red_upper = np.array([50, 56, 200], dtype="uint8")
129+
130+
# perform the filtering. mask is another word for filter
131+
mask = cv2.inRange(image, red_lower, red_upper)
132+
output = cv2.bitwise_and(image, image, mask=mask)
133+
# convert the image to grayscale, then calculate the center of the red (only remaining color)
134+
output_gray = rgb2gray(output)
135+
y, x = ndimage.center_of_mass(output_gray)
136+
137+
data = {
138+
"x": x,
139+
"y": y,
140+
"xmax": output_gray.shape[1],
141+
"ymax": output_gray.shape[0],
142+
"time": time.time()
143+
}
144+
return data
145+
```
146+
147+
As you can see above, I'm using a color mask to filter the pixels in an image. It's
148+
a simple but intuitive approach. And more importantly **it works**. Take a look:
149+
150+
<div class="row">
151+
<div class="col-sm-6">
152+
![](http://blog.yhat.com/static/img/towel-pic.png)
153+
Raw camera feed
154+
</div>
155+
<div class="col-sm-6">
156+
![](http://blog.yhat.com/static/img/towel-pic-filtered.png)
157+
Processed with red filter
158+
</div>
159+
</div>
160+
161+
It's learning! Ok well maybe not quite like a T-800 Model 101, but it's at least a start.
162+
163+
![](http://blog.yhat.com/static/img/terminator-dot.jpg)
164+
165+
Is the red dot a coincidence? Think again...
166+
<div class="row">
167+
<div class="col-sm-6">
168+
![](http://blog.yhat.com/static/img/drone/raw/raw-view.gif)
169+
Raw camera feed
170+
</div>
171+
<div class="col-sm-6">
172+
![](http://blog.yhat.com/static/img/drone/processed/drone-view.gif)
173+
Processed with red filter
174+
</div>
175+
</div>
176+
177+
### Stitching things together
178+
179+
Ok here comes the tricky part. We've got our little node.js script that can control
180+
the drone's navigation, and we've got the python bit that can detect where red things
181+
are in an image, but the question looms: **How do we glue them together?**
182+
183+
![](http://blog.yhat.com/static/img/duct-tape.jpg)
184+
185+
Well my friends, to do this I'm going to use Yhat's own model deployment software, [ScienceOps](https://www.yhat.com/products/scienceops).
186+
I'm going to deploy my Python code onto ScienceOps, where it'll be accessible via an API, and then from node.js
187+
I can call my model on ScienceOps. What this means is that I've boiled my OpenCV red-filtering
188+
model into a really simple HTTP endpoint. I'm using ScienceOps to make my childhood drone bull fighting dreams come true, but
189+
you could use it to embed any R or Python model into any application capable of making API requests, be it drone or otherwise.
190+
191+
![](http://blog.yhat.com/static/img/braveheart-freedom.jpg)
192+
193+
No more recoding to get models into production. FREEDOM!
194+
195+
I don't need to mess around with any cross-platform
196+
baloney, and if I need to up the horsepower of my model (say for instance if I'm controlling more than
197+
one drone), I can let ScienceOps scale out my model automatically. If you want more info about
198+
deploying models (or drones) into production using ScienceOps, head over to our [site](www.yhat.com) or
199+
schedule a demo to see it live.
200+
```python
201+
from yhat import Yhat, YhatModel
202+
203+
class DroneModel(YhatModel):
204+
REQUIREMENTS = [
205+
"opencv"
206+
]
207+
def execute(self, data):
208+
return get_coords(data['image64'])
209+
210+
yh = Yhat(USERNAME, APIKEY, "https://sandbox.yhathq.com/")
211+
yh.deploy("DroneModel", DroneModel, globals(), True)
212+
```
213+
214+
What does all this mean? Well for one, it means my node.js code just got a lot simpler. I can even
215+
use the Yhat node.js library to execute my model:
216+
```python
217+
var fs = require('fs');
218+
var img = fs.readFileSync('./example-image.png').toString('base64');
219+
220+
var yhat = require('yhat');
221+
var cli = yhat.init('greg', 'my-apikey-goes-here', 'https://sandbox.yhathq.com/');
222+
223+
cli.predict('DroneModel', base64edImage, function(err, data) {
224+
console.log(JSON.stringify(data, null, 2));
225+
// {
226+
// "result": {
227+
// "time": 1460067540.30213,
228+
// "total_red": 5.810973333333334,
229+
// "x": 425.0256166460453,
230+
// "xmax": 640,
231+
// "y": 220.03434178824077,
232+
// "ymax": 360
233+
// },
234+
// "version": 1,
235+
// "yhat_id": "529b84c9c4957008446a56faadc152a6",
236+
// "yhat_model": "DroneModel"
237+
// }
238+
var x = data.x / data.xmax - 0.5
239+
, y = data.y / data.ymax - 0.5;
240+
241+
if (x > 0) {
242+
drone.right(Math.abs(x));
243+
} else {
244+
drone.left(Math.abs(x));
245+
}
246+
if (y > 0) {
247+
drone.up(Math.abs(y));
248+
} else {
249+
drone.down(Math.abs(y));
250+
}
251+
});
252+
```
253+
254+
Sweet! Now I can pretty much just drop this into my navigation script. All I need to do is tell
255+
my script how I want to react to the response. In this case it's going to be a couple steps:
256+
257+
* Call the `DroneModel` model hosted on ScienceOps
258+
* If there weren't any errors, look at the result. The result will give me the `x` and `y` coordinates
259+
of the red in the image.
260+
* Make course adjustments to the drone that attempt to move the red to the center of the drone's
261+
field of vision.
262+
263+
So simple! What could possibly go wrong?
264+
265+
266+
<iframe width="560" height="315" src="https://www.youtube.com/embed/ZVDfMPHqHKc?t=7" frameborder="0" allowfullscreen=""></iframe>
267+
268+
269+
### Mending my metaphorical stitching
270+
271+
As the adage goes, If at first you don't succeed try, try again. It took me a few
272+
iterations to get the autonomous piece to actually work. Turns out, combining individual
273+
components has the propensity to compound your error!
274+
275+
But not to worry! My drone took its fair share of bumps and bruises but it's a tough
276+
little guy--**Pro Tip: **You can patch up your drone with duct tape. Just be sure to apply
277+
equal amounts to each side of the drone so it's balanced!
278+
279+
![](http://blog.yhat.com/static/img/drone-duct-tape.jpg)
280+
281+
Duct Tape: More than a metaphor.
282+
283+
A couple of things I learned the hard way:
284+
285+
* **_Build a helper app: _** After a few trial runs I built a helper app (see video below) to
286+
determine what/why things were happening. Let me tell you, **this should've been step #1**. It
287+
was invaluable being able to see what code my program was executing and what the processed
288+
images looked like.
289+
* **_Don't over-correct: _** For simple things like this, if you tell the drone to do
290+
too much at the same time, it freaks out and either (a) just sits there or (b) starts
291+
errantly flying all over the room (see video above).
292+
* **_Always have an emergency landing script handy: _** I mentioned this earlier but it can't
293+
be overemphasized. The reason is that if your program crashes and you haven't landed your
294+
drone, you're in deep ... trouble. Your drone is going to keep flying (possibly errantly) until
295+
you tell it to land. Having [`emergency-landing.js`](https://gist.github.com/glamp/0b6f0ef87525e3cefcfb4f5bd146712c) handy
296+
will save you some maintenance (and possibly from a lawsuit).
297+
* **_If it's windy, don't fly your drone: _** Learned this one the _really_ hard way...
298+
299+
In the end with some persistence and a little luck, I was able to get a couple of
300+
good autonomous runs in!
301+
302+
303+
<iframe width="560" height="315" src="https://www.youtube.com/embed/VdgQajDuA5E" frameborder="0" allowfullscreen=""></iframe>
304+
305+
306+
### In the wild
307+
308+
I wound up presenting this at [PAPIs Valencia](http://www.papis.io/connect-valencia-2016/program) which
309+
was a lot of fun (BTW PAPIs is awesome! I highly recommend it for anyone interested
310+
in predictive analytics). Unfortunately [my PAPIs demo didn't go quite as smoothly](http://www.papis.io/connect-valencia-2016/talks/2016/3/using-ml-to-build-an-autonomous-drone-greg-lamp). The lighting
311+
in the lecture hall was different than in our office and as a result, the red didn't
312+
quite get filtered the same way. Despite the less than stellar performance, it was
313+
still a lot of fun!
314+
315+
### Resources
316+
317+
Want to learn more about programming your own drone? Here are some great resources
318+
for getting started:
319+
320+
* [NodeCopter](http://www.nodecopter.com/) - JS community for drones. Not really active anymore, but has some great getting started guides.
321+
* [node-ar-drone](https://github.com/felixge/node-ar-drone) - Node library for programming your drone.
322+
* [Parrot AR Drone 2.0](http://www.amazon.com/Parrot-Drone-Quadricopter-Edition-Orange/dp/B007HZLLOK) - Buy it here.
323+
* [OpenCV](http://opencv.org/) - Info for using OpenCV.
324+
* [scikit-image](http://scikit-image.org/) - Higher level, more ML focused computer vision library.
325+
* [dronestream](https://github.com/bkw/node-dronestream) - Realtime video feed for Parrot AR 2.0.
326+
327+
Also, here's a [link](https://github.com/yhat/semi-autonomous-drone) to the repo if you want my code.
328+
329+
330+
<iframe width="420" height="315" src="https://www.youtube.com/embed/2fWr6CBARMw" frameborder="0" allowfullscreen=""></iframe>
331+
332+
One for the road.

Python Common/Idiomatic Python

Whitespace-only changes.

0 commit comments

Comments
 (0)