一文搞懂基于透视变换的车道线拟合
文章代碼?? 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。可以根據這些關系對數據進行過濾。
在擬合后進行過濾
由于使用二階多項式對車道線擬合, 而二階多項式系數在二次多項式方程中具有幾何意義,這個方程一般表示為:
\[f(x) = ax^2 + bx + c \]其中,\(a\), \(b\), 和 \(c\) 是系數,決定了二次多項式的形狀。系數的組合產生了不同形狀和位置的二次曲線,反映了二次多項式方程在平面上的幾何特征。
上文我們已經知道了車道線的間距在20~30像素,可以通過比較相鄰兩二次多項式在 \(0 < f(x) < \text{{height}}\) 的情況下,以x的最大值作差,作為兩車道線的間距。若間距小于20,則代表是一條車道線,保留其中系數 b 最接近于0的(曲率最小的)作為車道線。
將擬合后的車道線投影到原圖上
在完成車道線的擬合后,可以將擬合出的車道線投影回原始圖像中。這個過程涉及逆透視變換,將鳥瞰圖上的車道線投影回原始圖像上。
總結
以上是生活随笔為你收集整理的一文搞懂基于透视变换的车道线拟合的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java各历史版本官网下载
- 下一篇: 动态规划——流水作业调度问题