1、项目介绍
本项目的主要目标是通过视频输入流实时检测并显示车道线。通过在GUI界面中集成OpenCV图像处理功能,用户可以加载视频,启动检测过程,并在窗口中查看每帧图像的处理结果。
2、效果演示
3、项目搭建
文件夹videofiles放置视频检测文件,lane.ui为PyQt设计的ui文件,lane.py为lane.ui转换后的代码文件,lanedet.py为车道线检测的逻辑主代码,run.py为继承自lane.py的界面代码。
4、原理讲解
车道线检测模块的核心目标是从输入的视频流或图像中准确地检测出车道线,并将其实时标记在视频中。整个检测过程涉及多个步骤,包括图像预处理、区域选择、车道线检测及结果绘制。
图像预处理
车道线检测的第一步是图像的预处理。通过高斯模糊对图像进行去噪,减少图像中的噪声对后续处理的影响。之后,将图像转换为灰度图,以便于后续的边缘检测。边缘检测采用了Canny算法,这是一个经典的边缘检测方法,能够有效提取出图像中显著的边缘信息。通过设置适当的低、高阈值,Canny算法可以有效地提取出车道线的轮廓,作为后续处理的输入。
def Preprocess_images(image, ksize, sigma=None, low_threshold=50, high_threshold=250):
if sigma is None:
sigma = 0.3 * ((ksize - 1) * 0.5 - 1) + 0.8
gaussianblur_img = cv2.GaussianBlur(image, ksize=(ksize, ksize), sigmaX=sigma)
gray_img = cv2.cvtColor(gaussianblur_img, cv2.COLOR_BGR2GRAY)
canny_img = cv2.Canny(gray_img, low_threshold, high_threshold)
return canny_img
区域选择
车道线通常出现在图像的特定区域,为了提高检测效率和准确性,采用了感兴趣区域(ROI)的技术。通过设定一个多边形区域,我们限制了车道线检测只在图像的下半部分进行,从而减少了不相关区域的干扰。在此区域内,只对车道线进行进一步处理,提升了处理速度。
def Roi_Mask(gray_image):
mask = np.zeros_like(gray_image)
height, width = gray_image.shape
# 如果效果不是很好, 可以调整此处的比例
left_bottom = [0, height]
right_bottom = [width, height]
left_top = [width / 3, height / 1.5]
right_top = [width / 3 * 2, height / 1.5]
vertices = np.array([left_top,
right_top,
right_bottom,
left_bottom
], np.int32)
cv2.fillPoly(mask, [vertices], (255, ))
masked_image = cv2.bitwise_and(gray_image, mask)
return masked_image
如果你发现在你自己的视频文件的效果不是很好,可以调制vertices这里的比例,或者修改canny阈值。
车道线检测
车道线检测采用了霍夫变换的方法,这是检测直线的一种经典算法。在此过程中,首先对ROI区域的图像进行霍夫变换,提取出图像中的直线。然后,根据直线的斜率对其进行分类:斜率较小的直线为右车道线,斜率较大的直线为左车道线。接下来,使用最小二乘法对每个车道线的端点进行拟合,从而得到更加平滑且准确的车道线。
结果绘制与显示
通过拟合得到的左右车道线在图像中进行绘制。为便于用户查看,车道线用不同颜色的线条标注在视频帧上。此外,为了进一步增强视觉效果,可以在车道线的区域填充绿色透明多边形,以突出显示车道线区域。检测到的车道线在实时视频中显示,提供了实时反馈,方便用户查看车道线检测的效果。
lanedet.py
import cv2
import time
import numpy as np
def Preprocess_images(image, ksize, sigma=None, low_threshold=50, high_threshold=250):
if sigma is None:
sigma = 0.3 * ((ksize - 1) * 0.5 - 1) + 0.8
gaussianblur_img = cv2.GaussianBlur(image, ksize=(ksize, ksize), sigmaX=sigma)
gray_img = cv2.cvtColor(gaussianblur_img, cv2.COLOR_BGR2GRAY)
canny_img = cv2.Canny(gray_img, low_threshold, high_threshold)
return canny_img
def Roi_Mask(gray_image):
mask = np.zeros_like(gray_image)
height, width = gray_image.shape
# 如果效果不是很好, 可以调整此处的比例
left_bottom = [0, height]
right_bottom = [width, height]
left_top = [width / 3, height / 1.5]
right_top = [width / 3 * 2, height / 1.5]
vertices = np.array([left_top,
right_top,
right_bottom,
left_bottom
], np.int32)
cv2.fillPoly(mask, [vertices], (255, ))
masked_image = cv2.bitwise_and(gray_image, mask)
return masked_image
def draw_lines(img, lines, color=(0, 255, 255), thickness=2, is_draw_polygon=True):
left_lines_x = []
left_lines_y = []
right_lines_x = []
right_lines_y = []
line_y_max = 0
line_y_min = 999
try:
for line in lines:
for x1, y1, x2, y2 in line:
if y1 > line_y_max:
line_y_max = y1
if y2 > line_y_max:
line_y_max = y2
if y1 < line_y_min:
line_y_min = y1
if y2 < line_y_min:
line_y_min = y2
k = ( (y2 - y1) / (1e-6 + x2 - x1))
if k < -0.3:
left_lines_x.append(x1)
left_lines_y.append(y1)
left_lines_x.append(x2)
left_lines_y.append(y2)
elif k > 0.3:
right_lines_x.append(x1)
right_lines_y.append(y1)
right_lines_x.append(x2)
right_lines_y.append(y2)
# 最小二乘直线拟合
left_line_k, left_line_b = np.polyfit(left_lines_x, left_lines_y, 1)
right_line_k, right_line_b = np.polyfit(right_lines_x, right_lines_y, 1)
cv2.line(img,
(int((line_y_max - left_line_b) / left_line_k), line_y_max),
(int((line_y_min - left_line_b) / left_line_k), line_y_min),
color, thickness)
cv2.line(img,
(int((line_y_max - right_line_b) / right_line_k), line_y_max),
(int((line_y_min - right_line_b) / right_line_k), line_y_min),
color, thickness)
zero_img = np.zeros((img.shape), dtype=np.uint8)
if is_draw_polygon:
polygon = np.array([
[int((line_y_max - left_line_b) / left_line_k), line_y_max],
[int((line_y_max - right_line_b) / right_line_k), line_y_max],
[int((line_y_min - right_line_b) / right_line_k), line_y_min],
[int((line_y_min - left_line_b) / left_line_k), line_y_min]
])
cv2.fillConvexPoly(zero_img, polygon, color=(0, 255, 0))
img = cv2.addWeighted(img, 1, zero_img, beta = 0.2, gamma=0)
except Exception:
pass
return img
class FPS:
def __init__(self):
self.pTime = time.time()
def update(self, img=None, pos=(20, 50), color=(255, 0, 0), scale=3, thickness=3):
cTime = time.time()
try:
fps = 1 / (cTime - self.pTime)
self.pTime = cTime
if img is None:
return fps
else:
cv2.putText(img, f'FPS: {int(fps)}', pos, cv2.FONT_HERSHEY_PLAIN,
scale, color, thickness)
return fps, img
except:
return 0
def dectet_lane_line(video_path, ksize=3, end_k=27, is_draw_polygon=True):
cap = cv2.VideoCapture(video_path)
fps_counter = FPS()
while True:
ret, img = cap.read()
video = Preprocess_images(img, ksize=ksize)
roi_image = Roi_Mask(video)
line_img = cv2.HoughLinesP(roi_image,
rho=1,
theta=np.pi / 180,
threshold=15,
minLineLength=40,
maxLineGap=20)
img = draw_lines(img, line_img, is_draw_polygon=is_draw_polygon)
fps, img = fps_counter.update(img)
cv2.imshow('frame', img)
k = cv2.waitKey(1)
if k == end_k:
break
cap.release()
cv2.destroyAllWindows()
def dectet_lane_line_ui(
img,
ksize,
minVotesForLine=15,
minLineLength=40,
maxLineGap=20,
is_draw_polygon=True):
video = Preprocess_images(img, ksize=ksize)
roi_image = Roi_Mask(video)
line_img = cv2.HoughLinesP(roi_image,
rho=1,
theta=np.pi / 180,
threshold=minVotesForLine,
minLineLength=minLineLength,
maxLineGap=maxLineGap)
img = draw_lines(img, line_img, is_draw_polygon=is_draw_polygon)
return img
if __name__=="__main__":
video_path = r"videofiles/video1.mp4"
dectet_lane_line(video_path)
lane.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'lane.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Laneline(object):
def setupUi(self, Laneline):
Laneline.setObjectName("Laneline")
Laneline.resize(1043, 708)
self.centralwidget = QtWidgets.QWidget(Laneline)
self.centralwidget.setObjectName("centralwidget")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(30, 90, 991, 561))
self.layoutWidget.setObjectName("layoutWidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.layoutWidget)
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setObjectName("gridLayout_2")
self.RSettingFm = QtWidgets.QFrame(self.layoutWidget)
self.RSettingFm.setMaximumSize(QtCore.QSize(16777215, 200))
self.RSettingFm.setFrameShape(QtWidgets.QFrame.Box)
self.RSettingFm.setFrameShadow(QtWidgets.QFrame.Plain)
self.RSettingFm.setObjectName("RSettingFm")
self.RunBt = QtWidgets.QPushButton(self.RSettingFm)
self.RunBt.setGeometry(QtCore.QRect(120, 80, 93, 28))
self.RunBt.setObjectName("RunBt")
self.gridLayout_2.addWidget(self.RSettingFm, 1, 2, 1, 1)
self.DispLb = QtWidgets.QLabel(self.layoutWidget)
self.DispLb.setEnabled(True)
self.DispLb.setMaximumSize(QtCore.QSize(800, 800))
self.DispLb.setFrameShape(QtWidgets.QFrame.Box)
self.DispLb.setFrameShadow(QtWidgets.QFrame.Plain)
self.DispLb.setText("")
self.DispLb.setObjectName("DispLb")
self.gridLayout_2.addWidget(self.DispLb, 0, 1, 2, 1)
self.SettingFm = QtWidgets.QFrame(self.layoutWidget)
self.SettingFm.setMaximumSize(QtCore.QSize(350, 500))
self.SettingFm.setFrameShape(QtWidgets.QFrame.Box)
self.SettingFm.setFrameShadow(QtWidgets.QFrame.Plain)
self.SettingFm.setObjectName("SettingFm")
self.PFilePathLiEd = QtWidgets.QLineEdit(self.SettingFm)
self.PFilePathLiEd.setGeometry(QtCore.QRect(100, 30, 161, 31))
self.PFilePathLiEd.setObjectName("PFilePathLiEd")
self.PFilePathLab = QtWidgets.QLabel(self.SettingFm)
self.PFilePathLab.setGeometry(QtCore.QRect(20, 40, 81, 16))
font = QtGui.QFont()
font.setFamily("宋体")
font.setPointSize(10)
self.PFilePathLab.setFont(font)
self.PFilePathLab.setObjectName("PFilePathLab")
self.PFilePathBt = QtWidgets.QPushButton(self.SettingFm)
self.PFilePathBt.setGeometry(QtCore.QRect(270, 30, 71, 28))
self.PFilePathBt.setObjectName("PFilePathBt")
self.gridLayout_2.addWidget(self.SettingFm, 0, 2, 1, 1)
self.TitleLab = QtWidgets.QLabel(self.centralwidget)
self.TitleLab.setGeometry(QtCore.QRect(310, 20, 451, 61))
self.TitleLab.setMaximumSize(QtCore.QSize(500, 100))
font = QtGui.QFont()
font.setFamily("宋体")
font.setPointSize(24)
self.TitleLab.setFont(font)
self.TitleLab.setObjectName("TitleLab")
Laneline.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(Laneline)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1043, 26))
self.menubar.setObjectName("menubar")
self.menu = QtWidgets.QMenu(self.menubar)
self.menu.setObjectName("menu")
Laneline.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(Laneline)
self.statusbar.setObjectName("statusbar")
Laneline.setStatusBar(self.statusbar)
self.laneaction = QtWidgets.QAction(Laneline)
self.laneaction.setObjectName("laneaction")
self.yinaction = QtWidgets.QAction(Laneline)
self.yinaction.setObjectName("yinaction")
self.menu.addAction(self.laneaction)
self.menu.addAction(self.yinaction)
self.menubar.addAction(self.menu.menuAction())
self.retranslateUi(Laneline)
QtCore.QMetaObject.connectSlotsByName(Laneline)
def retranslateUi(self, Laneline):
_translate = QtCore.QCoreApplication.translate
Laneline.setWindowTitle(_translate("Laneline", "MainWindow"))
self.RunBt.setText(_translate("Laneline", "运行"))
self.PFilePathLab.setText(_translate("Laneline", "文件路径:"))
self.PFilePathBt.setText(_translate("Laneline", "..."))
self.TitleLab.setText(_translate("Laneline", "基于Opencv的车道线检测"))
self.menu.setTitle(_translate("Laneline", "选择"))
self.laneaction.setText(_translate("Laneline", "车道检测"))
self.yinaction.setText(_translate("Laneline", "音量控制"))
run.py
from lane import Ui_Laneline
from lanedet import dectet_lane_line_ui
import sys
import cv2
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from PyQt5.QtCore import QTimer, QCoreApplication
import qimage2ndarray
class LaneDetectionShow(QMainWindow, Ui_Laneline):
def __init__(self, parent=None):
super(LaneDetectionShow, self).__init__(parent)
self.setupUi(self)
self.PrepParameters()
self.CallBackFunctions()
self.InitUI() # 初始化UI组件的额外设置
self.ksize = 3
# self.min_votes_for_line = 15
# self.min_line_length=40,
# self.max_line_gap=20,
# self.is_draw_polygon=True,
def PrepParameters(self):
self.PFilePath = r'./videofiles\video1.mp4'
self.PFilePathLiEd.setText(self.PFilePath)
video_path = self.PFilePath
self.camera = cv2.VideoCapture(video_path)
if not self.camera.isOpened():
print("Error: Could not open video.")
def CallBackFunctions(self):
"""将lineedit与button连接"""
self.PFilePathBt.clicked.connect(self.SetPFilePath)
self.RunBt.clicked.connect(self.StartVideo)
def SetPFilePath(self):
file_filter = "Video Files (*.mp4 *.avi *.mov *.mkv)"
options = QFileDialog.Options()
start_dir = '.'
file_name, _ = QFileDialog.getOpenFileName(self, "Select Video File", start_dir, file_filter, options=options)
if file_name:
self.PFilePathLiEd.setText(file_name)
self.PFilePath = file_name
self.camera.release()
self.camera = cv2.VideoCapture(file_name)
if not self.camera.isOpened():
print("Error: Could not open selected video.")
def DispImg(self):
if self.Image is not None:
self.Image = dectet_lane_line_ui(
self.Image,
self.ksize,
# minVotesForLine=,
# minLineLength=,
# maxLineGap=,
# is_draw_polygon=,
)
self.Image = cv2.cvtColor(self.Image, cv2.COLOR_BGR2RGB)
qimg = qimage2ndarray.array2qimage(self.Image)
self.DispLb.setPixmap(QPixmap.fromImage(qimg))
self.DispLb.setScaledContents(True)
def StartVideo(self):
self.Timer = QTimer()
self.Timer.timeout.connect(self.TimerOutFun)
self.Timer.start(30) # 启动定时器,每30毫秒触发一次
def TimerOutFun(self):
success, self.Image = self.camera.read()
if success:
self.DispImg()
else:
self.Timer.stop()
print("Video playback finished.")
def InitUI(self):
self.RunBt.setEnabled(True)
def ExitApp(self):
self.Timer.Stop()
self.camera.release()
QCoreApplication.quit()
if __name__ == '__main__':
app = QApplication(sys.argv)
ui = LaneDetectionShow()
ui.show()
sys.exit(app.exec_())
5、项目总结
本项目利用 PyQt5 作为图形用户界面框架,并结合 OpenCV 进行图像处理,开发了一个车道线检测系统。该系统能够从视频文件中实时读取每一帧图像,并通过车道线检测算法对视频进行处理,最终在界面中展示车道线检测结果。用户可以选择视频文件并查看车道线检测的实时效果。
6、参考文章
opencv入门项目——车道线检测_opencv车道线检测-CSDN博客
车道线检测1.0(Python+Opencv基础版)-CSDN博客
车道线检测(传统基于Opencv的方法)_基于opencv的车道线检测-CSDN博客
标签:25,车道,img,Laneline,self,cv2,Opencv,line From: https://blog.csdn.net/m0_62919535/article/details/144691213