從車道檢測(cè)項(xiàng)目入門open cv
從車道檢測(cè)項(xiàng)目入門open cv
前提聲明:非常感謝b站up主 嘉然今天吃帶變,感謝其視頻的幫助。同時(shí)希望各位大佬積積極提出寶貴的意見(jiàn)。??????(?′?`?)(●'?'●)╰(°▽°)╯
github地址:https://github.com/zhongzhengli13/openCV_Lane_Detection
基礎(chǔ)知識(shí)
cv2.imread & cv2.imshow & cv2.imwrite
import cv2 as cv
img = cv.imread("img.png", cv.IMREAD_GRAYSCALE) #將圖片轉(zhuǎn)為灰度圖
但是當(dāng)前會(huì)存在一些問(wèn)題,圖片會(huì)閃一下,看不清楚,所以我們可以加上阻塞
cv2.waitKey()函數(shù)
#完整版
import cv2 as cv
img = cv.imread("img.png", cv.IMREAD_GRAYSCALE)
print(type(img))
print(img.shape)
cv.imshow('image', img)
k = cv.waitKey(0) # 阻塞 #k相當(dāng)于檢測(cè)你的輸入的ascii值
print(k)
# while True:
# if cv.waitKey(0) == ord('q'):
# cv.destroyAllWindows()
# else:
# img = cv.imread("img.png", cv.IMREAD_GRAYSCALE)
cv.imwrite("img_gray.png", img)#生成img圖片,保存到當(dāng)前目錄中~
效果展示
原始圖像:
![]()
灰度圖:
![]()
Canny邊緣檢測(cè)
通過(guò)求取圖像上每一個(gè)像素點(diǎn)周邊圖像像素變化的梯度,來(lái)確定這個(gè)點(diǎn)是否是邊緣。
梯度的方向一般總是與邊界垂直,梯度的方向被歸為四類:垂直、水平和兩個(gè)對(duì)角線(即,0度、45度、90度和135度四個(gè)方向)。
我們現(xiàn)在的想法是設(shè)置一個(gè)閾值,當(dāng)梯度大于閾值時(shí),我們可以認(rèn)為該點(diǎn)是邊緣。但是隨之而來(lái)的是,圖片會(huì)產(chǎn)生一些毛邊或者光線,角度等問(wèn)題,導(dǎo)致可能會(huì)誤判。
為了解決這個(gè)問(wèn)題,我們采用雙閾值的方法,一個(gè)上閾值,一個(gè)下閾值。
我們認(rèn)為高于上閾值的點(diǎn)為強(qiáng)邊緣,在上閾值和下閾值之間的我們認(rèn)為是弱邊緣。
我們認(rèn)為只有弱邊緣與強(qiáng)邊緣相連的話,才是邊緣。B不認(rèn)為是邊緣,可能是噪聲;C與A強(qiáng)邊緣相連,我們認(rèn)為C是邊緣。
import cv2
img = cv2.imread("img.png", cv2.IMREAD_GRAYSCALE)
edge_img = cv2.Canny(img, 190, 350) # 下邊緣和上邊緣的閾值設(shè)定 #需要自行更改
cv2.imshow("edge", edge_img)
cv2.waitKey(0)
當(dāng)上邊緣和下邊緣都升高時(shí),邊緣顯示會(huì)越來(lái)越少。
ROI mask
簡(jiǎn)單來(lái)講就是類似于摳圖,就是剔除無(wú)關(guān)信息的邊緣。
roi : region of interest 感興趣的區(qū)域
? 數(shù)組切片
? 布爾運(yùn)算(與運(yùn)算)
-
cv2.fillPoly是 OpenCV 中的一個(gè)函數(shù),用于在圖像中填充多邊形區(qū)域。它常用于繪制、遮罩或標(biāo)記圖像中的特定區(qū)域。通過(guò)指定多邊形的頂點(diǎn),cv2.fillPoly可以將這些區(qū)域填充為指定的顏色。 -
cv2.bitwise_and是 OpenCV 中的一個(gè)函數(shù),用于對(duì)兩個(gè)圖像或數(shù)組進(jìn)行按位與(bitwise AND)操作。按位與操作是逐像素進(jìn)行的,只有當(dāng)兩個(gè)圖像的對(duì)應(yīng)像素都為非零值時(shí),結(jié)果圖像的該像素才為非零值。-
圖像遮罩:
- 使用掩碼提取圖像的特定區(qū)域。例如,將一個(gè)形狀(如矩形、圓形或多邊形)作為掩碼,只保留掩碼內(nèi)的圖像內(nèi)容。
-
圖像合成:
- 將兩個(gè)圖像的特定部分組合在一起。
-
圖像處理:
- 在圖像處理中,按位與操作常用于對(duì)圖像進(jìn)行區(qū)域選擇或區(qū)域遮擋。
-
圖像以矩陣np.array形式存儲(chǔ)在內(nèi)存中
? np.zeros_like : np.zeros_like 是 NumPy 庫(kù)中的一個(gè)函數(shù),用于創(chuàng)建一個(gè)與給定數(shù)組形狀和數(shù)據(jù)類型相同的數(shù)組,但所有元素都初始化為零。
# @Author : LiZhongzheng
# 開(kāi)發(fā)時(shí)間 :2025-04-28 17:30
import cv2
import numpy as np
edge_img = cv2.imread("edge_img.png", cv2.IMREAD_GRAYSCALE)
mask = np.zeros_like(edge_img) # 獲取一個(gè)與edge_img大小相同的數(shù)組
mask = cv2.fillPoly(mask, np.array([[[0, 569], [661, 195], [914, 248], [979, 592]]]),
color=255) # array中的存放的是想要識(shí)別區(qū)域的四個(gè)頂點(diǎn) #順序?yàn)樽笙?、左上、右上、右?
masked_edge_img = cv2.bitwise_and(edge_img, mask)
# cv2.imshow('mask', mask)
# cv2.waitKey(0)
cv2.imshow("edged", masked_edge_img)
cv2.waitKey(0)
霍夫變換
提取圖片中的直線。
注意:霍夫變換是針對(duì)灰度圖的。
min是最短線段的長(zhǎng)度,max是兩點(diǎn)之間的最大距離,超過(guò)這個(gè)距離就不認(rèn)為是線段了。
-
首先經(jīng)過(guò) cv2.HoughLinesP()函數(shù)獲取到所有的線條,然后計(jì)算線條的斜率,根據(jù)斜率的正負(fù)判斷是左車道線還是右車道線。
-
# @Author : LiZhongzheng # 開(kāi)發(fā)時(shí)間 :2025-04-29 8:54 import cv2 import numpy as np def calculate_slope(line): """ 計(jì)算線段line的斜率 :param line: np.array([[x_1, y_1, x_2, y_2]]) :return: """ x_1, y_1, x_2, y_2 = line[0] return (y_2 - y_1) / (x_2 - x_1) edge_img = cv2.imread('masked_edge_img.jpg', cv2.IMREAD_GRAYSCALE) # 獲取所有線段 lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40, maxLineGap=20) # 按照斜率分成車道線 left_lines = [line for line in lines if calculate_slope(line) > 0] right_lines = [line for line in lines if calculate_slope(line) < 0] print("left_lines =", len(left_lines)) print("right_lines =", len(right_lines))
-
離群值過(guò)濾
剔除出因?yàn)檎`差而被識(shí)別出的直線。
如何分解出噪點(diǎn)和車道線那?
- 我們可以知道,車道線的斜率大致是相同的,進(jìn)而可以分辨出噪點(diǎn)和車道線。
# @Author : LiZhongzheng
# 開(kāi)發(fā)時(shí)間 :2025-04-29 9:01
import cv2
import numpy as np
"""
剔除出因?yàn)檎`差而被識(shí)別出的直線。
如何分解出噪點(diǎn)和車道線那?
我們可以知道,車道線的斜率大致是相同的,進(jìn)而可以分辨出噪點(diǎn)和車道線。
"""
def calculate_slope(line):
"""
計(jì)算線段line的斜率
:param line: np.array([[x_1, y_1, x_2, y_2]])
:return:
"""
x_1, y_1, x_2, y_2 = line[0]
return (y_2 - y_1) / (x_2 - x_1)
edge_img = cv2.imread('masked_edge_img.jpg', cv2.IMREAD_GRAYSCALE)
# 獲取所有線段
lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40, maxLineGap=20)
# 按照斜率分成車道線
left_lines = [line for line in lines if calculate_slope(line) > 0]
right_lines = [line for line in lines if calculate_slope(line) < 0]
def reject_abnormal_lines(lines, threshold):
"""
剔除斜率不一致的線段
:param lines: 線段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
"""
slopes = [calculate_slope(line) for line in lines]
while len(lines) > 0:
mean = np.mean(slopes) # 使用 NumPy 的 np.mean 函數(shù)計(jì)算當(dāng)前所有斜率的平均值
diff = [abs(s - mean) for s in slopes] # 遍歷 slopes 列表,計(jì)算每個(gè)斜率與平均斜率的絕對(duì)差值,并將結(jié)果存儲(chǔ)在 diff 列表中
idx = np.argmax(diff) # 使用 NumPy 的 np.argmax 函數(shù)找到 diff 列表中最大值的索引,即斜率差異最大的線段。
if diff[idx] > threshold: # 如果最大差異大于閾值 threshold,則認(rèn)為該線段是異常的,將其從 slopes 和 lines 列表中移除。
slopes.pop(idx)
lines.pop(idx)
else: # 如果最大差異小于或等于閾值,則認(rèn)為所有線段的斜率已經(jīng)足夠一致,退出循環(huán)。
break
return lines # 如果最大差異小于或等于閾值,則認(rèn)為所有線段的斜率已經(jīng)足夠一致,退出循環(huán)。
print('before filter:')
print('left lines number=')
print(len(left_lines))
print('right lines number=')
print(len(right_lines))
reject_abnormal_lines(left_lines, threshold=0.2)
reject_abnormal_lines(right_lines, threshold=0.2)
print('after filter:')
print('left lines number=')
print(len(left_lines))
print('right lines number=')
print(len(right_lines))
最小二乘擬合
將lines的線段擬合成一條直線。
np.ravel 將高維數(shù)組拉成一維
np.polyfit 多項(xiàng)式擬合
np.polyval 多項(xiàng)式求值
-
經(jīng)過(guò)上述的步驟,進(jìn)而我們可以求出車道線的數(shù)量等信息,同時(shí)又剔除了噪點(diǎn)。然后我們就可以將在同一個(gè)區(qū)域的線段擬合一條直線
-
# @Author : LiZhongzheng # 開(kāi)發(fā)時(shí)間 :2025-04-29 15:58 import cv2 import numpy as np def calculate_slope(line): """ 計(jì)算線段line的斜率 :param line: np.array([[x_1, y_1, x_2, y_2]]) :return: """ x_1, y_1, x_2, y_2 = line[0] return (y_2 - y_1) / (x_2 - x_1) edge_img = cv2.imread("masked_edge_img.jpg", cv2.IMREAD_GRAYSCALE) # 獲取所有線段 lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40, maxLineGap=20) # 按照斜率分成車道線 left_lines = [line for line in lines if calculate_slope(line) > 0] right_lines = [line for line in lines if calculate_slope(line) < 0] def reject_abnormal_lines(lines, threshold): """ 剔除斜率不一致的線段 :param lines: 線段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])] """ slopes = [calculate_slope(line) for line in lines] while len(lines) > 0: mean = np.mean(slopes) diff = [abs(s - mean) for s in slopes] idx = np.argmax(diff) if (diff[idx] > threshold): slopes.pop(idx) lines.pop(idx) else: break return lines left_lines = reject_abnormal_lines(left_lines, threshold=0.2) right_lines = reject_abnormal_lines(right_lines, threshold=0.2) def least_squares_fit(lines): """ 將lines中的線段擬合成一條線段 :param lines: 線段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])] :return: 線段上的兩點(diǎn),np.array([[xmin, ymin], [xmax, ymax]]) """ # 1. 取出所有坐標(biāo)點(diǎn) """ 在 OpenCV 中,線段通常用一個(gè)形狀為 (1, 4) 的 NumPy 數(shù)組表示,其中包含線段的兩個(gè)端點(diǎn)的坐標(biāo)。 具體來(lái)說(shuō),數(shù)組的格式為 [x1, y1, x2, y2],分別表示起點(diǎn) (x1, y1) 和終點(diǎn) (x2, y2)。 """ x_coords = np.ravel([[line[0][0], line[0][2]] for line in lines]) # np.ravel 將二維列表展平為一維數(shù)組 y_coords = np.ravel([[line[0][1], line[0][3]] for line in lines]) # 2. 進(jìn)行直線擬合.得到多項(xiàng)式系數(shù) poly = np.polyfit(x_coords, y_coords, deg=1) # 3. 根據(jù)多項(xiàng)式系數(shù),計(jì)算兩個(gè)直線上的點(diǎn),用于唯一確定這條直線 point_min = (np.min(x_coords), np.polyval(poly, np.min(x_coords))) point_max = (np.max(x_coords), np.polyval(poly, np.max(x_coords))) return np.array([point_min, point_max], dtype=np.int32) print("left lane") print(least_squares_fit(left_lines)) print("right lane") print(least_squares_fit(right_lines))
-
直線繪制
繪制車道線 cv2.line
# @Author : LiZhongzheng
# 開(kāi)發(fā)時(shí)間 :2025-04-29 16:23
import cv2
import numpy as np
def calculate_slope(line):
"""
計(jì)算線段line的斜率
:param line: np.array([[x_1, y_1, x_2, y_2]])
:return:
"""
x_1, y_1, x_2, y_2 = line[0]
return (y_2 - y_1) / (x_2 - x_1)
edge_img = cv2.imread('masked_edge_img.jpg', cv2.IMREAD_GRAYSCALE)
# 獲取所有線段
lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40,
maxLineGap=20)
# 按照斜率分成車道線
left_lines = [line for line in lines if calculate_slope(line) > 0]
right_lines = [line for line in lines if calculate_slope(line) < 0]
def reject_abnormal_lines(lines, threshold):
"""
剔除斜率不一致的線段
:param lines: 線段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
"""
slopes = [calculate_slope(line) for line in lines]
while len(lines) > 0:
mean = np.mean(slopes)
diff = [abs(s - mean) for s in slopes]
idx = np.argmax(diff)
if diff[idx] > threshold:
slopes.pop(idx)
lines.pop(idx)
else:
break
return lines
left_lines = reject_abnormal_lines(left_lines, threshold=0.2)
right_lines = reject_abnormal_lines(right_lines, threshold=0.2)
def least_squares_fit(lines):
"""
將lines中的線段擬合成一條線段
:param lines: 線段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
:return: 線段上的兩點(diǎn),np.array([[xmin, ymin], [xmax, ymax]])
"""
# 1. 取出所有坐標(biāo)點(diǎn)
x_coords = np.ravel([[line[0][0], line[0][2]] for line in lines])
y_coords = np.ravel([[line[0][1], line[0][3]] for line in lines])
# 2. 進(jìn)行直線擬合.得到多項(xiàng)式系數(shù)
poly = np.polyfit(x_coords, y_coords, deg=1)
# 3. 根據(jù)多項(xiàng)式系數(shù),計(jì)算兩個(gè)直線上的點(diǎn),用于唯一確定這條直線
point_min = (np.min(x_coords), np.polyval(poly, np.min(x_coords)))
point_max = (np.max(x_coords), np.polyval(poly, np.max(x_coords)))
return np.array([point_min, point_max], dtype=np.int32)
left_line = least_squares_fit(left_lines)
right_line = least_squares_fit(right_lines)
img = cv2.imread('img.jpg', cv2.IMREAD_COLOR)
cv2.line(img, tuple(left_line[0]), tuple(left_line[1]), color=(0, 255, 255), thickness=5)
cv2.line(img, tuple(right_line[0]), tuple(right_line[1]), color=(0, 255, 255), thickness=5)
cv2.imshow('lane', img)
cv2.waitKey(0)
重難點(diǎn)講解:least_squares_fit()函數(shù)講解
該函數(shù)主要做了三個(gè)部分:提取坐標(biāo)點(diǎn)、進(jìn)行直線擬合、計(jì)算直線上的兩個(gè)點(diǎn)。
1. 提取所有坐標(biāo)點(diǎn)
x_coords = np.ravel([[line[0][0], line[0][2]] for line in lines])
y_coords = np.ravel([[line[0][1], line[0][3]] for line in lines])
lines:- 輸入的線段集合,每個(gè)線段是一個(gè)形狀為
(1, 4)的 NumPy 數(shù)組,表示線段的兩個(gè)端點(diǎn)坐標(biāo) [x1,y1,x2,y2]。
- 輸入的線段集合,每個(gè)線段是一個(gè)形狀為
x_coords:- 提取所有線段的 x 坐標(biāo)。
line[0][0]是起點(diǎn)的 x 坐標(biāo),line[0][2]是終點(diǎn)的 x 坐標(biāo)。 - 使用列表推導(dǎo)式
[[line[0][0], line[0][2]] for line in lines]生成一個(gè)二維列表,包含所有線段的起點(diǎn)和終點(diǎn)的 x 坐標(biāo)。 - 使用
np.ravel將二維列表展平為一維數(shù)組。
- 提取所有線段的 x 坐標(biāo)。
y_coords:- 提取所有線段的 y 坐標(biāo)。
line[0][1]是起點(diǎn)的 y 坐標(biāo),line[0][3]是終點(diǎn)的 y 坐標(biāo)。 - 使用列表推導(dǎo)式
[[line[0][1], line[0][3]] for line in lines]生成一個(gè)二維列表,包含所有線段的起點(diǎn)和終點(diǎn)的 y 坐標(biāo)。 - 使用
np.ravel將二維列表展平為一維數(shù)組。
- 提取所有線段的 y 坐標(biāo)。
2. 進(jìn)行直線擬合
poly = np.polyfit(x_coords, y_coords, deg=1)
np.polyfit:- 這是 NumPy 中的一個(gè)函數(shù),用于對(duì)給定的數(shù)據(jù)點(diǎn)進(jìn)行多項(xiàng)式擬合。
- 參數(shù):
x_coords:自變量 x 的值。y_coords:因變量 y 的值。deg=1:指定擬合多項(xiàng)式的次數(shù)為 1,即線性擬合。
- 返回值:
- 返回?cái)M合多項(xiàng)式的系數(shù),從最高次項(xiàng)到常數(shù)項(xiàng)。對(duì)于線性擬合,返回兩個(gè)值
[slope, intercept],分別表示直線的斜率和截距。
- 返回?cái)M合多項(xiàng)式的系數(shù),從最高次項(xiàng)到常數(shù)項(xiàng)。對(duì)于線性擬合,返回兩個(gè)值
3. 計(jì)算直線上的兩個(gè)點(diǎn)
point_min = (np.min(x_coords), np.polyval(poly, np.min(x_coords)))
point_max = (np.max(x_coords), np.polyval(poly, np.max(x_coords)))
np.min(x_coords)和np.max(x_coords):- 分別計(jì)算 x 坐標(biāo)中的最小值和最大值。
np.polyval(poly, x):- 這是 NumPy 中的一個(gè)函數(shù),用于計(jì)算多項(xiàng)式在給定的 x 值處的 y 值。
- 參數(shù):
poly:擬合多項(xiàng)式的系數(shù)數(shù)組。x:輸入的 x 值。
- 返回值:
- 返回多項(xiàng)式在 x 處的 y 值。
point_min和point_max:point_min是直線上的一個(gè)點(diǎn),其 x 坐標(biāo)為最小值,y 坐標(biāo)通過(guò)多項(xiàng)式計(jì)算得到。point_max是直線上的一個(gè)點(diǎn),其 x 坐標(biāo)為最大值,y 坐標(biāo)通過(guò)多項(xiàng)式計(jì)算得到。
4. 返回值
return np.array([point_min, point_max], dtype=np.int32)
- 返回一個(gè)形狀為
(2, 2)的 NumPy 數(shù)組,表示直線上的兩個(gè)點(diǎn)的坐標(biāo)。這兩個(gè)點(diǎn)可以唯一確定一條直線。
視頻流讀寫
cv2.VideoCapture
? capture.read
- 基礎(chǔ)代碼介紹:
# @Author : LiZhongzheng
# 開(kāi)發(fā)時(shí)間 :2025-04-29 17:06
import cv2
capture = cv2.VideoCapture('video.mp4')
# capture = cv2.VideoCapture(0) #讀取當(dāng)前設(shè)備第0個(gè)攝像頭
while True:
ret, frame = capture.read() # ret 視頻流的狀態(tài),frame 當(dāng)前幀的圖像
cv2.imshow('frame', frame)
cv2.waitKey(20) # 相當(dāng)于播放速率
cv2.VideoWriter
最后我們不僅可以識(shí)別圖片的車道線還可以識(shí)別視頻的車道線,原理相同,因?yàn)橐曨l是一幀一幀的,每一幀就是一個(gè)圖片。
# @Author : LiZhongzheng
# 開(kāi)發(fā)時(shí)間 :2025-04-29 17:12
import cv2
import numpy as np
def get_edge_img(color_img, gaussian_ksize=5, gaussian_sigmax=1, canny_threshold1=50, canny_threshold2=100):
"""
灰度化,模糊,canny變換,提取邊緣
:param color_img: 彩色圖,channels=3
"""
"""
cv2.GaussianBlur() 函數(shù)參數(shù)
color_img:
輸入的彩色圖像,必須是 3 通道的 BGR 圖像。
gaussian_ksize(可選):
高斯模糊的核大小。必須是正奇數(shù),默認(rèn)值為 5。
gaussian_sigmax(可選):
高斯模糊的 X 方向標(biāo)準(zhǔn)差,默認(rèn)值為 1。
"""
gaussian = cv2.GaussianBlur(color_img, (gaussian_ksize, gaussian_ksize),
gaussian_sigmax) # 使用 cv2.GaussianBlur 對(duì)輸入圖像進(jìn)行高斯模糊處理。高斯模糊可以減少圖像中的噪聲,使邊緣檢測(cè)更加穩(wěn)定。
gray_img = cv2.cvtColor(gaussian, cv2.COLOR_BGR2GRAY)
edges_img = cv2.Canny(gray_img, canny_threshold1, canny_threshold2)
return edges_img
def roi_mask(gray_img):
"""
對(duì)gray_img進(jìn)行掩膜
:param gray_img: 灰度圖,channels=1
"""
poly_pts = np.array([[[0, 368], [300, 210], [340, 210], [640, 368]]])
mask = np.zeros_like(gray_img)
mask = cv2.fillPoly(mask, pts=poly_pts, color=255)
img_mask = cv2.bitwise_and(gray_img, mask)
return img_mask
def get_lines(edge_img):
"""
獲取edge_img中的所有線段
:param edge_img: 標(biāo)記邊緣的灰度圖
"""
def calculate_slope(line):
"""
計(jì)算線段line的斜率
:param line: np.array([[x_1, y_1, x_2, y_2]])
:return:
"""
x_1, y_1, x_2, y_2 = line[0]
return (y_2 - y_1) / (x_2 - x_1)
def reject_abnormal_lines(lines, threshold=0.2):
"""
剔除斜率不一致的線段
:param lines: 線段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
"""
slopes = [calculate_slope(line) for line in lines]
while len(lines) > 0:
mean = np.mean(slopes)
diff = [abs(s - mean) for s in slopes]
idx = np.argmax(diff)
if (diff[idx] > threshold):
slopes.pop(idx)
diff.pop(idx)
else:
break
return lines
def least_squares_fit(lines):
"""
將lines中的線段擬合成一條線段
:param lines: 線段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
:return: 線段上的兩點(diǎn),np.array([[xmin, ymin], [xmax, ymax]])
"""
"""
np.polyfit 是 NumPy 庫(kù)中的一個(gè)函數(shù),用于對(duì)給定的數(shù)據(jù)點(diǎn)進(jìn)行多項(xiàng)式擬合。
它通過(guò)最小二乘法找到一個(gè)多項(xiàng)式,使得這個(gè)多項(xiàng)式在給定數(shù)據(jù)點(diǎn)上的值與實(shí)際值之間的誤差平方和最小。
"""
x_coords = np.ravel([[line[0][0], line[0][2]] for line in lines])
y_coords = np.ravel([[line[0][1], line[0][3]] for line in lines])
poly = np.polyfit(x_coords, y_coords, deg=1)
point_min = (np.min(x_coords), np.polyval(poly, np.min(
x_coords))) # 這行代碼的作用是計(jì)算擬合直線(或多項(xiàng)式曲線)上的一個(gè)特定點(diǎn)的坐標(biāo)。具體來(lái)說(shuō),它計(jì)算的是當(dāng) x 取最小值時(shí),對(duì)應(yīng)的 y 值,并將這個(gè)點(diǎn)的坐標(biāo)存儲(chǔ)為一個(gè)元組 (x_min, y_min)。
point_max = (np.max(x_coords), np.polyval(poly, np.max(x_coords)))
return np.array([point_min, point_max], dtype=np.int32)
# 獲取所有線段
lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40,
maxLineGap=20)
# 按照斜率分成車道線
left_lines = [line for line in lines if calculate_slope(line) > 0]
right_lines = [line for line in lines if calculate_slope(line) < 0]
# 剔除離群線段
left_lines = reject_abnormal_lines(left_lines)
right_lines = reject_abnormal_lines(right_lines)
return least_squares_fit(left_lines), least_squares_fit(right_lines)
def draw_lines(img, lines):
"""
在img上繪制lines
:param img:
:param lines: 兩條線段: [np.array([[xmin1, ymin1], [xmax1, ymax1]]), np.array([[xmin2, ymin2], [xmax2, ymax2]])]
:return:
"""
left_line, right_line = lines
cv2.line(img, tuple(left_line[0]), tuple(left_line[1]), color=(0, 255, 255),
thickness=5)
cv2.line(img, tuple(right_line[0]), tuple(right_line[1]),
color=(0, 255, 255), thickness=5)
def show_lane(color_img): # 封裝
"""
在color_img上畫出車道線
:param color_img: 彩色圖,channels=3
:return:
"""
edge_img = get_edge_img(color_img)
mask_gray_img = roi_mask(edge_img)
lines = get_lines(mask_gray_img)
draw_lines(color_img, lines)
return color_img
if __name__ == '__main__':
capture = cv2.VideoCapture('video.mp4')
while True:
ret, frame = capture.read()
frame = show_lane(frame)
cv2.imshow('frame', frame)
cv2.waitKey(10)
以上就是我對(duì)這個(gè)項(xiàng)目的總結(jié)。
同時(shí)再次說(shuō)明我已經(jīng)將項(xiàng)目上傳到github項(xiàng)目中,歡迎大家多多支持,你們的支持是我最大的前進(jìn)動(dòng)力~~~
再次感謝b站up主 嘉然今天吃帶變,以及各位大佬的寶貴意見(jiàn)。
祝好~

浙公網(wǎng)安備 33010602011771號(hào)