随着社会的发展,道路的质量直接关系到交通的安全性与效率。长期暴露在复杂的环境下,许多道路会出现裂缝、坑洼、沉降等缺陷,这些缺陷不仅影响行驶舒适性,还可能导致交通事故。因此,及时、准确地检测并修复道路缺陷是城市管理的重要任务。为了解决这一问题,我开发了一个基于 PyQt 和 YOLOv8 的"道路缺陷检测识别系统"。
该系统旨在通过深度学习和图形界面相结合的方式,实现对道路缺陷的自动检测、识别和标注。用户通过简单操作即可使用该系统完成道路缺陷的检测任务,并生成详细的缺陷报告,从而为道路维护和修复提供科学依据。
一、系统开发技术
本系统采用了两项关键技术:
1. YOLOv8 (You Only Look Once Version 8)
YOLOv8 是当前最前沿的目标检测模型之一,具有快速、精确的特点。相比传统的逐步检测方法,YOLOv8 能够一次性通过神经网络预测出图像中的多个目标,并为每个目标生成位置框和类别预测。这一优势使得该模型非常适用于实时检测任务。
在本系统中,YOLOv8 被用于检测道路表面各种缺陷,如裂缝、坑洼等。通过训练和调整模型参数,系统可以在较高的精度下识别并标注道路缺陷。
2. PyQt
PyQt 是 Python 的一个图形界面开发工具包,基于 Qt 库构建。它提供了强大的界面控件和丰富的功能,适合开发跨平台应用程序。在本系统中,PyQt 用于搭建用户界面,使得用户可以通过界面选择检测模式、加载文件、查看检测结果等操作。与 YOLOv8 的结合,PyQt 实现了系统的可视化和用户交互。
主要功能代码
def open_img(self):
try:
# 更新下拉列表的状态
self.update_comboBox_default()
# 选择文件 ;;All Files (*)
self.img_path, filetype = QFileDialog.getOpenFileName(None, "选择文件", self.ProjectPath,
"JPEG Image (*.jpeg);;PNG Image (*.png);;JFIF Image (*.jfif)")
if self.img_path == "": # 未选择文件
self.start_type = None
return
self.img_name = os.path.basename(self.img_path)
# 显示相对应的文字
self.label_img_path.setText(" " + self.img_path)
self.label_dir_path.setText(" 选择图片文件夹")
self.label_video_path.setText(" 选择视频文件")
self.label_camera_path.setText(" 打开摄像头")
self.start_type = 'img'
# 读取中文路径下图片
self.img = cv2.imdecode(np.fromfile(self.img_path, dtype=np.uint8), cv2.IMREAD_COLOR)
# 显示原图
self.show_frame(self.img)
except Exception as e:
traceback.print_exc()
def open_dir(self):
try:
# 更新下拉列表的状态
self.update_comboBox_default()
self.img_path_dir = QFileDialog.getExistingDirectory(None, "选择文件夹")
if not self.img_path_dir:
self.start_type = None
return
self.start_type = 'dir'
# 显示相对应的文字
self.label_dir_path.setText(" " + self.img_path_dir)
self.label_img_path.setText(" 选择图片文件")
self.label_video_path.setText(" 选择视频文件")
self.label_camera_path.setText(" 打开摄像头")
self.image_files = [file for file in os.listdir(self.img_path_dir) if file.lower().endswith(
('.bmp', '.dib', '.png', '.jpg', '.jpeg', '.pbm', '.pgm', '.ppm', '.tif', '.tiff'))]
if not self.image_files:
QMessageBox.information(self, "信息", "文件夹中没有符合条件的图片", QMessageBox.Yes)
return
self.current_index = 0
self.img_path = os.path.join(self.img_path_dir, self.image_files[self.current_index])
self.img_name = self.image_files[self.current_index]
self.img = cv2.imdecode(np.fromfile(self.img_path, dtype=np.uint8), cv2.IMREAD_COLOR)
self.show_frame(self.img)
except Exception as e:
traceback.print_exc()
def open_video(self):
try:
# 更新下拉列表的状态
self.update_comboBox_default()
# 选择文件
self.video_path, filetype = QFileDialog.getOpenFileName(None, "选择文件", self.ProjectPath,
"mp4 Video (*.mp4);;avi Video (*.avi)")
if not self.video_path:
self.start_type = None
return
self.start_type = 'video'
# 显示相对应的文字
self.label_video_path.setText(" " + self.video_path)
self.label_img_path.setText(" 选择图片文件")
self.label_dir_path.setText(" 选择图片文件夹")
self.label_camera_path.setText(" 打开摄像头")
self.video_name = os.path.basename(self.video_path)
self.video = cv2.VideoCapture(self.video_path)
# 读取第一帧
ret, self.img = self.video.read()
self.show_frame(self.img)
except Exception as e:
traceback.print_exc()
def open_camera(self):
try:
# 更新下拉列表的状态
self.update_comboBox_default()
if self.label_camera_path.text() == ' 打开摄像头' or self.label_camera_path.text() == ' 摄像头已关闭':
self.start_type = 'camera'
# 显示相对应的文字
self.label_img_path.setText(" 选择图片文件")
self.label_dir_path.setText(" 选择图片文件夹")
self.label_video_path.setText(" 选择视频文件")
self.label_camera_path.setText(" 摄像头已打开")
self.video_name = camera_num
self.video = cv2.VideoCapture(self.video_name)
ret, self.img = self.video.read()
self.show_frame(self.img)
elif self.label_camera_path.text() == ' 摄像头已打开':
# 修改文本为开始运行
self.pushButton_start.setText("开始运行 >")
self.label_camera_path.setText(" 摄像头已关闭")
except Exception as e:
traceback.print_exc()
def show_all(self, img, info):
'''
展示所有的信息
'''
self.show_frame(img)
self.show_info(info)
def start(self):
self.update_comboBox_default()
try:
if self.start_type == None:
QMessageBox.information(self, "信息", "请先选择输入类型!", QMessageBox.Yes)
return
if self.start_type == 'img':
# 读取中文路径下图片
self.img = cv2.imdecode(np.fromfile(self.img_path, dtype=np.uint8), cv2.IMREAD_COLOR)
_, result_info = self.predict_img(self.img)
self.show_all(self.img_show, result_info)
elif self.start_type in ['dir', 'video', 'camera']:
if self.pushButton_start.text() == '开始运行 >':
self.timer.start(20) # 每20毫秒处理一张图片或一帧
self.pushButton_start.setText("结束运行 >")
if self.start_type == 'camera':
self.label_camera_path.setText(" 摄像头已打开")
elif self.pushButton_start.text() == '结束运行 >':
self.pushButton_start.setText("开始运行 >")
self.timer.stop()
if self.start_type == 'camera':
self.label_camera_path.setText(" 摄像头已关闭")
self.video.release()
except Exception:
traceback.print_exc()
def update_frame(self):
if self.start_type == 'dir':
# 检查是否处理完文件夹中的所有图像
if self.current_index >= len(self.image_files):
self.pushButton_start.setText("开始运行 >")
self.timer.stop()
self.frame_number = 0 # 重置帧计数器
if self.current_index >= len(self.image_files):
QMessageBox.information(self, "信息", "此文件夹已识别完", QMessageBox.Yes)
return
# 获取当前图像的名称和路径
self.img_name = self.image_files[self.current_index]
self.img_path = os.path.join(self.img_path_dir, self.img_name)
# 读取图像并解码
self.img = cv2.imdecode(np.fromfile(self.img_path, dtype=np.uint8), -1)
# 更新索引以处理下一张图像
self.current_index += 1
elif self.start_type in ['video', 'camera']:
if self.frame_number == 0 and self.start_type == 'video':
# 对于视频,处理第一帧
self.video.set(cv2.CAP_PROP_POS_FRAMES, 0)
ret, self.img = self.video.read()
if ret:
# 获取当前帧号
frame_number = int(self.video.get(cv2.CAP_PROP_POS_FRAMES))
# 设置图像名称
self.img_name = f"{self.video_name}_{frame_number}.jpg"
self.img_path = self.video_path
self.frame_number = 0 # 重置帧计数器
else:
# 如果读取失败,停止计时器并释放视频资源
self.pushButton_start.setText("开始运行 >")
self.timer.stop()
self.video.release()
self.frame_number = 0 # 重置帧计数器
QMessageBox.information(self, "信息", "视频识别已完成", QMessageBox.Yes)
return
else:
# 读取下一帧
ret, self.img = self.video.read()
if not ret:
# 如果读取失败,停止计时器并释放视频资源
self.pushButton_start.setText("开始运行 >")
self.timer.stop()
self.video.release()
self.frame_number = 0 # 重置帧计数器
if self.start_type == 'video':
QMessageBox.information(self, "信息", "视频识别已完成", QMessageBox.Yes)
elif self.start_type == 'camera':
self.label_camera_path.setText(" 摄像头已关闭")
QMessageBox.information(self, "信息", "摄像头关闭", QMessageBox.Yes)
return
if self.start_type == 'video':
# 获取当前帧号并设置图像名称
frame_number = int(self.video.get(cv2.CAP_PROP_POS_FRAMES))
self.img_name = f"{self.video_name}_{frame_number}.jpg"
self.img_path = self.video_path
elif self.start_type == 'camera':
# 对于摄像头,增加帧号并设置图像名称
self.frame_number += 1
self.img_name = f"camera_{self.frame_number}.jpg"
self.img_path = 'camera'
# 进行图像预测
results, result_info = self.predict_img(self.img)
# 显示识别结果
self.show_all(self.img_show, result_info)
if self.start_type == 'video':
# 对于视频,增加帧号以处理下一帧
self.frame_number += 1
def predict_img(self, img):
# 初始化结果信息字典
result_info = {}
# 记录开始时间以计算处理时间
t1 = time.time()
# 设置结果图像的路径
self.result_img_name = os.path.join(self.result_img_path, self.img_name)
# 模型识别
self.results = yolo.predict(img, imgsz=imgsz, conf=conf_thres, device=device, classes=classes)
# 整理格式
self.results = format_data(self.results)
# 计算并记录消耗时间
self.consum_time = str(round(time.time() - t1, 2)) + 's'
# 记录输入时间
self.input_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 将结果写入文件
with open(self.result_txt, 'a+') as result_file:
result_file.write(
str([self.number, self.img_path, self.input_time, self.results, len(self.results), self.consum_time,
self.result_img_name])[1:-1])
result_file.write('\n')
# 显示识别信息表格
self.show_table()
# 增加编号
self.number += 1
# 获取下拉列表的值
self.get_comboBox_value(self.results)
if len(self.results) > 0:
# 如果有识别结果,获取第一个结果的信息
box = self.results[0][2]
score = self.results[0][1]
cls_name = self.results[0][0]
else:
# 如果无识别结果,设置默认值
box = [0, 0, 0, 0]
score = 0
cls_name = '无目标'
# 格式化结果信息
result_info = result_info_format(result_info, box, score, cls_name)
# 在图像上绘制识别结果
self.img_show = draw_info(img, self.results)
# 保存结果图像
cv2.imencode('.jpg', self.img_show)[1].tofile(self.result_img_name)
return self.results, result_info
def get_comboBox_value(self, results):
'''
获取当前所有的类别和ID,点击下拉列表时,使用
'''
# 默认第一个是 所有目标
lst = ["所有目标"]
for bbox in results:
cls_name = bbox[0]
lst.append(str(cls_name))
self.comboBox_value = lst
def show_info(self, result):
try:
if len(result) == 0:
print("未识别到目标")
return
cls_name = result['cls_name']
if len(self.chinese_name) > 3:
cls_name = self.chinese_name[cls_name]
if len(cls_name) > 10:
# 当字符串太长时,显示不完整
lst_cls_name = cls_name.split('_')
cls_name = lst_cls_name[0][:10] + '...'
self.label_class.setText(str(cls_name))
self.label_score.setText(str(result['score']))
self.label_xmin_v.setText(str(result['label_xmin_v']))
self.label_ymin_v.setText(str(result['label_ymin_v']))
self.label_xmax_v.setText(str(result['label_xmax_v']))
self.label_ymax_v.setText(str(result['label_ymax_v']))
self.update() # 刷新界面
except Exception as e:
traceback.print_exc()
def update_comboBox_default(self):
"""
将下拉列表更新为 所有目标 默认状态
"""
# 清空内容
self.comboBox.clear()
# 添加更新内容
self.comboBox.addItems([self.comboBox_text])
def show_table(self):
try:
# 显示表格
self.RowLength = self.RowLength + 1
self.tableWidget_info.setRowCount(self.RowLength)
for column, content in enumerate(
[self.number, self.img_path, self.input_time, self.results, len(self.results), self.consum_time,
self.result_img_name]):
# self.tableWidget_info.setColumnWidth(3, 0) # 将第二列的宽度设置为0,即不显示
row = self.RowLength - 1
item = QtWidgets.QTableWidgetItem(str(content))
# 居中
item.setTextAlignment(QtCore.Qt.AlignCenter)
# 设置字体颜色
item.setForeground(QColor.fromRgb(column_color[0], column_color[1], column_color[2]))
self.tableWidget_info.setItem(row, column, item)
# 滚动到底部
self.tableWidget_info.scrollToBottom()
except Exception as e:
traceback.print_exc()
def write_files(self):
"""
导出 excel、csv 数据
"""
path, filetype = QFileDialog.getSaveFileName(None, "另存为", self.ProjectPath,
"Excel 工作簿(*.xls);;CSV (逗号分隔)(*.csv)")
with open(self.result_txt, 'r') as f:
lst_txt = f.readlines()
data = [list(eval(x.replace('\n', ''))) for x in lst_txt]
if path == "": # 未选择
return
if filetype == 'Excel 工作簿(*.xls)':
writexls(data, path)
elif filetype == 'CSV (逗号分隔)(*.csv)':
writecsv(data, path)
QMessageBox.information(None, "成功", "数据已保存!", QMessageBox.Yes)
二、系统主要功能
基于以上技术,本系统具备以下功能:
1. 道路缺陷检测与识别
系统通过接入摄像头或加载预录制视频,实时检测道路缺陷。YOLOv8 模型可以精准识别出图像中的缺陷类型,例如:
- 裂缝:由于路面老化或重载荷导致的线状裂缝。
- 坑洼:由于自然因素或长期使用引起的路面塌陷。
- 沉降:道路的表面不均匀性,可能导致行车颠簸。
检测到的缺陷会在图像中以边界框的形式标注,并标记缺陷类别,方便用户快速识别和定位。
2. 多种检测模式
系统支持多种工作模式,灵活应对不同的场景需求:
- 实时监控模式:系统通过摄像头实时捕捉道路影像,自动进行缺陷检测,适合用于道路监控、巡检等场景。
- 离线分析模式:用户可以加载预录制的道路视频或图片,系统会自动进行缺陷识别和分析。这一模式适用于道路事后检查和大批量数据处理。
3. 用户友好的图形界面
通过 PyQt 开发的图形界面,用户可以方便地进行操作。界面设计直观,所有功能模块均可通过界面控件进行控制,如文件加载、检测参数设置、实时视频显示等。用户无需具备深厚的技术背景,即可快速上手。
4. 区域选择与重点检测
在某些场景下,用户可能只关心图像中的某个特定区域。为此,系统提供了手动选择检测区域的功能。用户可以在图像中框选某一感兴趣区域,系统将优先对该区域进行缺陷检测。这样,在处理复杂或大规模路面时,能够大幅提高检测效率和精度。
5. 检测结果报告与统计
系统自动生成道路缺陷的详细报告,报告包含以下内容:
- 缺陷类型(如裂缝、坑洼等)
- 缺陷的具体位置(在图像中的坐标)
- 缺陷的面积或长度(根据检测结果计算)
- 检测置信度
这些报告可以导出为文档,供后续维护和修复工作使用。同时,系统还提供简单的统计功能,用户可以根据检测结果进行缺陷统计,如道路上总共出现多少个裂缝,坑洼的面积总和等。
6. 自定义模型参数
为了满足不同用户的需求,系统允许用户自定义 YOLOv8 模型的检测参数。用户可以通过图形界面调整模型的置信度阈值、NMS(非极大值抑制)参数等,以优化模型的检测结果。这一功能使得系统在应对各种不同场景时更具灵活性。
7. 扩展性与兼容性
系统设计充分考虑了扩展性,用户不仅可以使用现有的缺陷检测模型,还可以根据需要进行模型训练与更新。例如,可以通过训练其他类型的数据集,将系统扩展到桥梁检测、建筑裂缝检测等领域,极大提高了该系统的适用范围。
三、系统的实际应用场景
本系统可以广泛应用于各类道路维护场景中,具体包括:
- 市政道路管理:用于日常巡检和维护,提高道路安全。
- 高速公路监控:实时监控高速路面,及时发现潜在的安全隐患。
- 建筑行业:扩展到其他场景下的检测任务,如建筑裂缝、墙面剥落等。
四、总结
基于 PyQt 和 YOLOv8 开发的"道路缺陷检测识别系统"为道路维护和检测提供了一种高效、便捷的解决方案。它不仅能够实时检测和识别道路上的缺陷,还可以生成详细的报告,为道路的修复与维护提供重要参考。凭借其强大的检测能力和灵活的界面设计,该系统能够在城市道路管理、交通安全保障等多个领域发挥重要作用。
在未来的发展中,系统还可以进一步优化和扩展,例如通过引入更多的深度学习模型和检测算法,提升检测精度,或者与无人机等设备结合,实现更加智能化的道路监控与维护。
B站视频演示及软件下载地址:基于yolov8和pyqt5的道路缺陷检测识别系统_哔哩哔哩_bilibili