三維資料用二維影象呈現,如何利用 Python 繪製酷炫的 車輛軌跡
問題由來
最近遇到了一類圖,非常好看,並且在其他論文中也多次遇到。比如,在 Trajectory data-based traffic flow studies: A revisit 一文中的圖 1 ,如下圖所示1:
由於原圖較長,這裡僅引用了部分圖。從上圖中可以清楚的看出幾個關鍵資訊:
- 橫軸表示時間變化,說明資料需要是時間序列的
- 縱軸表示空間位置,可以理解為從離開某一道路截面後,車輛行駛的距離
- 每條軌跡線均表示一輛車的行駛路徑變化,而線條的顏色則表示瞬時速度值
因此,如果想要繪製出上圖,那麼就需要有車輛的瞬時軌跡、速度、時間等資訊的資料集。
PS:如有需要Python學習資料的小夥伴可以加下方的群去找免費管理員領取
可以免費領取原始碼、專案實戰視訊、PDF檔案等
2. 準備資料
為了嘗試繪製出該影象,現在需要做兩個工作。第一,找到合適的資料;第二,找到順手的繪圖工具。
基礎資料
對於 NGSIM 資料集,作簡要介紹2:其中包含 4 個路段的車輛軌跡資料,任何一個均是採用在路段的周邊高層建築上設定高清攝像機錄影,然後通過影象處理,將每輛車的軌跡變化採集出來,進而可計算出車輛所在的車道、瞬時速度、瞬時加速度、瞬時車頭時距等等。
作為與本次繪圖相關的關鍵資訊,我們僅需知道以下資料:
- 作為演示,僅使用一個車道的資料即可
- 每輛車從進入攝像區域到離開,中間的瞬時速度資訊,NGSIM 中對應的資料標籤是 v_Vel
- 每輛車從進入攝像區域到離開,中間的位置座標變化,NGSIM 中對應的資料標籤是 Local_X、Local_Y
- 與之對應的時刻,NGSIM 中對應的資料標籤是 Global_Time
好了,知道了上述三個關鍵資料後,就可以利用繪圖工具繪製了。
繪圖工具
考慮到可重複性、可移植性和方便程度,本次採用 Python 中的 Matplotlib 包來繪製
3. 開始繪圖
在開始繪圖之前,需要在 Python 環境中已經配置好以下三個包:
Pandas,本文主要作用在於讀取、處理資料
Numpy,本文主要用於科學計算
Matplotlib,本文主要用於繪圖
匯入必要的包
毫無疑問,這幾個包是會被用到的,因此,我們先在程式中匯入它們:
import pandas as pd import numpy as np import matplotlib import matplotlib.pyplot as plt
準備資料
然後,我們需要將包含時間、位移、速度的資料匯入 Python 中。這裡需要注意,按照常規思路,我們只需將這三類資料賦值給不同變數即可,但本次繪製的圖則不能直接這樣做。
因為,從圖 1 中我們可以看出,該圖是有很多軌跡線組成的,也就是說每輛車都是一組資料,並且依賴於時間變化,且存在先後順序。
比如,同一車道內,1 號先進入研究區域,那麼它的資料則被記錄;隨後,2 號車進入研究區域,同樣被記錄位置變化、瞬時速度、時間。但是,這裡的時間是不同的,是逐漸推移的。
一個思路是同時將所有車輛繪製在:X 軸是時間變化,Y 軸是位置變化的影象中。但是,這個操作需要將每輛車的資料均準備好,這對於同時繪製 300~400 輛車的軌跡圖是不現實的。
為了解決這個問題,一個可行的思路是:迴圈繪製每輛車的軌跡隨時間變化。這樣可以只讀取一次資料,然後依據車輛 ID 來迴圈繪製在同一座標象限內。由於每輛車存在先後順序,因此無需擔心每輛車之間的軌跡重疊。
於是,這一步我們需要做的是:
- 讀取基礎資料集,並篩選出需要使用的某車道資料,比如車道 3
- 需要將該車道中所有的車輛 ID 提取出來,並且是依據時間先後順序排序,也就是根據資料標籤 Global_Time 來排序,這個是全域性時間
於是,我們可寫出以下程式碼(所有程式碼可以左右滑動):
# 讀取資料 data = pd.read_csv(r'F:\NGSIMData.csv') # 提取車道資料 lanedata = data[data.Lane_ID == 3] # 提取車輛編號 x_vehID = lanedata.drop_duplicates(['Vehicle_ID']) # 依據 Global_Time 按照時間先後順序排序 x_vehID = x_vehID.sort_values(by='Global_Time') # 對排序後的車輛 ID 的索引進行重置,方便索引 x_vehID = x_vehID.reset_index(drop = True)
基本設定與繪圖
到這裡,我們已經將某個車道的資料提取了出來,並且將其中所有的車輛編號也提取出來了,且按照時間先後順序。此時,僅需要按照前文說的:迴圈繪製每輛車的軌跡即可。在開始之前,我們需要先預設一下圖的大小,以及字型屬性。
# 匯入字型屬性相關的包或類 from matplotlib.font_manager import FontProperties # 預設字型型別、大小 font = FontProperties(fname=r"C:\Windows\Fonts\times.TTF", size=10) #設定畫布的尺寸 plt.figure(figsize=(10, 4))
接下來就是迴圈繪製軌跡圖了。這裡有幾個問題需要解釋一下:
- 既然是迴圈繪製,自然就是每次繪製的思路完全一致,具體體現在程式碼中
- 對於單次繪圖,僅需通過第一個車輛 ID 索引對應所有的資料,然後將時間、位移、速度分別賦值給 x、y、v
- 注意,對於時間,NGSIM 給出的是時間戳格式,需要轉換
- 注意,對於距離和速度,NGSIM 給出的單位是英尺,需要轉換
- 時間將體現在 X 軸,距離將體現在 Y 軸,速度將體現在軌跡線的顏色上
其中,也有一個棘手的問題。那就是,假設某一車輛進入某車道一段時間後,換道至其他車道,隨後過了一段時間,該車輛又換道至當前車道。這樣就會出現,在時間的前半部分有軌跡線,中間部分沒有軌跡線,後半部分有軌跡線。
對於這種情況,如果我們選擇折線圖繪製,其便會將斷開的部分直接用線段連線起來,這是折線圖的特徵。但是,對於這類現象,我們並不期望它們是連線的,也就是沒有軌跡的時間段內,最好是空白的。
為了解決這個問題,我選擇了散點圖。由於 NGSIM 採集資料的頻率是 0.1s,資料點非常密集,也就是說對於連續的軌跡,即便我們用散點圖繪製,出來的效果也是類似於折線的。比如:
注意,右上角是有斷開的區域的,如果是用折線圖繪製,就不會出現這種應該出現的效果。
另一個問題是顏色的處理,也就是需要將每輛車的速度值對應到同一時刻的軌跡點上。而這一點,散點圖中可以將散點的顏色對映給數值,也就是我們需要的,具體就是程式碼中的 c=v,但這個命令僅是將速度值對映給顏色。
如果要將某一顏色主題應用於軌跡圖中,那麼就需要做兩個工作:
- 定義顏色主題,也就是散點圖中的命令 cmap='jet_r'
- 因為後邊我們需要設定顏色條圖例,所以需要將顏色條中的顏色數值與軌跡圖中的顏色歸一化,讓其一一對應。
我們給出這一迴圈繪製軌跡-速度圖的程式碼:
i = 0 while i < (len(x_vehID)-1): # 迴圈繪製軌跡圖 cardata = lanedata[lanedata.Vehicle_ID == x_vehID[i]] # 將時間賦值給變數 x x = cardata['Global_Time'] # 計算相對移動距離,並賦值給變數 y y = np.sqrt(np.square(cardata['Local_Y']) + np.square(cardata['Local_X']) ) # 將速度賦值給變數 v,同時定義速度為顏色對映 v = cardata['v_Vel'] #設定每個圖的colormap和colorbar所表示範圍是一樣的,即歸一化 norm = matplotlib.colors.Normalize(vmin=0, vmax=25) # 繪製散點圖 ax = plt.scatter(x,y, marker = '.', s=1, c=v, cmap='jet_r', norm = norm) print(i) i = i + 1
其中,我們對 y 值求了平方和的開方,也就是根據車輛的橫縱座標計算位移。我們還將繪製的散點圖 ax = plt.scatter 賦給了 ax ,方便後邊對散點圖的屬性進行設定。
這裡需要注意的是,對於散點的繪製,我們利用迴圈解決。而對於座標軸等的設定,只需設定一次,無需迴圈。到這裡,我們可以得到一個不完美的軌跡-速度圖:
不知為何,初步繪製出來的圖的兩側存在大量的空缺,猜測可能是由於我在資料篩選階段出了問題,又或者這些本就沒有資料點。無論怎樣,這樣不影響我們來學習繪製該類影象。
4. 美化影象
新增顏色條圖例
此時,需要再給上圖加一個顏色條圖例,以表示不同顏色對應的速度數值,這裡只需要兩行程式碼搞定:
# 新增顏色條 plt.clim(0, 25) plt.colorbar()
即可得到如下所示的影象,圖中修改了一下顏色主題:
這裡需要注意在迴圈繪圖的程式碼內部的一行程式碼:
# 設定每個圖的colormap和colorbar所表示範圍是一樣的,即歸一化 norm = matplotlib.colors.Normalize(vmin=0, vmax=25)
該行程式碼決定了顏色條圖例中的顏色和左邊軌跡圖中的顏色是一致的,也就是必須按照顏色條設定的色階來繪製軌跡和呈現色彩。
如果沒有該行程式碼,就算添加了顏色條的程式碼,繪製出來的顏色條和左邊的軌跡圖也不對應,大家可以試試看~
新增橫縱座標軸及刻度值
這個操作就比較常規了,根據自己需要新增即可,常規的程式碼隨便網上搜一個就有,包括刻度值得設定等等。
這一步僅重點說明一點:就是 X 軸繪製時利用的是時間戳資料,由於數值太密集,所以圖中我關掉了 X 軸的刻度值。如果想要顯示如圖 1 所示的時間格式,一個可行的辦法是向影象中新增文字。
也就是說,手動將時間放置在想要防止的刻度下,我們先看一組程式碼:
# 設定 X 座標軸刻度 plt.text(x, y, '8:05', fontproperties=font2) plt.text(x, y, '8:10', fontproperties=font2) plt.text(x, y, '8:15', fontproperties=font2) plt.text(x, y, '8:20', fontproperties=font2)
通過這組程式碼,我們便可以將制定的時間放置在圖中指定的位置,基於座標(x,y)。
最後,附上一個個人認為比較完美的影象,供大家參考:
忽略缺失資料後的效果:
將座標軸設定為漢字,是因為漢字與英文同時出現時比較難處理,大家可以嘗試調一下。
本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯絡我們以作處理。
作者:小兵麼麼