基于YOLOv8的“道路缺陷检测识别系统“,python源码+Pyqt5界面

        随着社会的发展,道路的质量直接关系到交通的安全性与效率。长期暴露在复杂的环境下,许多道路会出现裂缝、坑洼、沉降等缺陷,这些缺陷不仅影响行驶舒适性,还可能导致交通事故。因此,及时、准确地检测并修复道路缺陷是城市管理的重要任务。为了解决这一问题,我开发了一个基于 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

### PyQt5 缺陷检测界面设计概述 使用 PyQt5 进行缺陷检测系统的界面设计涉及多个方面,包括但不限于 GUI 布局的设计、信号与槽机制的应用以及与其他模块(如 YOLOv5 模型)的集成。以下是关于如何利用 PyQt5 实现 PCB 缺陷检测系统的一些关键点: #### 1. **环境搭建** 为了确保开发顺利进行,需先完成 PyQt5 及其工具包的安装。可以通过以下命令快速配置所需依赖项[^2]: ```bash pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install pyqt5-tools -i https://pypi.tuna.tsinghua.edu.cn/simple ``` 这些工具提供了构建图形用户界面所需的组件支持。 --- #### 2. **GUI 主窗口布局设计** 在 PyQt5 中,通常会通过 `QMainWindow` 或 `QWidget` 来创建主窗口,并结合各种控件来实现功能需求。对于 PCB 缺陷检测系统而言,常见的界面元素可能包括图像显示区域、按钮组和状态栏等。 下面是一个简单的示例代码片段用于展示基本框架[^1]: ```python import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLabel, QFileDialog from PyQt5.QtGui import QPixmap class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("PCB 缺陷检测系统") layout = QVBoxLayout() # 添加图片显示区 self.image_label = QLabel(self) self.image_label.setText("请选择一张图片...") layout.addWidget(self.image_label) # 文件选择按钮 select_button = QPushButton("选择图片", self) select_button.clicked.connect(self.open_image_dialog) layout.addWidget(select_button) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def open_image_dialog(self): file_name, _ = QFileDialog.getOpenFileName(self, "打开图片", "", "Images (*.png *.jpg)") if file_name: pixmap = QPixmap(file_name).scaled(800, 600) self.image_label.setPixmap(pixmap) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) ``` 上述代码实现了基础的功能——允许用户加载并查看本地图片文件。这一步骤为后续引入 YOLOv5 检测逻辑奠定了基础。 --- #### 3. **YOLOv5 集成** 要使该界面具备实际意义,则需要进一步将 YOLOv5 模型嵌入其中。具体来说,在按下某个触发按钮后调用模型预测函数即可。例如,假设已存在名为 `detect()` 的方法负责执行目标检测操作,则可以在按钮点击事件中加入如下处理逻辑[^4]: ```python def detect_defects(self): image_path = self.image_label.pixmap().toImage() # 获取当前选中的图片路径 if not image_path.isNull(): from yolov5_module import run_detection # 自定义封装的YOLOv5推理接口 results = run_detection(image_path) print(f"Detection Results: {results}") # 更新UI以反映结果 (此处省略具体实现细节...) ``` 注意这里提到的 `run_detection()` 函数应由开发者自行编写或者借用官方文档推荐的方式完成初始化设置及推断过程。 --- #### 4. **模型评估与优化建议** 除了单纯提供可视化反馈外,还可以考虑增加更多高级特性比如实时监控各项指标变化趋势图等等。针对不同应用场景下的特殊要求适时调整网络结构参数也是提升整体效果的重要手段之一。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值