本筆記gitee源代碼:
https://gitee.com/hongtao-jiang/opencv_lanedetect.git
2023.8.5
1、OpenCV安裝
conda管理虛擬環(huán)境與否看自己
pip install opencv
import cv2
print(cv2.__version__)
查看該庫是否安裝成功
2、圖片的讀入、保存
import cv2
img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE) #此為以灰度圖格式讀入,彩色讀入則shape是3通道
print(type(img)) #<class 'numpy.ndarray'>
print(img.shape) #(368, 640)
cv2.imshow('image',img)#一閃而逝
#cv2.waitKey(0)#延時阻塞 0一直阻直到鍵入 單位毫秒
#k=cv2.waitKey(0)
#print(k) #輸出鍵入的ASCII值
cv2.imwrite('img_gray.jpg',img)#灰度圖重組到文件里,圖片要指定后綴,cv以此確定壓縮格式
3、Canny算法邊緣檢測
求取每個像素點周邊梯度,對比變化,以確定是否為邊緣
對于B,C點,不處于邊緣,求梯度無明顯變化,但是處在邊緣的A來說,梯度變化會很大,就可能是邊緣點
以上是理想例子,沿著邊緣法向求梯度求,實際上情況更復(fù)雜,例如:
1是左右邊緣,3上下左右都有,360°計算運算成本高,Canny算法則是選取4個梯度方向(有正負所以說4個),
(1)應(yīng)用高斯濾波器,平滑圖像,濾除噪聲。
邊緣檢測易受噪聲影響,所以平滑圖像,降低噪聲點
(2)計算圖像每個像素點的梯度大小和方向。
(3)非極大值抑制,消除邊緣檢測帶來的不利影響
遍歷圖像中所有的像素點,判斷當前像素點是否是周圍像素點中具有相同方向梯度的最大值
保留黃色背景像素點,其他的歸0
(4)應(yīng)用雙閾值檢測確定真實和潛在的邊緣先設(shè)置高、低兩個閾值(一般高閾值是低閾值的2~3倍),遍歷整個灰度矩陣,若某點的梯度高于高閾值,則在結(jié)果中置1,若該點的梯度值低于低閾值,則在結(jié)果中置0,若該點的梯度值介于高低閾值之間,則需要進行如下判斷:檢查該點(將其視為中心點)的8鄰域點,看是否存在梯度值高于高閾值的點,若存在,則說明該中心點和確定的邊緣點相連接,故在結(jié)果中置1,否則置0。
(5)抑制孤立的弱邊緣完成邊緣檢測
雙閾值檢測確定真是和潛在的邊緣
原圖:
邊緣輪廓:
閾值:edge_img=cv2.Canny(img,50,100)
閾值:edge_img=cv2.Canny(img,70,120)
相比前面面的圖,邊緣線少了
import cv2
img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE)
edge_img=cv2.Canny(img,70,120)
cv2.imshow('edges',edge_img)
cv2.waitKey(0)
cv2.imwrite('edge_img.jpg',edge_img)
附一段可動態(tài)調(diào)節(jié)閾值的實時檢測效果代碼:
import cv2
cv2.namedWindow('edge_detection')
cv2.createTrackbar('minThreshold','edge_detection',50,1000,lambda x: x)
cv2.createTrackbar('maxThreshold','edge_detection',100,1000,lambda x: x)
img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE)
while True:
minThreshold=cv2.getTrackbarPos('minThreshold', 'edge_detection')
maxThreshold = cv2.getTrackbarPos('maxThreshold', 'edge_detection')
edges = cv2.Canny(img, minThreshold, maxThreshold)
cv2.imshow('edge_detection', edges)
cv2.waitKey(10)
效果如下:
用以選擇合適閾值
4、ROI mask
region of interest 感興趣區(qū)域
· 數(shù)組切片
· 布爾運算(AND與運算)
原圖以矩陣np.array形式存儲在內(nèi)存
zeros_like生成一個大小一致的全0矩陣
fillpoly填充掩碼部分,留作與操作
cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)
這部分是將四個頂點包住的梯形填充,255為灰度值,純白色
import cv2
import numpy as np
edge_img=cv2.imread('edge_img.jpg',cv2.IMREAD_GRAYSCALE)
mask=np.zeros_like(edge_img)
cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)
cv2.imshow('mask',mask)
cv2.waitKey(0)
import cv2
import numpy as np
edge_img=cv2.imread('edge_img.jpg',cv2.IMREAD_GRAYSCALE)
mask=np.zeros_like(edge_img)
mask=cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)
#cv2.imshow('mask',mask)
#cv2.waitKey(0)
mask_edge_img=cv2.bitwise_and(edge_img,mask)
cv2.imshow('mask_edge_img.jpg',mask_edge_img)
#cv2.imwrite('mask_edge_img.jpg',mask_edge_img)
cv2.waitKey(0)
有點偏,多了雜點,往右挪一挪
np.array([[[0, 368], [300, 210], [340, 210], [640, 368]]]),
color=255)
5、霍夫變換
找直線用
笛卡爾坐標系中的一條線,在霍夫空間中是個點;
霍夫空間中的一條線,代表笛卡爾系中所有經(jīng)過某點的直線;
?。?!
所以,經(jīng)過這樣一個對應(yīng)變換的思路,霍夫空間中的一個點作為參數(shù)可以確定笛卡爾空間中多數(shù)點確定的一條直線
右圖是極坐標系對應(yīng)霍夫空間交線圖
如圖,兩條直線,右圖兩個兩點,說明相交次數(shù)多,根據(jù)這兩個點的坐標參數(shù),確定直線
api:
返回值是一個列表,代表線段起點和終點的坐標值
opencv中霍夫變換要在灰度圖中進行,讀取圖片必須注意格式
在控制臺逐行輸入
import cv2
import numpy as np
img=cv2.imread('lines.jpg',cv2.IMREAD_GRAYSCALE)
lines = cv2.HoughLinesP(img, 1, np.pi / 180, 15, minLineLength=40,
maxLineGap=20)
lines
len(lines)
我們直觀感受到的2條線,發(fā)現(xiàn)輸出會有32條直線
其實是因為我們所處理的原圖,直線有寬度,粗了
看參數(shù),其中有很多線起點終點坐標相差不大,就是一條線段
為了先驗獲得兩條,我們得進行分類,按斜率分為兩組
import cv2
import numpy as np
def calculate_slope(line):
"""
計算線段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('mask_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]
注意,opencv中,坐標原點在左上角
6、離群值過濾
上節(jié)根據(jù)斜率分為左右兩組線,但實際因為誤差,噪點,會誤識別為車道線,我們要進一步剔除一部分,我們知道,落在車道線上的線占大多數(shù),與之斜率差別大的我們過濾掉
import cv2
import numpy as np
def calculate_slope(line):
"""
計算線段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('mask_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)# 獲取array中數(shù)值最大的 找差值最大線段的索引
if diff[idx] > threshold:#和閾值比一下,差得多不,超標就滾蛋
slopes.pop(idx)#斜率列表中刪掉
lines.pop(idx)#從線段列表中刪掉
else:
break
return lines
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))
7、最小二乘擬合
將所有認為是左或右車道的線擬合為一條
np.ravel :將高維數(shù)組拉成一維數(shù)組
np.polyfit: 多項式擬合
np.polyval:多項式求值,第一個參數(shù)是多項式系數(shù),第二參數(shù)是任意X
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: 線段上的兩點,np.array([[xmin, ymin], [xmax, ymax]])
"""
# 1. 取出所有坐標點
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. 進行直線擬合.得到多項式系數(shù)
poly = np.polyfit(x_coords, y_coords, deg=1)
# 3. 根據(jù)多項式系數(shù),計算兩個直線上的點,用于唯一確定這條直線
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=int)
print("left lane")
print(least_squares_fit(left_lines))
print("right lane")
print(least_squares_fit(right_lines))
在上節(jié)代碼后加入這段處理
兩點練成一條線
8、直線繪制
cv2.line
添加如下代碼
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)
為什么左側(cè)車道線肉眼可見的偏移大?
因為在Canny邊緣檢測閾值調(diào)整,和前面ROI mask處掩碼區(qū)域沒有設(shè)置好,如下圖,左車道線處有車輛的邊緣,我沒有細心調(diào)節(jié)閾值,后面掩碼區(qū)域也沒有掩蓋掉
將梯形塊右移,得到新的圖取參與繪圖:
所以調(diào)整需要看情況文章來源:http://www.zghlxwxcb.cn/news/detail-627080.html
9、視頻流讀寫
文章來源地址http://www.zghlxwxcb.cn/news/detail-627080.html
capture = cv2.VideoCapture('video.mp4')
while True:
ret, frame = capture.read()#ret視頻流狀況,是否關(guān)閉
frame = show_lane(frame)
cv2.imshow('frame', frame)
cv2.waitKey(100)
到了這里,關(guān)于OpenCV學(xué)習(xí)筆記--以車道線檢測入門的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!