最近參與了一個非常蛋疼的業余時間小項目:給定賽道和賽車模擬程序,求賽車跑完賽道的最快的辦法。對於這個問題,我一開始的想法是:就像Google的自動駕駛一樣,給定足夠的訓練數據,然后汽車對前方的畫面做出判斷決定當前時刻的駕駛策略。不過查了查文獻,從游戲開發的角度,似乎一般都是用先找出賽車的最優路徑,然后沿着路徑駕駛就行了。我們最后采用的是后者,我負責了其中賽道產生中的一部分:近似求解最短路徑(Shortest Path)和最小曲率路徑(Minimum Curvature Path)。
給定的輸入是賽道的圖像文件,用OpenCV中的邊緣檢測模塊得到賽道的線條和中心輪廓線:一堆按順序排列好的像素點,描述了賽道的形狀,輸出則是最短路徑和最小曲率路徑按順序排列的坐標點。

有了賽道輪廓之后,求解路徑的辦法主要采用的是[1]和[2]中描述的方法。
對賽道進行分段:

分段的辦法是在輪廓中心線上取間隔相等的垂直線段,然后將賽道上的控制點坐標表示為在垂直線段上的相對位置。比如上面這幅圖中的第i個線段,假設這個線段的上面端點叫\(P_{i,1}\),下面的端點叫\(P_{i,0}\),令\(W_{i}=P_{i,1}-P_{i,0}\),則線段上任意一點\(P_{i}\)可以表示為:
\[\begin{align}
& {{P}_{i}}={{P}_{i,0}}+{{\alpha }_{i}}{{W}_{i}}=\left( {{x}_{i,0}}+{{\alpha }_{i}}\left( {{x}_{i,1}}-{{x}_{i,0}} \right),{{y}_{i,0}}+{{\alpha }_{i}}\left( {{y}_{i,1}}-{{y}_{i,0}} \right) \right)=\left( {{x}_{i,0}}+{{\alpha }_{i}}\Delta {{x}_{i}},{{y}_{i,0}}+{{\alpha }_{i}}\Delta {{y}_{i}} \right) \\
\end{align}\]
這樣做的好處是把需要用二維點坐標表示的賽道轉化為一維的在賽道上的相對位置,如此一來需要處理的問題就變得簡單而直觀了。
最短路徑:
來看\(P_{i}\)和\(P_{i+1}\),兩點間的距離為
\[\begin{align}
& {{d}_{i}}=\left| {{P}_{i+1}}-{{P}_{i}} \right|=\sqrt{\Delta {{x}_{i+1,i}}^{2}+\Delta {{y}_{i+1,i}}^{2}} \\
\end{align}\]
其中,
\[\begin{align}
& \Delta {{x}_{i+1,i}}={{x}_{i+1,0}}+{{\alpha }_{i+1}}\Delta {{x}_{i+1}}-\left( {{x}_{i,0}}+{{\alpha }_{i}}\Delta {{x}_{i}} \right)=\left( {{x}_{i+1,0}}-{{x}_{i,0}} \right)+\left[ \begin{matrix}
\Delta {{x}_{i+1}} & -\Delta {{x}_{i}} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
\end{matrix} \right) \\
\end{align}\]
\(\Delta y_{i+1,i}\)類似,就不寫出來了。那么最短路徑就可以看作是求下面這個優化問題:
\[\text{min: }\sum\limits_{i=1}^{n}{{{d}_{i}}^{2}} \]
\(\begin{align}
& \text{subject to: }0\le {{\alpha }_{i}}\le 1 \\
\end{align}\)
注意到這里我們求的是\(\sum{{{d}_{i}}^{2}}\)而並非\(\sum{\left| {{d}_{i}} \right|}\),為什么呢?當然不僅僅是為了避免求絕對值,將\(d_{i}^{2}\)展開我們得到:
\( {{d}_{i}}^{2}=\Delta {{x}_{i+1,i}}^{2}+\Delta {{y}_{i+1,i}}^{2} \)
\( ={{\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
\end{matrix} \right)}^{T}}\left[ \begin{matrix}
\Delta {{x}_{i+1}}^{2} & -\Delta {{x}_{i+1}}\Delta {{x}_{i}} \\
-\Delta {{x}_{i+1}}\Delta {{x}_{i}} & \Delta {{x}_{i}}^{2} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
\end{matrix} \right)+{{\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
\end{matrix} \right)}^{T}}\left[ \begin{matrix}
\Delta {{y}_{i+1}}^{2} & -\Delta {{y}_{i+1}}\Delta {{y}_{i}} \\
-\Delta {{y}_{i+1}}\Delta {{y}_{i}} & \Delta {{y}_{i}}^{2} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
\end{matrix} \right) \)
\( +2\left( {{x}_{i+1,0}}-{{x}_{i,0}} \right)\left[ \begin{matrix}
\Delta {{x}_{i+1}} & -\Delta {{x}_{i}} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
\end{matrix} \right)+2\left( {{y}_{i+1,0}}-{{y}_{i,0}} \right)\left[ \begin{matrix}
\Delta {{y}_{i+1}} & -\Delta {{y}_{i}} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
\end{matrix} \right) \)
\(\begin{align}
& +\text{costant terms} \\
\end{align}\)
這個形式的表達式,正是標准的Simply bounded Quadratic Programming問題啊,於是求解\(\alpha\)就方便多了。至於為什么\(\sum{{{d}_{i}}^{2}}\)和\(\sum{\left| {{d}_{i}} \right|}\)基本等效,是因為在我們的問題中有個假設是賽道分割的寬度近似相等,關於這個假設后面還有進一步討論。
最小曲率路徑:
需要最小曲率路徑的出發點是曲率越小,賽車能達到的最大速度越大,相應的關系[1]中有簡單介紹。對於求解,這塊的思路和最短路徑思路類似,都是在一定假設下,先求局部量(曲率,距離),找到形如\(ax+b\)的表達式,然后平方一下轉化為QP問題進行求解。在[1]中求曲率使用的cubic spline,實際實現其實未必用得着(當然也是因為我比較懶……二次的實現比三次簡單很多),二次擬合和三次擬合求曲率的主要區別在於方向的影響,二次擬合求曲率時認為一個點周圍的兩個臨近點是完全對稱的,這和賽道分割的假設一致,所以可以用下面的公式來進行參數化:
\[\begin{align}
& {{P}_{i}}={\mathbf{a}_{i}}+{\mathbf{b}_{i}}t+{\mathbf{c}_{i}}{{t}^{2}} \\
& t=\frac{s-{{s}_{i}}}{{{s}_{i+1}}-{{s}_{i-1}}} \\
\end{align}\]
其中s是某一點到起點的路程,所以t的取值范圍是-1到1。結合前面的假設來看只要分段等間隔可以被滿足,那么\(\frac{dt}{ds}\)可以近似認為是個常數,經過一番和最短路徑部分類似的繁瑣推導,可以得到每一個路徑點對應的曲率正比於\(\left| \mathbf{c} \right|\)。於是最小曲率路徑又化成了一個QP問題:
\[ \text{min: }\sum\limits_{i=1}^{n}{{{c}_{i}}^{2}} \]
\(\begin{align}
& \text{subject to: }0\le {{\alpha }_{i}}\le 1 \\
\end{align}\)
其中\(c_{i}^{2}\)的展開為:
\( {{c}_{i}}^{2}={{\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
{{\alpha }_{i-1}} \\
\end{matrix} \right)}^{T}}\left[ \begin{matrix}
\Delta {{x}_{i+1}}^{2} & -2\Delta {{x}_{i+1}}\Delta {{x}_{i}} & \Delta {{x}_{i+1}}\Delta {{x}_{i-1}} \\
-2\Delta {{x}_{i}}\Delta {{x}_{i+1}} & 4\Delta {{x}_{i}}^{2} & -2\Delta {{x}_{i}}\Delta {{x}_{i-1}} \\
\Delta {{x}_{i-1}}\Delta {{x}_{i+1}} & -2\Delta {{x}_{i-1}}\Delta {{x}_{i}} & \Delta {{x}_{i-1}}^{2} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
{{\alpha }_{i-1}} \\
\end{matrix} \right) \)
\( +{{\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
{{\alpha }_{i-1}} \\
\end{matrix} \right)}^{T}}\left[ \begin{matrix}
\Delta {{y}_{i+1}}^{2} & -2\Delta {{y}_{i+1}}\Delta {{y}_{i}} & \Delta {{y}_{i+1}}\Delta {{y}_{i-1}} \\
-2\Delta {{y}_{i}}\Delta {{y}_{i+1}} & 4\Delta {{y}_{i}}^{2} & -2\Delta {{y}_{i}}\Delta {{y}_{i-1}} \\
\Delta {{y}_{i-1}}\Delta {{y}_{i+1}} & -2\Delta {{y}_{i-1}}\Delta {{x}_{i}} & \Delta {{y}_{i-1}}^{2} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
{{\alpha }_{i-1}} \\
\end{matrix} \right) \)
\( +2\left( \left( {{x}_{i+1,0}}-{{x}_{i,0}} \right)-\left( {{x}_{i,0}}-{{x}_{i-1,0}} \right) \right)\left[ \begin{matrix}
\Delta {{x}_{i+1}} & -2\Delta {{x}_{i}} & \Delta {{x}_{i-1}} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
{{\alpha }_{i-1}} \\
\end{matrix} \right) \)
\( +2\left( \left( {{y}_{i+1,0}}-{{y}_{i,0}} \right)-\left( {{y}_{i,0}}-{{y}_{i-1,0}} \right) \right)\left[ \begin{matrix}
\Delta {{y}_{i+1}} & -2\Delta {{y}_{i}} & \Delta {{y}_{i-1}} \\
\end{matrix} \right]\left( \begin{matrix}
{{\alpha }_{i+1}} \\
{{\alpha }_{i}} \\
{{\alpha }_{i-1}} \\
\end{matrix} \right) \)
\(\begin{align}
& +\text{constant terms} \\
\end{align}\)
這種解法近似求得了全局曲率和的最小解,但是這個解對於賽車而言未必是最優的,因為求和值最小的解法,對於賽道這種分段數量龐大的情況,單個極值的影響總會被平均掉,仍很可能有的地方曲率特別大,導致成了整個賽道上保持高速的一個瓶頸。
更多關於等間隔假設:
基於最短路徑和最小曲率路徑的推導,可以看到等間隔假設在這種分段方法中非常重要。在最短路徑中因為有了等間隔假設, 所以相鄰的兩段路徑長度就被耦合在一起,不會出現相鄰兩段的路徑差得很遠的情況,所以最短路徑推導中用\(\sum{{{d}_{i}}^{2}}\)才近似合理。而在最小曲率路徑的推導中就更重要了,否則曲率項就變得非常復雜。然而這個假設是不是真的實用呢,其實還需要一個更強的假設,那就是每段之間的間隔遠大於賽道寬度。

比如圖中的例子,因為賽道間隔比較密,轉彎半徑特別小的時候,靠彎道內部分的間隔會很小,彎道外側部分間隔很大,這使得最短路徑和最小曲率路徑的假設都受到影響,尤其是最小曲率路徑,會得到很不好的結果,比如下圖的最小曲率路徑:

然而如果線段的間隔過大,當轉彎很急的時候,采樣點的數量會顯得不夠。對於這種情況,我解決的辦法是將分段的線段分組,比如每隔一個取一個分段線段,相當於得到了錯位開來的兩組分段線段,每組的間隔都是原來分段間隔的二倍,然后做兩次路徑求解,取平均,當然也可以加入一些后處理,比如向上重采樣然后做平均平滑等等,結果在下面一節的圖里可以看到。不過即便采用了一些手段來確保產生合理的賽道,對於最小曲率路徑而言,這還是一個trade off的問題,因為間隔越大的情況對急轉彎的采樣會越差,如果特別急的彎道還會因為漏采樣出現“撞賽道”問題,需要更多后處理。總結來說,就是這種基於QP的算法,無論二次還是三次,都不能很好應對急轉彎,不過在實際的賽車游戲當中,像上面圖里的那種急轉彎相對來說不是特別常見,所以用起來效果還可以,需要考慮的往往是在賽道預處理、分段和QP時因為采樣過密導致的時間和內存消耗。
基於最短路徑和最小曲率路徑的最優賽道:
有了最短路徑和最小曲率路徑后,求最佳路徑的辦法可以參考[2],大體描述如下:首先找到最短路徑和最小曲率路徑的每個交點,將兩個交點之間的划為一組,這樣相當於降維,把優化所有的\(\alpha\)轉化為優化好幾組\(\alpha\)。對於第j組分組中,\(\alpha_{j}\)的值表示為最短路徑的\(\alpha_{j,sp}\)和最小曲率路徑的\(\alpha_{j,mcp}\)的加權平均:
\[\begin{align}
& {{\alpha }_{j}}=\varepsilon {{\alpha }_{j,sp}}+\left( 1-\varepsilon \right){{\alpha }_{j,mcp}} \\
\end{align}\]
比如下圖是令\(\varepsilon\)為0.5時得到的一個路徑,和對應的最短路徑、最小曲率路徑,以及分段。

對得到的路徑進行模擬,得到跑圈時間,於是轉化成一個全局優化的問題:
\[ \text{min: lap time} \]
\(\begin{align}
& \text{subject to: }0\le {{\varepsilon }_{j}}\le 1 \\
\end{align}\)
[2]中用的是基因算法(Genetic Algorithm),其實如果賽道不是很復雜的話,分段應該不多,低維情況可以考慮用比GA收斂快的算法。
如何沿指定賽道駕駛:
這部分我沒有參與,也沒有進行過調研,應該也沒有時間去參與了。不過我覺得一個也許可行的辦法是將賽道上每一點對應的最大速度和該點到起點的距離表達出來\(v_{\max }\left( s \right)\),最大速度的求法根據不同情況會有不同答案,對於一般的賽道而言,近似認為最大速度被轉彎離心力和風阻限制,相應的公式[2]中有提到。另外從任意速度開始以恆定加速度進行加速和減速也可以得到一個以該點為起點距離為變量的速度表達式\(v\left( r \right)\)(似乎大約是個\(\propto \sqrt{r-{{r}_{0}}}\)的關系),那么又轉化為一個優化問題:
\[ \text{max: }\int{v\left( s \right)} \]
\(\begin{align}
& \text{subject to: }v\left( s \right)<{{v}_{\max }}\left( s \right) \\
\end{align}\)
這個問題甚至也許不需要優化就能求出解析解。
參考文獻:
[1] F. Braghin, F. Cheli, S. Melzi, and E. Sabbioni. Race driver model. Comput. Struct., 86(13-14):1503–1516, 2008.
[2] Cardamone, L., Loiacono, D., Lanzi, P.L., & Bardelli, A.P. Searching for the optimal racing line using genetic algorithms. In Proceedings of the 2010 IEEE Conference on Computational Intelligence and Games (pp. 388–394).
