在“造”出能進行物體識別的機器人后,下一步就很清晰了,我要“造”一個能飛的東西!我決定搞一個能自主飛行的無人機,并要它能識別人臉和響應(yīng)語音指令。
選擇一臺成品無人機
開始“黑入”一臺無人機的最難的部分就是如何開始。我最初的選擇是用零件組裝一臺無人機。但和我完成的大部分的DIY項目一樣,從零開始一般都耗費了太多的時間,還不如直接買一個成品。實話實說,我自己組裝的無人機從來沒有正常的飛行過。而買一臺成品機就既省事又省錢。
絕大部分無人機廠商都宣稱提供API接口,但對于業(yè)余玩家來說就沒有一個明確的最佳選擇。大部分能提供貌似可用的API接口的無人機都要超過1000美元。這對于入門級的玩家來說就太貴了。
但經(jīng)過搜索,我發(fā)現(xiàn)(見圖1)是一個低端可“黑”入無人機的極佳選擇。新的售價是200美元,不過很多人在買了以后就不怎么再玩了,所以在eBay上花130美元或更少就能買到一個不錯的二手貨。

圖1:我車庫里的無人機收藏。最左邊的就是Parrot AR無人機。圖片由Lukas Biewald授權(quán)使用
論飛行穩(wěn)定性,Parrot AR型不如更貴的Parrot Bebop 2型(550美元)。但是Parrot AR型所帶的叫node-ar-drone的node.js客戶端庫是非常好用的。
Parrot AR型的另外一個優(yōu)點是它非常皮實、耐摔。在測試自主飛行代碼的時候,我的無人機頻繁地撞到墻、家具、植物甚至是我們家的客人,并墜落。但它還是依舊能正常飛行。
比起“造”能在地上跑的機器人,“造”無人機最不爽的一點就是它的電池可用時間太短了?;旧鲜浅潆妿仔r,飛行十幾分鐘。所以我建議多買兩塊電池,循環(huán)充電使用。
給我的無人機開發(fā)程序
我經(jīng)過實踐發(fā)現(xiàn),Javascript內(nèi)在的事件驅(qū)動的特性使得它成為一種非常好的控制無人機的語言。請相信我,無人機飛行的時候,是有非常多的異步事件發(fā)生。我寫Node.JS的時間并不長,但在這個項目過程中,我對它的印象非常深刻。上一次我正兒八經(jīng)地為機器人寫程序用的是C語言。處理C語言的線程和異常是一件非常讓人頭疼的事,所以我盡量避免再使用它。我希望有人能為其他的機器人平臺開發(fā)出JavaScript的接口,因為這會讓為機器人開發(fā)程序(完全無法預(yù)知會發(fā)生什么事)變得更簡單更有趣。
架構(gòu)設(shè)計
我決定在我的筆記本上運行控制邏輯,同時在云端運行機器學習的部分。這種架構(gòu)設(shè)計比在樹莓派板上直接運行神經(jīng)網(wǎng)絡(luò)的延遲要低。我認為這種架構(gòu)對于業(yè)余無人機項目而言是合適的。
微軟、谷歌、IBM和亞馬遜都提供快速且便宜的機器學習API。最終我選擇了微軟的認知服務(wù)API來完成這個項目,因為它是唯一提供定制化的人臉識別功能的API。
圖2里展示了整個無人機項目的系統(tǒng)架構(gòu)。

圖2:智能無人機的系統(tǒng)架構(gòu)。圖片由Lukas Biewald授權(quán)使用
開始動手
默認地,Parrot AR無人機2.0版自己可以提供一個無線網(wǎng)絡(luò),供客戶端接入,但這個是功能卻極度煩人。每次你要實驗點東西,你都需要從本地網(wǎng)絡(luò)斷開,再連到無人機的無線網(wǎng)里。好消息是,有一個叫ardrone-wpa2的項目,非常有用,用它你可以“黑”進無人機,并讓無人機連到你自己的無線網(wǎng)絡(luò)里。
Telnet到無人機操作系統(tǒng)上并游蕩一番是挺有趣的。Parrot無人機使用的是一個簡化版的Linux操作系統(tǒng)。你上次Telnet到某個系統(tǒng)上是什么時間哪?下面的命令例子就演示了如何打開一個終端并直接登錄到無人機的操作系統(tǒng)上。
% script/connect “The Optics Lab” -p “particleorwave” -a 192.168.0.1 -d 192.168.7.43
% telnet 192.168.7.43
通過命令行來控制飛行
在安裝了node庫之后,下一步就是生成一個node.js的命令行運行環(huán)境,然后就可以開始控制你的無人機了:
var arDrone = require(‘ar-drone’);
var client = arDrone.createClient({ip: ‘192.168.7.43’});
client.createRepl();
drone> takeoff()
true
drone> client.animate(‘yawDance, 1.0)
如果你按照我上面所說的一步一步地實驗到這里,你的無人機肯定已經(jīng)墜落過了——至少好幾次。我已經(jīng)無數(shù)次地把我的無人機的保護外殼給它粘回機身上,直到它徹底解體,隨后我只好再買了一個新的。我不得不說,其實Parrot AR型在沒有保護外殼的時候飛行得更好。但這種方式會使無人機變得很危險。因為沒有保護外殼的話,一旦無人機撞到東西,它的螺旋槳就會直接打到物體上,并留下刮痕。
從網(wǎng)頁上控制無人機飛行
為無人機開發(fā)一個基于網(wǎng)頁的控制頁面挺簡單且效果不錯。用如下所示的express.js框架就可以搭建一個很小巧的網(wǎng)頁服務(wù)器。
var express = require(‘express’);
app.get(‘/’, function (req, res) {
res.sendFile(path.join(__dirname + ‘/index.html’));
});
app.get(‘/land’, function(req, res) {
client.land();
});
app.get(‘/takeoff’, function(req, res) {
client.takeoff();
});
app.listen(3000, function () {
});
我用下面的代碼來通過一個按鈕發(fā)送AJAX請求。
<html>
<script language=’javascript’>
function call(name) {
var xhr = new XMLHttpRequest();
xhr.open(‘GET’, name, true);
xhr.send();
}
</script>
<body>
<a onclick=”call(‘takeoff’);”>Takeoff</a>
<a onclick=”call(‘land’);”>Land</a>
</body>
</html>
從無人機上導出視頻流
我發(fā)現(xiàn)把無人機上的攝像頭拍攝的視頻導出的最佳方法就是:建立一個持續(xù)的連接,并把攝像頭拍攝的PNG圖片發(fā)送到我的網(wǎng)站的網(wǎng)頁上。通過使用AR無人機的庫(見下面的代碼),網(wǎng)頁服務(wù)器不斷地把無人機攝像頭拍攝的PNG畫面拉取出來。
var pngStream = client.getPngStream();
pngStream
.on(‘error’, console.log)
.on(‘data’, function(pngBuffer) {
sendPng(pngBuffer);
}
function sendPng(buffer) {
res.write(‘–daboundary\nContent-Type: image/png\nContent-length: ‘ + buff
er.length + ‘\n\n’);
res.write(buffer);
});
對從無人機獲取的圖像進行人臉識別
微軟的Azure Face API系統(tǒng)很容易上手,且功能強大。你上傳你朋友的照片給它,這個系統(tǒng)就能識別出他們是誰。它也能猜測人物的年齡和性別。我發(fā)現(xiàn)這兩個功能的識別準確率是驚人的高。整個識別的延遲大概是200毫秒。識別1000次請求花費1.5美元。對我而言,這個價格對于這種應(yīng)用是相當合理的。下面是我的代碼,它實現(xiàn)了發(fā)送圖片給API來做人臉識別的功能。
var oxford = require(‘project-oxford’),
oxc = new oxford.Client(CLIENT_KEY);
loadFaces = function() {
chris_url = “https://media.licdn.com/mpr/mpr/shrinknp_400_400/AAEAAQAAAAAAAALyAAAAJGMyNmIzNWM0LTA5MTYtNDU4Mi05YjExLTgyMzVlMTZjYjEwYw.jpg”;
lukas_url = “https://media.licdn.com/mpr/mpr/shrinknp_400_400/p/3/000/058/147/34969d0.jpg”;
oxc.face.faceList.create(‘myFaces’);
oxc.face.faceList.addFace(‘myFaces’, {url => chris_url, name=> ‘Chris’});
oxc.face.faceList.addFace(‘myFaces’, {url => lukas_url, name=> ‘Lukas’});
}
oxc.face.detect({
path: ‘camera.png’,
analyzesAge: true,
analyzesGender: true
}).then(function (response) {
if (response.length > 0) {
drawFaces(response, filename)
}
});
我用了ImageMagick庫來對我收集的PNG圖片做打標簽,效果相當好。對于這個部分其實可以有很多的擴展可能。比如用一個情感API來識別人臉所表現(xiàn)出來的情感。
運用語音來控制無人機
進行語音識別部分開發(fā)的難點并不是識別本身,而是如何把語音流從運行在我本地服務(wù)器上的網(wǎng)頁里轉(zhuǎn)換成微軟Speech API可以使用格式。下面的代碼就是實現(xiàn)這個功能的。一旦你能把語音保存成單聲道和以正確的采樣頻率采樣后,這個語音識別API就能很方便地識別語音內(nèi)容。這個API的花費是1000次請求4美元。對于業(yè)余應(yīng)用來說,基本相當于是免費了。
RecordRTC是一個很好的庫,可以用來作為以網(wǎng)頁為客戶端的語音采集的新手入門的工具。在客戶端,我就加入了保存語音文件的代碼。
app.post(‘/audio’, function(req, res) {
var form = new formidable.IncomingForm();
// 設(shè)定允許客戶在一個請求里上傳多個文件
form.multiples = true;
form.uploadDir = path.join(__dirname, ‘/uploads’);
form.on(‘file’, function(field, file) {
filename = “audio.wav”
fs.rename(file.path, path.join(form.uploadDir, filename));
});
// 記錄發(fā)生的錯誤日志
form.on(‘error’, function(err) {
console.log(‘An error has occured: \n’ + err);
});
// 一旦所有文件上傳完成,才給客戶端發(fā)相應(yīng)
form.on(‘end’, function() {
res.end(‘success’);
});
// 解析出請求里包含的表單數(shù)據(jù)
form.parse(req)
speech.parseWav(‘uploads/audio.wav’, function(text) {
console.log(text);
controlDrone(text);
});
});
我使用FFmpeg工具來降低音頻的采樣率,并把多聲道合并成單聲道,以供微軟API使用。
exports.parseWav = function(wavPath, callback) {
var cmd = ‘ffmpeg -i ‘ + wavPath + ‘ -ar 8000 -ac 1 -y tmp.wav’;
exec(cmd, function(error, stdout, stderr) {
console.log(stderr); // command output is in stdout
});
postToOxford(callback);
});
盡管我開發(fā)的功能就是這些,但是還是可以繼續(xù)擴展。比如用微軟的文字變語音的API來讓無人機說話!
開發(fā)自主搜索路徑
我使用ardrone-autonomy庫來為無人機開發(fā)自主搜索路徑。在此過程中,我無數(shù)次地把無人機弄得撞到了客廳的家具和植物上。最后,我妻子很“客氣”地建議我去車庫里繼續(xù)我的項目,因為那里沒多少可以撞的東西。但是車庫的地方有點小,使得操控空間有限。

圖3:在我的“實驗室”里試飛無人機。圖片由Lukas Biewald授權(quán)使用
在我能有一個更大的實驗空間后,我會嘗試更智能的搜索算法。不過,現(xiàn)在我還是只會讓無人機做起飛和旋轉(zhuǎn)的動作,以此來搜索發(fā)現(xiàn)人,并識別是敵還是友。
var autonomy = require(‘ardrone-autonomy’);
var mission = autonomy.createMission({ip: ‘10.0.1.3’, frameRate: 1, imageSize: ‘640:320’});
console.log(“Here we go!”)
mission.takeoff()
.zero()? ?????? // 把當前狀態(tài)作為參考基準
.altitude(1)
.taskSync(console.log(“Checkpoint 1”))
.go({x: 0, y: 0, z: 1, yaw: 90})
.taskSync(console.log(“Checkpoint 2”))
.hover(1000)
.go({x: 0, y: 0, z: 1, yaw: 180})
.taskSync(console.log(“Checkpoint 3”))
.hover(1000)
.go({x: 0, y: 0, z: 1, yaw: 270})
.taskSync(console.log(“Checkpoint 4”));
.hover(1000)
.go({x: 0, y: 0, z: 1, yaw: 0
.land()
全都搞定后的效果
看下面的視頻。我讓無人機起飛并去找我的朋友Chris:
結(jié)論
在一切都配置妥當之后,就可以通過API來控制無人機,獲得拍攝到的視頻圖片,這一切都爽爆了!隨著新的圖像識別技術(shù)可供使用,可能的應(yīng)用必將越來越多。比如,讓無人機根據(jù)房屋平面圖來刷墻。雖然Parrot無人機并不是設(shè)計來為在狹小空間(比如我的房子)里安全飛行的,但隨著無人機越來越皮實,價格變得更低,我相信真正有用的應(yīng)用將會進入爆發(fā)期。
微軟的認知服務(wù)云API是相當?shù)煤糜们冶阋恕W畛跷冶容^擔心無人機所用的廣角攝像頭所拍攝的圖片會影響人臉識別的準確度,另外螺旋槳的噪聲可能會對語音識別產(chǎn)生干擾。但整體而言,這兩個API的表現(xiàn)遠超我的期望。同時處理延遲也低于我的預(yù)期。從架構(gòu)設(shè)計角度來看,在云端運行機器學習實時圖像處理似乎是一個奇怪的選擇,但它可能會成為未來很多應(yīng)用的架構(gòu)選擇。
Lukas Biewald
Lukas Biewald是CrowdFlower的創(chuàng)始人兼CEO。CrowdFlower始于2009年,是一個數(shù)據(jù)增強的平臺,可以幫助企業(yè)獲得隨需的人力來收集、產(chǎn)生訓練數(shù)據(jù),以及參與人-機器學習循環(huán)的工作。 在從斯坦福大學拿到數(shù)學學士和計算機科學碩士學位后,Lukas領(lǐng)導了雅虎日本的搜索相關(guān)團隊。隨后他去了Powerset,作為一個資深數(shù)據(jù)科學家進行工作。2008年P(guān)owerset被微軟收購。Lukas還被《公司》雜志評選為30位30歲以下的著名人士。 Lukas還是一位專家級的圍棋選手。

