文章程式碼👉 laugh12321/RoadLaneFitting 歡迎star ✨
將前檢視轉為鳥瞰圖
將前檢視轉為鳥瞰圖的方法有兩種:
- 有標定的情況下,可以直接使用標定引數進行轉換。
- 沒有標定的情況下,可以選擇四個點計算透視變換矩陣來進行轉換。
在沒有標定的情況下,透視變換需要使用一個3x3的變換矩陣,確保直線在變換後仍然保持直線的性質。為了得到這個變換矩陣,需要在輸入影像上選擇4個點,並提供它們在輸出影像上的對應點。這4個點中,至少有3個點不能共線。透過使用cv2.getPerspectiveTransform
函式,可以計算出這個變換矩陣,隨後可以透過cv2.warpPerspective
將其應用於影像。
簡而言之,透視變換需要選取4個非共線的點,並透過這些點之間的對映關係來計算變換矩陣,最終應用於影像。
以上圖為例,選擇1,2,3,4四個點,用以進行透視變換。經查閱,高速公路上的白色虛線標準長度為長度6米,間隔9米。高速公路單條車道寬度是3.75米。這裡假定,直線14,直線23長4米,直線12,直線34長30米。則輸入影像與輸出影像點的座標如上圖所示。
# GET MATRIX
src = np.float32([
(243.3086, 2006.09253), (987.90594, 1271.23894),
(1410.03022, 1272.49526), (2073.4596, 2003.7979)
])
dst = np.float32([
(90, 500), (90, 200), (130, 200), (130, 500)
])
Matrix = cv2.getPerspectiveTransform(src, dst)
warped_image = cv2.warpPerspective(image, Matrix, (300, 500))
車道線定位
假設已經獲得了車道線的分割影像,並將其轉換為鳥瞰圖。
現在有了車道線分割圖的鳥瞰圖,那麼如何確定當前有幾條車道線以及車道線所處的位置呢?
可以對鳥瞰圖進行垂直方向的累加投影。理論上,有幾個峰值就有幾條車道線,而峰值點的位置即為車道線的位置座標。
從上圖可以看出,一共有四條車道線,且車道線的大致位置也是已知的。之後可以透過滑動視窗法,以峰值點為起點對車道線的點進行搜尋。
滑動視窗法的工作原理如下:
- 設定視窗大小
- 確定視窗的寬度和高度,通常是矩形區域。
- 視窗的高度可以根據影像的大小和問題的特定要求進行調整。
- 滑動視窗
- 從影像底部開始,以固定步長(通常是一個視窗的高度)向上滑動視窗。
- 對於每個視窗,統計視窗內的非零畫素的個數
- 更新視窗
- 若視窗內的非零畫素數量超過閾值,更新視窗中心位置為當前視窗內非零畫素的平均橫座標。
- 擬合曲線
- 針對每個滑動視窗內的非零畫素,使用
np.polyfit
對這些點進行二階多項式擬合,得到曲線的係數。
- 針對每個滑動視窗內的非零畫素,使用
def finding_line(warped_mask, x_points, sliding_window_num=9, margin=15, min_pixels_threshold=50):
# 獲取影像的高度和寬度
height, width = warped_mask.shape
# 獲取影像中所有非零畫素的座標
nonzero_y, nonzero_x = np.nonzero(warped_mask)
# 計算滑動視窗的高度
sliding_window_height = height // sliding_window_num
# 用於儲存每個滑動視窗內的畫素索引
line_pixel_indexes = [[] for _ in range(len(x_points))]
# 遍歷滑動視窗
for i in range(sliding_window_num):
for idx, x_point in enumerate(x_points):
# 確定視窗在y軸上的邊界
top, bottom = height - (i + 1) * sliding_window_height, height - i * sliding_window_height
# 確定視窗在x軸上的邊界
left, right = x_point - margin, x_point + margin
# 獲取視窗內的非零畫素索引
window_pixel_indexes = ((nonzero_y >= top) & (nonzero_y < bottom) &
(nonzero_x >= left) & (nonzero_x < right)).nonzero()[0]
# 儲存當前視窗內的畫素索引
line_pixel_indexes[idx].append(window_pixel_indexes)
# 如果畫素數量足夠,更新視窗中心位置
if len(window_pixel_indexes) > min_pixels_threshold:
x_point = int(np.mean(nonzero_x[window_pixel_indexes]))
# 用於儲存擬合的曲線係數
lines = []
# 處理每個滑動視窗的畫素索引
for line_pixel_index in line_pixel_indexes:
# 合併畫素索引
line_pixel_index = np.concatenate(line_pixel_index)
# 提取座標
line_x, line_y = nonzero_x[line_pixel_index], nonzero_y[line_pixel_index]
# 使用多項式擬合曲線,並將結果新增到lines中
lines.append(np.polyfit(line_y, line_x, 2))
return lines
上述為擬合後的車道線在鳥瞰圖上的效果。
複雜情況
然而,上述結果是在理想條件下(車道線分割結果準確無誤、車道線曲率不大)得到的結果。當情況複雜時,直接以峰值點作為車道線的個數以及大致位置的方式可能行不通。
從上圖可以發現,實際共有5條車道線,但得到了10個峰值點,且擬合出的10條曲線有3條是重疊的(紅色、棕色分別重疊2、1次)。
根據這些資訊,可以採取兩種解決辦法:
- 在擬合前進行過濾
- 在擬合後進行過濾
在擬合前進行過濾
透過直方圖不難看出,車道線的間距在20~30畫素,且每條車道線的峰值畫素個數不小於50。可以根據這些關係對資料進行過濾。
在擬合後進行過濾
由於使用二階多項式對車道線擬合, 而二階多項式係數在二次多項式方程中具有幾何意義,這個方程一般表示為:
其中,\(a\), \(b\), 和 \(c\) 是係數,決定了二次多項式的形狀。係數的組合產生了不同形狀和位置的二次曲線,反映了二次多項式方程在平面上的幾何特徵。
上文我們已經知道了車道線的間距在20~30畫素,可以透過比較相鄰兩二次多項式在 \(0 < f(x) < \text{{height}}\) 的情況下,以x的最大值作差,作為兩車道線的間距。若間距小於20,則代表是一條車道線,保留其中係數 b 最接近於0的(曲率最小的)作為車道線。
將擬合後的車道線投影到原圖上
在完成車道線的擬合後,可以將擬合出的車道線投影回原始影像中。這個過程涉及逆透視變換,將鳥瞰圖上的車道線投影回原始影像上。