嚴羽 王永眾 楊來邦



摘 要:民用領域上無人機的航拍系統,不僅給專業工作者帶來方便,還給普通生活帶來了樂趣。本文以無人機本身自帶的圖傳系統為基礎通過視頻處理技術將視頻流處理后通過Android手機4G網絡實時上傳到搭建好的服務器上,同時也將無人機飛行參數一并上傳,然后通過PC端獲取服務器的視頻流數據以及飛行參數,PC端通過軟件能實時顯示前方無人機拍攝的畫面以及當前無人機的飛行狀態。
關鍵詞:無人機;實時圖傳;視頻傳輸文章編號:2095-2163(2019)04-0065-06 中圖分類號:TP311.52 文獻標志碼:A
0 引 言
無人機出現在1917年,早期的無人駕駛飛行器的研制和應用主要用作靶機,應用范圍主要是在軍事上,后來逐漸用于作戰、偵察及民用遙感飛行平臺。20世紀80年代以來,隨著計算機技術、通訊技術的迅速發展以及各種數字化、重量輕、體積小、探測精度高的新型傳感器的不斷面世,無人機的性能不斷提高,應用范圍和應用領域迅速拓展。世界范圍內的各種用途、各種性能指標的無人機的類型已達數百種之多。續航時間從1 h延長到幾十個h,任務載荷從幾kg到幾百kg,這為長時間、大范圍的遙感監測提供了保障,也為搭載多種傳感器和執行多種任務創造了有利條件。
近年來,傳統的衛星遙感系統,已經不能夠滿足于高分辨率影像的需求,所以在這樣的情形之下,無人機[1]因其自身所獨有的優勢被廣泛應用,成為了衛星遙感系統檢測的補充。
無人機遙感技術可快速地對地質環境信息和過時的 GIS數據庫進行更新、修正和升級 。為政府和相關部門的行政管理、土地、地質環境治理,提供及時的技術保證。
隨著國家改革開放的逐步深入,經濟建設迅猛發展,各地區的地貌發生巨大變遷。現有的航空遙感技術手段已無法適應經濟發展的需要。新的遙感技術為日益發展的經濟建設和文化事業服務。以無人駕駛飛機為空中遙感平臺的技術,是為適應這一需要而發展起來的一項新型應用性技術,能夠較好地滿足現階段國家對航空遙感業務的需求,對陳舊的地理資料進行更新。
目前低空無人機遙感技術在航拍、航測、國土、農業、環保、應急救災和科學研究等領域應用廣泛[2-3],無人機遙感航空技術以低速無人駕駛飛機為空中遙感平臺,用彩色、黑白、紅外、攝像技術拍攝空中影像數據;并用計算機對圖像信息加工處理。該系統在設計和最優化組合方面具有突出的特點,是集成了遙感、遙控、遙測技術與計算機技術的新型應用技術。
1 無人機飛行參數獲取與圖傳系統
1.1 軟硬件平臺簡介
(1)大疆系列無人機一臺;
(2)搭載Android系統手機一臺(要帶有4 G流量卡);
(3)X86系列多核處理服務器一臺,該服務器要固定IP同時要有大容量帶寬;
(4)通用計算機一臺作為接收終端使用。
本文無人機采用大疆精靈系列無人機。該無人機搭載了6個視覺傳感器、主相機、2組紅外傳感器、1組超聲波傳感器、GPS/GLONASS雙模衛星定位系統、IMU和指南針雙冗余傳感器。通過無人機的無線傳輸設備連接遙控器,遙控器一端與手機相連。無人機將拍攝到的圖像畫面以及飛行參數通過無線傳回到遙控器,手機從遙控器獲取數據并通過4G網絡傳遞到已經搭建好的服務器平臺,PC端通過網絡可以實時獲取無人機拍攝的影像以及各飛行參數。
1.2 H264視頻流簡介
H264是新一代的編碼標準,以高壓縮、高質量和支持多種網絡的流媒體傳輸著稱[4]。在H264協議里定義了3種幀:完整編碼的幀叫I幀;參考之前的I幀生成的只包含差異部分編碼的幀叫P幀;參考前后的幀編碼的幀叫B幀。 H264采用的核心算法是幀內壓縮和幀間壓縮,幀內壓縮是生成I幀的算法,幀間壓縮是生成B幀和P幀的算法。3種幀的說明:
1.2.1 I幀
I幀:幀內編碼幀。為關鍵幀,是一幀畫面的完整保留;解碼時只需要I幀就可以完成。
I幀特點:
(1)解碼時僅用I幀的數據就可重構完整圖像;
(2)I幀是P幀和B幀的參考幀;
(3)I幀是基礎幀即第一幀,在一組中只有一個I幀;
(4)幀所占數據的信息量比較大。
1.2.2 P幀
P幀:前向預測編碼幀。P幀表示的是這一幀跟之前的一個關鍵幀(或P幀)的差別,解碼時需要用之前緩存的畫面疊加上本幀定義的差別,生成最終畫面。
P幀特點:
(1)P幀是I幀后面相隔1~2幀的編碼幀;
(2)P幀采用運動補償的方法傳送其與前面的I或P幀的差值及運動矢量(預測誤差);
(3)解碼時必須將I幀中的預測值與預測誤差求和后才能重構完整的P幀圖像;
(4)P幀屬于前向預測的幀間編碼。其只參考前面最靠近其的I幀或P幀;
(5)由于P幀是參考幀,可能造成解碼錯誤的擴散。
1.2.3 B幀
B幀:雙向預測內插編碼幀。是雙向差別幀,也就是B幀記錄的是本幀與前后幀的差別。要解碼B幀,不僅要取得之前的緩存畫面,還要解碼之后的畫面,通過前后畫面與本幀數據的疊加取得最終的畫面。
B幀特點
(1)B幀是由前面的I或P幀和后面的P幀來進行預測的;
(2)B幀傳送的是其與前面的I或P幀和后面的P幀之間的預測誤差及運動矢量;
(3)B幀壓縮比最高,因為其只反映參考幀間運動主體的變化情況,預測比較準確;
H264功能分為2層:視頻編碼層面(VCL)和網絡抽象層面(NAL)。其中,前者負責有效表示視頻數據的內容,而后者負責格式化數據并提供頭信息,以保證數據適合各種信道和存儲介質上的傳輸[5]。因此每幀數據就是一個NAL單元(SPS與PPS除外)。在實際的H264數據幀中,往往幀前面帶有00 00 00 01 或 00 00 01分隔符,一般來說編碼器編出的首幀數據為PPS與SPS,接著為I幀。
通過NALU類型人們可以判斷幀類型見表1。
以00 00 00 01分割之后的下一個字節就是NALU類型,將其轉為二進制數據后:
(1)第1位禁止位,值為1表示語法出錯;
(2)第2~3位為參考級別;
(3)第4~8為是nal單元類型。
圖1所示是從無人機視頻流中截取的視頻文件,以16進制的格式打開的視頻流數據。
從劃紅線處可以看出以00 00 00 01分割之后有67 68 65 61
其中,0x67的二進制碼為:0110 0111
4-8位00111,轉為十進制7,參考表1,7對應序列參數集SPS。
其中,0x68的二進制碼為:0110 1000
4-8位01000,轉為十進制8,參考表1,8對應圖像參數集PPS。
其中,0x65的二進制碼為:0110 0101
4-8位00101,轉為十進制5,參考表1,5對應IDR圖像中的片(I幀)。
其中,0x61的二進制碼為:01100001
4-8位00001,轉為十進制1,參考表1,1對應非IDR圖像的片即非I幀,因為該幀在I幀后面則根據P幀特點可以判斷出是P幀而不可能是B幀。
H264的SPS和PPS串,包含了初始化H264解碼器所需要的信息參數,包括編碼所用的profile、level、圖像的寬和高,deblock濾波器等。SPS中的信息至關重要。如果其中的數據丟失或出現錯誤,那么解碼過程很可能會失敗,分離H264碼流時,需要首先寫入SPS和PPS,否則會導致分離出來的數據沒有SPS、PPS而無法播放。
1.3 H264視頻流處理
手機端實時從遙控器獲取的H264視頻流是一段非常簡短且由多個或一個幀組成的流文件。如圖1所示,所做的工作就是把每一幀給分離出來。處理的方法是組建一個通道,該通道的作用是一端一直獲取傳輸過來的H264視頻流且將該視頻流的每一幀分離出來,通過管道傳輸到另一端。而另一端封裝成flv的格式通過網絡發送到服務器,為保證視頻質量以及實時性,上述過程都是并行化執行。因為實時圖傳對運行效率有較高的要求,因此上述涉及到大量運算的過程都是在c環境中運行的。
1.4 RTMP協議
本地視頻流數據需要通過流媒體協議(如RTMP、HTTP、UDP、TCP、RTP等)推送至服務器,而本文以RTMP協議推送為主。
RTMP是Real Time Messaging Protocol(實時消息傳輸協議)的首字母縮寫。該協議基于TCP,是一個協議族,包括RTMP基本協議及RTMPT/RTMPS/RTMPE等多種變種[6]。RTMP是一種設計用來進行實時數據通信的網絡協議,主要用來在Flash/AIR平臺和支持RTMP協議的流媒體/交互服務器之間進行音視頻和數據通信。支持該協議的軟件包括Adobe Media Server/Ultrant Media Server/red5等。
通過從官網下載的開源函數庫編譯后的libtrmp提供的API來進行推流,其中的RTMP協議已經封裝在相關函數中。librtmp提供的API中只需要常見的幾個API就可以將數據流推送到服務器,所需要的API如下:
RTMP_Init()//初始化結構體
RTMP_Free()
RTMP_Alloc()
RTMP_SetupURL()//設置rtmp server地址
RTMP_EnableWrite()//打開可寫選項,設定為推流狀態
RTMP_Connect()//建立NetConnection
RTMP_Close()//關閉連接
RTMP_ConnectStream()//建立NetStream
RTMP_DeleteStream()//刪除NetStream
RTMP_SendPacket()//發送數據
推流函數流程如圖2所示。
1.5 基于RTMP協議的H264推流器
通過RTMP協議將H264格式的視頻流發送到服務器中實現推流效果具體程序流程如圖3所示。
整個程序框圖包含4個接口函數:
GetH264():獲取H264視頻流數據。
RTMPH264_Connect():建立RTMP連接。
RTMPH264_Send():發送數據。
RTMPH264_Close():關閉RTMP連接。
按照先后順序依次調用上述函數可以推流H264格式視頻流到服務器。上述4個接口函數中又包含以下函數,這些函數主要功能如下:
GetH264()中包含以下函數:
FilterH264():對原始視頻流過濾。
H264toMem():對接收到的H264視頻流以隊列的形式放入內存緩存中。
RTMPH264_Connect()中包含以下函數:
InitSockets():初始化Socket。
RTMP_Alloc():為結構體“RTMP”分配內存。
RTMP_Init():初始化結構體“RTMP”中的成員變量。
RTMP_SetupURL():設置輸入的RTMP連接的URL。
RTMP_EnableWrite():發布流的時候必須要使用。如果不使用則代表接收流。
RTMP_Connect():建立RTMP連接,創建一個RTMP協議規范中的NetConnection。
RTMP_ConnectStream():創建一個RTMP協議規范中的NetStream。
RTMPH264_Send()中包含以下函數:
ReadFirstNaluFromBuf():從內存中讀取出第一個NAL單元。
ReadOneNaluFromBuf():從內存中讀取出一個NAL單元。
H264_decode_sps():解碼SPS,獲取視頻的寬、高、幀率信息。
SendH264Packet():發送一個NAL單元。
SendH264Packet()中包含以下函數:
SendVideoSpsPps():如果是關鍵幀,則在發送該幀之前先發送SPS和PPS。
SendPacket():組裝一個RTMPPacket,調用RTMP_SendPacket()發送出去。
RTMP_SendPacket():發送一個RTMP數據RTMPPacket。
RTMPH264_Close()中包含以下函數:
RTMP_Close():關閉RTMP連接。
RTMP_Free():釋放結構體“RTMP”。
CleanupSockets():關閉Socket。
1.6 服務器技術
由于視頻的實時性對服務器提出較高的要求,數據傳輸時要盡量避免延遲所以選用較為成熟的nginx web服務器來接收發送過來的視頻流。
Nginx("engine x")是一款由俄羅斯程序設計師Igor Sysoev所開發高性能的 Web和反向代理服務器,也是一個 IMAP/POP3/SMTP代理服務器[7]。該服務器具有以下優勢:
(1)Nginx使用基于事件驅動架構,使得其可以支持數以百萬級別的TCP連接。
(2)高度的模塊化和自由軟件許可證使得第三方模塊層出不窮。
(3)Nginx是一個跨平臺服務器,可以運行在Linux、 FreeBSD、 Solaris、 AIX、 Mac OS、 Windows等操作系統上。
最重要的一點是該服務器完全開源,可以通過自己的需要專門定制自己的需求同時該服務器具有的模塊化更易于開發和維護,這些優秀的設計帶來的極大的穩定性能,使得大量從事軟件開發的人員使用和維護之。
Nginx帶有的RTMP模塊使得配置該服務器變得非常簡單且高效,因此配置時只要修改nginx.conf配置文件就行,在配置文件中主要加入以下程序:
rtmp {
server {
listen 1935;
application rtmplive{
live on;
record off;
}
}
RTMP表示的是網絡傳輸RTMP協議而非HTTP協議,其中1935是監聽端口,服務器一直監聽該端口若有數據進來則從該端口拉取數據,獲取的數據可以存放在硬盤或內存中,本文涉及到延遲問題則直接存放在內存中。
1.7 程序流程圖及主要代碼
具體的程序流程如圖4所示。
上述流程是無人機視頻流部分,下面是關于無人機相關參數獲取過程,其中包括:高度、速度、經緯度、飛行參數等。
Aircraft aircraft = (Aircraft) DJISDKManager.getInstance().getProduct();
if(aircraft!=null){aircraft.getFlight Controller().setStateCallback(new
FlightControllerState.Callback(){
Public void onUpdate(FlightController State flightControllerState){
if (flightControllerState.getGPSSignal Level() != null) {
GPSSignalLevel gpsSignalLevel=flight ControllerState.
getGPSSignalLevel();
....}
}
});}
函數onUpdate()是獲取無人機飛行時的相關參數包括GPS信號強度、當前飛行的高度、速度、經緯度、飛行時的姿態角等。
public String SendData(String url, Wurenji express, String contentType) {
try {
vehicle = new JSONStringer().object().key("Wurenji").object()
.key("guid").value(express.guid)
.key("Site").value(express.Site)
.key("Pitch").value(express.Pitch)
.key("Yaw").value(express.Yaw)
.key("Roll").value(express.Roll)
....
.endObject();
StringEntity entity = new StringEntity(vehicle.toString(), encode);