完整版:?Python----計算機視覺處理(Opencv:道路檢測完整版:透視變換,提取車道線,車道線擬合,車道線顯示)
一、獲取左右車道線的原始位置?
導入模塊
import cv2
import numpy as np
from matplotlib import pyplot as plt
輸入圖像?
img = cv2.imread('img_road.png')
獲取圖像的高和寬?
height, width, _ = img.shape
對圖像的高進行求和?
num_ax0 = np.sum(img, axis=0)
顯示圖像求和圖?
plt.plot(num_ax0)
plt.show()
得到直方圖左右兩側最高點的位置?
img_left_argmax=np.argmax(num_ax0[:width//2])img_right_argmax=np.argmax(num_ax0[width//2:])+width//2
二、繪制小窗口
獲取圖像中所有非零像素的x和y的位置
nonzeroy,nonzerox=np.array(img.nonzero())
定義一些小窗口的概念參數
# 定義小窗口的個數
windows_num=10#小窗口的寬度和高度
windows_height=height//windows_num
windows_width=30#小窗口白色個數閾值
min_pix=40#初始化當前窗口的位置
left_current=img_left_argmax
right_current=img_right_argmax
創建空列表接收左側和右側車道線像素的索引
left_lane_inds=[]
right_lane_inds=[]
繪制小窗口一
for window in range(windows_num):# 計算當前窗口的上邊界的y坐標win_y_high=height-windows_height*(window+1)# 計算當前窗口的下邊界的y坐標win_y_low = height - windows_height * window# 計算左邊窗口左右邊界的x坐標win_x_left_left=left_current - windows_widthwin_x_left_right=left_current + windows_width# 計算右邊窗口左右邊界的x坐標win_x_right_left=right_current - windows_widthwin_x_right_right=right_current + windows_widthcv2.rectangle(out_img, (win_x_left_left, win_y_high), (win_x_left_right, win_y_low), (0, 255, 0), 2)cv2.rectangle(out_img, (win_x_right_left, win_y_high), (win_x_right_right, win_y_low), (0, 255, 0), 2)
?
? ? ? ? 通過上述代碼繪畫出的小窗口沒有隨路線的彎曲程度而彎曲,需要該進?
改進小窗口,讓窗口隨路線的走勢而走勢
for window in range(windows_num):# 計算當前窗口的上邊界的y坐標win_y_high=height-windows_height*(window+1)# 計算當前窗口的下邊界的y坐標win_y_low = height - windows_height * window# 計算左邊窗口左右邊界的x坐標win_x_left_left=left_current - windows_widthwin_x_left_right=left_current + windows_width# 計算右邊窗口左右邊界的x坐標win_x_right_left=right_current - windows_widthwin_x_right_right=right_current + windows_widthcv2.rectangle(out_img, (win_x_left_left, win_y_high), (win_x_left_right, win_y_low), (0, 255, 0), 2)cv2.rectangle(out_img, (win_x_right_left, win_y_high), (win_x_right_right, win_y_low), (0, 255, 0), 2)good_left_index=((nonzeroy >= win_y_high) & (nonzeroy < win_y_low)& (nonzerox >= win_x_left_left) & (nonzerox < win_x_left_right)).nonzero()[0]good_right_index=((nonzeroy >= win_y_high) & (nonzeroy < win_y_low)& (nonzerox >= win_x_right_left) & (nonzerox < win_x_right_right)).nonzero()[0]left_lane_inds.append(good_left_index)right_lane_inds.append(good_right_index)if len(good_left_index)>min_pix:left_current = int(np.mean(nonzerox[good_left_index]))else:if len(good_right_index)>min_pix:offset=int(np.mean(nonzerox[good_right_index]))-right_preleft_current=left_current+offsetif len(good_right_index)>min_pix:right_current = int(np.mean(nonzerox[good_right_index]))else:if len(good_left_index)>min_pix:offset=int(np.mean(nonzerox[good_left_index]))-left_preright_current=right_current+offsetleft_pre=left_currentright_pre=right_current
三、擬合車道線
#連接索引的列表,為了后續更方便的提取出這些像素點的和y的坐標,以便進行車道線的擬合left_lane_inds=np.concatenate(left_lane_inds)right_lane_inds=np.concatenate(right_lane_inds)# 提取左側和右側車道線像素的位置# left_lane_inds 是一個一維數組,它包含了左側車道線在滑動窗口中找到的白色像素點的x坐標的索引# 通過將這些索引作為索引器應用到 nonzerox數組上,就可以得到相應的左側車道線的x坐標# leftx 包含了左側車道線自色像素點的華標leftx=nonzerox[left_lane_inds]lefty=nonzeroy[left_lane_inds]rightx=nonzerox[right_lane_inds]righty=nonzeroy[right_lane_inds]# 有了坐標之后,就要去對左側和右側車道線進行多項式擬合,從而得到擬合的車道線# np.polyfit()是numpy中用于進行多項式擬合的函數# 他接受三個參數:xy 和deg# x:自變量數組,y:因變量數組# deg:多項式的次數,如果是y=ax^2+b^x+C# left_fit里存放的就是 a、b、c的參數,left_fit=np.polyfit(lefty,leftx,2)right_fit=np.polyfit(righty, rightx, 2)# 使用np.linspace 生成一組均勻分布的數值,用于表示豎直方向上的像素坐標,方便后續的車道線的繪制# 使用多項式擬合來估計左側和右側車道線的x坐標# left_fitx 就是左側擬合出來的車道線ploty=np.linspace(0,height-1,height)left_fitx=left_fit[0]*ploty**2+left_fit[1]*ploty+left_fit[2]right_fitx = right_fit[0] * ploty ** 2 + right_fit[1] * ploty + right_fit[2]# 計算中間車道線的位置middle_fitx=(left_fitx+right_fitx)//2# 使用不同顏色將車道線標出來out_img[lefty,leftx]=[255,0,0]out_img[righty,rightx]=[0,0,255]
四、完整代碼
?
import cv2
import numpy as np
import matplotlib.pyplot as plt # 讀取圖像
img = cv2.imread('img_road.png') # 將圖像復制到三個通道,以便于處理
out_img = np.dstack((img, img, img)) # 計算每列像素的總和,以便找到車道線的最高點
num_ax0 = np.sum(img, axis=0) # 獲取左右兩側最高點的位置
img_left_argmax = np.argmax(num_ax0[:width // 2]) # 左側最高點
img_right_argmax = np.argmax(num_ax0[width // 2:]) + width // 2 # 右側最高點 # 獲取圖像中所有非零像素的x和y的位置
nonzeroy, nonzerox = np.array(img.nonzero()) # 定義小窗口的個數
windows_num = 10 # 小窗口的高度和寬度
windows_height = height // windows_num
windows_width = 30 # 小窗口白色像素的個數閾值
min_pix = 400 # 初始化當前窗口的位置
left_current = img_left_argmax
right_current = img_right_argmax # 記錄上一個窗口的位置
left_pre = left_current
right_pre = right_current # 創建空列表以接收左側和右側車道線像素的索引
left_lane_inds = []
right_lane_inds = [] # 遍歷每個窗口
for window in range(windows_num): # 計算當前窗口的上邊界y坐標 win_y_high = height - windows_height * (window + 1) # 計算當前窗口的下邊界y坐標 win_y_low = height - windows_height * window # 計算左側窗口的左右邊界x坐標 win_x_left_left = left_current - windows_width win_x_left_right = left_current + windows_width # 計算右側窗口的左右邊界x坐標 win_x_right_left = right_current - windows_width win_x_right_right = right_current + windows_width # 在圖像上繪制窗口 cv2.rectangle(out_img, (win_x_left_left, win_y_high), (win_x_left_right, win_y_low), (0, 255, 0), 2) cv2.rectangle(out_img, (win_x_right_left, win_y_high), (win_x_right_right, win_y_low), (0, 255, 0), 2) # 找到在當前窗口內的好像素索引 good_left_index = ((nonzeroy >= win_y_high) & (nonzeroy < win_y_low) & (nonzerox >= win_x_left_left) & (nonzerox < win_x_left_right)).nonzero()[0] good_right_index = ((nonzeroy >= win_y_high) & (nonzeroy < win_y_low) & (nonzerox >= win_x_right_left) & (nonzerox < win_x_right_right)).nonzero()[0] # 將找到的索引添加到列表中 left_lane_inds.append(good_left_index) right_lane_inds.append(good_right_index) # 更新當前窗口位置 if len(good_left_index) > min_pix: left_current = int(np.mean(nonzerox[good_left_index])) # 更新左側窗口位置 else: if len(good_right_index) > min_pix: offset = int(np.mean(nonzerox[good_right_index])) - right_pre # 計算偏移量 left_current = left_current + offset # 更新左側窗口位置 if len(good_right_index) > min_pix: right_current = int(np.mean(nonzerox[good_right_index])) # 更新右側窗口位置 else: if len(good_left_index) > min_pix: offset = int(np.mean(nonzerox[good_left_index])) - left_pre # 計算偏移量 right_current = right_current + offset # 更新右側窗口位置 # 更新上一個窗口位置 left_pre = left_current right_pre = right_current # 將找到的索引合并為一個數組
left_lane_inds = np.concatenate(left_lane_inds)
right_lane_inds = np.concatenate(right_lane_inds) # 獲取左右車道線的x和y坐標
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds] rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds] # 使用多項式擬合左側和右側車道線
# 有了坐標之后,就要去對左側和右側車道線進行多項式擬合,從而得到擬合的車道線# np.polyfit()是numpy中用于進行多項式擬合的函數
# 他接受三個參數:xy 和deg
# x:自變量數組,y:因變量數組
# deg:多項式的次數,如果是2y=ax^2+b^x+C
# left_fit里存放的就是 a、b、c的參數,
left_fit = np.polyfit(lefty, leftx, 2) # 左側擬合
right_fit = np.polyfit(righty, rightx, 2) # 右側擬合 # 創建y坐標的線性空間
ploty = np.linspace(0, height - 1, height) # 根據擬合的多項式計算x坐標
left_fitx = left_fit[0] * ploty ** 2 + left_fit[1] * ploty + left_fit[2]
right_fitx = right_fit[0] * ploty ** 2 + right_fit[1] * ploty + right_fit[2] # 計算中間車道線的x坐標
middle_fitx = (left_fitx + right_fitx) // 2 # 在輸出圖像上標記左側和右側車道線像素
out_img[lefty, leftx] = [255, 0, 0] # 左側車道線為紅色
out_img[righty, rightx] = [0, 0, 255] # 右側車道線為藍色 # 顯示結果圖像
cv2.imshow('out_img', out_img)
cv2.waitKey(0)
- 圖像讀取和準備:讀取圖像并為處理做好準備。
- 直方圖計算:計算像素值的總和,以找到車道的位置。
- 滑動窗口:使用滑動窗口技術識別車道像素。
- 車道像素索引:收集左側和右側車道的好像素索引。
- 多項式擬合:對識別出的車道點進行多項式擬合,以生成平滑的車道線。
- 可視化:在輸出圖像上繪制檢測到的車道線并展示。
五、庫函數
5.1、nonzero()
????????返回數組中非零元素的索引
numpy.nonzero(a)
用途:它通常用來查找非零元素的位置,常用于圖像處理中篩選出特定的像素。
返回值:函數返回一個元組,每個元素是一個數組,表示非零元素在每個維度上的索引。例如,對于二維數組?A
,A.nonzero()
?返回兩個數組,第一個數組表示行索引,第二個數組表示列索引。
import numpy as nparr=np.array([[0,1,2,3,4,5,6],[1,2,3,4,5,6,7],[0,0,0,0,0,0,0]]
)
print(arr.nonzero())
#(array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]), array([1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6]))
5.2、polyfit()
????????用于計算數據點的多項式擬合
numpy.polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False)
方法 | 描述 |
---|---|
x | M 個采樣點的 x 坐標。(x[i],?y[i]) |
y | 采樣點的 y 坐標。樣本的多個數據集 共享相同 x 坐標的點可以通過以下方式一次擬合 傳入一個 2D 數組,該數組每列包含一個數據集。 |
deg | 擬合多項式的次數 |
rcond | 擬合的相對條件編號。小于 this 相對于最大奇異值將被忽略。這 默認值為 len(x)*eps,其中 eps 是 float 類型,大多數情況下約為 2E-16。 |
full | Switch 確定返回值的性質。當為 False 時( default) 僅返回系數,當 True diagnostic 還會返回來自奇異值分解的信息。 |
w | 權重。如果不是 None,則權重適用于未平方的 殘差 。理想情況下,權重為 選中,這樣產品的誤差都具有 相同的方差。使用逆方差加權時,請使用 。默認值為 None。w[i] y[i]?-?y_hat[i] x[i] w[i]*y[i] w[i]?=?1/sigma(y[i]) |
cov | 如果給定且非?False,則不僅返回估計值,還返回其 協方差矩陣。默認情況下,協方差的縮放比例為 chi2/dof,其中 dof = M - (度 + 1),即假定權重 不可靠,除非在相對意義上,并且一切都是縮放的 使得還原的 Chi2 是 Unity。如果 ,則省略此縮放,因為與權重 w = 1/sigma,其中已知 sigma 是 不確定性。cov='unscaled' |
import numpy as np
x = np.array([1, 2, 3, 4])
y = np.array([2, 3, 5, 7])
# ax**2+bx+c
a,b,c = np.polyfit(x, y, 2) # 進行二次多項式擬合
print(a,b,c)
#[0.25 0.45 1.25]
5.3、linspace()
????????用于生成指定范圍內的等間距數字
numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0, *, device=None)
方法 | 描述 |
---|---|
start | 序列的起始值。 |
stop | 序列的結束值,除非?endpoint?設置為 False。 在這種情況下,序列由除最后一個均勻分布的樣本之外的所有樣本組成,因此不包括?stop。請注意,步驟 當?endpoint?為 False 時,size 會發生變化。num?+?1 |
num | 要生成的樣本數。默認值為 50。必須為非負數。 |
endpoint | 如果為 True,則 stop?是最后一個樣本。否則,不包括在內。 默認值為 True。 |
retstep | 如果為 True,則返回 (samples,?step),其中?step?是間距 樣本之間。 |
dtype | 輸出數組的類型。如果未給出,則數據類型 是從?start?和?stop?推斷出來的。推斷的 dtype 永遠不會是 整數;float?的 整數數組。 |
axis | 結果中用于存儲樣本的軸。僅在啟動時相關 或 stop 是類似數組的。默認情況下 (0),樣本將沿著 在開頭插入新軸。使用 -1 在末尾獲取一個軸。 |
device | 要放置創建的陣列的設備。默認值:None。 僅對于 Array-API 互作性,如果通過,則必須這樣做。 |
import numpy as np
samples = np.linspace(0, 1, 5) # 生成 0 到 1 之間的 5 個等間距點
print(samples)
# [0. 0.25 0.5 0.75 1. ]
5.4、dstack()
????????用于沿著深度(第三個軸)拼接數組。
numpy.dstack(tup)
方法 | 描述 |
---|---|
tup | 數組沿除第三個軸之外的所有軸必須具有相同的形狀。 一維或二維數組必須具有相同的形狀。 |
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
c = np.dstack((a, b)) # 沿著深度軸拼接,結果形狀為 (2, 2, 2)
print(c)
'''
[[[1 5][2 6]][[3 7][4 8]]]
'''