diff --git a/src/src/YOLOv8/.idea/.gitignore b/src/src/YOLOv8/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/src/src/YOLOv8/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/src/YOLOv8/.idea/YOLOv8face.iml b/src/src/YOLOv8/.idea/YOLOv8face.iml new file mode 100644 index 0000000..fe1c2ea --- /dev/null +++ b/src/src/YOLOv8/.idea/YOLOv8face.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/src/YOLOv8/.idea/inspectionProfiles/Project_Default.xml b/src/src/YOLOv8/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..2df9d6b --- /dev/null +++ b/src/src/YOLOv8/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/src/src/YOLOv8/.idea/inspectionProfiles/profiles_settings.xml b/src/src/YOLOv8/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/src/src/YOLOv8/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/src/YOLOv8/.idea/misc.xml b/src/src/YOLOv8/.idea/misc.xml new file mode 100644 index 0000000..c95d785 --- /dev/null +++ b/src/src/YOLOv8/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/src/YOLOv8/.idea/modules.xml b/src/src/YOLOv8/.idea/modules.xml new file mode 100644 index 0000000..fdf08a8 --- /dev/null +++ b/src/src/YOLOv8/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/src/YOLOv8/AppLauncher.py b/src/src/YOLOv8/AppLauncher.py new file mode 100644 index 0000000..0ba6a4a --- /dev/null +++ b/src/src/YOLOv8/AppLauncher.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +""" +室内多传感器寻敌系统应用启动器 +整合启动画面、欢迎界面和主程序 +从根目录启动版本 +""" + +import sys +import os +from PyQt5.QtWidgets import QApplication +from PyQt5.QtCore import QTimer, Qt +from PyQt5.QtGui import QIcon + +# 添加UIProgram目录到系统路径 +current_dir = os.path.dirname(os.path.abspath(__file__)) +ui_dir = os.path.join(current_dir, 'UIProgram') +sys.path.append(ui_dir) +sys.path.append(current_dir) + +# 导入自定义模块 +from UIProgram.SplashScreen import SplashScreen +from UIProgram.InitialWindow import InitialWindow +from UIProgram.ModernMainWindow import ModernMainWindow + +class AppLauncher: + """应用程序启动器""" + + def __init__(self): + self.app = None + self.splash = None + self.initial_window = None + self.main_window = None + + def run(self): + """运行应用程序""" + # 设置高DPI支持(必须在创建QApplication之前) + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) + QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) + + # 创建QApplication + self.app = QApplication(sys.argv) + self.app.setApplicationName("室内多传感器寻敌系统") + self.app.setApplicationVersion("2.0.0") + + # 设置应用图标 + icon_path = "UIProgram/ui_imgs/icons/face.png" + if os.path.exists(icon_path): + self.app.setWindowIcon(QIcon(icon_path)) + + # 显示启动画面 + self.showSplashScreen() + + # 运行应用 + sys.exit(self.app.exec_()) + + def showSplashScreen(self): + """显示启动画面""" + self.splash = SplashScreen() + self.splash.splashFinished.connect(self.showInitialWindow) + self.splash.show() + + def showInitialWindow(self): + """显示欢迎界面""" + # 确保启动画面已关闭 + if self.splash: + self.splash.close() + self.splash = None + + # 创建并显示欢迎界面 + self.initial_window = InitialWindow() + self.initial_window.enterSystem.connect(self.showMainWindow) + self.initial_window.show() + + def showMainWindow(self): + """显示主程序界面""" + # 关闭欢迎界面 + if self.initial_window: + self.initial_window.close() + self.initial_window = None + + # 创建并显示主程序 + try: + self.main_window = ModernMainWindow() + self.main_window.show() + + # 确保主窗口在最前面 + self.main_window.raise_() + self.main_window.activateWindow() + + except Exception as e: + print(f"启动主程序时出现错误: {e}") + # 如果主程序启动失败,提供备用方案 + from PyQt5.QtWidgets import QMessageBox + msg = QMessageBox() + msg.setIcon(QMessageBox.Critical) + msg.setWindowTitle("启动错误") + msg.setText(f"无法启动主程序:\n{str(e)}") + msg.setInformativeText("请检查依赖库是否正确安装。") + msg.exec_() + + # 重新显示欢迎界面 + self.showInitialWindow() + +def main(): + """主函数""" + launcher = AppLauncher() + launcher.run() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/src/YOLOv8/CITATION.cff b/src/src/YOLOv8/CITATION.cff new file mode 100644 index 0000000..8e85b7a --- /dev/null +++ b/src/src/YOLOv8/CITATION.cff @@ -0,0 +1,20 @@ +cff-version: 1.2.0 +preferred-citation: + type: software + message: If you use this software, please cite it as below. + authors: + - family-names: Jocher + given-names: Glenn + orcid: "https://orcid.org/0000-0001-5950-6979" + - family-names: Chaurasia + given-names: Ayush + orcid: "https://orcid.org/0000-0002-7603-6750" + - family-names: Qiu + given-names: Jing + orcid: "https://orcid.org/0000-0003-3783-7069" + title: "YOLO by Ultralytics" + version: 8.0.0 + # doi: 10.5281/zenodo.3908559 # TODO + date-released: 2023-1-10 + license: AGPL-3.0 + url: "https://github.com/ultralytics/ultralytics" diff --git a/src/src/YOLOv8/CameraTest.py b/src/src/YOLOv8/CameraTest.py new file mode 100644 index 0000000..fac501a --- /dev/null +++ b/src/src/YOLOv8/CameraTest.py @@ -0,0 +1,46 @@ +#coding:utf-8 +import cv2 +from ultralytics import YOLO + +# 所需加载的模型目录 +path = 'models/best.pt' + +# Load the YOLOv8 model +model = YOLO(path) + +ID = 0 +while(ID<10): + cap = cv2.VideoCapture(ID) + # get a frame + ret, frame = cap.read() + if ret == False: + ID += 1 + else: + print('摄像头ID:',ID) + break + +# Loop through the video frames +while cap.isOpened(): + # Read a frame from the video + success, frame = cap.read() + + if success: + # Run YOLOv8 inference on the frame + results = model(frame) + + # Visualize the results on the frame + annotated_frame = results[0].plot() + + # Display the annotated frame + cv2.imshow("YOLOv8 Inference", annotated_frame) + + # Break the loop if 'q' is pressed + if cv2.waitKey(1) & 0xFF == ord("q"): + break + else: + # Break the loop if the end of the video is reached + break + +# Release the video capture object and close the display window +cap.release() +cv2.destroyAllWindows() \ No newline at end of file diff --git a/src/src/YOLOv8/Config.py b/src/src/YOLOv8/Config.py new file mode 100644 index 0000000..6cb59b7 --- /dev/null +++ b/src/src/YOLOv8/Config.py @@ -0,0 +1,9 @@ +#coding:utf-8 + +# 图片及视频检测结果保存路径 +save_path = 'save_data' + +# 使用的模型路径 +model_path = 'models/best.pt' +names = {0: 'face'} +CH_names = ['人脸'] diff --git a/src/src/YOLOv8/Font/platech.ttf b/src/src/YOLOv8/Font/platech.ttf new file mode 100644 index 0000000..d66a970 Binary files /dev/null and b/src/src/YOLOv8/Font/platech.ttf differ diff --git a/src/src/YOLOv8/MainProgram.py b/src/src/YOLOv8/MainProgram.py new file mode 100644 index 0000000..d6b3bfd --- /dev/null +++ b/src/src/YOLOv8/MainProgram.py @@ -0,0 +1,616 @@ +# -*- coding: utf-8 -*- +import time +from PyQt5.QtWidgets import QApplication , QMainWindow, QFileDialog, \ + QMessageBox,QWidget,QHeaderView,QTableWidgetItem, QAbstractItemView +import sys +import os +from PIL import ImageFont +from ultralytics import YOLO +sys.path.append('UIProgram') +from UIProgram.UiMain import Ui_MainWindow +import sys +from PyQt5.QtCore import QTimer, Qt, QThread, pyqtSignal,QCoreApplication +import detect_tools as tools +import cv2 +import Config +from UIProgram.QssLoader import QSSLoader +from UIProgram.precess_bar import ProgressBar +import numpy as np +# import torch + +class MainWindow(QMainWindow): + def __init__(self, parent=None): + super(QMainWindow, self).__init__(parent) + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + self.initMain() + self.signalconnect() + + # 加载css渲染效果 + style_file = 'UIProgram/style.css' + qssStyleSheet = QSSLoader.read_qss_file(style_file) + self.setStyleSheet(qssStyleSheet) + + def signalconnect(self): + self.ui.PicBtn.clicked.connect(self.open_img) + self.ui.comboBox.activated.connect(self.combox_change) + self.ui.VideoBtn.clicked.connect(self.vedio_show) + self.ui.CapBtn.clicked.connect(self.camera_show) + self.ui.SaveBtn.clicked.connect(self.save_detect_video) + self.ui.ExitBtn.clicked.connect(QCoreApplication.quit) + self.ui.FilesBtn.clicked.connect(self.detact_batch_imgs) + + def initMain(self): + # 更新显示区域尺寸以适配新UI + self.show_width = 880 + self.show_height = 580 + + self.org_path = None + + self.is_camera_open = False + self.cap = None + + # self.device = 0 if torch.cuda.is_available() else 'cpu' + + # 加载检测模型 + self.model = YOLO(Config.model_path, task='detect') + self.model(np.zeros((48, 48, 3))) #预先加载推理模型 + self.fontC = ImageFont.truetype("Font/platech.ttf", 25, 0) + + # 用于绘制不同颜色矩形框 + self.colors = tools.Colors() + + # 更新视频图像 + self.timer_camera = QTimer() + + # 更新检测信息表格 + # self.timer_info = QTimer() + # 保存视频 + self.timer_save_video = QTimer() + + # 表格 - 更新列宽以适配新的宽屏布局 + self.ui.tableWidget.verticalHeader().setSectionResizeMode(QHeaderView.Fixed) + self.ui.tableWidget.verticalHeader().setDefaultSectionSize(40) + self.ui.tableWidget.setColumnWidth(0, 80) # 序号 + self.ui.tableWidget.setColumnWidth(1, 400) # 文件路径 - 增加宽度 + self.ui.tableWidget.setColumnWidth(2, 150) # 类别 + self.ui.tableWidget.setColumnWidth(3, 120) # 置信度 + self.ui.tableWidget.setColumnWidth(4, 300) # 坐标位置 - 增加宽度 + self.ui.tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) # 设置表格整行选中 + self.ui.tableWidget.verticalHeader().setVisible(False) # 隐藏列标题 + self.ui.tableWidget.setAlternatingRowColors(True) # 表格背景交替 + + + def open_img(self): + if self.cap: + # 打开图片前关闭摄像头 + self.video_stop() + self.is_camera_open = False + self.ui.CaplineEdit.setText('摄像头未开启') + self.cap = None + + # 弹出的窗口名称:'打开图片' + # 默认打开的目录:'./' + # 只能打开.jpg与.gif结尾的图片文件 + # file_path, _ = QFileDialog.getOpenFileName(self.ui.centralwidget, '打开图片', './', "Image files (*.jpg *.gif)") + file_path, _ = QFileDialog.getOpenFileName(None, '打开图片', './', "Image files (*.jpg *.jepg *.png)") + if not file_path: + return + + self.ui.comboBox.setDisabled(False) + self.org_path = file_path + self.org_img = tools.img_cvread(self.org_path) + + # 目标检测 + t1 = time.time() + self.results = self.model(self.org_path)[0] + t2 = time.time() + take_time_str = '{:.3f} s'.format(t2 - t1) + self.ui.time_lb.setText(take_time_str) + + location_list = self.results.boxes.xyxy.tolist() + self.location_list = [list(map(int, e)) for e in location_list] + cls_list = self.results.boxes.cls.tolist() + self.cls_list = [int(i) for i in cls_list] + self.conf_list = self.results.boxes.conf.tolist() + self.conf_list = ['%.2f %%' % (each*100) for each in self.conf_list] + + # now_img = self.cv_img.copy() + # for loacation, type_id, conf in zip(self.location_list, self.cls_list, self.conf_list): + # type_id = int(type_id) + # color = self.colors(int(type_id), True) + # # cv2.rectangle(now_img, (int(x1), int(y1)), (int(x2), int(y2)), colors(int(type_id), True), 3) + # now_img = tools.drawRectBox(now_img, loacation, Config.CH_names[type_id], self.fontC, color) + now_img = self.results.plot() + self.draw_img = now_img + # 获取缩放后的图片尺寸 + self.img_width, self.img_height = self.get_resize_size(now_img) + resize_cvimg = cv2.resize(now_img,(self.img_width, self.img_height)) + pix_img = tools.cvimg_to_qpiximg(resize_cvimg) + self.ui.label_show.setPixmap(pix_img) + self.ui.label_show.setAlignment(Qt.AlignCenter) + # 设置路径显示 + self.ui.PiclineEdit.setText(self.org_path) + + # 目标数目 + target_nums = len(self.cls_list) + self.ui.label_nums.setText(str(target_nums)) + + # 设置目标选择下拉框 + choose_list = ['全部'] + target_names = [Config.names[id]+ '_'+ str(index) for index,id in enumerate(self.cls_list)] + # object_list = sorted(set(self.cls_list)) + # for each in object_list: + # choose_list.append(Config.CH_names[each]) + choose_list = choose_list + target_names + + self.ui.comboBox.clear() + self.ui.comboBox.addItems(choose_list) + + if target_nums >= 1: + self.ui.type_lb.setText(Config.CH_names[self.cls_list[0]]) + self.ui.label_conf.setText(str(self.conf_list[0])) + # 默认显示第一个目标框坐标 + # 设置坐标位置值 + self.ui.label_xmin.setText(str(self.location_list[0][0])) + self.ui.label_ymin.setText(str(self.location_list[0][1])) + self.ui.label_xmax.setText(str(self.location_list[0][2])) + self.ui.label_ymax.setText(str(self.location_list[0][3])) + else: + self.ui.type_lb.setText('') + self.ui.label_conf.setText('') + self.ui.label_xmin.setText('') + self.ui.label_ymin.setText('') + self.ui.label_xmax.setText('') + self.ui.label_ymax.setText('') + + # # 删除表格所有行 + self.ui.tableWidget.setRowCount(0) + self.ui.tableWidget.clearContents() + self.tabel_info_show(self.location_list, self.cls_list, self.conf_list,path=self.org_path) + + + def detact_batch_imgs(self): + if self.cap: + # 打开图片前关闭摄像头 + self.video_stop() + self.is_camera_open = False + self.ui.CaplineEdit.setText('摄像头未开启') + self.cap = None + directory = QFileDialog.getExistingDirectory(self, + "选取文件夹", + "./") # 起始路径 + if not directory: + return + self.org_path = directory + img_suffix = ['jpg','png','jpeg','bmp'] + for file_name in os.listdir(directory): + full_path = os.path.join(directory,file_name) + if os.path.isfile(full_path) and file_name.split('.')[-1].lower() in img_suffix: + # self.ui.comboBox.setDisabled(False) + img_path = full_path + self.org_img = tools.img_cvread(img_path) + # 目标检测 + t1 = time.time() + self.results = self.model(img_path)[0] + t2 = time.time() + take_time_str = '{:.3f} s'.format(t2 - t1) + self.ui.time_lb.setText(take_time_str) + + location_list = self.results.boxes.xyxy.tolist() + self.location_list = [list(map(int, e)) for e in location_list] + cls_list = self.results.boxes.cls.tolist() + self.cls_list = [int(i) for i in cls_list] + self.conf_list = self.results.boxes.conf.tolist() + self.conf_list = ['%.2f %%' % (each * 100) for each in self.conf_list] + + now_img = self.results.plot() + + self.draw_img = now_img + # 获取缩放后的图片尺寸 + self.img_width, self.img_height = self.get_resize_size(now_img) + resize_cvimg = cv2.resize(now_img, (self.img_width, self.img_height)) + pix_img = tools.cvimg_to_qpiximg(resize_cvimg) + self.ui.label_show.setPixmap(pix_img) + self.ui.label_show.setAlignment(Qt.AlignCenter) + # 设置路径显示 + self.ui.PiclineEdit.setText(img_path) + + # 目标数目 + target_nums = len(self.cls_list) + self.ui.label_nums.setText(str(target_nums)) + + # 设置目标选择下拉框 + choose_list = ['全部'] + target_names = [Config.names[id] + '_' + str(index) for index, id in enumerate(self.cls_list)] + choose_list = choose_list + target_names + + self.ui.comboBox.clear() + self.ui.comboBox.addItems(choose_list) + + if target_nums >= 1: + self.ui.type_lb.setText(Config.CH_names[self.cls_list[0]]) + self.ui.label_conf.setText(str(self.conf_list[0])) + # 默认显示第一个目标框坐标 + # 设置坐标位置值 + self.ui.label_xmin.setText(str(self.location_list[0][0])) + self.ui.label_ymin.setText(str(self.location_list[0][1])) + self.ui.label_xmax.setText(str(self.location_list[0][2])) + self.ui.label_ymax.setText(str(self.location_list[0][3])) + else: + self.ui.type_lb.setText('') + self.ui.label_conf.setText('') + self.ui.label_xmin.setText('') + self.ui.label_ymin.setText('') + self.ui.label_xmax.setText('') + self.ui.label_ymax.setText('') + + # # 删除表格所有行 + # self.ui.tableWidget.setRowCount(0) + # self.ui.tableWidget.clearContents() + self.tabel_info_show(self.location_list, self.cls_list, self.conf_list, path=img_path) + self.ui.tableWidget.scrollToBottom() + QApplication.processEvents() #刷新页面 + + def draw_rect_and_tabel(self, results, img): + now_img = img.copy() + location_list = results.boxes.xyxy.tolist() + self.location_list = [list(map(int, e)) for e in location_list] + cls_list = results.boxes.cls.tolist() + self.cls_list = [int(i) for i in cls_list] + self.conf_list = results.boxes.conf.tolist() + self.conf_list = ['%.2f %%' % (each * 100) for each in self.conf_list] + + for loacation, type_id, conf in zip(self.location_list, self.cls_list, self.conf_list): + type_id = int(type_id) + color = self.colors(int(type_id), True) + # cv2.rectangle(now_img, (int(x1), int(y1)), (int(x2), int(y2)), colors(int(type_id), True), 3) + now_img = tools.drawRectBox(now_img, loacation, Config.CH_names[type_id], self.fontC, color) + + # 获取缩放后的图片尺寸 + self.img_width, self.img_height = self.get_resize_size(now_img) + resize_cvimg = cv2.resize(now_img, (self.img_width, self.img_height)) + pix_img = tools.cvimg_to_qpiximg(resize_cvimg) + self.ui.label_show.setPixmap(pix_img) + self.ui.label_show.setAlignment(Qt.AlignCenter) + # 设置路径显示 + self.ui.PiclineEdit.setText(self.org_path) + + # 目标数目 + target_nums = len(self.cls_list) + self.ui.label_nums.setText(str(target_nums)) + if target_nums >= 1: + self.ui.type_lb.setText(Config.CH_names[self.cls_list[0]]) + self.ui.label_conf.setText(str(self.conf_list[0])) + self.ui.label_xmin.setText(str(self.location_list[0][0])) + self.ui.label_ymin.setText(str(self.location_list[0][1])) + self.ui.label_xmax.setText(str(self.location_list[0][2])) + self.ui.label_ymax.setText(str(self.location_list[0][3])) + else: + self.ui.type_lb.setText('') + self.ui.label_conf.setText('') + self.ui.label_xmin.setText('') + self.ui.label_ymin.setText('') + self.ui.label_xmax.setText('') + self.ui.label_ymax.setText('') + + # 删除表格所有行 + self.ui.tableWidget.setRowCount(0) + self.ui.tableWidget.clearContents() + self.tabel_info_show(self.location_list, self.cls_list, self.conf_list, path=self.org_path) + return now_img + + def combox_change(self): + com_text = self.ui.comboBox.currentText() + if com_text == '全部': + cur_box = self.location_list + cur_img = self.results.plot() + self.ui.type_lb.setText(Config.CH_names[self.cls_list[0]]) + self.ui.label_conf.setText(str(self.conf_list[0])) + else: + index = int(com_text.split('_')[-1]) + cur_box = [self.location_list[index]] + cur_img = self.results[index].plot() + self.ui.type_lb.setText(Config.CH_names[self.cls_list[index]]) + self.ui.label_conf.setText(str(self.conf_list[index])) + + # 设置坐标位置值 + self.ui.label_xmin.setText(str(cur_box[0][0])) + self.ui.label_ymin.setText(str(cur_box[0][1])) + self.ui.label_xmax.setText(str(cur_box[0][2])) + self.ui.label_ymax.setText(str(cur_box[0][3])) + + resize_cvimg = cv2.resize(cur_img, (self.img_width, self.img_height)) + pix_img = tools.cvimg_to_qpiximg(resize_cvimg) + self.ui.label_show.clear() + self.ui.label_show.setPixmap(pix_img) + self.ui.label_show.setAlignment(Qt.AlignCenter) + + + def get_video_path(self): + file_path, _ = QFileDialog.getOpenFileName(None, '打开视频', './', "Image files (*.avi *.mp4 *.jepg *.png)") + if not file_path: + return None + self.org_path = file_path + self.ui.VideolineEdit.setText(file_path) + return file_path + + def video_start(self): + # 删除表格所有行 + self.ui.tableWidget.setRowCount(0) + self.ui.tableWidget.clearContents() + + # 清空下拉框 + self.ui.comboBox.clear() + + # 定时器开启,每隔一段时间,读取一帧 + self.timer_camera.start(1) + self.timer_camera.timeout.connect(self.open_frame) + + def tabel_info_show(self, locations, clses, confs, path=None): + path = path + for location, cls, conf in zip(locations, clses, confs): + row_count = self.ui.tableWidget.rowCount() # 返回当前行数(尾部) + self.ui.tableWidget.insertRow(row_count) # 尾部插入一行 + item_id = QTableWidgetItem(str(row_count+1)) # 序号 + item_id.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # 设置文本居中 + item_path = QTableWidgetItem(str(path)) # 路径 + # item_path.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + + item_cls = QTableWidgetItem(str(Config.CH_names[cls])) + item_cls.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # 设置文本居中 + + item_conf = QTableWidgetItem(str(conf)) + item_conf.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # 设置文本居中 + + item_location = QTableWidgetItem(str(location)) # 目标框位置 + # item_location.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # 设置文本居中 + + self.ui.tableWidget.setItem(row_count, 0, item_id) + self.ui.tableWidget.setItem(row_count, 1, item_path) + self.ui.tableWidget.setItem(row_count, 2, item_cls) + self.ui.tableWidget.setItem(row_count, 3, item_conf) + self.ui.tableWidget.setItem(row_count, 4, item_location) + self.ui.tableWidget.scrollToBottom() + + def video_stop(self): + self.cap.release() + self.timer_camera.stop() + # self.timer_info.stop() + + def open_frame(self): + ret, now_img = self.cap.read() + if ret: + # 目标检测 + t1 = time.time() + results = self.model(now_img)[0] + t2 = time.time() + take_time_str = '{:.3f} s'.format(t2 - t1) + self.ui.time_lb.setText(take_time_str) + + location_list = results.boxes.xyxy.tolist() + self.location_list = [list(map(int, e)) for e in location_list] + cls_list = results.boxes.cls.tolist() + self.cls_list = [int(i) for i in cls_list] + self.conf_list = results.boxes.conf.tolist() + self.conf_list = ['%.2f %%' % (each * 100) for each in self.conf_list] + + now_img = results.plot() + + # 获取缩放后的图片尺寸 + self.img_width, self.img_height = self.get_resize_size(now_img) + resize_cvimg = cv2.resize(now_img, (self.img_width, self.img_height)) + pix_img = tools.cvimg_to_qpiximg(resize_cvimg) + self.ui.label_show.setPixmap(pix_img) + self.ui.label_show.setAlignment(Qt.AlignCenter) + + # 目标数目 + target_nums = len(self.cls_list) + self.ui.label_nums.setText(str(target_nums)) + + # 设置目标选择下拉框 + choose_list = ['全部'] + target_names = [Config.names[id] + '_' + str(index) for index, id in enumerate(self.cls_list)] + # object_list = sorted(set(self.cls_list)) + # for each in object_list: + # choose_list.append(Config.CH_names[each]) + choose_list = choose_list + target_names + + self.ui.comboBox.clear() + self.ui.comboBox.addItems(choose_list) + + if target_nums >= 1: + self.ui.type_lb.setText(Config.CH_names[self.cls_list[0]]) + self.ui.label_conf.setText(str(self.conf_list[0])) + # 默认显示第一个目标框坐标 + # 设置坐标位置值 + self.ui.label_xmin.setText(str(self.location_list[0][0])) + self.ui.label_ymin.setText(str(self.location_list[0][1])) + self.ui.label_xmax.setText(str(self.location_list[0][2])) + self.ui.label_ymax.setText(str(self.location_list[0][3])) + else: + self.ui.type_lb.setText('') + self.ui.label_conf.setText('') + self.ui.label_xmin.setText('') + self.ui.label_ymin.setText('') + self.ui.label_xmax.setText('') + self.ui.label_ymax.setText('') + + + # 删除表格所有行 + # self.ui.tableWidget.setRowCount(0) + # self.ui.tableWidget.clearContents() + self.tabel_info_show(self.location_list, self.cls_list, self.conf_list, path=self.org_path) + + else: + self.cap.release() + self.timer_camera.stop() + + def vedio_show(self): + if self.is_camera_open: + self.is_camera_open = False + self.ui.CaplineEdit.setText('摄像头未开启') + + video_path = self.get_video_path() + if not video_path: + return None + self.cap = cv2.VideoCapture(video_path) + self.video_start() + self.ui.comboBox.setDisabled(True) + + def camera_show(self): + self.is_camera_open = not self.is_camera_open + if self.is_camera_open: + self.ui.CaplineEdit.setText('摄像头开启') + self.cap = cv2.VideoCapture(0) + self.video_start() + self.ui.comboBox.setDisabled(True) + else: + self.ui.CaplineEdit.setText('摄像头未开启') + self.ui.label_show.setText('') + if self.cap: + self.cap.release() + cv2.destroyAllWindows() + self.ui.label_show.clear() + + def get_resize_size(self, img): + _img = img.copy() + img_height, img_width , depth= _img.shape + ratio = img_width / img_height + if ratio >= self.show_width / self.show_height: + self.img_width = self.show_width + self.img_height = int(self.img_width / ratio) + else: + self.img_height = self.show_height + self.img_width = int(self.img_height * ratio) + return self.img_width, self.img_height + + def save_detect_video(self): + if self.cap is None and not self.org_path: + QMessageBox.about(self, '提示', '当前没有可保存信息,请先打开图片或视频!') + return + + if self.is_camera_open: + QMessageBox.about(self, '提示', '摄像头视频无法保存!') + return + + if self.cap: + res = QMessageBox.information(self, '提示', '保存视频检测结果可能需要较长时间,请确认是否继续保存?',QMessageBox.Yes | QMessageBox.No , QMessageBox.Yes) + if res == QMessageBox.Yes: + self.video_stop() + com_text = self.ui.comboBox.currentText() + self.btn2Thread_object = btn2Thread(self.org_path, self.model, com_text) + self.btn2Thread_object.start() + self.btn2Thread_object.update_ui_signal.connect(self.update_process_bar) + else: + return + else: + if os.path.isfile(self.org_path): + fileName = os.path.basename(self.org_path) + name , end_name= fileName.rsplit(".",1) + save_name = name + '_detect_result.' + end_name + save_img_path = os.path.join(Config.save_path, save_name) + # 保存图片 + cv2.imwrite(save_img_path, self.draw_img) + QMessageBox.about(self, '提示', '图片保存成功!\n文件路径:{}'.format(save_img_path)) + else: + img_suffix = ['jpg', 'png', 'jpeg', 'bmp'] + for file_name in os.listdir(self.org_path): + full_path = os.path.join(self.org_path, file_name) + if os.path.isfile(full_path) and file_name.split('.')[-1].lower() in img_suffix: + name, end_name = file_name.rsplit(".",1) + save_name = name + '_detect_result.' + end_name + save_img_path = os.path.join(Config.save_path, save_name) + results = self.model(full_path)[0] + now_img = results.plot() + # 保存图片 + cv2.imwrite(save_img_path, now_img) + + QMessageBox.about(self, '提示', '图片保存成功!\n文件路径:{}'.format(Config.save_path)) + + + def update_process_bar(self,cur_num, total): + if cur_num == 1: + self.progress_bar = ProgressBar(self) + self.progress_bar.show() + if cur_num >= total: + self.progress_bar.close() + QMessageBox.about(self, '提示', '视频保存成功!\n文件在{}目录下'.format(Config.save_path)) + return + if self.progress_bar.isVisible() is False: + # 点击取消保存时,终止进程 + self.btn2Thread_object.stop() + return + value = int(cur_num / total *100) + self.progress_bar.setValue(cur_num, total, value) + QApplication.processEvents() + + +class btn2Thread(QThread): + """ + 进行检测后的视频保存 + """ + # 声明一个信号 + update_ui_signal = pyqtSignal(int,int) + + def __init__(self, path, model, com_text): + super(btn2Thread, self).__init__() + self.org_path = path + self.model = model + self.com_text = com_text + # 用于绘制不同颜色矩形框 + self.colors = tools.Colors() + self.is_running = True # 标志位,表示线程是否正在运行 + + def run(self): + # VideoCapture方法是cv2库提供的读取视频方法 + cap = cv2.VideoCapture(self.org_path) + # 设置需要保存视频的格式“xvid” + # 该参数是MPEG-4编码类型,文件名后缀为.avi + fourcc = cv2.VideoWriter_fourcc(*'XVID') + # 设置视频帧频 + fps = cap.get(cv2.CAP_PROP_FPS) + # 设置视频大小 + size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) + # VideoWriter方法是cv2库提供的保存视频方法 + # 按照设置的格式来out输出 + fileName = os.path.basename(self.org_path) + name, end_name = fileName.split('.') + save_name = name + '_detect_result.avi' + save_video_path = os.path.join(Config.save_path, save_name) + out = cv2.VideoWriter(save_video_path, fourcc, fps, size) + + prop = cv2.CAP_PROP_FRAME_COUNT + total = int(cap.get(prop)) + print("[INFO] 视频总帧数:{}".format(total)) + cur_num = 0 + + # 确定视频打开并循环读取 + while (cap.isOpened() and self.is_running): + cur_num += 1 + print('当前第{}帧,总帧数{}'.format(cur_num, total)) + # 逐帧读取,ret返回布尔值 + # 参数ret为True 或者False,代表有没有读取到图片 + # frame表示截取到一帧的图片 + ret, frame = cap.read() + if ret == True: + # 检测 + results = self.model(frame)[0] + frame = results.plot() + out.write(frame) + self.update_ui_signal.emit(cur_num, total) + else: + break + # 释放资源 + cap.release() + out.release() + + def stop(self): + self.is_running = False + + +if __name__ == "__main__": + app = QApplication(sys.argv) + win = MainWindow() + win.show() + sys.exit(app.exec_()) diff --git a/src/src/YOLOv8/TestFiles/20221221212931_ad528.thumb.1000_0.jpg b/src/src/YOLOv8/TestFiles/20221221212931_ad528.thumb.1000_0.jpg new file mode 100644 index 0000000..08dbcf8 Binary files /dev/null and b/src/src/YOLOv8/TestFiles/20221221212931_ad528.thumb.1000_0.jpg differ diff --git a/src/src/YOLOv8/TestFiles/20221221212931_ad528.thumb.1000_0.jpg WPS图片 2024-10-09 21-10-27.mp4 b/src/src/YOLOv8/TestFiles/20221221212931_ad528.thumb.1000_0.jpg WPS图片 2024-10-09 21-10-27.mp4 new file mode 100644 index 0000000..1846312 Binary files /dev/null and b/src/src/YOLOv8/TestFiles/20221221212931_ad528.thumb.1000_0.jpg WPS图片 2024-10-09 21-10-27.mp4 differ diff --git a/src/src/YOLOv8/TestFiles/R-C (1).jpg b/src/src/YOLOv8/TestFiles/R-C (1).jpg new file mode 100644 index 0000000..42d98c1 Binary files /dev/null and b/src/src/YOLOv8/TestFiles/R-C (1).jpg differ diff --git a/src/src/YOLOv8/TestFiles/R-C.jpg b/src/src/YOLOv8/TestFiles/R-C.jpg new file mode 100644 index 0000000..8483768 Binary files /dev/null and b/src/src/YOLOv8/TestFiles/R-C.jpg differ diff --git a/src/src/YOLOv8/TestFiles/a022-1910cca2e109eb5a1728411efcad2572.jpg b/src/src/YOLOv8/TestFiles/a022-1910cca2e109eb5a1728411efcad2572.jpg new file mode 100644 index 0000000..9a18a75 Binary files /dev/null and b/src/src/YOLOv8/TestFiles/a022-1910cca2e109eb5a1728411efcad2572.jpg differ diff --git a/src/src/YOLOv8/TestFiles/noKatj5rwf_small.jpg b/src/src/YOLOv8/TestFiles/noKatj5rwf_small.jpg new file mode 100644 index 0000000..da1a675 Binary files /dev/null and b/src/src/YOLOv8/TestFiles/noKatj5rwf_small.jpg differ diff --git a/src/src/YOLOv8/UIProgram/BattleMapTools.py b/src/src/YOLOv8/UIProgram/BattleMapTools.py new file mode 100644 index 0000000..18ea96c --- /dev/null +++ b/src/src/YOLOv8/UIProgram/BattleMapTools.py @@ -0,0 +1,851 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +室内多传感器寻敌系统 - 作战地图工具 +=============================== + +提供作战地图显示和标绘功能,支持在地图上进行标绘操作 + +功能特性: +- 支持导入地图图片(PNG、JPG等格式) +- 支持自由绘制线条,多种颜色和线宽 +- 支持标记敌人位置(红色圆圈) +- 支持标记人质位置(蓝色圆圈) +- 支持清除和撤销操作 +- 支持保存标绘结果 + +作者: 郭晋鹏团队 +版本: 2.0.0 +""" + +from PyQt5.QtWidgets import (QWidget, QPushButton, QColorDialog, QSlider, QLabel, + QHBoxLayout, QVBoxLayout, QFileDialog, QMessageBox, + QButtonGroup, QFrame, QSizePolicy) +from PyQt5.QtCore import Qt, QPoint, QRect, QSize +from PyQt5.QtGui import QPainter, QPen, QColor, QPixmap, QPainterPath, QImage, QBrush, QIcon, QFont + +import cv2 +import numpy as np +import sys +import os + +# 确保可以从任何位置导入 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +class MapDrawingArea(QWidget): + """ + 地图绘图区域类 + + 提供一个可以进行地图标绘的区域,支持多种绘制工具和操作 + + 主要属性: + drawing: 是否正在绘制 + last_point: 上一个绘制点 + current_point: 当前绘制点 + pixmap: 绘制的图像 + temp_pixmap: 临时绘制的图像 + history: 历史记录,用于撤销操作 + scale_factor: 缩放因子 + + 主要方法: + loadMap: 加载地图图片 + setDrawingTool: 设置绘图工具 + setDrawingColor: 设置绘图颜色 + setLineWidth: 设置线宽 + undo: 撤销操作 + clear: 清除所有绘制 + getImage: 获取绘制后的图像 + zoomIn: 放大地图 + zoomOut: 缩小地图 + """ + + # 绘图工具枚举 + TOOL_PEN = 0 # 自由绘制 + TOOL_ENEMY = 1 # 标记敌人 + TOOL_HOSTAGE = 2 # 标记人质 + TOOL_ERASER = 3 # 橡皮擦 + + # 符号尺寸 + SYMBOL_SIZE = 20 + + # 缩放设置 + MIN_SCALE = 0.1 # 最小缩放比例 + MAX_SCALE = 5.0 # 最大缩放比例 + ZOOM_FACTOR = 1.2 # 每次缩放的比例 + + def __init__(self, parent=None): + """初始化地图绘图区域""" + super().__init__(parent) + + # 初始化绘图属性 + self.drawing = False + self.last_point = QPoint() + self.current_point = QPoint() + self.pixmap = QPixmap() + self.temp_pixmap = QPixmap() + self.original_pixmap = QPixmap() # 保存原始图像 + self.history = [] + self.has_map = False + + # 缩放和平移属性 + self.scale_factor = 1.0 + self.offset = QPoint(0, 0) + self.panning = False + self.pan_start_pos = QPoint() + + # 绘图设置 + self.drawing_tool = self.TOOL_PEN + self.pen_color = QColor(0, 255, 0) # 默认绿色 + self.pen_width = 3 + + # 敌人和人质标记颜色 + self.enemy_color = QColor(255, 0, 0) # 红色 + self.hostage_color = QColor(0, 0, 255) # 蓝色 + + # 橡皮擦设置 + self.eraser_size = 20 + + # 设置鼠标跟踪 + self.setMouseTracking(True) + + # 设置焦点策略,使其能接收键盘事件 + self.setFocusPolicy(Qt.StrongFocus) + + # 创建空白图像 + self.createEmptyMap() + + def createEmptyMap(self): + """创建空白地图""" + self.pixmap = QPixmap(800, 600) + self.pixmap.fill(Qt.white) + self.history = [self.pixmap.copy()] + self.has_map = False + self.update() + + def loadMap(self, file_path=None): + """ + 加载地图图片 + + 参数: + file_path: 图片文件路径,如果为None则弹出文件选择对话框 + + 返回: + bool: 是否成功加载地图 + """ + if file_path is None: + file_path, _ = QFileDialog.getOpenFileName( + self, '导入地图', './', + "图像文件 (*.jpg *.jpeg *.png *.bmp)" + ) + + if not file_path: + return False + + try: + # 加载原始图片 + original_pixmap = QPixmap(file_path) + if original_pixmap.isNull(): + QMessageBox.warning(self, "错误", "无法加载地图文件") + return False + + # 保存原始图片 + self.original_pixmap = original_pixmap.copy() + + # 重置缩放和偏移 + self.scale_factor = 1.0 + self.offset = QPoint(0, 0) + + # 适配图像大小到窗口 + self.fitToView() + + # 保存初始状态到历史记录 + self.history = [self.pixmap.copy()] + self.has_map = True + + # 更新界面 + self.update() + return True + + except Exception as e: + QMessageBox.warning(self, "错误", f"加载地图失败: {str(e)}") + return False + + def fitToView(self): + """适配图像大小到当前视图""" + if self.original_pixmap.isNull(): + return + + # 获取父窗口大小(如果有) + parent_size = self.parentWidget().size() if self.parentWidget() else self.size() + + # 计算适合的缩放比例 + width_ratio = (parent_size.width() - 40) / self.original_pixmap.width() + height_ratio = (parent_size.height() - 40) / self.original_pixmap.height() + + # 选择较小的比例,确保图像完全可见 + self.scale_factor = min(width_ratio, height_ratio, 1.0) + + # 缩放图像 + self.updateScaledPixmap() + + def updateScaledPixmap(self): + """根据当前缩放因子更新缩放后的图像""" + if self.original_pixmap.isNull(): + return + + # 计算缩放后的尺寸 + new_width = int(self.original_pixmap.width() * self.scale_factor) + new_height = int(self.original_pixmap.height() * self.scale_factor) + + # 缩放图像 + self.pixmap = self.original_pixmap.scaled( + new_width, new_height, + Qt.KeepAspectRatio, + Qt.SmoothTransformation + ) + + # 设置最小尺寸 + self.setMinimumSize(new_width, new_height) + + # 更新界面 + self.update() + + def zoomIn(self): + """放大地图""" + if self.scale_factor < self.MAX_SCALE: + self.scale_factor *= self.ZOOM_FACTOR + self.updateScaledPixmap() + + def zoomOut(self): + """缩小地图""" + if self.scale_factor > self.MIN_SCALE: + self.scale_factor /= self.ZOOM_FACTOR + self.updateScaledPixmap() + + def setDrawingTool(self, tool): + """设置绘图工具""" + self.drawing_tool = tool + + def setDrawingColor(self, color): + """设置绘图颜色""" + self.pen_color = color + + def setLineWidth(self, width): + """设置线宽""" + self.pen_width = width + + def undo(self): + """撤销上一步操作""" + if len(self.history) > 1: + self.history.pop() + self.pixmap = self.history[-1].copy() + self.update() + + def clear(self): + """清除所有绘制,恢复到初始状态""" + if self.history: + self.pixmap = self.history[0].copy() + self.history = [self.pixmap.copy()] + self.update() + + def getImage(self): + """获取绘制后的图像(QPixmap格式)""" + return self.pixmap.copy() + + def saveImage(self, filename=None): + """ + 保存绘制后的图像到文件 + + 参数: + filename: 文件路径,如果为None则弹出文件选择对话框 + + 返回: + bool: 是否成功保存 + """ + if filename is None: + filename, _ = QFileDialog.getSaveFileName( + self, "保存地图标绘", "./", + "PNG图像 (*.png);;JPEG图像 (*.jpg);;所有文件 (*.*)" + ) + + if filename: + return self.pixmap.save(filename) + return False + + def paintEvent(self, event): + """绘制事件处理""" + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.SmoothPixmapTransform) + + # 绘制背景 + painter.fillRect(self.rect(), QColor(40, 40, 40)) + + # 绘制底图(居中显示) + if not self.pixmap.isNull(): + # 计算居中位置 + x = (self.width() - self.pixmap.width()) // 2 + self.offset.x() + y = (self.height() - self.pixmap.height()) // 2 + self.offset.y() + painter.drawPixmap(x, y, self.pixmap) + + # 绘制临时内容(如拖动中的形状) + if self.drawing and not self.temp_pixmap.isNull(): + x = (self.width() - self.temp_pixmap.width()) // 2 + self.offset.x() + y = (self.height() - self.temp_pixmap.height()) // 2 + self.offset.y() + painter.drawPixmap(x, y, self.temp_pixmap) + + def wheelEvent(self, event): + """鼠标滚轮事件处理""" + if self.has_map: + # 获取滚轮方向 + delta = event.angleDelta().y() + + # 根据滚轮方向缩放 + if delta > 0: + self.zoomIn() # 放大 + else: + self.zoomOut() # 缩小 + + # 阻止事件传播 + event.accept() + + def mousePressEvent(self, event): + """鼠标按下事件处理""" + if event.button() == Qt.LeftButton: + # 计算图像坐标系中的点击位置 + img_pos = self._mapToImagePos(event.pos()) + + # 检查点击是否在图像范围内 + if self._isPointInImage(img_pos): + if event.modifiers() & Qt.ControlModifier: + # 按住Ctrl键进行平移 + self.panning = True + self.pan_start_pos = event.pos() + else: + # 正常绘制 + self.drawing = True + self.last_point = img_pos + + # 对于敌人和人质标记,直接在点击位置绘制符号 + if self.drawing_tool == self.TOOL_ENEMY: + self._drawSymbol(img_pos, self.enemy_color) + + elif self.drawing_tool == self.TOOL_HOSTAGE: + self._drawSymbol(img_pos, self.hostage_color) + + def mouseMoveEvent(self, event): + """鼠标移动事件处理""" + if self.panning: + # 平移地图 + delta = event.pos() - self.pan_start_pos + self.offset += delta + self.pan_start_pos = event.pos() + self.update() + elif self.drawing: + # 计算图像坐标系中的当前位置 + img_pos = self._mapToImagePos(event.pos()) + + # 检查是否在图像范围内 + if self._isPointInImage(img_pos): + self.current_point = img_pos + + if self.drawing_tool == self.TOOL_PEN: + # 自由绘制,直接在pixmap上绘制 + painter = QPainter(self.pixmap) + painter.setPen(QPen(self.pen_color, self.pen_width, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) + painter.drawLine(self.last_point, self.current_point) + painter.end() + + # 更新上一个点 + self.last_point = self.current_point + + elif self.drawing_tool == self.TOOL_ERASER: + # 橡皮擦,绘制白色圆形 + painter = QPainter(self.pixmap) + painter.setPen(Qt.NoPen) + painter.setBrush(QBrush(Qt.white)) + painter.drawEllipse(self.current_point, self.eraser_size, self.eraser_size) + painter.end() + + # 更新上一个点 + self.last_point = self.current_point + + # 更新界面 + self.update() + + def mouseReleaseEvent(self, event): + """鼠标释放事件处理""" + if event.button() == Qt.LeftButton: + if self.panning: + self.panning = False + elif self.drawing: + self.drawing = False + + # 保存到历史记录 + self.history.append(self.pixmap.copy()) + + # 更新界面 + self.update() + + def _mapToImagePos(self, pos): + """将窗口坐标映射到图像坐标""" + # 计算图像左上角在窗口中的位置 + x_offset = (self.width() - self.pixmap.width()) // 2 + self.offset.x() + y_offset = (self.height() - self.pixmap.height()) // 2 + self.offset.y() + + # 计算相对于图像左上角的坐标 + return QPoint(pos.x() - x_offset, pos.y() - y_offset) + + def _isPointInImage(self, pos): + """检查点是否在图像范围内""" + return (0 <= pos.x() < self.pixmap.width() and + 0 <= pos.y() < self.pixmap.height()) + + def _drawSymbol(self, position, color): + """ + 绘制符号(敌人或人质标记) + + 参数: + position: 符号位置 + color: 符号颜色 + """ + try: + painter = QPainter(self.pixmap) + painter.setRenderHint(QPainter.Antialiasing) # 抗锯齿 + + # 设置画笔 + pen = QPen(color, 3, Qt.SolidLine) + painter.setPen(pen) + + # 设置画刷 - 半透明填充 + brush = QBrush(QColor(color.red(), color.green(), color.blue(), 80)) + painter.setBrush(brush) + + # 符号尺寸 + size = self.SYMBOL_SIZE + + # 绘制外圆 + painter.drawEllipse(position, size, size) + + # 绘制内圆 + painter.setBrush(QBrush(QColor(color.red(), color.green(), color.blue(), 150))) + painter.drawEllipse(position, int(size/2), int(size/2)) + + # 绘制十字线 - 确保使用整数坐标 + painter.setPen(QPen(Qt.white, 2, Qt.SolidLine)) + x1 = int(position.x() - size/2) + y1 = int(position.y()) + x2 = int(position.x() + size/2) + y2 = int(position.y()) + painter.drawLine(x1, y1, x2, y2) + + x1 = int(position.x()) + y1 = int(position.y() - size/2) + x2 = int(position.x()) + y2 = int(position.y() + size/2) + painter.drawLine(x1, y1, x2, y2) + + # 添加文字标签 + if color == self.enemy_color: + text = "敌" + elif color == self.hostage_color: + text = "质" + else: + text = "" + + if text: + painter.setPen(Qt.white) + font = QFont("Arial", 10) + font.setBold(True) + painter.setFont(font) + painter.drawText(int(position.x() - 7), int(position.y() + 5), text) + + painter.end() + + # 保存到历史记录 + self.history.append(self.pixmap.copy()) + + # 更新界面 + self.update() + + except Exception as e: + print(f"绘制符号错误: {e}") + # 如果出错,确保painter被结束 + if painter.isActive(): + painter.end() + + +class BattleMapToolbar(QWidget): + """ + 作战地图工具栏类 + + 提供地图操作、绘图工具选择、颜色选择等功能 + + 主要属性: + map_area: 关联的地图绘图区域 + + 主要方法: + setupUI: 设置用户界面 + connectSignals: 连接信号和槽 + """ + + def __init__(self, map_area, parent=None): + """ + 初始化作战地图工具栏 + + 参数: + map_area: 关联的地图绘图区域 + parent: 父窗口 + """ + super().__init__(parent) + self.map_area = map_area + self.setupUI() + self.connectSignals() + + def setupUI(self): + """设置用户界面""" + # 主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(5, 5, 5, 5) + main_layout.setSpacing(5) + + # 地图操作布局 + map_layout = QHBoxLayout() + + # 导入地图按钮 + self.import_map_btn = QPushButton("📷 导入地图") + self.import_map_btn.setFixedSize(100, 30) + + # 清除地图按钮 + self.clear_map_btn = QPushButton("🗑️ 清除地图") + self.clear_map_btn.setFixedSize(100, 30) + + # 保存地图按钮 + self.save_map_btn = QPushButton("💾 保存地图") + self.save_map_btn.setFixedSize(100, 30) + + # 撤销按钮 + self.undo_btn = QPushButton("↩️ 撤销") + self.undo_btn.setFixedSize(80, 30) + + # 将按钮添加到地图操作布局 + map_layout.addWidget(self.import_map_btn) + map_layout.addWidget(self.clear_map_btn) + map_layout.addWidget(self.save_map_btn) + map_layout.addWidget(self.undo_btn) + map_layout.addStretch() + + # 工具选择布局 + tools_layout = QHBoxLayout() + + # 创建工具按钮组 + self.tools_group = QButtonGroup(self) + + # 笔刷工具按钮 + self.pen_btn = QPushButton("✏️ 笔刷") + self.pen_btn.setCheckable(True) + self.pen_btn.setChecked(True) + self.pen_btn.setFixedSize(80, 30) + self.tools_group.addButton(self.pen_btn, MapDrawingArea.TOOL_PEN) + + # 标记敌人按钮 + self.enemy_btn = QPushButton("🔴 敌人") + self.enemy_btn.setCheckable(True) + self.enemy_btn.setFixedSize(80, 30) + self.tools_group.addButton(self.enemy_btn, MapDrawingArea.TOOL_ENEMY) + + # 标记人质按钮 + self.hostage_btn = QPushButton("🔵 人质") + self.hostage_btn.setCheckable(True) + self.hostage_btn.setFixedSize(80, 30) + self.tools_group.addButton(self.hostage_btn, MapDrawingArea.TOOL_HOSTAGE) + + # 橡皮擦按钮 + self.eraser_btn = QPushButton("🧽 橡皮擦") + self.eraser_btn.setCheckable(True) + self.eraser_btn.setFixedSize(80, 30) + self.tools_group.addButton(self.eraser_btn, MapDrawingArea.TOOL_ERASER) + + # 将按钮添加到工具布局 + tools_layout.addWidget(self.pen_btn) + tools_layout.addWidget(self.enemy_btn) + tools_layout.addWidget(self.hostage_btn) + tools_layout.addWidget(self.eraser_btn) + + # 添加分隔符 + separator = QFrame() + separator.setFrameShape(QFrame.VLine) + separator.setFrameShadow(QFrame.Sunken) + separator.setStyleSheet("background-color: rgba(150, 150, 150, 0.5);") + separator.setFixedSize(1, 25) + tools_layout.addWidget(separator) + + # 缩放按钮 + self.zoom_in_btn = QPushButton("🔍+") + self.zoom_in_btn.setFixedSize(40, 30) + self.zoom_in_btn.setToolTip("放大地图") + + self.zoom_out_btn = QPushButton("🔍-") + self.zoom_out_btn.setFixedSize(40, 30) + self.zoom_out_btn.setToolTip("缩小地图") + + self.fit_view_btn = QPushButton("🔍⟲") + self.fit_view_btn.setFixedSize(40, 30) + self.fit_view_btn.setToolTip("适应窗口大小") + + tools_layout.addWidget(self.zoom_in_btn) + tools_layout.addWidget(self.zoom_out_btn) + tools_layout.addWidget(self.fit_view_btn) + tools_layout.addStretch() + + # 颜色和线宽布局 + settings_layout = QHBoxLayout() + + # 颜色选择按钮 + self.color_btn = QPushButton("🎨 颜色") + self.color_btn.setFixedSize(80, 30) + + # 颜色预览 + self.color_preview = QLabel() + self.color_preview.setFixedSize(30, 30) + self.color_preview.setStyleSheet("background-color: #00FF00; border: 1px solid black;") + + # 线宽标签 + self.width_label = QLabel("线宽:") + self.width_label.setStyleSheet("color: white;") + + # 线宽滑动条 + self.width_slider = QSlider(Qt.Horizontal) + self.width_slider.setMinimum(1) + self.width_slider.setMaximum(20) + self.width_slider.setValue(3) + self.width_slider.setFixedWidth(100) + + # 线宽数值显示 + self.width_value = QLabel("3") + self.width_value.setStyleSheet("color: white;") + self.width_value.setFixedWidth(20) + + # 将控件添加到设置布局 + settings_layout.addWidget(self.color_btn) + settings_layout.addWidget(self.color_preview) + settings_layout.addWidget(self.width_label) + settings_layout.addWidget(self.width_slider) + settings_layout.addWidget(self.width_value) + settings_layout.addStretch() + + # 将所有布局添加到主布局 + main_layout.addLayout(map_layout) + main_layout.addLayout(tools_layout) + main_layout.addLayout(settings_layout) + + # 设置样式 + self.setStyleSheet(""" + QPushButton { + background: rgba(60, 60, 60, 0.8); + color: white; + font-size: 12px; + border: 1px solid rgba(100, 100, 100, 0.8); + border-radius: 5px; + } + QPushButton:hover { + background: rgba(80, 80, 80, 1.0); + border: 1px solid rgba(150, 150, 150, 1.0); + } + QPushButton:checked { + background: rgba(0, 120, 215, 0.8); + border: 1px solid rgba(0, 150, 255, 1.0); + } + QLabel { + color: white; + font-size: 12px; + } + QSlider::groove:horizontal { + height: 8px; + background: rgba(80, 80, 80, 0.8); + border-radius: 4px; + } + QSlider::handle:horizontal { + background: rgba(0, 120, 215, 0.8); + border: 1px solid rgba(0, 150, 255, 1.0); + width: 18px; + margin: -5px 0; + border-radius: 9px; + } + """) + + # 设置工具提示 + self.import_map_btn.setToolTip("导入地图图片文件") + self.clear_map_btn.setToolTip("清除当前标绘内容") + self.save_map_btn.setToolTip("保存标绘结果") + self.undo_btn.setToolTip("撤销上一步操作") + self.pen_btn.setToolTip("自由绘制工具") + self.enemy_btn.setToolTip("标记敌人位置") + self.hostage_btn.setToolTip("标记人质位置") + self.eraser_btn.setToolTip("橡皮擦工具") + self.color_btn.setToolTip("选择绘制颜色") + + def connectSignals(self): + """连接信号和槽""" + # 地图操作按钮信号 - 这些信号将由BattleMapWidget处理 + # self.import_map_btn.clicked.connect(self.map_area.loadMap) + # self.clear_map_btn.clicked.connect(self.map_area.clear) + # self.save_map_btn.clicked.connect(self.map_area.saveImage) + self.undo_btn.clicked.connect(self.map_area.undo) + + # 工具按钮组信号 + self.tools_group.buttonClicked.connect(self._onToolButtonClicked) + + # 颜色按钮信号 + self.color_btn.clicked.connect(self._onColorButtonClicked) + + # 线宽滑动条信号 + self.width_slider.valueChanged.connect(self._onWidthChanged) + + # 缩放按钮信号 + self.zoom_in_btn.clicked.connect(self.map_area.zoomIn) + self.zoom_out_btn.clicked.connect(self.map_area.zoomOut) + self.fit_view_btn.clicked.connect(self.map_area.fitToView) + + def _onToolButtonClicked(self, button): + """工具按钮点击处理""" + tool_id = self.tools_group.id(button) + self.map_area.setDrawingTool(tool_id) + + def _onColorButtonClicked(self): + """颜色按钮点击处理""" + color = QColorDialog.getColor(self.map_area.pen_color, self, "选择颜色") + if color.isValid(): + self.map_area.setDrawingColor(color) + self.color_preview.setStyleSheet(f"background-color: {color.name()}; border: 1px solid black;") + + def _onWidthChanged(self, value): + """线宽改变处理""" + self.map_area.setLineWidth(value) + self.width_value.setText(str(value)) + + +class BattleMapWidget(QWidget): + """ + 作战地图组件类 + + 集成地图显示和工具栏,提供完整的作战地图功能 + + 主要属性: + map_area: 地图绘图区域 + toolbar: 工具栏 + + 主要方法: + setupUI: 设置用户界面 + loadMap: 加载地图 + saveMap: 保存地图 + """ + + def __init__(self, parent=None): + """ + 初始化作战地图组件 + + 参数: + parent: 父窗口 + """ + super().__init__(parent) + self.setupUI() + + def setupUI(self): + """设置用户界面""" + # 主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # 创建标题区域 + title_frame = QFrame() + title_frame.setFixedHeight(50) + title_frame.setStyleSheet("background: rgba(30, 30, 30, 0.7);") + title_layout = QHBoxLayout(title_frame) + title_layout.setContentsMargins(20, 5, 20, 5) + + # 标题标签 + title_label = QLabel("作战地图标绘系统") + title_label.setStyleSheet(""" + color: #FFD700; + font-size: 18px; + font-weight: bold; + """) + + # 状态标签 + self.status_label = QLabel("准备就绪") + self.status_label.setStyleSheet(""" + color: #00FF00; + font-size: 12px; + """) + + title_layout.addWidget(title_label) + title_layout.addStretch() + title_layout.addWidget(self.status_label) + + # 创建地图绘图区域 + self.map_area = MapDrawingArea() + + # 创建工具栏 + self.toolbar = BattleMapToolbar(self.map_area) + + # 创建滚动区域容器 + map_container = QFrame() + map_container.setStyleSheet("background: rgba(40, 40, 40, 0.5);") + map_container_layout = QVBoxLayout(map_container) + map_container_layout.setContentsMargins(10, 10, 10, 10) + map_container_layout.addWidget(self.map_area) + + # 将工具栏和地图区域添加到主布局 + main_layout.addWidget(title_frame) + main_layout.addWidget(self.toolbar) + main_layout.addWidget(map_container, 1) # 1表示拉伸因子 + + # 连接信号 + self.toolbar.import_map_btn.clicked.connect(self._onMapLoaded) + self.toolbar.save_map_btn.clicked.connect(self._onMapSaved) + self.toolbar.clear_map_btn.clicked.connect(self._onMapCleared) + + def _onMapLoaded(self): + """地图加载回调""" + if self.map_area.loadMap(): + self.status_label.setText("地图已加载") + self.status_label.setStyleSheet("color: #00FF00; font-size: 12px;") + + def _onMapSaved(self): + """地图保存回调""" + if self.map_area.saveImage(): + self.status_label.setText("地图已保存") + self.status_label.setStyleSheet("color: #00FF00; font-size: 12px;") + + def _onMapCleared(self): + """地图清除回调""" + self.map_area.clear() + self.status_label.setText("地图已清除") + self.status_label.setStyleSheet("color: #FFA500; font-size: 12px;") + + def loadMap(self, file_path=None): + """ + 加载地图 + + 参数: + file_path: 图片文件路径,如果为None则弹出文件选择对话框 + + 返回: + bool: 是否成功加载地图 + """ + return self.map_area.loadMap(file_path) + + def saveMap(self, file_path=None): + """ + 保存地图 + + 参数: + file_path: 文件路径,如果为None则弹出文件选择对话框 + + 返回: + bool: 是否成功保存 + """ + return self.map_area.saveImage(file_path) \ No newline at end of file diff --git a/src/src/YOLOv8/UIProgram/InitialWindow.py b/src/src/YOLOv8/UIProgram/InitialWindow.py new file mode 100644 index 0000000..ebfba74 --- /dev/null +++ b/src/src/YOLOv8/UIProgram/InitialWindow.py @@ -0,0 +1,287 @@ +# -*- coding: utf-8 -*- +""" +室内多传感器寻敌系统欢迎界面 +=============================== + +欢迎界面模块,启动画面后显示的系统介绍页面 + +功能特性: +- 显示系统欢迎信息和介绍 +- 提供"进入系统"按钮 +- 支持窗口拖拽移动 +- 自定义关闭按钮 +- 智能背景图片加载 +- 响应式布局设计 + +窗口尺寸: 600x400 +背景图片: BA.png (自动缩放适配) + +交互功能: +- 点击"进入系统"按钮进入主程序 +- 支持拖拽移动窗口 +- 点击关闭按钮退出程序 + +作者: 郭晋鹏团队 +版本: 2.0.0 +""" + +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame, QApplication +from PyQt5.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve, QTimer +from PyQt5.QtGui import QPixmap, QPainter, QLinearGradient, QBrush, QPen, QFont, QColor, QMouseEvent +import os + +class InitialWindow(QWidget): + """ + 欢迎界面窗口类 + + 继承自QWidget,提供系统介绍和进入功能。 + 支持拖拽移动和自定义样式。 + + 信号: + enterSystem: 进入系统信号,用于通知启动主程序 + + 属性: + drag_start_position: 拖拽起始位置 + is_dragging (bool): 是否正在拖拽窗口 + """ + + # 进入系统信号 + enterSystem = pyqtSignal() + + def __init__(self, parent=None): + """ + 初始化欢迎界面 + + 参数: + parent: 父窗口,默认为None + """ + super().__init__(parent) + self.drag_start_position = None # 拖拽起始位置 + self.is_dragging = False # 拖拽状态标志 + self.setupUI() # 设置用户界面 + + def setupUI(self): + """设置用户界面""" + # 设置窗口属性 - 缩小到一半 + self.setFixedSize(600, 400) + self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) + + # 主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # 创建背景标签(全屏背景) - 调整为新尺寸 + self.background_label = QLabel(self) + self.background_label.setGeometry(0, 0, 600, 400) + + # 加载BA.png作为背景并缩放填充 + # 智能路径查找 + def find_image_path(image_name): + possible_paths = [ + image_name, + os.path.join("UIProgram", image_name), + os.path.join("..", "UIProgram", image_name) if os.path.basename(os.getcwd()) == "UIProgram" else os.path.join("UIProgram", image_name) + ] + for path in possible_paths: + if os.path.exists(path): + return path + return image_name + + bg_path = find_image_path("ui_imgs/icons/BA.png") + if os.path.exists(bg_path): + pixmap = QPixmap(bg_path) + if not pixmap.isNull(): + scaled_pixmap = pixmap.scaled(600, 400, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation) + self.background_label.setPixmap(scaled_pixmap) + self.background_label.setScaledContents(False) + self.background_label.setAlignment(Qt.AlignCenter) + + # 确保背景在最底层 + self.background_label.lower() + + # 主框架(透明,用于布局) + self.background_frame = QFrame() + self.background_frame.setObjectName("backgroundFrame") + self.background_frame.setStyleSheet("background: transparent;") + main_layout.addWidget(self.background_frame) + + # 背景框架布局 + bg_layout = QVBoxLayout(self.background_frame) + bg_layout.setContentsMargins(50, 50, 50, 50) + bg_layout.setSpacing(30) + + # 创建自定义关闭按钮 - 调整位置 + self.close_btn = QPushButton("×", self) + self.close_btn.setFixedSize(30, 30) + self.close_btn.move(600 - 40, 10) + self.close_btn.setObjectName("closeButton") + self.close_btn.clicked.connect(self.close) + + # 移除Logo区域,只保留背景图片 + + # 添加弹性空间 + bg_layout.addStretch() + + # 欢迎文字 + self.welcome_label = QLabel("欢迎指挥官") + self.welcome_label.setAlignment(Qt.AlignCenter) + self.welcome_label.setObjectName("welcomeLabel") + bg_layout.addWidget(self.welcome_label) + + # 系统描述 + self.desc_label = QLabel("室内多传感器寻敌系统") + self.desc_label.setAlignment(Qt.AlignCenter) + self.desc_label.setObjectName("descLabel") + bg_layout.addWidget(self.desc_label) + + # 版本信息 + self.version_label = QLabel("智能识别 • 精准定位 • 实时检测") + self.version_label.setAlignment(Qt.AlignCenter) + self.version_label.setObjectName("versionLabel") + bg_layout.addWidget(self.version_label) + + # 按钮区域 - 按照UI文件的样式 + button_layout = QHBoxLayout() + button_layout.addStretch() + + self.enter_btn = QPushButton("进入系统") + self.enter_btn.setFixedSize(150, 40) # 缩小到一半 + self.enter_btn.setObjectName("enterSystemButton") + self.enter_btn.clicked.connect(self.onEnterSystem) + button_layout.addWidget(self.enter_btn) + + button_layout.addStretch() + bg_layout.addLayout(button_layout) + + # 底部空间 + bg_layout.addStretch() + + # 底部信息 + self.bottom_label = QLabel("© 2025 室内多传感器寻敌系统") + self.bottom_label.setAlignment(Qt.AlignCenter) + self.bottom_label.setObjectName("bottomLabel") + bg_layout.addWidget(self.bottom_label) + + # 设置样式 + self.setStyles() + + # 居中显示 + self.centerWindow() + + def setStyles(self): + """设置样式""" + # 按照UI文件的样式设计 + self.setStyleSheet(""" + InitialWindow { + background: transparent; + } + + #welcomeLabel { + color: #FFD700; + font-size: 36px; + font-weight: bold; + font-family: 'Impact', 'Arial Black', sans-serif; + + background: rgba(0, 0, 0, 0.3); + border-radius: 15px; + padding: 30px; + border: 2px solid rgba(255, 215, 0, 0.6); + } + + QLabel#descLabel { + color: rgba(255, 255, 255, 200); + font-size: 24px; + font-weight: normal; + background: transparent; + margin: 10px; + } + + QLabel#versionLabel { + color: rgba(76, 175, 80, 255); + font-size: 16px; + font-weight: normal; + background: transparent; + margin: 10px; + } + + QLabel#bottomLabel { + color: rgba(255, 255, 255, 120); + font-size: 12px; + background: transparent; + margin: 10px; + } + + #enterSystemButton { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgba(255, 69, 0, 0.9), stop:1 rgba(220, 20, 60, 0.9)); + color: #FFFFFF; + font-size: 12px; + font-weight: bold; + font-family: 'Arial Black', sans-serif; + border: 3px solid rgba(255, 255, 255, 0.8); + border-radius: 15px; + } + + #enterSystemButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgba(255, 99, 71, 1.0), stop:1 rgba(255, 69, 0, 1.0)); + border: 3px solid rgba(255, 215, 0, 1.0); + } + + #enterSystemButton:pressed { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgba(178, 34, 34, 0.9), stop:1 rgba(139, 0, 0, 0.9)); + } + + #closeButton { + background: rgba(220, 20, 60, 0.8); + color: white; + font-size: 14px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 20px; + } + + #closeButton:hover { + background: rgba(255, 69, 0, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + + #closeButton:pressed { + background: rgba(139, 0, 0, 1.0); + } + """) + + def centerWindow(self): + """窗口居中显示""" + screen = QApplication.desktop().screenGeometry() + x = (screen.width() - self.width()) // 2 + y = (screen.height() - self.height()) // 2 + self.move(x, y) + + def onEnterSystem(self): + """进入系统按钮点击""" + self.enterSystem.emit() + self.close() + + def mousePressEvent(self, event: QMouseEvent): + """鼠标按下事件""" + if event.button() == Qt.LeftButton and self.isDraggableArea(event.pos()): + self.is_dragging = True + self.drag_start_position = event.globalPos() - self.pos() + + def mouseMoveEvent(self, event: QMouseEvent): + """鼠标移动事件""" + if self.is_dragging and event.buttons() == Qt.LeftButton: + self.move(event.globalPos() - self.drag_start_position) + + def mouseReleaseEvent(self, event: QMouseEvent): + """鼠标释放事件""" + self.is_dragging = False + + def isDraggableArea(self, pos): + """判断是否在可拖动区域""" + # 除了按钮区域外都可以拖动 + return not (self.enter_btn.geometry().contains(pos) or + self.close_btn.geometry().contains(pos)) \ No newline at end of file diff --git a/src/src/YOLOv8/UIProgram/ModernMainWindow.py b/src/src/YOLOv8/UIProgram/ModernMainWindow.py new file mode 100644 index 0000000..ebdd217 --- /dev/null +++ b/src/src/YOLOv8/UIProgram/ModernMainWindow.py @@ -0,0 +1,1056 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +室内多传感器寻敌系统主程序界面 +======================================== + +现代化主窗口程序,提供完整的人脸检测功能 + +功能特性: +- 支持图片、视频、摄像头、批量检测 +- 现代化UI设计,半透明背景效果 +- 支持窗口自由调整大小和全屏模式 +- 批量检测支持图片浏览功能(上一张/下一张) +- 实时检测结果显示和性能统计 +- 智能路径查找,支持多种启动方式 + +界面布局: +- 左侧侧边栏:主要功能按钮 +- 右侧显示区域:堆叠页面切换 + - 主页:系统介绍 + - 检测页面:检测功能和结果显示 + +检测功能: +- 图片检测:支持jpg、png、jpeg、bmp格式 +- 视频检测:支持mp4、avi、mov、mkv格式 +- 摄像头检测:实时检测和显示 +- 批量检测:文件夹批量处理,支持图片浏览 + +窗口控制: +- F11键:切换全屏/窗口模式 +- ESC键:全屏时退出全屏 +- 支持拖拽移动和调整大小 + +作者: 郭晋鹏团队 +版本: 2.0.0 +""" + +import sys +import os +import time +import cv2 +import numpy as np +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PIL import ImageFont +from ultralytics import YOLO + +# 确保可以从任何位置导入 +current_dir = os.path.dirname(os.path.abspath(__file__)) +parent_dir = os.path.dirname(current_dir) +sys.path.append(parent_dir) + +# 导入检测模块 +import detect_tools as tools +import Config + +# 导入作战地图模块 +from UIProgram.BattleMapTools import BattleMapWidget +# 导入目标导航模块 +from UIProgram.NavigationTools import NavigationWidget + + +class ModernMainWindow(QMainWindow): + """ + 现代化主窗口类 + + 继承自QMainWindow,提供完整的人脸检测功能界面。 + 包含检测模型加载、UI创建、事件处理等功能。 + + 主要属性: + model: YOLO检测模型 + batch_images: 批量检测图片列表 + current_batch_index: 当前显示的图片索引 + is_fullscreen: 全屏状态标志 + + 主要方法: + initDetection(): 初始化检测模型和变量 + setupUI(): 创建用户界面 + open_img(): 图片检测 + video_show(): 视频检测 + camera_show(): 摄像头检测 + batch_detect(): 批量检测 + """ + + def __init__(self, parent=None): + """ + 初始化主窗口 + + 参数: + parent: 父窗口,默认为None + """ + super().__init__(parent) + self.initDetection() # 初始化检测功能 + self.setupUI() # 设置用户界面 + + # 窗口拖拽相关变量 + self.dragging = False + self.drag_position = QPoint() + + def initDetection(self): + """初始化检测相关变量""" + # 检测结果显示尺寸 + self.show_width = 500 + self.show_height = 350 + + # 当前处理的文件路径 + self.org_path = None + self.org_img = None + self.draw_img = None + + # 摄像头相关 + self.is_camera_open = False + self.cap = None + + # 批量检测相关 + self.batch_images = [] # 批量检测的图片列表 + self.current_batch_index = 0 # 当前显示的图片索引 + self.batch_results = {} # 批量检测结果缓存 + + # 加载检测模型 + try: + # 智能路径查找 - 支持从根目录或UIProgram目录启动 + def find_file(filename): + """智能查找文件路径""" + # 尝试多个可能的路径 + possible_paths = [ + filename, # 当前目录 + os.path.join('..', filename), # 上级目录 + os.path.join(os.path.dirname(os.path.dirname(__file__)), filename), # 项目根目录 + ] + + for path in possible_paths: + if os.path.exists(path): + return path + return filename # 如果都找不到,返回原文件名 + + # 修正模型路径 + model_path = find_file(Config.model_path) + + self.model = YOLO(model_path, task='detect') + self.model(np.zeros((48, 48, 3))) # 预先加载推理模型 + + # 修正字体路径 + font_path = find_file(os.path.join('Font', 'platech.ttf')) + + self.fontC = ImageFont.truetype(font_path, 25, 0) + self.colors = tools.Colors() + print("模型加载成功") + except Exception as e: + print(f"模型加载失败: {e}") + self.model = None + self.fontC = None + + # 检测结果 + self.results = None + self.location_list = [] + self.cls_list = [] + self.conf_list = [] + + # 定时器 + self.timer_camera = QTimer() + self.timer_camera.timeout.connect(self.open_frame) + + def setupUI(self): + """设置用户界面""" + # 设置窗口属性 - 支持自由调整大小和全屏 + self.setWindowTitle("室内环境多传感器寻敌系统") + self.setMinimumSize(700, 500) # 设置最小尺寸(稍微缩减) + self.resize(1000, 700) # 初始尺寸(稍微缩减) + self.setWindowFlags(Qt.FramelessWindowHint) + + # 全屏状态标志 + self.is_fullscreen = False + + # 创建中央组件和主布局 - 按照UI文件的方式 + self.central_widget = QWidget(self) + self.central_widget.setStyleSheet("background: transparent;") + self.setCentralWidget(self.central_widget) + + self.main_layout = QHBoxLayout(self.central_widget) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) + + # 创建侧边栏 + self.createSidebar() + + # 创建主工作区 + self.createMainWorkspace() + + # 设置样式 + self.setStyles() + + # 窗口居中显示 + self.centerWindow() + + def createSidebar(self): + """创建侧边栏""" + # 创建左侧侧边栏 - 按照UI文件的方式 + self.sidebar = QFrame() + self.sidebar.setObjectName("sidebar") + self.sidebar.setFixedWidth(100) + + self.sidebar_layout = QVBoxLayout(self.sidebar) + self.sidebar_layout.setContentsMargins(10, 50, 10, 10) + self.sidebar_layout.setSpacing(30) + + # 创建主功能按钮 - 按照UI文件的方式 + self.target_detection_btn = QPushButton("目标识别") + self.target_detection_btn.setObjectName("sidebarButton") + self.target_detection_btn.setFixedHeight(30) + self.target_detection_btn.setToolTip("目标识别功能") + + self.map_btn = QPushButton("作战地图") + self.map_btn.setObjectName("sidebarButton") + self.map_btn.setFixedHeight(30) + self.map_btn.setToolTip("作战地图标绘功能") + + self.navigation_btn = QPushButton("目标导航") + self.navigation_btn.setObjectName("sidebarButton") + self.navigation_btn.setFixedHeight(30) + self.navigation_btn.setToolTip("目标导航功能") + + # 添加到布局 + self.sidebar_layout.addWidget(self.target_detection_btn) + self.sidebar_layout.addWidget(self.map_btn) + self.sidebar_layout.addWidget(self.navigation_btn) + self.sidebar_layout.addStretch() + + # 创建垂直布局容器并添加到主布局 + left_container = QWidget() + left_container.setStyleSheet("background: transparent;") + left_layout = QVBoxLayout(left_container) + left_layout.setContentsMargins(0, 0, 0, 0) + left_layout.setSpacing(0) + left_layout.addWidget(self.sidebar) + + self.main_layout.addWidget(left_container) + + def createMainWorkspace(self): + """创建主工作区""" + # 创建右侧显示区域 - 按照UI文件的方式 + self.display_area = QFrame() + self.display_area.setObjectName("displayArea") + + self.display_layout = QVBoxLayout(self.display_area) + self.display_layout.setContentsMargins(0, 0, 0, 0) + self.display_layout.setSpacing(0) + + # 创建堆叠窗口部件 - 按照UI文件的方式 + self.stacked_widget = QStackedWidget() + self.stacked_widget.setStyleSheet("background: transparent;") + self.display_layout.addWidget(self.stacked_widget) + + # 创建主页页面 + self.createHomePage() + + # 创建识别页面 + self.createDetectionPage() + + # 创建作战地图页面 + self.createBattleMapPage() + + # 创建目标导航页面 + self.createNavigationPage() + + # 创建关闭按钮和调整大小控件 + self.createCloseButton() + self.createResizeGrip() + + # 连接信号 + self.connectSignals() + + self.main_layout.addWidget(self.display_area) + + def createHomePage(self): + """创建主页页面""" + self.home_page = QWidget() + self.home_page.setStyleSheet("background: transparent;") + + home_layout = QVBoxLayout(self.home_page) + home_layout.setContentsMargins(50, 50, 50, 50) + home_layout.setSpacing(0) + + # 系统名称标签 - 按照UI文件的样式 + self.system_name_label = QLabel("室内环境多传感器寻敌系统") + self.system_name_label.setObjectName("systemNameLabel") + self.system_name_label.setAlignment(Qt.AlignCenter) + + # 添加弹性空间和标签 + home_layout.addStretch(2) + home_layout.addWidget(self.system_name_label) + home_layout.addStretch(3) + + self.stacked_widget.addWidget(self.home_page) + + def createDetectionPage(self): + """创建识别页面""" + self.detection_page = QWidget() + self.detection_page.setStyleSheet("background: transparent;") + + detection_layout = QVBoxLayout(self.detection_page) + detection_layout.setContentsMargins(20, 20, 20, 20) + detection_layout.setSpacing(10) + + # 页面标题 + title_label = QLabel("目标识别模式") + title_label.setAlignment(Qt.AlignCenter) + title_label.setStyleSheet(""" + QLabel { + color: #FFD700; + font-size: 18px; + font-weight: bold; + background: rgba(0, 0, 0, 0.5); + border-radius: 10px; + padding: 10px; + border: 2px solid rgba(255, 215, 0, 0.6); + } + """) + detection_layout.addWidget(title_label) + + # 功能按钮区域 + button_layout = QHBoxLayout() + + # 图片检测按钮 + self.image_btn = QPushButton("📷 图片检测") + self.image_btn.setFixedSize(120, 40) + self.image_btn.setStyleSheet(""" + QPushButton { + background: rgba(76, 175, 80, 0.8); + color: white; + font-size: 12px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 8px; + } + QPushButton:hover { + background: rgba(76, 175, 80, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + """) + + # 视频检测按钮 + self.video_btn = QPushButton("🎥 视频检测") + self.video_btn.setFixedSize(120, 40) + self.video_btn.setStyleSheet(""" + QPushButton { + background: rgba(33, 150, 243, 0.8); + color: white; + font-size: 12px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 8px; + } + QPushButton:hover { + background: rgba(33, 150, 243, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + """) + + # 摄像头检测按钮 + self.camera_btn = QPushButton("📹 摄像头检测") + self.camera_btn.setFixedSize(120, 40) + self.camera_btn.setStyleSheet(""" + QPushButton { + background: rgba(255, 152, 0, 0.8); + color: white; + font-size: 12px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 8px; + } + QPushButton:hover { + background: rgba(255, 152, 0, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + """) + + # 批量检测按钮 + self.batch_btn = QPushButton("📁 批量检测") + self.batch_btn.setFixedSize(120, 40) + self.batch_btn.setStyleSheet(""" + QPushButton { + background: rgba(156, 39, 176, 0.8); + color: white; + font-size: 12px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 8px; + } + QPushButton:hover { + background: rgba(156, 39, 176, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + """) + + button_layout.addWidget(self.image_btn) + button_layout.addWidget(self.video_btn) + button_layout.addWidget(self.camera_btn) + button_layout.addWidget(self.batch_btn) + button_layout.addStretch() + + detection_layout.addLayout(button_layout) + + # 显示区域 - 设置为可伸缩 + self.display_label = QLabel("请选择检测模式") + self.display_label.setAlignment(Qt.AlignCenter) + self.display_label.setMinimumSize(400, 300) + self.display_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.display_label.setScaledContents(False) # 保持图像比例 + self.display_label.setStyleSheet(""" + QLabel { + background: rgba(255, 255, 255, 0.1); + border: 2px dashed rgba(255, 255, 255, 0.5); + border-radius: 10px; + color: white; + font-size: 14px; + } + """) + detection_layout.addWidget(self.display_label) + + # 批量检测导航按钮区域 + self.batch_nav_layout = QHBoxLayout() + + # 上一张按钮 + self.prev_btn = QPushButton("⬅ 上一张") + self.prev_btn.setFixedSize(80, 30) + self.prev_btn.setEnabled(False) # 默认禁用 + self.prev_btn.setStyleSheet(""" + QPushButton { + background: rgba(96, 96, 96, 0.8); + color: white; + font-size: 10px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 5px; + } + QPushButton:enabled { + background: rgba(63, 81, 181, 0.8); + } + QPushButton:enabled:hover { + background: rgba(63, 81, 181, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + """) + + # 图片索引标签 + self.batch_index_label = QLabel("") + self.batch_index_label.setAlignment(Qt.AlignCenter) + self.batch_index_label.setStyleSheet(""" + QLabel { + color: white; + font-size: 10px; + background: rgba(0, 0, 0, 0.5); + padding: 5px; + border-radius: 5px; + } + """) + + # 下一张按钮 + self.next_btn = QPushButton("下一张 ➡") + self.next_btn.setFixedSize(80, 30) + self.next_btn.setEnabled(False) # 默认禁用 + self.next_btn.setStyleSheet(""" + QPushButton { + background: rgba(96, 96, 96, 0.8); + color: white; + font-size: 10px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 5px; + } + QPushButton:enabled { + background: rgba(63, 81, 181, 0.8); + } + QPushButton:enabled:hover { + background: rgba(63, 81, 181, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + """) + + self.batch_nav_layout.addStretch() + self.batch_nav_layout.addWidget(self.prev_btn) + self.batch_nav_layout.addWidget(self.batch_index_label) + self.batch_nav_layout.addWidget(self.next_btn) + self.batch_nav_layout.addStretch() + + # 默认隐藏导航按钮 + self.batch_nav_widget = QWidget() + self.batch_nav_widget.setLayout(self.batch_nav_layout) + self.batch_nav_widget.setVisible(False) + detection_layout.addWidget(self.batch_nav_widget) + + # 信息显示区域 + info_layout = QHBoxLayout() + + # 检测结果 + self.result_label = QLabel("检测结果:待检测") + self.result_label.setStyleSheet(""" + QLabel { + color: #00FF00; + font-size: 12px; + background: rgba(0, 0, 0, 0.5); + padding: 5px; + border-radius: 5px; + } + """) + + # 检测时间 + self.time_label = QLabel("检测时间:0ms") + self.time_label.setStyleSheet(""" + QLabel { + color: #FFD700; + font-size: 12px; + background: rgba(0, 0, 0, 0.5); + padding: 5px; + border-radius: 5px; + } + """) + + info_layout.addWidget(self.result_label) + info_layout.addWidget(self.time_label) + info_layout.addStretch() + + detection_layout.addLayout(info_layout) + + self.stacked_widget.addWidget(self.detection_page) + + def createBattleMapPage(self): + """创建作战地图页面""" + # 创建作战地图组件 + self.battle_map_widget = BattleMapWidget() + + # 添加到堆叠窗口 + self.stacked_widget.addWidget(self.battle_map_widget) + + def showBattleMapPage(self): + """显示作战地图页面""" + self.stacked_widget.setCurrentWidget(self.battle_map_widget) + + def createNavigationPage(self): + """创建目标导航页面""" + # 创建导航组件 + self.navigation_widget = NavigationWidget() + + # 添加到堆叠窗口 + self.stacked_widget.addWidget(self.navigation_widget) + + def showNavigationPage(self): + """显示目标导航页面""" + self.stacked_widget.setCurrentWidget(self.navigation_widget) + + def connectSignals(self): + """连接信号""" + # 侧边栏按钮信号 + self.target_detection_btn.clicked.connect(self.showDetectionPage) + self.map_btn.clicked.connect(self.showBattleMapPage) # 连接到作战地图页面 + self.navigation_btn.clicked.connect(self.showNavigationPage) # 连接到目标导航页面 + + # 检测功能按钮信号 + self.image_btn.clicked.connect(self.open_img) + self.video_btn.clicked.connect(self.video_show) + self.camera_btn.clicked.connect(self.camera_show) + self.batch_btn.clicked.connect(self.batch_detect) + + # 批量检测导航按钮信号 + self.prev_btn.clicked.connect(self.show_prev_image) + self.next_btn.clicked.connect(self.show_next_image) + + def showHomePage(self): + """显示主页""" + self.stacked_widget.setCurrentWidget(self.home_page) + + def showDetectionPage(self): + """显示识别页面""" + self.stacked_widget.setCurrentWidget(self.detection_page) + + def createCloseButton(self): + """创建关闭按钮和全屏按钮""" + # 全屏/还原按钮 + self.fullscreen_button = QPushButton("□", self) + self.fullscreen_button.setFixedSize(40, 40) + self.fullscreen_button.setObjectName("fullscreenButton") + self.fullscreen_button.clicked.connect(self.toggleFullscreen) + + # 关闭按钮 + self.close_button = QPushButton("×", self) + self.close_button.setFixedSize(40, 40) + self.close_button.setObjectName("closeButton") + self.close_button.clicked.connect(self.close) + + def createResizeGrip(self): + """创建调整大小控件""" + # 添加右下角的调整大小控件 + self.resize_grip = QSizeGrip(self) + self.resize_grip.setFixedSize(20, 20) + self.resize_grip.setStyleSheet(""" + QSizeGrip { + background: rgba(255, 255, 255, 0.3); + border: none; + } + """) + + def resizeEvent(self, event): + """窗口大小改变事件""" + if event: + super().resizeEvent(event) + # 调整全屏按钮和关闭按钮位置 + self.fullscreen_button.move(self.width() - 100, 10) + self.close_button.move(self.width() - 50, 10) + # 调整调整大小控件位置(全屏时隐藏) + if hasattr(self, 'resize_grip'): + if self.is_fullscreen: + self.resize_grip.hide() + else: + self.resize_grip.show() + self.resize_grip.move(self.width() - 20, self.height() - 20) + + def mousePressEvent(self, event): + """鼠标按下事件 - 用于窗口拖拽""" + if event.button() == Qt.LeftButton: + self.dragging = True + self.drag_position = event.globalPos() - self.frameGeometry().topLeft() + event.accept() + + def mouseMoveEvent(self, event): + """鼠标移动事件 - 用于窗口拖拽""" + if event.buttons() == Qt.LeftButton and self.dragging: + self.move(event.globalPos() - self.drag_position) + event.accept() + + def mouseReleaseEvent(self, event): + """鼠标释放事件 - 结束窗口拖拽""" + self.dragging = False + + def keyPressEvent(self, event): + """按键事件处理""" + if event.key() == Qt.Key_F11: + self.toggleFullscreen() + elif event.key() == Qt.Key_Escape and self.is_fullscreen: + self.toggleFullscreen() + else: + super().keyPressEvent(event) + + def toggleFullscreen(self): + """切换全屏状态""" + if self.is_fullscreen: + # 退出全屏 + self.showNormal() + self.is_fullscreen = False + self.fullscreen_button.setText("□") + # 恢复窗口边框 + self.setWindowFlags(Qt.FramelessWindowHint) + self.show() + else: + # 进入全屏 + self.showFullScreen() + self.is_fullscreen = True + self.fullscreen_button.setText("⊡") + # 隐藏调整大小控件 + if hasattr(self, 'resize_grip'): + self.resize_grip.hide() + + # 更新按钮位置 + self.resizeEvent(None) + + def setStyles(self): + """设置样式表""" + # 按照UI文件的方式设置样式 + style_sheet = """ + ModernMainWindow { + background-image: url(UIProgram/ui_imgs/icons/R6.png); + background-repeat: no-repeat; + background-position: center; + background-attachment: fixed; + } + + #sidebar { + background: rgba(0, 0, 0, 0.4); + border-right: 2px solid rgba(255, 255, 255, 0.3); + } + + #sidebarButton { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgba(255, 69, 0, 0.8), stop:1 rgba(220, 20, 60, 0.8)); + color: #FFFFFF; + font-size: 12px; + font-weight: bold; + font-family: 'Arial Black', sans-serif; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 10px; + } + + #sidebarButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgba(255, 99, 71, 1.0), stop:1 rgba(255, 69, 0, 1.0)); + border: 2px solid rgba(255, 215, 0, 1.0); + } + + #displayArea { + background: transparent; + } + + #systemNameLabel { + color: #FF0000; + font-size: 24px; + font-weight: bold; + font-family: 'Impact', 'Arial Black', sans-serif; + + background: rgba(0, 0, 0, 0.5); + border-radius: 15px; + padding: 30px; + border: 2px solid rgba(255, 0, 0, 0.8); + } + + #fullscreenButton { + background: rgba(30, 144, 255, 0.8); + color: white; + font-size: 16px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 20px; + } + + #fullscreenButton:hover { + background: rgba(65, 105, 225, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + + #fullscreenButton:pressed { + background: rgba(25, 25, 112, 1.0); + } + + #closeButton { + background: rgba(220, 20, 60, 0.8); + color: white; + font-size: 20px; + font-weight: bold; + border: 2px solid rgba(255, 255, 255, 0.6); + border-radius: 20px; + } + + #closeButton:hover { + background: rgba(255, 69, 0, 1.0); + border: 2px solid rgba(255, 215, 0, 1.0); + } + + #closeButton:pressed { + background: rgba(139, 0, 0, 1.0); + } + """ + + self.setStyleSheet(style_sheet) + + def centerWindow(self): + """窗口居中显示""" + screen = QApplication.desktop().screenGeometry() + x = (screen.width() - self.width()) // 2 + y = (screen.height() - self.height()) // 2 + self.move(x, y) + + # =============== 检测功能方法 =============== + + def open_img(self): + """打开图片并进行检测""" + if self.model is None: + self.display_label.setText("模型未加载,无法进行检测") + return + + # 关闭摄像头 + if self.cap: + self.video_stop() + + # 隐藏批量导航按钮 + self.batch_nav_widget.setVisible(False) + + # 选择图片文件 + file_path, _ = QFileDialog.getOpenFileName( + self, '选择图片', './', + "Image files (*.jpg *.jpeg *.png *.bmp)" + ) + + if not file_path: + return + + try: + self.org_path = file_path + self.org_img = tools.img_cvread(self.org_path) + + # 进行检测 + t1 = time.time() + self.results = self.model(self.org_path)[0] + t2 = time.time() + + # 处理检测结果 + if len(self.results.boxes) > 0: + self.location_list = self.results.boxes.xyxy.tolist() + self.location_list = [list(map(int, e)) for e in self.location_list] + self.cls_list = [int(i) for i in self.results.boxes.cls.tolist()] + self.conf_list = ['%.2f%%' % (each*100) for each in self.results.boxes.conf.tolist()] + + # 绘制检测结果 + now_img = self.results.plot() + self.draw_img = now_img + + # 显示结果 + self.display_detection_result(now_img) + + # 更新信息 + take_time = (t2 - t1) * 1000 + self.result_label.setText(f"检测结果:发现 {len(self.cls_list)} 个目标") + self.time_label.setText(f"检测时间:{take_time:.1f}ms") + + else: + # 没有检测到目标 + self.display_detection_result(self.org_img) + self.result_label.setText("检测结果:未发现目标") + take_time = (t2 - t1) * 1000 + self.time_label.setText(f"检测时间:{take_time:.1f}ms") + + except Exception as e: + self.display_label.setText(f"检测失败:{str(e)}") + print(f"检测错误: {e}") + + def video_show(self): + """视频检测""" + if self.model is None: + self.display_label.setText("模型未加载,无法进行检测") + return + + # 关闭摄像头 + if self.cap: + self.video_stop() + + # 隐藏批量导航按钮 + self.batch_nav_widget.setVisible(False) + + # 选择视频文件 + file_path, _ = QFileDialog.getOpenFileName( + self, '选择视频', './', + "Video files (*.mp4 *.avi *.mov *.mkv)" + ) + + if not file_path: + return + + try: + self.cap = cv2.VideoCapture(file_path) + self.timer_camera.start(30) # 30ms间隔 + self.result_label.setText("检测结果:视频检测中...") + + except Exception as e: + self.display_label.setText(f"视频打开失败:{str(e)}") + + def camera_show(self): + """摄像头检测""" + if self.model is None: + self.display_label.setText("模型未加载,无法进行检测") + return + + # 隐藏批量导航按钮 + self.batch_nav_widget.setVisible(False) + + if not self.is_camera_open: + try: + self.cap = cv2.VideoCapture(0) + if self.cap.isOpened(): + self.is_camera_open = True + self.timer_camera.start(30) + self.result_label.setText("检测结果:摄像头检测中...") + else: + self.display_label.setText("无法打开摄像头") + except Exception as e: + self.display_label.setText(f"摄像头打开失败:{str(e)}") + else: + self.video_stop() + + def batch_detect(self): + """批量检测""" + if self.model is None: + self.display_label.setText("模型未加载,无法进行检测") + return + + # 关闭摄像头 + if self.cap: + self.video_stop() + + # 选择文件夹 + directory = QFileDialog.getExistingDirectory(self, "选择图片文件夹", "./") + if not directory: + return + + try: + img_suffix = ['jpg', 'png', 'jpeg', 'bmp'] + self.batch_images = [] + + # 收集所有图片文件 + for file_name in os.listdir(directory): + if file_name.split('.')[-1].lower() in img_suffix: + self.batch_images.append(os.path.join(directory, file_name)) + + if not self.batch_images: + self.display_label.setText("选择的文件夹中没有图片文件") + self.batch_nav_widget.setVisible(False) + return + + # 排序文件列表 + self.batch_images.sort() + + # 重置批量检测状态 + self.current_batch_index = 0 + self.batch_results = {} + + # 显示导航按钮 + self.batch_nav_widget.setVisible(True) + + # 进行批量检测 + total_targets = 0 + for i, img_path in enumerate(self.batch_images): + results = self.model(img_path)[0] + self.batch_results[i] = results + if len(results.boxes) > 0: + total_targets += len(results.boxes) + + # 显示第一张图片 + self.show_batch_image(0) + + self.result_label.setText(f"批量检测完成:处理 {len(self.batch_images)} 张图片,发现 {total_targets} 个目标") + + except Exception as e: + self.display_label.setText(f"批量检测失败:{str(e)}") + self.batch_nav_widget.setVisible(False) + + def show_batch_image(self, index): + """显示批量检测中的指定图片""" + if not self.batch_images or index < 0 or index >= len(self.batch_images): + return + + try: + # 更新当前索引 + self.current_batch_index = index + + # 获取图片路径和检测结果 + img_path = self.batch_images[index] + results = self.batch_results.get(index) + + # 更新索引标签 + self.batch_index_label.setText(f"{index + 1}/{len(self.batch_images)}") + + # 更新按钮状态 + self.prev_btn.setEnabled(index > 0) + self.next_btn.setEnabled(index < len(self.batch_images) - 1) + + # 显示图片 + if results and len(results.boxes) > 0: + # 有检测结果,显示标注图片 + now_img = results.plot() + self.display_detection_result(now_img) + target_count = len(results.boxes) + self.time_label.setText(f"当前图片:发现 {target_count} 个目标") + else: + # 无检测结果,显示原图 + img = tools.img_cvread(img_path) + self.display_detection_result(img) + self.time_label.setText("当前图片:未发现目标") + + except Exception as e: + print(f"显示批量图片错误: {e}") + + def show_prev_image(self): + """显示上一张图片""" + if self.batch_images and self.current_batch_index > 0: + self.show_batch_image(self.current_batch_index - 1) + + def show_next_image(self): + """显示下一张图片""" + if self.batch_images and self.current_batch_index < len(self.batch_images) - 1: + self.show_batch_image(self.current_batch_index + 1) + + def open_frame(self): + """处理摄像头/视频帧""" + if self.cap and self.cap.isOpened(): + ret, frame = self.cap.read() + if ret: + try: + # 进行检测 + results = self.model(frame)[0] + + if len(results.boxes) > 0: + frame = results.plot() + target_count = len(results.boxes) + self.result_label.setText(f"检测结果:发现 {target_count} 个目标") + else: + self.result_label.setText("检测结果:未发现目标") + + # 显示帧 + self.display_detection_result(frame) + + except Exception as e: + print(f"帧处理错误: {e}") + else: + self.video_stop() + + def video_stop(self): + """停止视频/摄像头""" + if self.timer_camera.isActive(): + self.timer_camera.stop() + if self.cap: + self.cap.release() + self.cap = None + self.is_camera_open = False + self.result_label.setText("检测结果:已停止") + + def display_detection_result(self, img): + """显示检测结果图像""" + try: + # 获取原始图像尺寸 + img_height, img_width = img.shape[:2] + + # 获取显示区域的实际尺寸(减去边距) + label_size = self.display_label.size() + display_width = max(label_size.width() - 20, 400) # 减去边距,最小400 + display_height = max(label_size.height() - 20, 300) # 减去边距,最小300 + + # 计算缩放比例以适应显示区域,保持图像比例 + scale_w = display_width / img_width + scale_h = display_height / img_height + scale = min(scale_w, scale_h) + + new_width = int(img_width * scale) + new_height = int(img_height * scale) + + # 缩放图像 + resized_img = cv2.resize(img, (new_width, new_height)) + + # 转换为Qt格式并显示 + pix_img = tools.cvimg_to_qpiximg(resized_img) + self.display_label.setPixmap(pix_img) + + except Exception as e: + print(f"图像显示错误: {e}") + self.display_label.setText("图像显示失败") + + +if __name__ == '__main__': + # 设置高DPI支持(必须在创建QApplication之前) + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) + QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) + + app = QApplication(sys.argv) + + window = ModernMainWindow() + window.show() + + sys.exit(app.exec_()) \ No newline at end of file diff --git a/src/src/YOLOv8/UIProgram/NavigationTools.py b/src/src/YOLOv8/UIProgram/NavigationTools.py new file mode 100644 index 0000000..7bb4685 --- /dev/null +++ b/src/src/YOLOv8/UIProgram/NavigationTools.py @@ -0,0 +1,1155 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +室内多传感器寻敌系统 - 目标导航工具 +=============================== + +提供目标导航功能,支持路径规划和导航动画 + +功能特性: +- 支持导入地图图片(黑白格式) +- 支持设置目标点位置 +- 支持设置小车当前位置 +- 自动规划避开障碍物的最短路径 +- 提供小车沿路径行进的动画效果 +- 支持缩放和平移地图 + +作者: 郭晋鹏团队 +版本: 2.0.0 +""" + +from PyQt5.QtWidgets import (QWidget, QPushButton, QLabel, QHBoxLayout, QVBoxLayout, + QFileDialog, QMessageBox, QButtonGroup, QFrame, QSizePolicy) +from PyQt5.QtCore import Qt, QPoint, QRect, QSize, QTimer, pyqtSignal +from PyQt5.QtGui import (QPainter, QPen, QColor, QPixmap, QImage, QBrush, QIcon, QFont, + QPainterPath, QTransform) +import cv2 +import numpy as np +import sys +import os +import heapq +import math + +# 确保可以从任何位置导入 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + +class NavigationArea(QWidget): + """ + 导航区域类 + + 提供一个可以进行导航操作的区域,支持路径规划和导航动画 + + 主要属性: + map_image: 导入的地图图像 + target_point: 目标点位置 + car_position: 小车当前位置 + path: 规划的路径 + + 主要方法: + loadMap: 加载地图图片 + setTargetPoint: 设置目标点 + setCarPosition: 设置小车位置 + planPath: 规划路径 + startNavigation: 开始导航动画 + """ + + # 操作模式枚举 + MODE_VIEW = 0 # 查看模式 + MODE_TARGET = 1 # 设置目标点 + MODE_CAR = 2 # 设置小车位置 + + # 符号尺寸 + TARGET_SIZE = 15 + CAR_SIZE = 20 + + # 缩放设置 + MIN_SCALE = 0.1 # 最小缩放比例 + MAX_SCALE = 5.0 # 最大缩放比例 + ZOOM_FACTOR = 1.2 # 每次缩放的比例 + + # 导航完成信号 + navigationFinished = pyqtSignal() + + def __init__(self, parent=None): + """初始化导航区域""" + super().__init__(parent) + + # 初始化属性 + self.map_image = None # 原始地图图像(QPixmap) + self.map_array = None # 地图的numpy数组(用于路径规划) + self.scaled_map = None # 缩放后的地图 + self.target_point = None # 目标点位置 + self.car_position = None # 小车位置 + self.path = [] # 规划的路径 + self.current_mode = self.MODE_VIEW # 当前操作模式 + self.has_map = False # 是否已加载地图 + + # 缩放和平移属性 + self.scale_factor = 1.0 + self.offset = QPoint(0, 0) + self.panning = False + self.pan_start_pos = QPoint() + + # 导航动画属性 + self.animation_timer = QTimer(self) + self.animation_timer.timeout.connect(self.updateNavigation) + self.animation_path_index = 0 + self.is_navigating = False + + # 设置鼠标跟踪 + self.setMouseTracking(True) + + # 设置焦点策略,使其能接收键盘事件 + self.setFocusPolicy(Qt.StrongFocus) + + # 创建空白图像 + self.createEmptyMap() + + def createEmptyMap(self): + """创建空白地图""" + self.map_image = QPixmap(800, 600) + self.map_image.fill(Qt.white) + self.scaled_map = self.map_image.copy() + self.has_map = False + self.update() + + def loadMap(self, file_path=None): + """ + 加载地图图片 + + 参数: + file_path: 图片文件路径,如果为None则弹出文件选择对话框 + + 返回: + bool: 是否成功加载地图 + """ + print("开始加载地图...") # 调试输出 + + if file_path is None: + print("弹出文件选择对话框") # 调试输出 + file_path, _ = QFileDialog.getOpenFileName( + self, '导入地图', './', + "图像文件 (*.jpg *.jpeg *.png *.bmp)" + ) + print(f"选择的文件路径: {file_path}") # 调试输出 + + if not file_path: + print("未选择文件") # 调试输出 + return False + + try: + print(f"尝试加载图片: {file_path}") # 调试输出 + # 加载原始图片 + self.map_image = QPixmap(file_path) + if self.map_image.isNull(): + print("图片加载失败") # 调试输出 + QMessageBox.warning(self, "错误", "无法加载地图文件") + return False + + print(f"图片尺寸: {self.map_image.width()}x{self.map_image.height()}") # 调试输出 + + # 将QPixmap转换为OpenCV格式以便路径规划 + img = self.pixmapToArray(self.map_image) + + # 将图像转换为二值图像(黑白) + if len(img.shape) > 2: + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + else: + gray = img + + # 阈值处理,将图像转换为二值图像(0和255) + _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) + + # 保存为numpy数组,用于路径规划 + self.map_array = binary + + # 重置状态 + self.target_point = None + self.car_position = None + self.path = [] + self.scale_factor = 1.0 + self.offset = QPoint(0, 0) + + # 适配图像大小到窗口 + self.fitToView() + + self.has_map = True + self.update() + print("地图加载成功") # 调试输出 + return True + + except Exception as e: + print(f"加载地图错误: {e}") # 调试输出 + import traceback + traceback.print_exc() # 打印详细错误信息 + QMessageBox.warning(self, "错误", f"加载地图失败: {str(e)}") + return False + + def pixmapToArray(self, pixmap): + """将QPixmap转换为numpy数组""" + # 转换为QImage + image = pixmap.toImage() + + # 获取图像尺寸 + width = image.width() + height = image.height() + + # 转换为numpy数组 + ptr = image.constBits() + ptr.setsize(image.byteCount()) + + # 根据图像格式确定通道数 + if image.format() == QImage.Format_RGB32 or image.format() == QImage.Format_ARGB32: + arr = np.array(ptr).reshape(height, width, 4) # RGBA + return cv2.cvtColor(arr, cv2.COLOR_RGBA2BGR) + else: + # 转换为标准格式 + converted = image.convertToFormat(QImage.Format_RGB32) + ptr = converted.constBits() + ptr.setsize(converted.byteCount()) + arr = np.array(ptr).reshape(height, width, 4) # RGBA + return cv2.cvtColor(arr, cv2.COLOR_RGBA2BGR) + + def fitToView(self): + """适配图像大小到当前视图""" + if self.map_image.isNull(): + return + + # 获取父窗口大小(如果有) + parent_size = self.parentWidget().size() if self.parentWidget() else self.size() + + # 计算适合的缩放比例 + width_ratio = (parent_size.width() - 40) / self.map_image.width() + height_ratio = (parent_size.height() - 40) / self.map_image.height() + + # 选择较小的比例,确保图像完全可见 + self.scale_factor = min(width_ratio, height_ratio, 1.0) + + # 缩放图像 + self.updateScaledMap() + + def updateScaledMap(self): + """根据当前缩放因子更新缩放后的图像""" + if self.map_image.isNull(): + return + + # 计算缩放后的尺寸 + new_width = int(self.map_image.width() * self.scale_factor) + new_height = int(self.map_image.height() * self.scale_factor) + + # 缩放图像 + self.scaled_map = self.map_image.scaled( + new_width, new_height, + Qt.KeepAspectRatio, + Qt.SmoothTransformation + ) + + # 设置最小尺寸 + self.setMinimumSize(new_width, new_height) + + # 更新界面 + self.update() + + def zoomIn(self): + """放大地图""" + if self.scale_factor < self.MAX_SCALE: + self.scale_factor *= self.ZOOM_FACTOR + self.updateScaledMap() + + def zoomOut(self): + """缩小地图""" + if self.scale_factor > self.MIN_SCALE: + self.scale_factor /= self.ZOOM_FACTOR + self.updateScaledMap() + + def setMode(self, mode): + """设置操作模式""" + self.current_mode = mode + if mode == self.MODE_TARGET: + QMessageBox.information(self, "设置目标点", "请在地图上点击设置目标点位置") + elif mode == self.MODE_CAR: + QMessageBox.information(self, "设置小车位置", "请在地图上点击设置小车当前位置") + + def setTargetPoint(self, point=None): + """ + 设置目标点 + + 参数: + point: 目标点位置,如果为None则进入设置目标点模式 + """ + if point is None: + self.setMode(self.MODE_TARGET) + else: + self.target_point = point + self.update() + + def setCarPosition(self, point=None): + """ + 设置小车位置 + + 参数: + point: 小车位置,如果为None则进入设置小车位置模式 + """ + if point is None: + self.setMode(self.MODE_CAR) + else: + self.car_position = point + self.update() + + def planPath(self): + """ + 规划路径 + + 使用A*算法规划从小车位置到目标点的路径 + + 返回: + bool: 是否成功规划路径 + """ + if not self.has_map or self.map_array is None: + QMessageBox.warning(self, "错误", "请先导入地图") + return False + + if self.car_position is None: + QMessageBox.warning(self, "错误", "请先设置小车位置") + return False + + if self.target_point is None: + QMessageBox.warning(self, "错误", "请先设置目标点") + return False + + # 转换为图像坐标 + start = self._mapToImageCoord(self.car_position) + goal = self._mapToImageCoord(self.target_point) + + print(f"起点坐标: {start}, 终点坐标: {goal}") + + # 检查起点和终点是否在可行区域(白色区域,像素值 > 240) + if start[1] < 0 or start[1] >= self.map_array.shape[0] or start[0] < 0 or start[0] >= self.map_array.shape[1]: + QMessageBox.warning(self, "错误", "小车位置超出地图范围") + return False + + if goal[1] < 0 or goal[1] >= self.map_array.shape[0] or goal[0] < 0 or goal[0] >= self.map_array.shape[1]: + QMessageBox.warning(self, "错误", "目标点超出地图范围") + return False + + # 打印起点和终点的像素值,用于调试 + start_pixel = self.map_array[start[1], start[0]] + goal_pixel = self.map_array[goal[1], goal[0]] + print(f"起点像素值: {start_pixel}, 终点像素值: {goal_pixel}") + + if start_pixel <= 240: # 非白色区域 + QMessageBox.warning(self, "错误", f"小车位置不在可行区域 (像素值: {start_pixel})") + return False + + if goal_pixel <= 240: # 非白色区域 + QMessageBox.warning(self, "错误", f"目标点不在可行区域 (像素值: {goal_pixel})") + return False + + # 使用A*算法规划路径 + print("开始规划路径...") + path = self.astar(start, goal) + + if not path: + QMessageBox.warning(self, "错误", "无法找到可行路径") + return False + + # 平滑路径 + self.path = self.smooth_path(path) + print(f"路径规划完成,共{len(self.path)}个点") + + # 更新界面 + self.update() + return True + + def astar(self, start, goal): + """ + A*算法实现路径规划 + + 参数: + start: 起点坐标 (x, y) + goal: 终点坐标 (x, y) + + 返回: + list: 路径点列表 [(x1, y1), (x2, y2), ...] + """ + # 定义启发式函数 - 使用欧几里得距离 + def heuristic(a, b): + return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) + + # 定义邻居节点 + def get_neighbors(node): + # 8个方向的邻居 + directions = [ + (0, 1), (1, 0), (0, -1), (-1, 0), # 上右下左 + (1, 1), (1, -1), (-1, -1), (-1, 1) # 对角线 + ] + + result = [] + for dx, dy in directions: + x, y = node[0] + dx, node[1] + dy + + # 检查边界 + if 0 <= x < self.map_array.shape[1] and 0 <= y < self.map_array.shape[0]: + # 只在白色区域行驶 (像素值 > 240) + if self.map_array[y, x] > 240: + # 对角线移动时,需要检查两个相邻点是否也是白色区域 + if dx != 0 and dy != 0: + # 检查水平和垂直相邻点 + if (self.map_array[y, node[0]] > 240 and + self.map_array[node[1], x] > 240): + result.append((x, y)) + else: + result.append((x, y)) + + return result + + # 初始化开放列表和关闭列表 + open_set = [] + closed_set = set() + + # 记录每个节点的父节点 + came_from = {} + + # g_score[n]表示从起点到节点n的实际代价 + g_score = {start: 0} + + # f_score[n]表示从起点经过节点n到终点的估计代价 + f_score = {start: heuristic(start, goal)} + + # 将起点加入开放列表 + heapq.heappush(open_set, (f_score[start], start)) + + # 最大迭代次数,防止无限循环 + max_iterations = 100000 + iterations = 0 + + while open_set and iterations < max_iterations: + iterations += 1 + + # 获取f值最小的节点 + _, current = heapq.heappop(open_set) + + # 如果到达终点,构建路径并返回 + if current == goal: + path = [] + while current in came_from: + path.append(current) + current = came_from[current] + path.append(start) + path.reverse() + print(f"路径规划完成,迭代次数: {iterations}") + return path + + # 将当前节点加入关闭列表 + closed_set.add(current) + + # 遍历邻居节点 + for neighbor in get_neighbors(current): + # 如果邻居节点在关闭列表中,跳过 + if neighbor in closed_set: + continue + + # 计算从起点经过当前节点到邻居节点的代价 + # 对角线移动的代价为√2,直线移动的代价为1 + if abs(neighbor[0] - current[0]) == 1 and abs(neighbor[1] - current[1]) == 1: + # 对角线移动 + move_cost = 1.414 # √2 + else: + # 直线移动 + move_cost = 1.0 + + tentative_g_score = g_score[current] + move_cost + + # 如果邻居节点不在开放列表中,或者找到了更好的路径 + if neighbor not in g_score or tentative_g_score < g_score[neighbor]: + # 更新路径信息 + came_from[neighbor] = current + g_score[neighbor] = tentative_g_score + f_score[neighbor] = g_score[neighbor] + heuristic(neighbor, goal) + + # 将邻居节点加入开放列表 + if neighbor not in [item[1] for item in open_set]: + heapq.heappush(open_set, (f_score[neighbor], neighbor)) + + print(f"无法找到路径,迭代次数: {iterations}") + # 如果无法找到路径,返回空列表 + return [] + + def is_safe_distance(self, x, y, safe_distance=3): + """ + 检查点(x,y)是否与障碍物(黑色或灰色区域)保持安全距离 + + 参数: + x, y: 点坐标 + safe_distance: 安全距离阈值 + + 返回: + bool: 是否安全 + """ + # 检查周围区域是否有障碍物 + min_x = max(0, x - safe_distance) + max_x = min(self.map_array.shape[1] - 1, x + safe_distance) + min_y = max(0, y - safe_distance) + max_y = min(self.map_array.shape[0] - 1, y + safe_distance) + + # 检查区域内是否有黑色或灰色像素(像素值小于240) + region = self.map_array[min_y:max_y+1, min_x:max_x+1] + return np.all(region > 240) + + def smooth_path(self, path): + """ + 平滑路径,减少路径点数量,但确保不经过障碍物 + + 参数: + path: 原始路径点列表 + + 返回: + list: 平滑后的路径点列表 + """ + if len(path) <= 2: + return path + + print(f"开始平滑路径,原始路径点数: {len(path)}") + + # 使用RDP算法简化路径 + def rdp(points, epsilon): + """ + Ramer-Douglas-Peucker算法实现 + + 参数: + points: 点列表 + epsilon: 简化阈值 + + 返回: + list: 简化后的点列表 + """ + if len(points) <= 2: + return points + + # 找到距离最远的点 + dmax = 0 + index = 0 + for i in range(1, len(points) - 1): + d = self.point_line_distance(points[i], points[0], points[-1]) + if d > dmax: + dmax = d + index = i + + # 如果最大距离大于阈值,则递归处理 + if dmax > epsilon: + # 递归处理前半部分和后半部分 + results1 = rdp(points[:index + 1], epsilon) + results2 = rdp(points[index:], epsilon) + + # 合并结果,去掉重复的点 + return results1[:-1] + results2 + else: + # 检查直线是否穿过障碍物 + if is_valid_path(points[0], points[-1]): + return [points[0], points[-1]] + else: + # 如果穿过障碍物,保留原始路径 + return points + + # 检查路径是否穿过障碍物 + def is_valid_path(p1, p2): + # 检查从p1到p2的直线是否穿过障碍物 + # 使用Bresenham算法获取直线上的所有点 + points = self.bresenham(p1[0], p1[1], p2[0], p2[1]) + + # 检查每个点是否在安全区域内 + for x, y in points: + if not (0 <= x < self.map_array.shape[1] and 0 <= y < self.map_array.shape[0]): + return False + + # 检查是否为白色区域 (像素值 > 240) + if self.map_array[y, x] <= 240: + return False + + return True + + # 使用RDP算法简化路径,阈值设为3(降低阈值以保留更多细节) + simplified_path = rdp(path, 3) + print(f"RDP简化后路径点数: {len(simplified_path)}") + + # 确保路径点数量不会太少,至少保留原始点数的20% + min_points = max(10, len(path) // 5) + + # 如果简化后的点数太少,增加中间点 + if len(simplified_path) < min_points: + # 重新分配点,使其均匀分布 + result = [simplified_path[0]] # 起点 + + # 计算路径总长度 + total_distance = 0 + for i in range(len(simplified_path) - 1): + p1 = simplified_path[i] + p2 = simplified_path[i + 1] + total_distance += math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2) + + # 计算点之间的间距 + spacing = total_distance / (min_points - 1) + + # 沿路径均匀分布点 + current_distance = 0 + next_point_distance = spacing + + for i in range(len(simplified_path) - 1): + p1 = simplified_path[i] + p2 = simplified_path[i + 1] + + # 计算当前段的长度 + segment_length = math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2) + + # 在当前段上添加点 + while current_distance + segment_length >= next_point_distance: + # 计算在当前段上的位置 + t = (next_point_distance - current_distance) / segment_length + x = int(p1[0] * (1 - t) + p2[0] * t) + y = int(p1[1] * (1 - t) + p2[1] * t) + + # 确保点在白色区域 + if 0 <= y < self.map_array.shape[0] and 0 <= x < self.map_array.shape[1]: + if self.map_array[y, x] > 240: + result.append((x, y)) + + # 更新下一个点的距离 + next_point_distance += spacing + + # 更新当前距离 + current_distance += segment_length + + # 确保终点被添加 + result.append(simplified_path[-1]) + print(f"重新分配后路径点数: {len(result)}") + return result + + print(f"最终路径点数: {len(simplified_path)}") + return simplified_path + + def bresenham(self, x1, y1, x2, y2): + """ + Bresenham算法获取直线上的所有点 + + 参数: + x1, y1: 起点坐标 + x2, y2: 终点坐标 + + 返回: + list: 直线上的所有点 + """ + points = [] + dx = abs(x2 - x1) + dy = abs(y2 - y1) + sx = 1 if x1 < x2 else -1 + sy = 1 if y1 < y2 else -1 + err = dx - dy + + while True: + points.append((x1, y1)) + + if x1 == x2 and y1 == y2: + break + + e2 = 2 * err + if e2 > -dy: + err -= dy + x1 += sx + if e2 < dx: + err += dx + y1 += sy + + return points + + def point_line_distance(self, point, line_start, line_end): + """ + 计算点到直线的距离 + + 参数: + point: 点坐标(x, y) + line_start: 直线起点(x, y) + line_end: 直线终点(x, y) + + 返回: + float: 点到直线的距离 + """ + x0, y0 = point + x1, y1 = line_start + x2, y2 = line_end + + # 如果直线实际上是一个点 + if x1 == x2 and y1 == y2: + return math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2) + + # 计算点到直线的距离 + numerator = abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) + denominator = math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2) + + return numerator / denominator + + def startNavigation(self): + """ + 开始导航动画 + + 返回: + bool: 是否成功开始导航 + """ + if not self.path: + QMessageBox.warning(self, "错误", "请先规划路径") + return False + + # 重置动画状态 + self.animation_path_index = 0 + self.is_navigating = True + + # 启动定时器,控制动画速度 + # 减慢动画速度,从100ms改为300ms + self.animation_timer.start(300) + + return True + + def stopNavigation(self): + """停止导航动画""" + self.animation_timer.stop() + self.is_navigating = False + self.update() + + def updateNavigation(self): + """更新导航动画""" + if not self.is_navigating or not self.path: + return + + # 获取当前路径点和下一个路径点 + if self.animation_path_index < len(self.path) - 1: + # 移动到下一个点 + self.animation_path_index += 1 + + # 更新界面 + self.update() + else: + # 到达终点,停止动画 + self.animation_timer.stop() + self.is_navigating = False + + # 发出导航完成信号 + self.navigationFinished.emit() + + # 显示提示 + QMessageBox.information(self, "导航完成", "小车已到达目标位置") + + def paintEvent(self, event): + """绘制事件""" + if not self.has_map or self.scaled_map.isNull(): + super().paintEvent(event) + return + + # 创建QPainter + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.SmoothPixmapTransform) + + # 计算图像在窗口中的位置(居中显示) + x_offset = (self.width() - self.scaled_map.width()) // 2 + self.offset.x() + y_offset = (self.height() - self.scaled_map.height()) // 2 + self.offset.y() + + # 绘制地图 + painter.drawPixmap(x_offset, y_offset, self.scaled_map) + + # 绘制目标点(如果有) + if self.target_point is not None: + # 转换为窗口坐标 + x = int(self.target_point.x() * self.scale_factor) + x_offset + y = int(self.target_point.y() * self.scale_factor) + y_offset + + # 绘制目标点(红色圆形) + painter.setPen(QPen(QColor(255, 0, 0), 2)) + painter.setBrush(QBrush(QColor(255, 0, 0, 150))) + painter.drawEllipse(x - self.TARGET_SIZE // 2, y - self.TARGET_SIZE // 2, + self.TARGET_SIZE, self.TARGET_SIZE) + + # 绘制标签 + painter.setPen(QPen(QColor(255, 0, 0), 1)) + painter.drawText(x + 10, y - 10, "目标点") + + # 绘制路径(如果有) + if self.path: + # 设置路径画笔(绿色实线) + painter.setPen(QPen(QColor(0, 255, 0), 2, Qt.SolidLine)) + + # 直接绘制路径线段 + for i in range(len(self.path) - 1): + # 转换为窗口坐标 + x1 = int(self.path[i][0] * self.scale_factor) + x_offset + y1 = int(self.path[i][1] * self.scale_factor) + y_offset + x2 = int(self.path[i+1][0] * self.scale_factor) + x_offset + y2 = int(self.path[i+1][1] * self.scale_factor) + y_offset + + # 绘制线段 + painter.drawLine(x1, y1, x2, y2) + + # 绘制小车位置(如果有) + if self.car_position is not None: + # 如果正在导航,使用路径点作为小车位置 + if self.is_navigating and self.path and self.animation_path_index < len(self.path): + car_pos = self.path[self.animation_path_index] + + # 转换为窗口坐标 + x = int(car_pos[0] * self.scale_factor) + x_offset + y = int(car_pos[1] * self.scale_factor) + y_offset + else: + # 使用设置的小车位置 + x = int(self.car_position.x() * self.scale_factor) + x_offset + y = int(self.car_position.y() * self.scale_factor) + y_offset + + # 绘制小车(蓝色圆形) + painter.setPen(QPen(QColor(0, 0, 255), 2)) + painter.setBrush(QBrush(QColor(0, 0, 255, 150))) + painter.drawEllipse(x - self.CAR_SIZE // 2, y - self.CAR_SIZE // 2, + self.CAR_SIZE, self.CAR_SIZE) + + # 绘制小车方向(如果在导航中) + if self.is_navigating and self.animation_path_index < len(self.path) - 1: + # 获取下一个点 + next_pos = self.path[self.animation_path_index + 1] + + # 转换为窗口坐标 + next_x = int(next_pos[0] * self.scale_factor) + x_offset + next_y = int(next_pos[1] * self.scale_factor) + y_offset + + # 计算方向 + angle = math.atan2(next_y - y, next_x - x) + + # 绘制方向指示器 + painter.save() + painter.translate(x, y) + painter.rotate(angle * 180 / math.pi) + + # 绘制三角形指示方向 + path = QPainterPath() + path.moveTo(self.CAR_SIZE // 2, 0) + path.lineTo(self.CAR_SIZE // 4, -self.CAR_SIZE // 4) + path.lineTo(self.CAR_SIZE // 4, self.CAR_SIZE // 4) + path.closeSubpath() + + painter.fillPath(path, QBrush(QColor(255, 255, 0))) + painter.restore() + + # 绘制标签 + painter.setPen(QPen(QColor(0, 0, 255), 1)) + painter.drawText(x + 10, y - 10, "小车") + + # 结束绘制 + painter.end() + + def wheelEvent(self, event): + """鼠标滚轮事件处理""" + if self.has_map: + # 获取滚轮方向 + delta = event.angleDelta().y() + + # 根据滚轮方向缩放 + if delta > 0: + self.zoomIn() # 放大 + else: + self.zoomOut() # 缩小 + + # 阻止事件传播 + event.accept() + + def mousePressEvent(self, event): + """鼠标按下事件""" + if event.button() == Qt.LeftButton: + # 获取鼠标位置 + pos = event.pos() + + # 将窗口坐标转换为图像坐标 + img_pos = self._mapToImagePos(pos) + + # 根据当前模式处理 + if self.current_mode == self.MODE_TARGET: + # 设置目标点 + if self.has_map and self._isPointInImage(img_pos): + self.setTargetPoint(img_pos) + self.current_mode = self.MODE_VIEW # 设置完后恢复查看模式 + self.update() + elif self.current_mode == self.MODE_CAR: + # 设置小车位置 + if self.has_map and self._isPointInImage(img_pos): + self.setCarPosition(img_pos) + self.current_mode = self.MODE_VIEW # 设置完后恢复查看模式 + self.update() + elif event.modifiers() & Qt.ControlModifier: + # 按住Ctrl键拖动地图 + self.panning = True + self.pan_start_pos = event.pos() + + event.accept() + + def mouseMoveEvent(self, event): + """鼠标移动事件处理""" + if self.panning: + # 平移地图 + delta = event.pos() - self.pan_start_pos + self.offset += delta + self.pan_start_pos = event.pos() + self.update() + + def mouseReleaseEvent(self, event): + """鼠标释放事件处理""" + if event.button() == Qt.LeftButton: + if self.panning: + self.panning = False + + def _mapToImagePos(self, pos): + """将窗口坐标映射到图像坐标""" + # 计算图像左上角在窗口中的位置 + x_offset = (self.width() - self.scaled_map.width()) // 2 + self.offset.x() + y_offset = (self.height() - self.scaled_map.height()) // 2 + self.offset.y() + + # 计算相对于图像左上角的坐标 + x = (pos.x() - x_offset) / self.scale_factor + y = (pos.y() - y_offset) / self.scale_factor + + return QPoint(int(x), int(y)) + + def _isPointInImage(self, pos): + """检查点是否在图像范围内""" + return (0 <= pos.x() < self.map_image.width() and + 0 <= pos.y() < self.map_image.height()) + + def _mapToImageCoord(self, point): + """将QPoint转换为图像坐标(x, y)元组""" + return (int(point.x()), int(point.y())) + + def _imageToMapCoord(self, point): + """将图像坐标(x, y)元组转换为QPoint""" + return QPoint(point[0], point[1]) + + +class NavigationToolbar(QWidget): + """ + 导航工具栏类 + + 提供导航操作按钮和控制功能 + + 主要属性: + navigation_area: 关联的导航区域 + + 主要方法: + setupUI: 设置用户界面 + connectSignals: 连接信号和槽 + """ + + def __init__(self, navigation_area, parent=None): + """ + 初始化导航工具栏 + + 参数: + navigation_area: 关联的导航区域 + parent: 父窗口 + """ + super().__init__(parent) + self.navigation_area = navigation_area + self.setupUI() + self.connectSignals() + + def setupUI(self): + """设置用户界面""" + # 主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(5, 5, 5, 5) + main_layout.setSpacing(5) + + # 地图操作布局 + map_layout = QHBoxLayout() + + # 导入地图按钮 + self.import_map_btn = QPushButton("📷 导入地图") + self.import_map_btn.setFixedSize(100, 30) + self.import_map_btn.setToolTip("导入地图文件") + + # 缩放按钮 + self.zoom_in_btn = QPushButton("🔍+") + self.zoom_in_btn.setFixedSize(40, 30) + self.zoom_in_btn.setToolTip("放大地图") + + self.zoom_out_btn = QPushButton("🔍-") + self.zoom_out_btn.setFixedSize(40, 30) + self.zoom_out_btn.setToolTip("缩小地图") + + self.fit_view_btn = QPushButton("🔍⟲") + self.fit_view_btn.setFixedSize(40, 30) + self.fit_view_btn.setToolTip("适应窗口大小") + + # 将按钮添加到地图操作布局 + map_layout.addWidget(self.import_map_btn) + map_layout.addWidget(self.zoom_in_btn) + map_layout.addWidget(self.zoom_out_btn) + map_layout.addWidget(self.fit_view_btn) + map_layout.addStretch() + + # 导航操作布局 + nav_layout = QHBoxLayout() + + # 设置目标点按钮 + self.set_target_btn = QPushButton("🎯 设置目标点") + self.set_target_btn.setFixedSize(120, 30) + self.set_target_btn.setToolTip("点击后在地图上设置目标位置") + + # 设置小车位置按钮 + self.set_car_btn = QPushButton("🚗 定位小车位置") + self.set_car_btn.setFixedSize(120, 30) + self.set_car_btn.setToolTip("点击后在地图上设置小车当前位置") + + # 规划路径按钮 + self.plan_path_btn = QPushButton("📝 绘制行进路线") + self.plan_path_btn.setFixedSize(120, 30) + self.plan_path_btn.setToolTip("规划从小车到目标点的路径") + + # 开始导航按钮 + self.start_nav_btn = QPushButton("▶️ 开始导航") + self.start_nav_btn.setFixedSize(120, 30) + self.start_nav_btn.setToolTip("开始导航动画") + + # 停止导航按钮 + self.stop_nav_btn = QPushButton("⏹️ 停止导航") + self.stop_nav_btn.setFixedSize(120, 30) + self.stop_nav_btn.setToolTip("停止导航动画") + + # 将按钮添加到导航操作布局 + nav_layout.addWidget(self.set_target_btn) + nav_layout.addWidget(self.set_car_btn) + nav_layout.addWidget(self.plan_path_btn) + nav_layout.addWidget(self.start_nav_btn) + nav_layout.addWidget(self.stop_nav_btn) + nav_layout.addStretch() + + # 将所有布局添加到主布局 + main_layout.addLayout(map_layout) + main_layout.addLayout(nav_layout) + + # 设置样式 + self.setStyleSheet(""" + QPushButton { + background: rgba(60, 60, 60, 0.8); + color: white; + font-size: 12px; + border: 1px solid rgba(100, 100, 100, 0.8); + border-radius: 5px; + } + QPushButton:hover { + background: rgba(80, 80, 80, 1.0); + border: 1px solid rgba(150, 150, 150, 1.0); + } + QPushButton:pressed { + background: rgba(40, 40, 40, 1.0); + } + QLabel { + color: white; + font-size: 12px; + } + """) + + def connectSignals(self): + """连接信号和槽""" + # 地图操作按钮信号 + self.import_map_btn.clicked.connect(lambda: self.onImportMapClicked()) + self.zoom_in_btn.clicked.connect(self.navigation_area.zoomIn) + self.zoom_out_btn.clicked.connect(self.navigation_area.zoomOut) + self.fit_view_btn.clicked.connect(self.navigation_area.fitToView) + + # 导航操作按钮信号 + self.set_target_btn.clicked.connect(lambda: self.navigation_area.setMode(self.navigation_area.MODE_TARGET)) + self.set_car_btn.clicked.connect(lambda: self.navigation_area.setMode(self.navigation_area.MODE_CAR)) + self.plan_path_btn.clicked.connect(self.navigation_area.planPath) + self.start_nav_btn.clicked.connect(self.navigation_area.startNavigation) + self.stop_nav_btn.clicked.connect(self.navigation_area.stopNavigation) + + def onImportMapClicked(self): + """导入地图按钮点击处理""" + print("导入地图按钮被点击") # 调试输出 + self.navigation_area.loadMap() + + +class NavigationWidget(QWidget): + """ + 导航组件类 + + 集成导航区域和工具栏,提供完整的导航功能 + + 主要属性: + navigation_area: 导航区域 + toolbar: 工具栏 + + 主要方法: + setupUI: 设置用户界面 + loadMap: 加载地图 + """ + + # 导航完成信号 + navigationFinished = pyqtSignal() + + def __init__(self, parent=None): + """ + 初始化导航组件 + + 参数: + parent: 父窗口 + """ + super().__init__(parent) + self.setupUI() + + def setupUI(self): + """设置用户界面""" + # 主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # 页面标题 + title_label = QLabel("目标导航") + title_label.setAlignment(Qt.AlignCenter) + title_label.setStyleSheet(""" + QLabel { + color: #FFD700; + font-size: 18px; + font-weight: bold; + background: rgba(0, 0, 0, 0.5); + border-radius: 10px; + padding: 10px; + border: 2px solid rgba(255, 215, 0, 0.6); + } + """) + main_layout.addWidget(title_label) + + # 创建导航区域 + self.navigation_area = NavigationArea(self) + + # 创建工具栏 + self.toolbar = NavigationToolbar(self.navigation_area, self) + + # 将导航区域和工具栏添加到布局 + main_layout.addWidget(self.toolbar) + main_layout.addWidget(self.navigation_area, 1) # 1表示拉伸因子,使导航区域占据更多空间 + + # 连接导航完成信号 + self.navigation_area.navigationFinished.connect(self._onNavigationFinished) + + # 设置初始状态 + self.setStyleSheet(""" + QWidget { + background: transparent; + } + """) + + def _onNavigationFinished(self): + """导航完成处理""" + # 转发信号 + self.navigationFinished.emit() + + def loadMap(self, file_path=None): + """加载地图""" + print("NavigationWidget.loadMap() 被调用") # 调试输出 + return self.navigation_area.loadMap(file_path) \ No newline at end of file diff --git a/src/src/YOLOv8/UIProgram/SplashScreen.py b/src/src/YOLOv8/UIProgram/SplashScreen.py new file mode 100644 index 0000000..0cf507c --- /dev/null +++ b/src/src/YOLOv8/UIProgram/SplashScreen.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +""" +室内多传感器寻敌系统启动画面 +=============================== + +启动画面模块,提供优雅的应用程序启动体验 + +功能特性: +- 显示系统名称和版本信息 +- 模拟加载进度动画 +- 动态状态提示文字 +- 自动进度更新 +- 支持半透明背景效果 +- 智能图片路径查找 + +窗口尺寸: 250x175 (紧凑型设计) +显示时长: 约5秒 (100个进度步骤,每步50ms) + +作者: 郭晋鹏团队 +版本: 2.0.0 +""" + +from PyQt5.QtWidgets import QSplashScreen, QLabel, QProgressBar, QVBoxLayout, QWidget +from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QPropertyAnimation, QEasingCurve +from PyQt5.QtGui import QPixmap, QPainter, QLinearGradient, QBrush, QPen, QFont, QColor +import os + +class SplashScreen(QSplashScreen): + """ + 启动画面窗口类 + + 继承自QSplashScreen,提供应用程序启动时的欢迎界面。 + 包含进度条、状态文字、系统信息等元素。 + + 信号: + splashFinished: 启动完成信号,用于通知主程序继续执行 + + 属性: + current_progress (int): 当前加载进度 (0-100) + progress_timer (QTimer): 进度更新定时器 + """ + + # 启动完成信号 + splashFinished = pyqtSignal() + + def __init__(self, parent=None): + """ + 初始化启动画面 + + 参数: + parent: 父窗口,默认为None + """ + super().__init__(parent) + self.current_progress = 0 # 初始化进度为0 + self.setupUI() # 设置用户界面 + self.startAnimation() # 启动加载动画 + + def setupUI(self): + """ + 设置用户界面 + + 创建启动画面的所有UI元素,包括: + - 系统标题和版本信息 + - 状态提示标签 + - 进度条 + - 背景图片和视觉效果 + """ + # 设置窗口属性 - 缩小到一半 + self.setFixedSize(250, 175) + self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) + self.setAttribute(Qt.WA_TranslucentBackground) + + # 创建主widget和布局 + main_widget = QWidget() + layout = QVBoxLayout(main_widget) + layout.setContentsMargins(10, 10, 10, 10) + layout.setSpacing(5) + + # 标题标签 + self.title_label = QLabel("室内多传感器寻敌系统") + self.title_label.setAlignment(Qt.AlignCenter) + self.title_label.setStyleSheet(""" + QLabel { + color: white; + font-size: 14px; + font-weight: bold; + background: transparent; + } + """) + layout.addWidget(self.title_label) + + # 版本标签 + self.version_label = QLabel("版本 2.0.0") + self.version_label.setAlignment(Qt.AlignCenter) + self.version_label.setStyleSheet(""" + QLabel { + color: rgba(255, 255, 255, 180); + font-size: 10px; + background: transparent; + } + """) + layout.addWidget(self.version_label) + + # 添加弹性空间 + layout.addStretch() + + # 状态标签 + self.status_label = QLabel("正在初始化...") + self.status_label.setAlignment(Qt.AlignCenter) + self.status_label.setStyleSheet(""" + QLabel { + color: rgba(255, 255, 255, 200); + font-size: 9px; + background: transparent; + } + """) + layout.addWidget(self.status_label) + + # 进度条 + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + self.progress_bar.setFixedHeight(4) + self.progress_bar.setStyleSheet(""" + QProgressBar { + background-color: rgba(255, 255, 255, 30); + border: 1px solid rgba(255, 255, 255, 50); + border-radius: 2px; + text-align: center; + } + + QProgressBar::chunk { + background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0 #4CAF50, stop: 1 #2196F3); + border-radius: 1px; + } + """) + layout.addWidget(self.progress_bar) + + # 创建背景图片 - 调整为新尺寸 + background_pixmap = QPixmap(250, 175) + background_pixmap.fill(Qt.transparent) + + painter = QPainter(background_pixmap) + painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.SmoothPixmapTransform) + + # 加载loading.png作为背景并缩放填充 + # 智能路径查找 + def find_image_path(image_name): + possible_paths = [ + image_name, + os.path.join("UIProgram", image_name), + os.path.join("..", "UIProgram", image_name) if os.path.basename(os.getcwd()) == "UIProgram" else os.path.join("UIProgram", image_name) + ] + for path in possible_paths: + if os.path.exists(path): + return path + return image_name + + loading_bg_path = find_image_path("ui_imgs/icons/loading.png") + if os.path.exists(loading_bg_path): + loading_image = QPixmap(loading_bg_path) + if not loading_image.isNull(): + # 按比例缩放并填充整个区域 + scaled_image = loading_image.scaled(250, 175, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation) + painter.drawPixmap(0, 0, 250, 175, scaled_image) + + # 绘制半透明遮罩层,使文字更清晰 + painter.setBrush(QBrush(QColor(0, 0, 0, 80))) + painter.setPen(Qt.NoPen) + painter.drawRoundedRect(0, 0, 250, 175, 8, 8) + + # 绘制边框 + painter.setPen(QPen(QColor(116, 185, 255), 1)) + painter.setBrush(Qt.NoBrush) + painter.drawRoundedRect(1, 1, 248, 173, 8, 8) + + painter.end() + self.setPixmap(background_pixmap) + + # 将主widget设置为layout + main_widget.setParent(self) + main_widget.setGeometry(0, 0, 250, 175) + main_widget.setStyleSheet("background: transparent;") + + def startAnimation(self): + """ + 启动动画效果 + + 创建并启动进度更新定时器,每50毫秒更新一次进度。 + 整个启动过程约持续5秒钟。 + """ + # 进度更新定时器 + self.progress_timer = QTimer() + self.progress_timer.timeout.connect(self.updateProgress) + self.progress_timer.start(50) # 50ms更新一次 + + def updateProgress(self): + """ + 更新加载进度 + + 定时器回调函数,负责: + - 更新进度条数值 + - 根据进度显示不同的状态文字 + - 检查是否完成加载并触发结束信号 + """ + self.current_progress += 1 + + # 更新进度条 + self.progress_bar.setValue(self.current_progress) + + # 更新状态文本 + if self.current_progress < 20: + self.status_label.setText("正在初始化系统...") + elif self.current_progress < 40: + self.status_label.setText("正在加载检测模型...") + elif self.current_progress < 60: + self.status_label.setText("正在初始化摄像头...") + elif self.current_progress < 80: + self.status_label.setText("正在配置用户界面...") + elif self.current_progress < 95: + self.status_label.setText("正在准备启动...") + else: + self.status_label.setText("启动完成!") + + # 完成启动 + if self.current_progress >= 100: + self.progress_timer.stop() + QTimer.singleShot(500, self.finishSplash) # 延迟500ms显示完成状态 + + def finishSplash(self): + """ + 完成启动流程 + + 发出启动完成信号并关闭启动画面窗口。 + """ + self.splashFinished.emit() + self.close() + + def paintEvent(self, event): + """ + 重写绘制事件 + + 参数: + event: 绘制事件对象 + + 注意: + 背景图片已经在setPixmap中设置,这里直接调用父类方法即可。 + """ + super().paintEvent(event) \ No newline at end of file diff --git a/src/src/YOLOv8/UIProgram/__init__.py b/src/src/YOLOv8/UIProgram/__init__.py new file mode 100644 index 0000000..48ff25f --- /dev/null +++ b/src/src/YOLOv8/UIProgram/__init__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +UIProgram包 +=========== + +YOLOv8人脸检测系统的用户界面模块 + +包含以下模块: +- SplashScreen: 启动画面,提供应用程序启动时的欢迎界面 +- InitialWindow: 欢迎窗口,用户进入系统前的主界面 +- ModernMainWindow: 主程序窗口,包含完整的检测功能 + +模块结构: +- ui_imgs/: 界面图片资源文件夹 + - icons/: 图标和背景图片 + - loading.png: 启动画面背景 + - BA.png: 欢迎界面背景 + - R6.png: 主程序背景 + - face.png: 应用程序图标 + +功能特性: +- 现代化UI设计风格 +- 半透明背景效果 +- 支持图片、视频、摄像头、批量检测 +- 批量检测支持图片浏览功能 +- 全屏显示支持 +- 智能路径查找,支持多种启动方式 + +版本: 1.0.0 +作者: YOLOv8人脸检测系统开发团队 +""" + +__version__ = "1.0.0" +__author__ = "YOLOv8人脸检测系统开发团队" + +# 导出主要类 +from .SplashScreen import SplashScreen +from .InitialWindow import InitialWindow +from .ModernMainWindow import ModernMainWindow + +__all__ = [ + 'SplashScreen', + 'InitialWindow', + 'ModernMainWindow' +] diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/bg13.png b/src/src/YOLOv8/UIProgram/ui_imgs/bg13.png new file mode 100644 index 0000000..f9c9688 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/bg13.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/bg14.png b/src/src/YOLOv8/UIProgram/ui_imgs/bg14.png new file mode 100644 index 0000000..f9c9688 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/bg14.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/BA.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/BA.png new file mode 100644 index 0000000..b849c1f Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/BA.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/R6.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/R6.png new file mode 100644 index 0000000..d0391f5 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/R6.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/camera.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/camera.png new file mode 100644 index 0000000..4782766 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/camera.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/face.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/face.png new file mode 100644 index 0000000..dbbd643 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/face.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/findpeople.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/findpeople.png new file mode 100644 index 0000000..d73c149 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/findpeople.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/folder.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/folder.png new file mode 100644 index 0000000..35535f9 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/folder.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/img.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/img.png new file mode 100644 index 0000000..c74fd58 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/img.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/loading.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/loading.png new file mode 100644 index 0000000..a7fb91e Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/loading.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/video.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/video.png new file mode 100644 index 0000000..18ccae8 Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/video.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/保存.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/保存.png new file mode 100644 index 0000000..179175c Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/保存.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/目标检测.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/目标检测.png new file mode 100644 index 0000000..935feeb Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/目标检测.png differ diff --git a/src/src/YOLOv8/UIProgram/ui_imgs/icons/退出.png b/src/src/YOLOv8/UIProgram/ui_imgs/icons/退出.png new file mode 100644 index 0000000..19cb50b Binary files /dev/null and b/src/src/YOLOv8/UIProgram/ui_imgs/icons/退出.png differ diff --git a/src/src/YOLOv8/VideoTest.py b/src/src/YOLOv8/VideoTest.py new file mode 100644 index 0000000..955e45a --- /dev/null +++ b/src/src/YOLOv8/VideoTest.py @@ -0,0 +1,37 @@ +#coding:utf-8 +import cv2 +from ultralytics import YOLO + +# 所需加载的模型目录 +path = 'models/best.pt' +# 需要检测的图片地址 +video_path = "TestFiles/1.mp4" + +# Load the YOLOv8 model +model = YOLO(path) +cap = cv2.VideoCapture(video_path) +# Loop through the video frames +while cap.isOpened(): + # Read a frame from the video + success, frame = cap.read() + + if success: + # Run YOLOv8 inference on the frame + results = model(frame) + + # Visualize the results on the frame + annotated_frame = results[0].plot() + + # Display the annotated frame + cv2.imshow("YOLOv8 Inference", annotated_frame) + + # Break the loop if 'q' is pressed + if cv2.waitKey(1) & 0xFF == ord("q"): + break + else: + # Break the loop if the end of the video is reached + break + +# Release the video capture object and close the display window +cap.release() +cv2.destroyAllWindows() \ No newline at end of file diff --git a/src/src/YOLOv8/datasets/count_size.py b/src/src/YOLOv8/datasets/count_size.py new file mode 100644 index 0000000..0e853fe --- /dev/null +++ b/src/src/YOLOv8/datasets/count_size.py @@ -0,0 +1,129 @@ +# 1、统计数据集中小、中、大 GT的个数 +# 2、统计某个类别小、中、大 GT的个数 +# 3、统计数据集中ss、sm、sl GT的个数 +import os +from pathlib import Path +import matplotlib.pyplot as plt + +# 设置中文字体为微软雅黑 +plt.rcParams['font.sans-serif'] = 'SimHei' + + +def getGtAreaAndRatio(label_dir): + """ + 得到不同尺度的gt框个数 + :params label_dir: label文件地址 + :return data_dict: {dict: 3} 3 x {'类别':{’area':[...]}, {'ratio':[...]}} + """ + data_dict = {} + assert Path(label_dir).is_dir(), "label_dir is not exist" + + txts = os.listdir(label_dir) # 得到label_dir目录下的所有txt GT文件 + + for txt in txts: # 遍历每一个txt文件 + with open(os.path.join(label_dir, txt), 'r') as f: # 打开当前txt文件 并读取所有行的数据 + lines = f.readlines() + + for line in lines: # 遍历当前txt文件中每一行的数据 + temp = line.split() # str to list{5} + coor_list = list(map(lambda x: x, temp[1:])) # [x, y, w, h] + area = float(coor_list[2]) * float(coor_list[3]) # 计算出当前txt文件中每一个gt的面积 + # center = (int(coor_list[0] + 0.5*coor_list[2]), + # int(coor_list[1] + 0.5*coor_list[3])) + if float(coor_list[3])!=0: + ratio = round(float(coor_list[2]) / float(coor_list[3]), 2) # 计算出当前txt文件中每一个gt的 w/h + + if temp[0] not in data_dict: + data_dict[temp[0]] = {} + data_dict[temp[0]]['area'] = [] + data_dict[temp[0]]['ratio'] = [] + + data_dict[temp[0]]['area'].append(area) + data_dict[temp[0]]['ratio'].append(ratio) + + return data_dict + + +def getSMLGtNumByClass(data_dict, class_num): + """ + 计算某个类别的小物体、中物体、大物体的个数 + params data_dict: {dict: 3} 3 x {'类别':{’area':[...]}, {'ratio':[...]}} + params class_num: 类别 0, 1, 2 + return s: 该类别小物体的个数 0 < area <= 0.5% + m: 该类别中物体的个数 0.5% < area <= 1% + l: 该类别大物体的个数 area > 1% + """ + s, m, l = 0, 0, 0 + # 图片的尺寸大小 注意修改!!! + h = 640 + w = 640 + for item in data_dict['{}'.format(class_num)]['area']: + if item * h * w <= h * w * 0.035: + s += 1 + elif item * h * w <= h * w * 0.165: + m += 1 + else: + l += 1 + return s, m, l + +def getAllSMLGtNum(data_dict, isEachClass=False): + """ + 数据集所有类别小、中、大GT分布情况 + isEachClass 控制是否按每个类别输出结构 + """ + S, M, L = 0, 0, 0 + # 需要手动初始化下,有多少个类别就需要写多个 + classDict = {'0': {'S': 0, 'M': 0, 'L': 0}} + + print(classDict['0']['S']) + # range(class_num)类别数 注意修改!!! + if isEachClass == False: + for i in range(1): + s, m, l = getSMLGtNumByClass(data_dict, i) + S += s + M += m + L += l + return [S, M, L] + else: + for i in range(3): + S = 0 + M = 0 + L = 0 + s, m, l = getSMLGtNumByClass(data_dict, i) + S += s + M += m + L += l + classDict[str(i)]['S'] = S + classDict[str(i)]['M'] = M + classDict[str(i)]['L'] = L + return classDict + + +# 画图函数 +def plotAllSML(SML): + x = ['S:[0, 120x120]', 'M:[120x120, 260x260]', 'L:[260*260, 640x640]'] + fig = plt.figure(figsize=(10, 8)) # 画布大小和像素密度 + plt.bar(x, SML, width=0.5, align="center", color=['skyblue', 'orange', 'green']) + for a, b, i in zip(x, SML, range(len(x))): # zip 函数 + plt.text(a, b + 0.01, "%d" % int(SML[i]), ha='center', fontsize=15, color="r") # plt.text 函数 + plt.xticks(fontsize=15) + plt.yticks(fontsize=15) + plt.xlabel('gt大小', fontsize=16) + plt.ylabel('数量', fontsize=16) + plt.title('人脸检测训练集小、中、大分布情况(640x640)', fontsize=16) + plt.show() + # 保存到本地 + # plt.savefig("") + + +if __name__ == '__main__': + labeldir = r'C:\Users\pc\Desktop\YOLOv8face\datasets\faceData\train\labels' + data_dict = getGtAreaAndRatio(labeldir) + # 1、数据集所有类别小、中、大GT分布情况 + # 控制是否按每个类别输出结构 + isEachClass = False + SML = getAllSMLGtNum(data_dict, isEachClass) + # print(SML) + if not isEachClass: + plotAllSML(SML) + diff --git a/src/src/YOLOv8/detect_tools.py b/src/src/YOLOv8/detect_tools.py new file mode 100644 index 0000000..23dfefb --- /dev/null +++ b/src/src/YOLOv8/detect_tools.py @@ -0,0 +1,222 @@ +# encoding:utf-8 +import cv2 +from PyQt5.QtGui import QPixmap, QImage +import numpy as np +from PIL import Image,ImageDraw,ImageFont +import csv +import os + +# fontC = ImageFont.truetype("Font/platech.ttf", 20, 0) + +# 绘图展示 +def cv_show(name,img): + cv2.imshow(name, img) + cv2.waitKey(0) + cv2.destroyAllWindows() + + +def drawRectBox(image, rect, addText, fontC, color): + """ + 绘制矩形框与结果 + :param image: 原始图像 + :param rect: 矩形框坐标, int类型 + :param addText: 类别名称 + :param fontC: 字体 + :return: + """ + # 绘制位置方框 + cv2.rectangle(image, (rect[0], rect[1]), + (rect[2], rect[3]), + color, 2) + + # 绘制字体背景框 + cv2.rectangle(image, (rect[0] - 1, rect[1] - 25), (rect[0] + 60, rect[1]), color, -1, cv2.LINE_AA) + # 图片 添加的文字 位置 字体 字体大小 字体颜色 字体粗细 + # cv2.putText(image, addText, (int(rect[0])+2, int(rect[1])-3), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2) + + img = Image.fromarray(image) + draw = ImageDraw.Draw(img) + draw.text((rect[0]+2, rect[1]-27), addText, (255, 255, 255), font=fontC) + imagex = np.array(img) + return imagex + + +def img_cvread(path): + # 读取含中文名的图片文件 + # img = cv2.imread(path) + img = cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR) + return img + + +def draw_boxes(img, boxes): + for each in boxes: + x1 = each[0] + y1 = each[1] + x2 = each[2] + y2 = each[3] + cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) + return img + + + +def cvimg_to_qpiximg(cvimg): + height, width, depth = cvimg.shape + cvimg = cv2.cvtColor(cvimg, cv2.COLOR_BGR2RGB) + qimg = QImage(cvimg.data, width, height, width * depth, QImage.Format_RGB888) + qpix_img = QPixmap(qimg) + return qpix_img + + +def save_video(): + # VideoCapture方法是cv2库提供的读取视频方法 + cap = cv2.VideoCapture('C:\\Users\\xxx\\Desktop\\sweet.mp4') + # 设置需要保存视频的格式“xvid” + # 该参数是MPEG-4编码类型,文件名后缀为.avi + fourcc = cv2.VideoWriter_fourcc(*'XVID') + # 设置视频帧频 + fps = cap.get(cv2.CAP_PROP_FPS) + # 设置视频大小 + size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) + # VideoWriter方法是cv2库提供的保存视频方法 + # 按照设置的格式来out输出 + out = cv2.VideoWriter('C:\\Users\\xxx\\Desktop\\out.avi', fourcc, fps, size) + + # 确定视频打开并循环读取 + while (cap.isOpened()): + # 逐帧读取,ret返回布尔值 + # 参数ret为True 或者False,代表有没有读取到图片 + # frame表示截取到一帧的图片 + ret, frame = cap.read() + if ret == True: + # 垂直翻转矩阵 + frame = cv2.flip(frame, 0) + + out.write(frame) + + cv2.imshow('frame', frame) + if cv2.waitKey(1) & 0xFF == ord('q'): + break + else: + break + + # 释放资源 + cap.release() + out.release() + # 关闭窗口 + cv2.destroyAllWindows() + + +# 封装函数:图片上显示中文 +def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=50): + if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型 + img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) + # 创建一个可以在给定图像上绘图的对象 + draw = ImageDraw.Draw(img) + # 字体的格式 + fontStyle = ImageFont.truetype( + "simsun.ttc", textSize, encoding="utf-8") + # 绘制文本 + draw.text(position, text, textColor, font=fontStyle) + # 转换回OpenCV格式 + return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR) + + +def insert_rows(path, lines ,header): + """ + 将n行数据写入csv文件 + :param path: + :param lines: + :return: + """ + no_header = False + if not os.path.exists(path): + no_header = True + start_num = 1 + else: + start_num = len(open(path).readlines()) + + csv_head = header + with open(path, 'a', newline='') as f: + csv_write = csv.writer(f) + if no_header: + csv_write.writerow(csv_head) # 写入表头 + + for each_list in lines: + # 添加序号 + each_list = [start_num] + each_list + csv_write.writerow(each_list) + # 序号 + 1 + start_num += 1 + +class Colors: + # 用于绘制不同颜色 + def __init__(self): + """Initialize colors as hex = matplotlib.colors.TABLEAU_COLORS.values().""" + hexs = ('FF3838', 'FF9D97', 'FF701F', 'FFB21D', 'CFD231', '48F90A', '92CC17', '3DDB86', '1A9334', '00D4BB', + '2C99A8', '00C2FF', '344593', '6473FF', '0018EC', '8438FF', '520085', 'CB38FF', 'FF95C8', 'FF37C7') + self.palette = [self.hex2rgb(f'#{c}') for c in hexs] + self.n = len(self.palette) + self.pose_palette = np.array([[255, 128, 0], [255, 153, 51], [255, 178, 102], [230, 230, 0], [255, 153, 255], + [153, 204, 255], [255, 102, 255], [255, 51, 255], [102, 178, 255], [51, 153, 255], + [255, 153, 153], [255, 102, 102], [255, 51, 51], [153, 255, 153], [102, 255, 102], + [51, 255, 51], [0, 255, 0], [0, 0, 255], [255, 0, 0], [255, 255, 255]], + dtype=np.uint8) + + def __call__(self, i, bgr=False): + """Converts hex color codes to rgb values.""" + c = self.palette[int(i) % self.n] + return (c[2], c[1], c[0]) if bgr else c + + @staticmethod + def hex2rgb(h): # rgb order (PIL) + return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4)) + + +def yolo_to_location(w,h,yolo_data): + # yolo文件转两点坐标,注意画图坐标要转换成int格式 + x_, y_, w_, h_ = yolo_data + x1 = int(w * x_ - 0.5 * w * w_) + x2 = int(w * x_ + 0.5 * w * w_) + y1 = int(h * y_ - 0.5 * h * h_) + y2 = int(h * y_ + 0.5 * h * h_) + # cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (255, 0, 0)) + return [x1,y1,x2,y2] + +def location_to_yolo(w, h, locations): + # x1,y1左上角坐标,x2,y2右上角坐标 + x1, y1, x2, y2 = locations + x_ = (x1 + x2) / 2 / w + x_ = float('%.5f' % x_) + y_ = (y1 + y2) / 2 / h + y_ = float('%.5f' % y_) + w_ = (x2 - x1) / w + w_ = float('%.5f' % w_) + h_ = (y2 - y1) / h + h_ = float('%.5f' % h_) + return [x_,y_,w_,h_] + +def draw_yolo_data(img_path, yolo_file_path): + # 读取yolo标注数据并显示 + img = cv2.imread(img_path) + h, w, _ = img.shape + print(img.shape) + # yolo标注数据文件名为786_rgb_0616.txt + with open(yolo_file_path, 'r') as f: + data = f.readlines() + for each in data: + temp = each.split() + # ['1', '0.43906', '0.52083', '0.34687', '0.15'] + # YOLO转换为两点坐标x1, x2, y1, y2 + x_, y_, w_, h_ = eval(temp[1]), eval(temp[2]), eval(temp[3]), eval(temp[4]) + x1, y1, x2, y2 = yolo_to_location(w,h,[x_, y_, w_, h_]) + # 画图验证框是否正确 + cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255)) + + cv2.imshow('windows', img) + cv2.waitKey(0) + +if __name__ == '__main__': + img_path = 'TestFiles/1.jpg' + yolo_file_path = 'save_data/yolo_labels/1.txt' + draw_yolo_data(img_path, yolo_file_path) + diff --git a/src/src/YOLOv8/imgTest.py b/src/src/YOLOv8/imgTest.py new file mode 100644 index 0000000..ffea71d --- /dev/null +++ b/src/src/YOLOv8/imgTest.py @@ -0,0 +1,21 @@ +#coding:utf-8 +from ultralytics import YOLO +import cv2 + +# 所需加载的模型目录 +path = 'models/best.pt' +# 需要检测的图片地址 +img_path = "TestFiles/test4.jpg" + +# 加载预训练模型 +# conf 0.25 object confidence threshold for detection +# iou 0.7 intersection over union (IoU) threshold for NMS +model = YOLO(path, task='detect') +# model = YOLO(path, task='detect',conf=0.5) + + +# 检测图片 +results = model(img_path) +res = results[0].plot() +cv2.imshow("YOLOv8 Detection", res) +cv2.waitKey(0) \ No newline at end of file diff --git a/src/src/YOLOv8/installPackages.py b/src/src/YOLOv8/installPackages.py new file mode 100644 index 0000000..d3430ab --- /dev/null +++ b/src/src/YOLOv8/installPackages.py @@ -0,0 +1,7 @@ +import os + +pkgs = ['ultralytics','PyQt5==5.15.2','pyqt5-tools==5.15.2.3.1'] + +for each in pkgs: + cmd_line = f"pip install {each} -i https://pypi.tuna.tsinghua.edu.cn/simple" + os.system(cmd_line) diff --git a/src/src/YOLOv8/main.py b/src/src/YOLOv8/main.py new file mode 100644 index 0000000..b004e8b --- /dev/null +++ b/src/src/YOLOv8/main.py @@ -0,0 +1,5 @@ +import torch +print(torch.cuda.is_available()) +print(torch.backends.cudnn.is_available()) +print(torch.cuda_version) +print(torch.backends.cudnn.version()) \ No newline at end of file diff --git a/src/src/YOLOv8/models/best.pt b/src/src/YOLOv8/models/best.pt new file mode 100644 index 0000000..44c52c2 Binary files /dev/null and b/src/src/YOLOv8/models/best.pt differ diff --git a/src/src/YOLOv8/requirements.txt b/src/src/YOLOv8/requirements.txt new file mode 100644 index 0000000..bedac81 --- /dev/null +++ b/src/src/YOLOv8/requirements.txt @@ -0,0 +1,31 @@ +certifi==2023.7.22 +charset-normalizer==3.3.0 +colorama==0.4.6 +contourpy==1.1.1 +cycler==0.12.1 +fonttools==4.43.1 +idna==3.4 +importlib-resources==6.1.0 +kiwisolver==1.4.5 +matplotlib==3.8.0 +numpy==1.26.1 +opencv-python==4.8.1.78 +packaging==23.2 +psutil==5.9.6 +py-cpuinfo==9.0.0 +pyparsing==3.1.1 +python-dateutil==2.8.2 +pyyaml==6.0.1 +requests==2.31.0 +scipy==1.11.3 +seaborn==0.13.0 +six==1.16.0 +thop==0.1.1-2209072238 +# torch==1.9.0 +tqdm==4.66.1 +typing-extensions==4.8.0 +ultralytics==8.0.199 +urllib3==2.0.6 +zipp==3.17.0 +PyQt5==5.15.2 +pyqt5-tools==5.15.2.3.1 \ No newline at end of file diff --git a/src/src/YOLOv8/runs/detect/Snipaste_2024-10-09_20-56-05.png b/src/src/YOLOv8/runs/detect/Snipaste_2024-10-09_20-56-05.png new file mode 100644 index 0000000..96854c8 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/Snipaste_2024-10-09_20-56-05.png differ diff --git a/src/src/YOLOv8/runs/detect/train/F1_curve.png b/src/src/YOLOv8/runs/detect/train/F1_curve.png new file mode 100644 index 0000000..ecf1777 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/F1_curve.png differ diff --git a/src/src/YOLOv8/runs/detect/train/PR_curve.png b/src/src/YOLOv8/runs/detect/train/PR_curve.png new file mode 100644 index 0000000..ec61b10 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/PR_curve.png differ diff --git a/src/src/YOLOv8/runs/detect/train/P_curve.png b/src/src/YOLOv8/runs/detect/train/P_curve.png new file mode 100644 index 0000000..5b1cf0d Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/P_curve.png differ diff --git a/src/src/YOLOv8/runs/detect/train/R_curve.png b/src/src/YOLOv8/runs/detect/train/R_curve.png new file mode 100644 index 0000000..8566af0 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/R_curve.png differ diff --git a/src/src/YOLOv8/runs/detect/train/args.yaml b/src/src/YOLOv8/runs/detect/train/args.yaml new file mode 100644 index 0000000..d9aa2ae --- /dev/null +++ b/src/src/YOLOv8/runs/detect/train/args.yaml @@ -0,0 +1,98 @@ +task: detect +mode: train +model: yolov8n.pt +data: datasets/faceData/data.yaml +epochs: 300 +patience: 50 +batch: 4 +imgsz: 640 +save: true +save_period: -1 +cache: true +device: 0 +workers: 12 +project: null +name: train +exist_ok: false +pretrained: true +optimizer: auto +verbose: true +seed: 0 +deterministic: true +single_cls: false +rect: false +cos_lr: false +close_mosaic: 10 +resume: false +amp: true +fraction: 1.0 +profile: false +freeze: null +overlap_mask: true +mask_ratio: 4 +dropout: 0.0 +val: true +split: val +save_json: false +save_hybrid: false +conf: null +iou: 0.7 +max_det: 300 +half: false +dnn: false +plots: true +source: null +show: false +save_txt: false +save_conf: false +save_crop: false +show_labels: true +show_conf: true +vid_stride: 1 +stream_buffer: false +line_width: null +visualize: false +augment: false +agnostic_nms: false +classes: null +retina_masks: false +boxes: true +format: torchscript +keras: false +optimize: false +int8: false +dynamic: false +simplify: false +opset: null +workspace: 4 +nms: false +lr0: 0.01 +lrf: 0.01 +momentum: 0.937 +weight_decay: 0.0005 +warmup_epochs: 3.0 +warmup_momentum: 0.8 +warmup_bias_lr: 0.1 +box: 7.5 +cls: 0.5 +dfl: 1.5 +pose: 12.0 +kobj: 1.0 +label_smoothing: 0.0 +nbs: 64 +hsv_h: 0.015 +hsv_s: 0.7 +hsv_v: 0.4 +degrees: 0.0 +translate: 0.1 +scale: 0.5 +shear: 0.0 +perspective: 0.0 +flipud: 0.0 +fliplr: 0.5 +mosaic: 1.0 +mixup: 0.0 +copy_paste: 0.0 +cfg: null +tracker: botsort.yaml +save_dir: runs\detect\train diff --git a/src/src/YOLOv8/runs/detect/train/confusion_matrix.png b/src/src/YOLOv8/runs/detect/train/confusion_matrix.png new file mode 100644 index 0000000..f1fefc3 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/confusion_matrix.png differ diff --git a/src/src/YOLOv8/runs/detect/train/confusion_matrix_normalized.png b/src/src/YOLOv8/runs/detect/train/confusion_matrix_normalized.png new file mode 100644 index 0000000..34df33a Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/confusion_matrix_normalized.png differ diff --git a/src/src/YOLOv8/runs/detect/train/events.out.tfevents.1728388845.yeman.14532.0 b/src/src/YOLOv8/runs/detect/train/events.out.tfevents.1728388845.yeman.14532.0 new file mode 100644 index 0000000..e4b823c Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/events.out.tfevents.1728388845.yeman.14532.0 differ diff --git a/src/src/YOLOv8/runs/detect/train/labels.jpg b/src/src/YOLOv8/runs/detect/train/labels.jpg new file mode 100644 index 0000000..d230875 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/labels.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/labels_correlogram.jpg b/src/src/YOLOv8/runs/detect/train/labels_correlogram.jpg new file mode 100644 index 0000000..b0dc1b2 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/labels_correlogram.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/results.csv b/src/src/YOLOv8/runs/detect/train/results.csv new file mode 100644 index 0000000..c59a365 --- /dev/null +++ b/src/src/YOLOv8/runs/detect/train/results.csv @@ -0,0 +1,301 @@ + epoch, train/box_loss, train/cls_loss, train/dfl_loss, metrics/precision(B), metrics/recall(B), metrics/mAP50(B), metrics/mAP50-95(B), val/box_loss, val/cls_loss, val/dfl_loss, lr/pg0, lr/pg1, lr/pg2 + 1, 1.8366, 1.6172, 1.23, 0.73216, 0.4057, 0.45729, 0.22342, 1.6173, 1.0335, 1.1176, 0.0033324, 0.0033324, 0.0033324 + 2, 1.6996, 1.1415, 1.1168, 0.74462, 0.42432, 0.48077, 0.23546, 1.5982, 0.9299, 1.0967, 0.0066437, 0.0066437, 0.0066437 + 3, 1.6942, 1.0916, 1.1144, 0.75214, 0.41787, 0.48389, 0.24459, 1.5876, 0.91869, 1.1124, 0.009933, 0.009933, 0.009933 + 4, 1.7002, 1.0721, 1.1165, 0.7459, 0.43503, 0.49319, 0.24855, 1.5479, 0.88047, 1.0963, 0.009901, 0.009901, 0.009901 + 5, 1.6564, 1.0036, 1.1029, 0.75251, 0.44131, 0.50205, 0.25468, 1.526, 0.86462, 1.0938, 0.009901, 0.009901, 0.009901 + 6, 1.6158, 0.96155, 1.0948, 0.77936, 0.44961, 0.51763, 0.26644, 1.5144, 0.82764, 1.092, 0.009868, 0.009868, 0.009868 + 7, 1.6037, 0.94048, 1.0802, 0.77111, 0.46326, 0.52994, 0.27099, 1.5069, 0.79548, 1.0745, 0.009835, 0.009835, 0.009835 + 8, 1.5843, 0.92476, 1.0833, 0.7791, 0.46007, 0.52712, 0.27167, 1.4899, 0.78321, 1.079, 0.009802, 0.009802, 0.009802 + 9, 1.5674, 0.89715, 1.0711, 0.77923, 0.46449, 0.53776, 0.28055, 1.47, 0.76636, 1.068, 0.009769, 0.009769, 0.009769 + 10, 1.5512, 0.87952, 1.0653, 0.77521, 0.47389, 0.54483, 0.28164, 1.4713, 0.7573, 1.0651, 0.009736, 0.009736, 0.009736 + 11, 1.5439, 0.87521, 1.0637, 0.77805, 0.48045, 0.55307, 0.28919, 1.4537, 0.74462, 1.0681, 0.009703, 0.009703, 0.009703 + 12, 1.5349, 0.86626, 1.0638, 0.79144, 0.48473, 0.55704, 0.2906, 1.4447, 0.74001, 1.0585, 0.00967, 0.00967, 0.00967 + 13, 1.5389, 0.8634, 1.0593, 0.79582, 0.48468, 0.55985, 0.29173, 1.4378, 0.72863, 1.0536, 0.009637, 0.009637, 0.009637 + 14, 1.527, 0.85232, 1.0575, 0.79959, 0.48924, 0.56194, 0.29369, 1.4325, 0.72248, 1.0529, 0.009604, 0.009604, 0.009604 + 15, 1.5167, 0.83858, 1.0472, 0.79612, 0.50162, 0.57528, 0.30143, 1.4291, 0.70915, 1.0517, 0.009571, 0.009571, 0.009571 + 16, 1.511, 0.84003, 1.0572, 0.79682, 0.49903, 0.57238, 0.29949, 1.4255, 0.70613, 1.0467, 0.009538, 0.009538, 0.009538 + 17, 1.5004, 0.82738, 1.0452, 0.80276, 0.50093, 0.57732, 0.30315, 1.4102, 0.70162, 1.04, 0.009505, 0.009505, 0.009505 + 18, 1.4945, 0.82371, 1.046, 0.80777, 0.5, 0.57768, 0.30523, 1.4061, 0.69825, 1.0444, 0.009472, 0.009472, 0.009472 + 19, 1.4989, 0.82164, 1.049, 0.80186, 0.50044, 0.57537, 0.3031, 1.4107, 0.69426, 1.0475, 0.009439, 0.009439, 0.009439 + 20, 1.4915, 0.81587, 1.0433, 0.80456, 0.51095, 0.58619, 0.30709, 1.4054, 0.68744, 1.0408, 0.009406, 0.009406, 0.009406 + 21, 1.4793, 0.80911, 1.0439, 0.80117, 0.50482, 0.58149, 0.30702, 1.4053, 0.68784, 1.0416, 0.009373, 0.009373, 0.009373 + 22, 1.4898, 0.81082, 1.045, 0.80043, 0.50707, 0.5833, 0.30586, 1.4035, 0.67973, 1.0387, 0.00934, 0.00934, 0.00934 + 23, 1.4828, 0.80697, 1.0414, 0.80335, 0.51238, 0.58912, 0.31067, 1.3903, 0.67495, 1.0341, 0.009307, 0.009307, 0.009307 + 24, 1.4826, 0.80751, 1.0396, 0.79396, 0.51665, 0.58972, 0.31185, 1.3907, 0.67116, 1.0299, 0.009274, 0.009274, 0.009274 + 25, 1.469, 0.79728, 1.037, 0.80462, 0.51567, 0.59328, 0.31328, 1.3887, 0.66722, 1.0345, 0.009241, 0.009241, 0.009241 + 26, 1.4824, 0.80153, 1.0417, 0.8068, 0.51665, 0.59521, 0.31561, 1.385, 0.66532, 1.0326, 0.009208, 0.009208, 0.009208 + 27, 1.4735, 0.79673, 1.0361, 0.81817, 0.51434, 0.59627, 0.3157, 1.3824, 0.6601, 1.0295, 0.009175, 0.009175, 0.009175 + 28, 1.4669, 0.79048, 1.0345, 0.80737, 0.51793, 0.59457, 0.31481, 1.3863, 0.6613, 1.0321, 0.009142, 0.009142, 0.009142 + 29, 1.478, 0.79455, 1.0352, 0.80684, 0.51922, 0.59626, 0.31542, 1.3801, 0.65883, 1.0294, 0.009109, 0.009109, 0.009109 + 30, 1.4666, 0.78563, 1.0305, 0.81345, 0.51743, 0.59667, 0.31766, 1.3758, 0.64901, 1.0265, 0.009076, 0.009076, 0.009076 + 31, 1.4614, 0.785, 1.0308, 0.80573, 0.52441, 0.60194, 0.31961, 1.3724, 0.6502, 1.0264, 0.009043, 0.009043, 0.009043 + 32, 1.4634, 0.78495, 1.03, 0.81169, 0.51881, 0.59713, 0.31653, 1.3714, 0.64888, 1.0239, 0.00901, 0.00901, 0.00901 + 33, 1.4562, 0.7776, 1.0319, 0.80824, 0.5239, 0.60092, 0.31811, 1.3725, 0.6479, 1.0243, 0.008977, 0.008977, 0.008977 + 34, 1.4608, 0.78239, 1.0343, 0.8152, 0.5246, 0.60352, 0.32082, 1.3738, 0.64521, 1.0266, 0.008944, 0.008944, 0.008944 + 35, 1.4568, 0.77935, 1.0331, 0.81581, 0.52456, 0.60224, 0.31993, 1.3704, 0.64374, 1.0275, 0.008911, 0.008911, 0.008911 + 36, 1.4511, 0.76946, 1.0274, 0.8176, 0.52902, 0.60728, 0.32229, 1.366, 0.64142, 1.0232, 0.008878, 0.008878, 0.008878 + 37, 1.45, 0.76984, 1.0284, 0.8169, 0.52652, 0.60466, 0.32245, 1.3641, 0.63698, 1.0221, 0.008845, 0.008845, 0.008845 + 38, 1.4495, 0.76749, 1.0282, 0.8147, 0.52902, 0.60759, 0.32213, 1.3639, 0.63651, 1.0216, 0.008812, 0.008812, 0.008812 + 39, 1.447, 0.76387, 1.0268, 0.82156, 0.52726, 0.60757, 0.32386, 1.3625, 0.63241, 1.0208, 0.008779, 0.008779, 0.008779 + 40, 1.4438, 0.76333, 1.0252, 0.81094, 0.5334, 0.61003, 0.32494, 1.3625, 0.63078, 1.0217, 0.008746, 0.008746, 0.008746 + 41, 1.4421, 0.76169, 1.0255, 0.81466, 0.53125, 0.60807, 0.32363, 1.361, 0.63154, 1.0219, 0.008713, 0.008713, 0.008713 + 42, 1.4381, 0.76428, 1.0251, 0.8128, 0.53541, 0.61123, 0.32527, 1.3571, 0.63195, 1.0211, 0.00868, 0.00868, 0.00868 + 43, 1.4432, 0.76062, 1.0273, 0.809, 0.53595, 0.61143, 0.32473, 1.359, 0.62995, 1.0218, 0.008647, 0.008647, 0.008647 + 44, 1.4449, 0.76623, 1.0273, 0.80624, 0.53644, 0.61108, 0.32432, 1.3596, 0.62857, 1.0207, 0.008614, 0.008614, 0.008614 + 45, 1.4417, 0.76109, 1.0217, 0.81111, 0.53605, 0.6127, 0.32521, 1.3593, 0.62836, 1.0198, 0.008581, 0.008581, 0.008581 + 46, 1.4321, 0.75374, 1.0216, 0.81368, 0.5364, 0.61196, 0.32574, 1.3587, 0.62687, 1.0201, 0.008548, 0.008548, 0.008548 + 47, 1.446, 0.7608, 1.0226, 0.80926, 0.53914, 0.61401, 0.32651, 1.3554, 0.62499, 1.0183, 0.008515, 0.008515, 0.008515 + 48, 1.4349, 0.75259, 1.0236, 0.8108, 0.53845, 0.61365, 0.32693, 1.3533, 0.62398, 1.0167, 0.008482, 0.008482, 0.008482 + 49, 1.4363, 0.75445, 1.0237, 0.81126, 0.53949, 0.61408, 0.32765, 1.3524, 0.62248, 1.0177, 0.008449, 0.008449, 0.008449 + 50, 1.4324, 0.74858, 1.0226, 0.81467, 0.53841, 0.61406, 0.32794, 1.352, 0.62139, 1.0183, 0.008416, 0.008416, 0.008416 + 51, 1.4338, 0.75026, 1.0216, 0.81477, 0.5388, 0.61559, 0.3293, 1.3514, 0.62012, 1.0187, 0.008383, 0.008383, 0.008383 + 52, 1.4261, 0.74897, 1.0223, 0.81789, 0.53772, 0.61498, 0.32866, 1.3509, 0.61872, 1.0174, 0.00835, 0.00835, 0.00835 + 53, 1.4313, 0.74894, 1.021, 0.81446, 0.53791, 0.6149, 0.32906, 1.3498, 0.6189, 1.0167, 0.008317, 0.008317, 0.008317 + 54, 1.4374, 0.75515, 1.0203, 0.81533, 0.53649, 0.61463, 0.32942, 1.3488, 0.61815, 1.0159, 0.008284, 0.008284, 0.008284 + 55, 1.4341, 0.75004, 1.02, 0.81816, 0.53693, 0.61413, 0.32926, 1.348, 0.61723, 1.0161, 0.008251, 0.008251, 0.008251 + 56, 1.4227, 0.74446, 1.0193, 0.82126, 0.53624, 0.61425, 0.3296, 1.3484, 0.61657, 1.0171, 0.008218, 0.008218, 0.008218 + 57, 1.4275, 0.7423, 1.0168, 0.82008, 0.53633, 0.61454, 0.33001, 1.3468, 0.61503, 1.0164, 0.008185, 0.008185, 0.008185 + 58, 1.4282, 0.74306, 1.0176, 0.81829, 0.53909, 0.61599, 0.33017, 1.3465, 0.61423, 1.0163, 0.008152, 0.008152, 0.008152 + 59, 1.4247, 0.73917, 1.015, 0.8196, 0.53988, 0.61715, 0.33048, 1.3466, 0.61296, 1.0165, 0.008119, 0.008119, 0.008119 + 60, 1.4332, 0.74758, 1.017, 0.82261, 0.53908, 0.61859, 0.33099, 1.3464, 0.61228, 1.0161, 0.008086, 0.008086, 0.008086 + 61, 1.4205, 0.74213, 1.0188, 0.82373, 0.53919, 0.61875, 0.33134, 1.3464, 0.61194, 1.0162, 0.008053, 0.008053, 0.008053 + 62, 1.425, 0.74274, 1.0166, 0.82279, 0.53968, 0.61843, 0.33131, 1.347, 0.61144, 1.0159, 0.00802, 0.00802, 0.00802 + 63, 1.4291, 0.74075, 1.017, 0.81851, 0.54154, 0.61889, 0.3315, 1.3469, 0.61152, 1.0161, 0.007987, 0.007987, 0.007987 + 64, 1.4219, 0.74298, 1.0177, 0.82097, 0.54026, 0.61851, 0.33164, 1.3466, 0.61163, 1.016, 0.007954, 0.007954, 0.007954 + 65, 1.4266, 0.73952, 1.017, 0.81926, 0.54022, 0.6187, 0.33209, 1.3463, 0.61171, 1.0163, 0.007921, 0.007921, 0.007921 + 66, 1.4278, 0.74037, 1.0147, 0.81862, 0.54081, 0.61909, 0.33213, 1.3457, 0.61062, 1.0158, 0.007888, 0.007888, 0.007888 + 67, 1.4183, 0.73243, 1.0126, 0.81686, 0.54248, 0.61944, 0.33257, 1.3448, 0.60959, 1.0152, 0.007855, 0.007855, 0.007855 + 68, 1.4215, 0.73568, 1.0146, 0.81829, 0.54322, 0.62038, 0.33265, 1.3439, 0.60876, 1.015, 0.007822, 0.007822, 0.007822 + 69, 1.4224, 0.7344, 1.0136, 0.81864, 0.54334, 0.62044, 0.333, 1.3438, 0.60831, 1.015, 0.007789, 0.007789, 0.007789 + 70, 1.416, 0.7341, 1.0113, 0.81974, 0.54415, 0.62089, 0.33331, 1.3433, 0.60708, 1.0146, 0.007756, 0.007756, 0.007756 + 71, 1.4174, 0.7324, 1.0141, 0.82096, 0.5444, 0.62096, 0.33341, 1.3424, 0.60636, 1.0143, 0.007723, 0.007723, 0.007723 + 72, 1.4189, 0.73294, 1.011, 0.81981, 0.54518, 0.62188, 0.33355, 1.3424, 0.60576, 1.0144, 0.00769, 0.00769, 0.00769 + 73, 1.4138, 0.73535, 1.0157, 0.81997, 0.54377, 0.62134, 0.33364, 1.342, 0.60532, 1.0143, 0.007657, 0.007657, 0.007657 + 74, 1.4145, 0.7302, 1.0136, 0.81922, 0.54459, 0.62154, 0.33371, 1.3424, 0.60538, 1.0144, 0.007624, 0.007624, 0.007624 + 75, 1.4123, 0.72954, 1.0118, 0.81753, 0.54651, 0.62168, 0.33367, 1.3424, 0.60525, 1.0142, 0.007591, 0.007591, 0.007591 + 76, 1.4134, 0.73017, 1.0138, 0.81633, 0.54607, 0.62139, 0.33364, 1.3417, 0.60494, 1.0138, 0.007558, 0.007558, 0.007558 + 77, 1.4047, 0.7226, 1.009, 0.81883, 0.54415, 0.62148, 0.33379, 1.3417, 0.60449, 1.0139, 0.007525, 0.007525, 0.007525 + 78, 1.4129, 0.73034, 1.0152, 0.81776, 0.54474, 0.62172, 0.33386, 1.3414, 0.60429, 1.0137, 0.007492, 0.007492, 0.007492 + 79, 1.4083, 0.72722, 1.0145, 0.8189, 0.54452, 0.62164, 0.33389, 1.3411, 0.60379, 1.0136, 0.007459, 0.007459, 0.007459 + 80, 1.4136, 0.73059, 1.0134, 0.81713, 0.54626, 0.62225, 0.33425, 1.3407, 0.60362, 1.0134, 0.007426, 0.007426, 0.007426 + 81, 1.4155, 0.72986, 1.0126, 0.81597, 0.54607, 0.6218, 0.33399, 1.3406, 0.60358, 1.0133, 0.007393, 0.007393, 0.007393 + 82, 1.4049, 0.72214, 1.0108, 0.81612, 0.54587, 0.62191, 0.33446, 1.3407, 0.60367, 1.0133, 0.00736, 0.00736, 0.00736 + 83, 1.4091, 0.72572, 1.0106, 0.8181, 0.54601, 0.62233, 0.33441, 1.3407, 0.60404, 1.0132, 0.007327, 0.007327, 0.007327 + 84, 1.4115, 0.7256, 1.0107, 0.81762, 0.54695, 0.62245, 0.33442, 1.3408, 0.6035, 1.0133, 0.007294, 0.007294, 0.007294 + 85, 1.4051, 0.72315, 1.0097, 0.81982, 0.54646, 0.62276, 0.33442, 1.3406, 0.60327, 1.0132, 0.007261, 0.007261, 0.007261 + 86, 1.4086, 0.72298, 1.0084, 0.81937, 0.5468, 0.6231, 0.33452, 1.3401, 0.60288, 1.0131, 0.007228, 0.007228, 0.007228 + 87, 1.4008, 0.71738, 1.0089, 0.81983, 0.5472, 0.62341, 0.33454, 1.3398, 0.60275, 1.0129, 0.007195, 0.007195, 0.007195 + 88, 1.4022, 0.71517, 1.0057, 0.81868, 0.54715, 0.62308, 0.33477, 1.3397, 0.60256, 1.0128, 0.007162, 0.007162, 0.007162 + 89, 1.4067, 0.71969, 1.008, 0.8195, 0.54729, 0.62348, 0.33471, 1.3396, 0.60243, 1.0129, 0.007129, 0.007129, 0.007129 + 90, 1.3997, 0.71888, 1.0106, 0.81971, 0.54727, 0.62343, 0.33495, 1.3394, 0.60221, 1.0129, 0.007096, 0.007096, 0.007096 + 91, 1.4022, 0.71719, 1.0091, 0.82048, 0.54767, 0.62366, 0.33503, 1.3392, 0.60175, 1.0129, 0.007063, 0.007063, 0.007063 + 92, 1.3989, 0.71485, 1.0078, 0.81964, 0.54788, 0.62409, 0.33514, 1.3395, 0.60185, 1.013, 0.00703, 0.00703, 0.00703 + 93, 1.4019, 0.71837, 1.007, 0.81886, 0.54813, 0.62434, 0.33525, 1.3396, 0.6019, 1.0129, 0.006997, 0.006997, 0.006997 + 94, 1.3981, 0.71338, 1.0055, 0.81984, 0.54822, 0.6247, 0.33542, 1.3391, 0.60186, 1.0127, 0.006964, 0.006964, 0.006964 + 95, 1.3901, 0.70669, 1.007, 0.82073, 0.54828, 0.62479, 0.3355, 1.3388, 0.60193, 1.0126, 0.006931, 0.006931, 0.006931 + 96, 1.3936, 0.71641, 1.006, 0.82038, 0.54833, 0.62487, 0.33563, 1.3384, 0.60175, 1.0124, 0.006898, 0.006898, 0.006898 + 97, 1.4024, 0.71776, 1.008, 0.82021, 0.54825, 0.62516, 0.33575, 1.3384, 0.60125, 1.0122, 0.006865, 0.006865, 0.006865 + 98, 1.3947, 0.71141, 1.0071, 0.82041, 0.54842, 0.62523, 0.33571, 1.3385, 0.60122, 1.0122, 0.006832, 0.006832, 0.006832 + 99, 1.3989, 0.71527, 1.006, 0.82181, 0.54768, 0.62527, 0.33585, 1.3382, 0.6009, 1.0121, 0.006799, 0.006799, 0.006799 + 100, 1.4007, 0.71242, 1.0033, 0.82046, 0.54783, 0.62513, 0.33594, 1.3381, 0.6008, 1.012, 0.006766, 0.006766, 0.006766 + 101, 1.3874, 0.70685, 1.0052, 0.82125, 0.54761, 0.62544, 0.33618, 1.3377, 0.60064, 1.0118, 0.006733, 0.006733, 0.006733 + 102, 1.3935, 0.71074, 1.0025, 0.82127, 0.54803, 0.62556, 0.33625, 1.3374, 0.60067, 1.0116, 0.0067, 0.0067, 0.0067 + 103, 1.4003, 0.71297, 1.0023, 0.82112, 0.54759, 0.62563, 0.33632, 1.3375, 0.6007, 1.0117, 0.006667, 0.006667, 0.006667 + 104, 1.3879, 0.70909, 1.0018, 0.82122, 0.54754, 0.62568, 0.33641, 1.3374, 0.60048, 1.0116, 0.006634, 0.006634, 0.006634 + 105, 1.3922, 0.71016, 1.0032, 0.82129, 0.54845, 0.62607, 0.33655, 1.3374, 0.60051, 1.0116, 0.006601, 0.006601, 0.006601 + 106, 1.3946, 0.71243, 1.0043, 0.8213, 0.54869, 0.62641, 0.33664, 1.3374, 0.6004, 1.0115, 0.006568, 0.006568, 0.006568 + 107, 1.3827, 0.70472, 1.0035, 0.81958, 0.54931, 0.62647, 0.33678, 1.3374, 0.60012, 1.0115, 0.006535, 0.006535, 0.006535 + 108, 1.3889, 0.70793, 1.0023, 0.82013, 0.5498, 0.62686, 0.33676, 1.3372, 0.59995, 1.0114, 0.006502, 0.006502, 0.006502 + 109, 1.3947, 0.70785, 1.0047, 0.82093, 0.54955, 0.62695, 0.33685, 1.337, 0.5999, 1.0114, 0.006469, 0.006469, 0.006469 + 110, 1.3854, 0.70081, 1.0033, 0.82111, 0.54945, 0.62726, 0.33697, 1.3369, 0.59983, 1.0113, 0.006436, 0.006436, 0.006436 + 111, 1.4004, 0.7117, 1.0024, 0.82131, 0.54931, 0.62743, 0.33715, 1.3366, 0.59944, 1.0112, 0.006403, 0.006403, 0.006403 + 112, 1.3912, 0.70301, 1.0046, 0.8217, 0.54985, 0.62777, 0.33721, 1.3367, 0.59945, 1.0112, 0.00637, 0.00637, 0.00637 + 113, 1.3875, 0.70827, 1.0035, 0.82263, 0.54887, 0.62762, 0.33739, 1.3365, 0.59899, 1.0111, 0.006337, 0.006337, 0.006337 + 114, 1.3873, 0.70509, 1.005, 0.82242, 0.54896, 0.62767, 0.33738, 1.3363, 0.59898, 1.0111, 0.006304, 0.006304, 0.006304 + 115, 1.3814, 0.70256, 0.99727, 0.82259, 0.54882, 0.62743, 0.33746, 1.3361, 0.59874, 1.0109, 0.006271, 0.006271, 0.006271 + 116, 1.3888, 0.70403, 0.99954, 0.8226, 0.54921, 0.62778, 0.33757, 1.3358, 0.59901, 1.0108, 0.006238, 0.006238, 0.006238 + 117, 1.3896, 0.70697, 1.0004, 0.82318, 0.54945, 0.62767, 0.33751, 1.3357, 0.59913, 1.0107, 0.006205, 0.006205, 0.006205 + 118, 1.3879, 0.70283, 0.99918, 0.82335, 0.5495, 0.62786, 0.33762, 1.3356, 0.59905, 1.0106, 0.006172, 0.006172, 0.006172 + 119, 1.3902, 0.70605, 0.99902, 0.82395, 0.54916, 0.62786, 0.3378, 1.3352, 0.59882, 1.0105, 0.006139, 0.006139, 0.006139 + 120, 1.3832, 0.69787, 1.0025, 0.82258, 0.5499, 0.62806, 0.33784, 1.3352, 0.59865, 1.0105, 0.006106, 0.006106, 0.006106 + 121, 1.3874, 0.70286, 0.9999, 0.82202, 0.55039, 0.62833, 0.33815, 1.3351, 0.59855, 1.0107, 0.006073, 0.006073, 0.006073 + 122, 1.3857, 0.70233, 0.99927, 0.82216, 0.5508, 0.629, 0.33831, 1.3352, 0.59858, 1.0107, 0.00604, 0.00604, 0.00604 + 123, 1.3858, 0.70197, 0.99712, 0.82175, 0.55103, 0.62892, 0.33843, 1.3349, 0.5982, 1.0105, 0.006007, 0.006007, 0.006007 + 124, 1.3786, 0.69728, 0.99963, 0.82326, 0.55073, 0.62877, 0.33858, 1.3349, 0.59783, 1.0106, 0.005974, 0.005974, 0.005974 + 125, 1.3786, 0.69268, 0.99773, 0.82349, 0.55068, 0.6297, 0.33879, 1.3349, 0.59766, 1.0104, 0.005941, 0.005941, 0.005941 + 126, 1.3949, 0.70367, 1.0003, 0.82373, 0.55044, 0.62983, 0.33901, 1.3348, 0.59776, 1.0104, 0.005908, 0.005908, 0.005908 + 127, 1.3867, 0.70239, 1.0001, 0.82395, 0.55014, 0.62936, 0.33886, 1.3346, 0.59752, 1.0103, 0.005875, 0.005875, 0.005875 + 128, 1.3803, 0.69582, 0.99711, 0.82454, 0.55068, 0.6298, 0.33921, 1.3342, 0.59731, 1.0103, 0.005842, 0.005842, 0.005842 + 129, 1.3777, 0.69471, 0.99872, 0.82423, 0.55085, 0.63037, 0.33947, 1.334, 0.59742, 1.0102, 0.005809, 0.005809, 0.005809 + 130, 1.3741, 0.69255, 1.0009, 0.82481, 0.55054, 0.63002, 0.33908, 1.3341, 0.59731, 1.0102, 0.005776, 0.005776, 0.005776 + 131, 1.3787, 0.69634, 0.9976, 0.82476, 0.55058, 0.63014, 0.33914, 1.3339, 0.59748, 1.0102, 0.005743, 0.005743, 0.005743 + 132, 1.3768, 0.69408, 0.99817, 0.82489, 0.55117, 0.63065, 0.3394, 1.3337, 0.59705, 1.01, 0.00571, 0.00571, 0.00571 + 133, 1.3823, 0.69617, 0.99889, 0.82564, 0.55115, 0.63098, 0.33948, 1.3333, 0.59665, 1.0097, 0.005677, 0.005677, 0.005677 + 134, 1.3765, 0.69265, 0.99652, 0.82532, 0.55085, 0.63073, 0.33965, 1.333, 0.59661, 1.0096, 0.005644, 0.005644, 0.005644 + 135, 1.3767, 0.69175, 0.99628, 0.82649, 0.55093, 0.6308, 0.33964, 1.3329, 0.59625, 1.0096, 0.005611, 0.005611, 0.005611 + 136, 1.3798, 0.69559, 0.99664, 0.82624, 0.55112, 0.63067, 0.33954, 1.3325, 0.59598, 1.0095, 0.005578, 0.005578, 0.005578 + 137, 1.3749, 0.69064, 0.99435, 0.82642, 0.55147, 0.6311, 0.3395, 1.3329, 0.59591, 1.0096, 0.005545, 0.005545, 0.005545 + 138, 1.3795, 0.69338, 0.99497, 0.82778, 0.55022, 0.63097, 0.33961, 1.3326, 0.59568, 1.0093, 0.005512, 0.005512, 0.005512 + 139, 1.3767, 0.69199, 0.9961, 0.8287, 0.55047, 0.63145, 0.33988, 1.3325, 0.59553, 1.0092, 0.005479, 0.005479, 0.005479 + 140, 1.3773, 0.69169, 0.99446, 0.82719, 0.55128, 0.6316, 0.34031, 1.3322, 0.59524, 1.0089, 0.005446, 0.005446, 0.005446 + 141, 1.3752, 0.69397, 0.99854, 0.824, 0.55235, 0.63169, 0.3403, 1.332, 0.59498, 1.0088, 0.005413, 0.005413, 0.005413 + 142, 1.3656, 0.68489, 0.99408, 0.82572, 0.55215, 0.63217, 0.34039, 1.3319, 0.59466, 1.0088, 0.00538, 0.00538, 0.00538 + 143, 1.3649, 0.68395, 0.99551, 0.82382, 0.55289, 0.63181, 0.34036, 1.3316, 0.59432, 1.0087, 0.005347, 0.005347, 0.005347 + 144, 1.3734, 0.6892, 0.99345, 0.8243, 0.55279, 0.63185, 0.34027, 1.3316, 0.59408, 1.0087, 0.005314, 0.005314, 0.005314 + 145, 1.3682, 0.68604, 0.99237, 0.82312, 0.55353, 0.63184, 0.34034, 1.3314, 0.59393, 1.0085, 0.005281, 0.005281, 0.005281 + 146, 1.3691, 0.68676, 0.99395, 0.82281, 0.55326, 0.63198, 0.34038, 1.3314, 0.59353, 1.0085, 0.005248, 0.005248, 0.005248 + 147, 1.3712, 0.68716, 0.99482, 0.82282, 0.55397, 0.63189, 0.34038, 1.3314, 0.59346, 1.0083, 0.005215, 0.005215, 0.005215 + 148, 1.3738, 0.6874, 0.99247, 0.82357, 0.55378, 0.63172, 0.34032, 1.3311, 0.59309, 1.0081, 0.005182, 0.005182, 0.005182 + 149, 1.3698, 0.68941, 0.99034, 0.82248, 0.55383, 0.63171, 0.34024, 1.331, 0.59295, 1.0079, 0.005149, 0.005149, 0.005149 + 150, 1.3654, 0.68589, 0.99287, 0.82155, 0.55422, 0.63179, 0.34017, 1.3311, 0.59295, 1.0078, 0.005116, 0.005116, 0.005116 + 151, 1.3675, 0.6843, 0.99281, 0.82365, 0.55324, 0.63179, 0.34035, 1.3308, 0.59272, 1.0079, 0.005083, 0.005083, 0.005083 + 152, 1.3683, 0.6835, 0.99382, 0.82408, 0.55304, 0.6317, 0.34042, 1.3307, 0.5924, 1.0078, 0.00505, 0.00505, 0.00505 + 153, 1.3698, 0.68492, 0.99352, 0.82535, 0.55189, 0.63188, 0.34066, 1.3305, 0.59222, 1.0078, 0.005017, 0.005017, 0.005017 + 154, 1.3685, 0.68492, 0.99299, 0.82573, 0.55243, 0.63208, 0.34078, 1.3304, 0.59185, 1.0078, 0.004984, 0.004984, 0.004984 + 155, 1.3626, 0.67738, 0.98902, 0.82532, 0.55294, 0.63223, 0.34076, 1.3305, 0.59177, 1.0078, 0.004951, 0.004951, 0.004951 + 156, 1.3636, 0.68, 0.99243, 0.82667, 0.55348, 0.63295, 0.34098, 1.3303, 0.59152, 1.0078, 0.004918, 0.004918, 0.004918 + 157, 1.3594, 0.6781, 0.98973, 0.82475, 0.55373, 0.6325, 0.34107, 1.3302, 0.59135, 1.0076, 0.004885, 0.004885, 0.004885 + 158, 1.3678, 0.68295, 0.99046, 0.82561, 0.55402, 0.6329, 0.34107, 1.3301, 0.59135, 1.0076, 0.004852, 0.004852, 0.004852 + 159, 1.3645, 0.6831, 0.99186, 0.82534, 0.55491, 0.63334, 0.34127, 1.3299, 0.59096, 1.0075, 0.004819, 0.004819, 0.004819 + 160, 1.3605, 0.67788, 0.99112, 0.82656, 0.55392, 0.63306, 0.34116, 1.3301, 0.59072, 1.0076, 0.004786, 0.004786, 0.004786 + 161, 1.3606, 0.6787, 0.99057, 0.82523, 0.55402, 0.63288, 0.3412, 1.3298, 0.59054, 1.0074, 0.004753, 0.004753, 0.004753 + 162, 1.3601, 0.68191, 0.98849, 0.82557, 0.55369, 0.63291, 0.34139, 1.3298, 0.58996, 1.0073, 0.00472, 0.00472, 0.00472 + 163, 1.3638, 0.67852, 0.99231, 0.82577, 0.5542, 0.63352, 0.34162, 1.3296, 0.58984, 1.0073, 0.004687, 0.004687, 0.004687 + 164, 1.3562, 0.67699, 0.98769, 0.82626, 0.55425, 0.63356, 0.34149, 1.3294, 0.58948, 1.0071, 0.004654, 0.004654, 0.004654 + 165, 1.3602, 0.67967, 0.99079, 0.82587, 0.55412, 0.63347, 0.34137, 1.3294, 0.58935, 1.0073, 0.004621, 0.004621, 0.004621 + 166, 1.3535, 0.67376, 0.98673, 0.82726, 0.55436, 0.63382, 0.34173, 1.3293, 0.58908, 1.0073, 0.004588, 0.004588, 0.004588 + 167, 1.3575, 0.6761, 0.99138, 0.82599, 0.55471, 0.63366, 0.34157, 1.3293, 0.58896, 1.0073, 0.004555, 0.004555, 0.004555 + 168, 1.3552, 0.67288, 0.9878, 0.82602, 0.55449, 0.63393, 0.34187, 1.3294, 0.58882, 1.0073, 0.004522, 0.004522, 0.004522 + 169, 1.36, 0.67704, 0.98764, 0.82613, 0.55421, 0.6339, 0.342, 1.3292, 0.58844, 1.0072, 0.004489, 0.004489, 0.004489 + 170, 1.3552, 0.67637, 0.98979, 0.82649, 0.55343, 0.63351, 0.34228, 1.3293, 0.58816, 1.0072, 0.004456, 0.004456, 0.004456 + 171, 1.3534, 0.67386, 0.9914, 0.82715, 0.55347, 0.63363, 0.34223, 1.3292, 0.58792, 1.0071, 0.004423, 0.004423, 0.004423 + 172, 1.351, 0.67177, 0.98792, 0.82616, 0.55343, 0.63372, 0.34239, 1.3292, 0.58784, 1.0071, 0.00439, 0.00439, 0.00439 + 173, 1.3505, 0.67166, 0.98884, 0.82556, 0.55388, 0.6339, 0.34246, 1.3292, 0.58732, 1.0071, 0.004357, 0.004357, 0.004357 + 174, 1.3579, 0.67493, 0.9908, 0.82571, 0.55353, 0.63399, 0.34258, 1.329, 0.58704, 1.007, 0.004324, 0.004324, 0.004324 + 175, 1.3508, 0.67169, 0.98861, 0.82545, 0.55255, 0.6333, 0.34272, 1.3289, 0.58701, 1.0069, 0.004291, 0.004291, 0.004291 + 176, 1.3554, 0.67474, 0.98644, 0.82523, 0.55275, 0.63357, 0.34269, 1.3289, 0.58682, 1.0069, 0.004258, 0.004258, 0.004258 + 177, 1.3573, 0.67108, 0.98482, 0.82587, 0.55319, 0.63386, 0.34288, 1.3288, 0.58651, 1.0068, 0.004225, 0.004225, 0.004225 + 178, 1.3527, 0.67021, 0.98847, 0.82658, 0.55338, 0.63437, 0.34309, 1.3286, 0.58672, 1.0067, 0.004192, 0.004192, 0.004192 + 179, 1.3435, 0.66617, 0.98638, 0.82482, 0.55368, 0.63379, 0.343, 1.3284, 0.58667, 1.0066, 0.004159, 0.004159, 0.004159 + 180, 1.3536, 0.67269, 0.98824, 0.82754, 0.5525, 0.63405, 0.34317, 1.328, 0.58654, 1.0065, 0.004126, 0.004126, 0.004126 + 181, 1.3479, 0.67108, 0.98709, 0.8272, 0.55235, 0.63396, 0.34326, 1.3279, 0.58644, 1.0065, 0.004093, 0.004093, 0.004093 + 182, 1.3417, 0.66367, 0.98255, 0.82548, 0.55378, 0.63396, 0.34317, 1.3279, 0.58615, 1.0064, 0.00406, 0.00406, 0.00406 + 183, 1.3506, 0.67055, 0.98606, 0.82441, 0.55384, 0.63389, 0.34303, 1.3276, 0.58593, 1.0062, 0.004027, 0.004027, 0.004027 + 184, 1.3402, 0.66512, 0.98497, 0.82432, 0.55396, 0.6343, 0.34318, 1.3272, 0.58589, 1.0061, 0.003994, 0.003994, 0.003994 + 185, 1.3476, 0.66572, 0.98524, 0.82387, 0.55442, 0.63424, 0.34343, 1.327, 0.58582, 1.0059, 0.003961, 0.003961, 0.003961 + 186, 1.3514, 0.66669, 0.98533, 0.82396, 0.55412, 0.63464, 0.34334, 1.3271, 0.58562, 1.0058, 0.003928, 0.003928, 0.003928 + 187, 1.3481, 0.66949, 0.98588, 0.82506, 0.55397, 0.63453, 0.34357, 1.3272, 0.58563, 1.0059, 0.003895, 0.003895, 0.003895 + 188, 1.3486, 0.66407, 0.98386, 0.824, 0.55413, 0.63456, 0.34367, 1.3271, 0.58584, 1.0058, 0.003862, 0.003862, 0.003862 + 189, 1.3432, 0.6625, 0.98552, 0.8245, 0.55402, 0.63437, 0.34367, 1.327, 0.58587, 1.0058, 0.003829, 0.003829, 0.003829 + 190, 1.3464, 0.6655, 0.98463, 0.82247, 0.55494, 0.63514, 0.34364, 1.327, 0.58567, 1.0057, 0.003796, 0.003796, 0.003796 + 191, 1.3392, 0.66056, 0.98377, 0.82379, 0.55427, 0.63507, 0.34372, 1.3268, 0.58581, 1.0056, 0.003763, 0.003763, 0.003763 + 192, 1.3409, 0.66068, 0.98347, 0.8232, 0.55474, 0.63537, 0.34361, 1.3266, 0.58574, 1.0055, 0.00373, 0.00373, 0.00373 + 193, 1.3443, 0.66483, 0.98454, 0.82259, 0.55516, 0.63514, 0.34364, 1.3266, 0.58565, 1.0055, 0.003697, 0.003697, 0.003697 + 194, 1.3424, 0.66293, 0.98034, 0.82476, 0.55474, 0.63523, 0.34369, 1.3264, 0.58597, 1.0055, 0.003664, 0.003664, 0.003664 + 195, 1.3364, 0.66169, 0.98331, 0.82378, 0.55515, 0.63541, 0.34383, 1.3262, 0.58583, 1.0054, 0.003631, 0.003631, 0.003631 + 196, 1.3357, 0.65875, 0.98083, 0.82561, 0.55466, 0.63539, 0.34401, 1.3264, 0.58553, 1.0055, 0.003598, 0.003598, 0.003598 + 197, 1.3376, 0.66148, 0.98121, 0.82485, 0.55535, 0.63549, 0.34413, 1.3264, 0.58569, 1.0054, 0.003565, 0.003565, 0.003565 + 198, 1.3356, 0.65735, 0.98116, 0.82474, 0.5555, 0.63568, 0.34423, 1.3265, 0.58561, 1.0055, 0.003532, 0.003532, 0.003532 + 199, 1.3422, 0.66173, 0.98166, 0.82604, 0.55505, 0.63582, 0.34425, 1.3267, 0.58572, 1.0054, 0.003499, 0.003499, 0.003499 + 200, 1.3381, 0.65961, 0.98269, 0.82542, 0.55569, 0.6361, 0.34465, 1.3266, 0.58572, 1.0055, 0.003466, 0.003466, 0.003466 + 201, 1.3346, 0.65792, 0.98265, 0.82729, 0.5551, 0.63659, 0.34436, 1.3267, 0.58536, 1.0055, 0.003433, 0.003433, 0.003433 + 202, 1.3293, 0.65254, 0.97846, 0.82559, 0.5556, 0.63664, 0.34472, 1.3267, 0.58525, 1.0055, 0.0034, 0.0034, 0.0034 + 203, 1.3385, 0.65796, 0.97954, 0.82573, 0.55525, 0.63674, 0.34476, 1.3268, 0.5849, 1.0054, 0.003367, 0.003367, 0.003367 + 204, 1.3289, 0.65451, 0.97813, 0.82564, 0.55535, 0.63653, 0.34502, 1.3268, 0.58477, 1.0054, 0.003334, 0.003334, 0.003334 + 205, 1.3332, 0.65623, 0.97782, 0.827, 0.55499, 0.63665, 0.34483, 1.3269, 0.58471, 1.0053, 0.003301, 0.003301, 0.003301 + 206, 1.3321, 0.65534, 0.97912, 0.82734, 0.55445, 0.63653, 0.34499, 1.3269, 0.58478, 1.0054, 0.003268, 0.003268, 0.003268 + 207, 1.3363, 0.65709, 0.9789, 0.82838, 0.55445, 0.63706, 0.34501, 1.3267, 0.58445, 1.0053, 0.003235, 0.003235, 0.003235 + 208, 1.3293, 0.65415, 0.97973, 0.82761, 0.55481, 0.63673, 0.34475, 1.327, 0.58425, 1.0053, 0.003202, 0.003202, 0.003202 + 209, 1.3353, 0.65727, 0.97824, 0.82762, 0.55529, 0.63693, 0.34461, 1.3271, 0.58431, 1.0052, 0.003169, 0.003169, 0.003169 + 210, 1.3279, 0.65116, 0.97499, 0.82611, 0.55597, 0.63706, 0.34464, 1.3269, 0.58407, 1.005, 0.003136, 0.003136, 0.003136 + 211, 1.3313, 0.65234, 0.97816, 0.82626, 0.55579, 0.63695, 0.34471, 1.3268, 0.58389, 1.0048, 0.003103, 0.003103, 0.003103 + 212, 1.3289, 0.65224, 0.98015, 0.82649, 0.55558, 0.63689, 0.34509, 1.3269, 0.58408, 1.0048, 0.00307, 0.00307, 0.00307 + 213, 1.3242, 0.64951, 0.97583, 0.82574, 0.5562, 0.63714, 0.34496, 1.3267, 0.58383, 1.0047, 0.003037, 0.003037, 0.003037 + 214, 1.3254, 0.65301, 0.97522, 0.82596, 0.55604, 0.6373, 0.34479, 1.3268, 0.58347, 1.0046, 0.003004, 0.003004, 0.003004 + 215, 1.3273, 0.65115, 0.97424, 0.82477, 0.55617, 0.63733, 0.34503, 1.3268, 0.58333, 1.0045, 0.002971, 0.002971, 0.002971 + 216, 1.3198, 0.64455, 0.97218, 0.82528, 0.55599, 0.63721, 0.34493, 1.3269, 0.58326, 1.0044, 0.002938, 0.002938, 0.002938 + 217, 1.3197, 0.64555, 0.97685, 0.82458, 0.55667, 0.63771, 0.34522, 1.3266, 0.58292, 1.0043, 0.002905, 0.002905, 0.002905 + 218, 1.3192, 0.64702, 0.97282, 0.82304, 0.55778, 0.63783, 0.34518, 1.3269, 0.58265, 1.0042, 0.002872, 0.002872, 0.002872 + 219, 1.3155, 0.64615, 0.97342, 0.82377, 0.55726, 0.63781, 0.34542, 1.3271, 0.58272, 1.0043, 0.002839, 0.002839, 0.002839 + 220, 1.3172, 0.64597, 0.97521, 0.82352, 0.55805, 0.63805, 0.34554, 1.3272, 0.58258, 1.0043, 0.002806, 0.002806, 0.002806 + 221, 1.3197, 0.64495, 0.97415, 0.82281, 0.5582, 0.63824, 0.34567, 1.3271, 0.58209, 1.0043, 0.002773, 0.002773, 0.002773 + 222, 1.3155, 0.64493, 0.97286, 0.82303, 0.5581, 0.63792, 0.34578, 1.3272, 0.58218, 1.0042, 0.00274, 0.00274, 0.00274 + 223, 1.3058, 0.64009, 0.97206, 0.8241, 0.55785, 0.63828, 0.34614, 1.3272, 0.58238, 1.0042, 0.002707, 0.002707, 0.002707 + 224, 1.3145, 0.64162, 0.97335, 0.82366, 0.55811, 0.63852, 0.34642, 1.3269, 0.5821, 1.004, 0.002674, 0.002674, 0.002674 + 225, 1.3184, 0.64613, 0.97399, 0.82315, 0.55839, 0.63871, 0.34641, 1.3265, 0.5821, 1.0039, 0.002641, 0.002641, 0.002641 + 226, 1.319, 0.64176, 0.97114, 0.82401, 0.55806, 0.639, 0.34655, 1.3265, 0.58218, 1.0038, 0.002608, 0.002608, 0.002608 + 227, 1.3133, 0.64004, 0.97166, 0.824, 0.55785, 0.63907, 0.34642, 1.3264, 0.58216, 1.0038, 0.002575, 0.002575, 0.002575 + 228, 1.3172, 0.64167, 0.97184, 0.824, 0.558, 0.63925, 0.34689, 1.3261, 0.58202, 1.0039, 0.002542, 0.002542, 0.002542 + 229, 1.3072, 0.63918, 0.97226, 0.82452, 0.55775, 0.6391, 0.34703, 1.3261, 0.58201, 1.0039, 0.002509, 0.002509, 0.002509 + 230, 1.3166, 0.63981, 0.9714, 0.82447, 0.55829, 0.6395, 0.34724, 1.3259, 0.58193, 1.0038, 0.002476, 0.002476, 0.002476 + 231, 1.3148, 0.64031, 0.97158, 0.82434, 0.55805, 0.63943, 0.3472, 1.326, 0.58157, 1.0038, 0.002443, 0.002443, 0.002443 + 232, 1.3059, 0.63532, 0.96838, 0.82427, 0.55883, 0.63974, 0.34757, 1.3255, 0.58141, 1.0036, 0.00241, 0.00241, 0.00241 + 233, 1.3115, 0.63841, 0.97033, 0.82429, 0.55869, 0.63968, 0.34763, 1.3257, 0.58155, 1.0037, 0.002377, 0.002377, 0.002377 + 234, 1.3013, 0.63355, 0.96933, 0.82559, 0.55816, 0.63973, 0.34783, 1.3253, 0.5814, 1.0035, 0.002344, 0.002344, 0.002344 + 235, 1.299, 0.6326, 0.96709, 0.82627, 0.5581, 0.63965, 0.34799, 1.3256, 0.58135, 1.0035, 0.002311, 0.002311, 0.002311 + 236, 1.3036, 0.63394, 0.96996, 0.82546, 0.55918, 0.63993, 0.34827, 1.3251, 0.58139, 1.0035, 0.002278, 0.002278, 0.002278 + 237, 1.3048, 0.6356, 0.96859, 0.82497, 0.56021, 0.64046, 0.34891, 1.3249, 0.58146, 1.0034, 0.002245, 0.002245, 0.002245 + 238, 1.3006, 0.63233, 0.96996, 0.82466, 0.56035, 0.64074, 0.34895, 1.3249, 0.58126, 1.0034, 0.002212, 0.002212, 0.002212 + 239, 1.3131, 0.63857, 0.97315, 0.82394, 0.56065, 0.64074, 0.34898, 1.3249, 0.58141, 1.0035, 0.002179, 0.002179, 0.002179 + 240, 1.3108, 0.63706, 0.97158, 0.82427, 0.56075, 0.64092, 0.34922, 1.3248, 0.58127, 1.0035, 0.002146, 0.002146, 0.002146 + 241, 1.2968, 0.63298, 0.96814, 0.82427, 0.56047, 0.64103, 0.34931, 1.3249, 0.58138, 1.0036, 0.002113, 0.002113, 0.002113 + 242, 1.298, 0.63112, 0.96519, 0.82379, 0.5605, 0.64119, 0.34932, 1.3248, 0.58139, 1.0036, 0.00208, 0.00208, 0.00208 + 243, 1.2945, 0.62723, 0.9649, 0.82462, 0.56026, 0.64131, 0.34936, 1.3249, 0.58146, 1.0035, 0.002047, 0.002047, 0.002047 + 244, 1.2958, 0.62663, 0.96582, 0.82466, 0.56021, 0.6413, 0.34952, 1.3247, 0.58134, 1.0034, 0.002014, 0.002014, 0.002014 + 245, 1.3012, 0.62934, 0.96644, 0.82466, 0.56013, 0.64147, 0.34946, 1.325, 0.5814, 1.0036, 0.001981, 0.001981, 0.001981 + 246, 1.294, 0.62692, 0.96783, 0.8263, 0.55913, 0.64104, 0.34912, 1.3253, 0.58135, 1.0036, 0.001948, 0.001948, 0.001948 + 247, 1.297, 0.62833, 0.96434, 0.82494, 0.55967, 0.64113, 0.34928, 1.3255, 0.5813, 1.0036, 0.001915, 0.001915, 0.001915 + 248, 1.2916, 0.62731, 0.9652, 0.82662, 0.55912, 0.64142, 0.34921, 1.3255, 0.58125, 1.0036, 0.001882, 0.001882, 0.001882 + 249, 1.2874, 0.62193, 0.96326, 0.82658, 0.55993, 0.64167, 0.34918, 1.3261, 0.58143, 1.0037, 0.001849, 0.001849, 0.001849 + 250, 1.2994, 0.62638, 0.96512, 0.82618, 0.56023, 0.64205, 0.34918, 1.3261, 0.58162, 1.0037, 0.001816, 0.001816, 0.001816 + 251, 1.293, 0.62706, 0.96573, 0.82672, 0.56055, 0.64226, 0.34939, 1.3261, 0.58145, 1.0038, 0.001783, 0.001783, 0.001783 + 252, 1.2876, 0.62387, 0.96027, 0.8271, 0.55957, 0.64236, 0.34923, 1.3262, 0.58179, 1.0037, 0.00175, 0.00175, 0.00175 + 253, 1.2878, 0.62273, 0.96041, 0.82664, 0.55922, 0.64211, 0.34928, 1.3264, 0.58163, 1.0037, 0.001717, 0.001717, 0.001717 + 254, 1.2917, 0.62056, 0.96255, 0.82564, 0.56021, 0.642, 0.34924, 1.3264, 0.58157, 1.0038, 0.001684, 0.001684, 0.001684 + 255, 1.292, 0.62485, 0.96082, 0.8261, 0.55993, 0.64192, 0.3491, 1.3265, 0.58147, 1.0038, 0.001651, 0.001651, 0.001651 + 256, 1.2835, 0.62036, 0.96122, 0.82533, 0.55996, 0.6419, 0.34927, 1.3264, 0.58141, 1.0038, 0.001618, 0.001618, 0.001618 + 257, 1.2875, 0.62276, 0.95954, 0.82592, 0.55991, 0.64201, 0.34945, 1.3264, 0.58152, 1.0038, 0.001585, 0.001585, 0.001585 + 258, 1.2869, 0.61985, 0.96011, 0.82681, 0.55996, 0.64185, 0.3492, 1.3265, 0.58145, 1.0037, 0.001552, 0.001552, 0.001552 + 259, 1.274, 0.61597, 0.9602, 0.82617, 0.56021, 0.64191, 0.34949, 1.3267, 0.58119, 1.0038, 0.001519, 0.001519, 0.001519 + 260, 1.2888, 0.62277, 0.96148, 0.8263, 0.56023, 0.6422, 0.3494, 1.3267, 0.58122, 1.0037, 0.001486, 0.001486, 0.001486 + 261, 1.2826, 0.61716, 0.96207, 0.82674, 0.56016, 0.6422, 0.34924, 1.3269, 0.58129, 1.0038, 0.001453, 0.001453, 0.001453 + 262, 1.279, 0.61271, 0.95932, 0.82688, 0.56001, 0.64199, 0.34946, 1.3269, 0.58127, 1.0039, 0.00142, 0.00142, 0.00142 + 263, 1.2758, 0.61523, 0.9586, 0.82744, 0.56006, 0.64238, 0.34951, 1.327, 0.5811, 1.004, 0.001387, 0.001387, 0.001387 + 264, 1.2824, 0.61845, 0.95818, 0.8279, 0.55942, 0.64239, 0.34957, 1.3272, 0.5813, 1.0039, 0.001354, 0.001354, 0.001354 + 265, 1.2762, 0.61366, 0.95706, 0.8287, 0.55929, 0.64228, 0.34947, 1.3272, 0.58107, 1.0039, 0.001321, 0.001321, 0.001321 + 266, 1.2807, 0.61507, 0.95664, 0.82774, 0.55952, 0.64258, 0.34954, 1.3272, 0.58129, 1.0039, 0.001288, 0.001288, 0.001288 + 267, 1.2714, 0.60982, 0.95798, 0.82847, 0.55928, 0.64241, 0.34979, 1.3271, 0.58129, 1.0038, 0.001255, 0.001255, 0.001255 + 268, 1.2727, 0.61025, 0.95609, 0.83076, 0.55888, 0.64255, 0.34974, 1.3273, 0.58119, 1.0039, 0.001222, 0.001222, 0.001222 + 269, 1.276, 0.61143, 0.95842, 0.82965, 0.55893, 0.64245, 0.34978, 1.3276, 0.58121, 1.004, 0.001189, 0.001189, 0.001189 + 270, 1.2735, 0.61147, 0.95654, 0.82926, 0.55863, 0.64251, 0.3499, 1.3277, 0.58163, 1.0039, 0.001156, 0.001156, 0.001156 + 271, 1.2714, 0.60847, 0.95571, 0.82997, 0.55854, 0.64248, 0.34989, 1.3276, 0.58144, 1.0038, 0.001123, 0.001123, 0.001123 + 272, 1.2699, 0.61123, 0.95451, 0.82883, 0.55885, 0.64262, 0.35004, 1.3277, 0.58135, 1.0038, 0.00109, 0.00109, 0.00109 + 273, 1.2736, 0.61214, 0.95547, 0.8288, 0.55873, 0.64279, 0.34999, 1.3282, 0.5814, 1.0039, 0.001057, 0.001057, 0.001057 + 274, 1.2728, 0.60935, 0.95188, 0.82797, 0.55938, 0.64302, 0.34996, 1.3284, 0.58143, 1.0038, 0.001024, 0.001024, 0.001024 + 275, 1.2681, 0.60683, 0.95302, 0.829, 0.55918, 0.64317, 0.35002, 1.3285, 0.58132, 1.0038, 0.000991, 0.000991, 0.000991 + 276, 1.2669, 0.60752, 0.95047, 0.82944, 0.55911, 0.64306, 0.35004, 1.3284, 0.5811, 1.0038, 0.000958, 0.000958, 0.000958 + 277, 1.2623, 0.60315, 0.95131, 0.8294, 0.55908, 0.64305, 0.34989, 1.3291, 0.58142, 1.0038, 0.000925, 0.000925, 0.000925 + 278, 1.2637, 0.60384, 0.95263, 0.82986, 0.55886, 0.64322, 0.35017, 1.3288, 0.58149, 1.0037, 0.000892, 0.000892, 0.000892 + 279, 1.2565, 0.60197, 0.9519, 0.82912, 0.55933, 0.64337, 0.35015, 1.3289, 0.58174, 1.0037, 0.000859, 0.000859, 0.000859 + 280, 1.2616, 0.60382, 0.95214, 0.82988, 0.55939, 0.64385, 0.35024, 1.329, 0.58166, 1.0037, 0.000826, 0.000826, 0.000826 + 281, 1.2603, 0.60163, 0.95026, 0.83005, 0.55893, 0.64375, 0.35017, 1.3292, 0.5818, 1.0037, 0.000793, 0.000793, 0.000793 + 282, 1.258, 0.60086, 0.95107, 0.83006, 0.55914, 0.64401, 0.35048, 1.3293, 0.58163, 1.0038, 0.00076, 0.00076, 0.00076 + 283, 1.259, 0.60176, 0.9523, 0.82992, 0.55928, 0.64398, 0.3504, 1.3294, 0.58141, 1.0038, 0.000727, 0.000727, 0.000727 + 284, 1.2575, 0.59749, 0.95011, 0.83031, 0.55918, 0.64389, 0.35026, 1.3295, 0.5815, 1.0038, 0.000694, 0.000694, 0.000694 + 285, 1.2563, 0.5975, 0.94853, 0.82958, 0.55977, 0.6441, 0.35033, 1.3295, 0.58122, 1.0037, 0.000661, 0.000661, 0.000661 + 286, 1.2556, 0.59814, 0.95015, 0.83046, 0.55928, 0.64411, 0.35052, 1.3297, 0.58101, 1.0038, 0.000628, 0.000628, 0.000628 + 287, 1.2569, 0.59836, 0.94949, 0.83075, 0.55859, 0.64377, 0.35045, 1.3298, 0.58096, 1.0038, 0.000595, 0.000595, 0.000595 + 288, 1.2467, 0.59551, 0.94928, 0.83142, 0.5582, 0.64347, 0.35043, 1.3301, 0.581, 1.0039, 0.000562, 0.000562, 0.000562 + 289, 1.2488, 0.59376, 0.94848, 0.83046, 0.5586, 0.64385, 0.35029, 1.3302, 0.58103, 1.0039, 0.000529, 0.000529, 0.000529 + 290, 1.2512, 0.59594, 0.94861, 0.83122, 0.5579, 0.64364, 0.35038, 1.3303, 0.58092, 1.0039, 0.000496, 0.000496, 0.000496 + 291, 1.2183, 0.55915, 0.94806, 0.83069, 0.55834, 0.64381, 0.35052, 1.3306, 0.58101, 1.004, 0.000463, 0.000463, 0.000463 + 292, 1.2135, 0.55606, 0.94714, 0.83051, 0.55849, 0.64382, 0.35047, 1.3307, 0.58106, 1.004, 0.00043, 0.00043, 0.00043 + 293, 1.2068, 0.5524, 0.94658, 0.83093, 0.558, 0.64399, 0.35041, 1.3307, 0.58107, 1.004, 0.000397, 0.000397, 0.000397 + 294, 1.2077, 0.55273, 0.94783, 0.83089, 0.5579, 0.64375, 0.35043, 1.3308, 0.58095, 1.0041, 0.000364, 0.000364, 0.000364 + 295, 1.2061, 0.55151, 0.94596, 0.83098, 0.55731, 0.64381, 0.35045, 1.3312, 0.58103, 1.0042, 0.000331, 0.000331, 0.000331 + 296, 1.1994, 0.54712, 0.94586, 0.83113, 0.55809, 0.64418, 0.35057, 1.3312, 0.5812, 1.0044, 0.000298, 0.000298, 0.000298 + 297, 1.1969, 0.54597, 0.94278, 0.83107, 0.55785, 0.64399, 0.35048, 1.3314, 0.5811, 1.0044, 0.000265, 0.000265, 0.000265 + 298, 1.1968, 0.54484, 0.94344, 0.83156, 0.55785, 0.64394, 0.35043, 1.3316, 0.5812, 1.0045, 0.000232, 0.000232, 0.000232 + 299, 1.1948, 0.54383, 0.9427, 0.83074, 0.5579, 0.64404, 0.35051, 1.3318, 0.5812, 1.0046, 0.000199, 0.000199, 0.000199 + 300, 1.1984, 0.54449, 0.94156, 0.83123, 0.55839, 0.64426, 0.35051, 1.3321, 0.5813, 1.0047, 0.000166, 0.000166, 0.000166 diff --git a/src/src/YOLOv8/runs/detect/train/results.png b/src/src/YOLOv8/runs/detect/train/results.png new file mode 100644 index 0000000..7c2364a Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/results.png differ diff --git a/src/src/YOLOv8/runs/detect/train/train_batch0.jpg b/src/src/YOLOv8/runs/detect/train/train_batch0.jpg new file mode 100644 index 0000000..e4a058f Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/train_batch0.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/train_batch1.jpg b/src/src/YOLOv8/runs/detect/train/train_batch1.jpg new file mode 100644 index 0000000..58bf972 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/train_batch1.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/train_batch2.jpg b/src/src/YOLOv8/runs/detect/train/train_batch2.jpg new file mode 100644 index 0000000..cecb409 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/train_batch2.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/train_batch987740.jpg b/src/src/YOLOv8/runs/detect/train/train_batch987740.jpg new file mode 100644 index 0000000..aecd910 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/train_batch987740.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/train_batch987741.jpg b/src/src/YOLOv8/runs/detect/train/train_batch987741.jpg new file mode 100644 index 0000000..d68fb2b Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/train_batch987741.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/train_batch987742.jpg b/src/src/YOLOv8/runs/detect/train/train_batch987742.jpg new file mode 100644 index 0000000..8e3f538 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/train_batch987742.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/val_batch0_labels.jpg b/src/src/YOLOv8/runs/detect/train/val_batch0_labels.jpg new file mode 100644 index 0000000..d1af7bb Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/val_batch0_labels.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/val_batch0_pred.jpg b/src/src/YOLOv8/runs/detect/train/val_batch0_pred.jpg new file mode 100644 index 0000000..0d4fb2b Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/val_batch0_pred.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/val_batch1_labels.jpg b/src/src/YOLOv8/runs/detect/train/val_batch1_labels.jpg new file mode 100644 index 0000000..5c19f5a Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/val_batch1_labels.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/val_batch1_pred.jpg b/src/src/YOLOv8/runs/detect/train/val_batch1_pred.jpg new file mode 100644 index 0000000..68129ec Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/val_batch1_pred.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/val_batch2_labels.jpg b/src/src/YOLOv8/runs/detect/train/val_batch2_labels.jpg new file mode 100644 index 0000000..2930776 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/val_batch2_labels.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/val_batch2_pred.jpg b/src/src/YOLOv8/runs/detect/train/val_batch2_pred.jpg new file mode 100644 index 0000000..5d5199d Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/val_batch2_pred.jpg differ diff --git a/src/src/YOLOv8/runs/detect/train/weights/best.pt b/src/src/YOLOv8/runs/detect/train/weights/best.pt new file mode 100644 index 0000000..44c52c2 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/weights/best.pt differ diff --git a/src/src/YOLOv8/runs/detect/train/weights/last.pt b/src/src/YOLOv8/runs/detect/train/weights/last.pt new file mode 100644 index 0000000..4aca144 Binary files /dev/null and b/src/src/YOLOv8/runs/detect/train/weights/last.pt differ diff --git a/src/src/YOLOv8/setup.py b/src/src/YOLOv8/setup.py new file mode 100644 index 0000000..7d9917b --- /dev/null +++ b/src/src/YOLOv8/setup.py @@ -0,0 +1,105 @@ +# Ultralytics YOLO 🚀, AGPL-3.0 license + +import re +from pathlib import Path + +from setuptools import setup + +# Settings +FILE = Path(__file__).resolve() +PARENT = FILE.parent # root directory +README = (PARENT / 'README.md').read_text(encoding='utf-8') + + +def get_version(): + """ + Retrieve the version number from the 'ultralytics/__init__.py' file. + + Returns: + (str): The version number extracted from the '__version__' attribute in the 'ultralytics/__init__.py' file. + """ + file = PARENT / 'ultralytics/__init__.py' + return re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', file.read_text(encoding='utf-8'), re.M)[1] + + +def parse_requirements(file_path: Path): + """ + Parse a requirements.txt file, ignoring lines that start with '#' and any text after '#'. + + Args: + file_path (str | Path): Path to the requirements.txt file. + + Returns: + (List[str]): List of parsed requirements. + """ + + requirements = [] + for line in Path(file_path).read_text().splitlines(): + line = line.strip() + if line and not line.startswith('#'): + requirements.append(line.split('#')[0].strip()) # ignore inline comments + + return requirements + + +setup( + name='ultralytics', # name of pypi package + version=get_version(), # version of pypi package + python_requires='>=3.8', + license='AGPL-3.0', + description=('Ultralytics YOLOv8 for SOTA object detection, multi-object tracking, instance segmentation, ' + 'pose estimation and image classification.'), + long_description=README, + long_description_content_type='text/markdown', + url='https://github.com/ultralytics/ultralytics', + project_urls={ + 'Bug Reports': 'https://github.com/ultralytics/ultralytics/issues', + 'Funding': 'https://ultralytics.com', + 'Source': 'https://github.com/ultralytics/ultralytics'}, + author='Ultralytics', + author_email='hello@ultralytics.com', + packages=['ultralytics'] + [str(x) for x in Path('ultralytics').rglob('*/') if x.is_dir() and '__' not in str(x)], + package_data={ + '': ['*.yaml'], + 'ultralytics.assets': ['*.jpg']}, + include_package_data=True, + install_requires=parse_requirements(PARENT / 'requirements.txt'), + extras_require={ + 'dev': [ + 'ipython', + 'check-manifest', + 'pre-commit', + 'pytest', + 'pytest-cov', + 'coverage', + 'mkdocs-material', + 'mkdocstrings[python]', + 'mkdocs-redirects', # for 301 redirects + 'mkdocs-ultralytics-plugin>=0.0.32', # for meta descriptions and images, dates and authors + ], + 'export': [ + 'coremltools>=7.0', + 'openvino-dev>=2023.0', + 'tensorflow<=2.13.1', + 'tensorflowjs', # automatically installs tensorflow + ], }, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Topic :: Software Development', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'Topic :: Scientific/Engineering :: Image Recognition', + 'Operating System :: POSIX :: Linux', + 'Operating System :: MacOS', + 'Operating System :: Microsoft :: Windows', ], + keywords='machine-learning, deep-learning, vision, ML, DL, AI, YOLO, YOLOv3, YOLOv5, YOLOv8, HUB, Ultralytics', + entry_points={'console_scripts': ['yolo = ultralytics.cfg:entrypoint', 'ultralytics = ultralytics.cfg:entrypoint']}) diff --git a/src/src/YOLOv8/train.py b/src/src/YOLOv8/train.py new file mode 100644 index 0000000..1347d68 --- /dev/null +++ b/src/src/YOLOv8/train.py @@ -0,0 +1,15 @@ +#coding:utf-8 +from ultralytics import YOLO + +# 加载模型 +model = YOLO("yolov8n.pt") # 加载预训练模型 +# Use the model +if __name__ == '__main__': + # Use the model + results = model.train(data='datasets/faceData/data.yaml', epochs=300,device=0, batch=4,imgsz=640,cache=True,workers=12) # 训练模型 + # 将模型转为onnx格式 + # success = model.export(format='onnx') + + + + diff --git a/src/src/YOLOv8/yolov8m.pt b/src/src/YOLOv8/yolov8m.pt new file mode 100644 index 0000000..cdb22b9 Binary files /dev/null and b/src/src/YOLOv8/yolov8m.pt differ diff --git a/src/src/YOLOv8/yolov8n.pt b/src/src/YOLOv8/yolov8n.pt new file mode 100644 index 0000000..d61ef50 Binary files /dev/null and b/src/src/YOLOv8/yolov8n.pt differ diff --git a/src/src/YOLOv8/yolov8s.pt b/src/src/YOLOv8/yolov8s.pt new file mode 100644 index 0000000..a00e856 Binary files /dev/null and b/src/src/YOLOv8/yolov8s.pt differ diff --git a/src/src/YOLOv8/目录文件说明.png b/src/src/YOLOv8/目录文件说明.png new file mode 100644 index 0000000..224a22e Binary files /dev/null and b/src/src/YOLOv8/目录文件说明.png differ diff --git a/src/src/YOLOv8/程序运行说明文档.txt b/src/src/YOLOv8/程序运行说明文档.txt new file mode 100644 index 0000000..df7101e --- /dev/null +++ b/src/src/YOLOv8/程序运行说明文档.txt @@ -0,0 +1,35 @@ +:python3.9 +ϵͳ濪pyqt5 + +---------Ŀļ˵--------- +Ŀ¼еġĿ¼ļ˵.pngͼƬ + +ò衾 +ע⣺·òҪġ + +---------һװpython3.9--------- +һƼ +Ȱװanancondaַhttps://www.anaconda.com/download +װɺcondaڣʹ"conda create -n py39 python=3.9"3.9⻷ +Ȼ󼤻⻷conda activate py39,Ȼٽеڶİװ + +ֱpythonpyhon3.9exeļװɡ + +---------ڶװ⡿--------- +ע⣺ǰȽĿĿ¼·£ȻʾҲļ +һƼ +ֱinstallPackages.pyһװĽűΪpython installPackages.py +: · +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple + +---------г--------- +ɺֱMainProgram.pyļɴ򿪳 +Ϊpython MainProgram.py + + +---------ģѵ--------- +ļdatasets/faceData/data.yamltrain,valݼľ·ΪԼĿݼľ· + +Ȼtrain.pyļɿʼģѵ,ѵĬϱrunsĿ¼С + + diff --git a/src/src/car_code/README.md b/src/src/car_code/README.md new file mode 100644 index 0000000..3ed58be --- /dev/null +++ b/src/src/car_code/README.md @@ -0,0 +1,302 @@ +# UVPTCCS 机器人系统 README 文档 + +## 项目概述 +UVPTCCS (Unmanned Vehicle Positioning Tracking Control and Communication System) 是一个基于ROS2的无人车定位、追踪、控制和通信系统。该系统提供了完整的机器人功能包集合,包括底盘驱动、视觉追踪、导航建图、SLAM、位姿发布等功能。 + +## 系统架构 +``` +UVPTCCS/ +├── car_astra/ # 颜色追踪与识别包 +├── car_bringup/ # 机器人底盘驱动包 +├── car_nav/ # 机器人导航包 +├── car_slam/ # SLAM与点云处理包 +├── robot_pose_publisher_ros2/ # 机器人位姿发布包 +└── laserscan_to_point_pulisher/ # 激光雷达点云转换包 +``` + +## 包功能说明 + +### 1. car_astra - 颜色追踪与识别包 +**主要功能**: +- 基于深度摄像头的颜色目标识别 +- HSV颜色空间参数调整 +- 实时颜色目标追踪和跟随 +- PID控制器优化运动控制 + +**核心节点**: +- `colorHSV`: 颜色识别和HSV参数调整 +- `colorTracker`: 基于深度信息的目标追踪 + +**应用场景**: 目标跟随、颜色识别、视觉导航 + +### 2. car_bringup - 机器人底盘驱动包 +**主要功能**: +- 支持多种机器人型号(X1、X3、R2) +- 底层运动控制和传感器数据发布 +- RGB灯光和蜂鸣器控制 +- 自主巡逻和校准功能 + +**核心节点**: +- `Mcnamu_driver_X3`: X3型机器人驱动 +- `Mcnamu_driver_x1`: X1型机器人驱动 +- `Ackman_driver_R2`: R2型阿克曼转向机器人驱动 +- `patrol_*`: 各种巡逻功能节点 + +**应用场景**: 机器人底层控制、传感器集成、自主巡逻 + +### 3. car_nav - 机器人导航包 +**主要功能**: +- 多种SLAM建图算法(Gmapping、Cartographer、RTABMap) +- 路径规划和自主导航(DWA、TEB) +- 激光雷达数据处理和滤波 +- 地图保存和加载 + +**核心节点**: +- `scan_filter`: 激光雷达数据滤波 + +**应用场景**: 自主导航、环境建图、路径规划 + +### 4. car_slam - SLAM与点云处理包 +**主要功能**: +- 基于ORB-SLAM的视觉SLAM +- RGB-D相机点云建图 +- 八叉树地图构建 +- 实时点云处理和可视化 + +**核心节点**: +- `pointcloud_mapping`: 点云建图节点 + +**应用场景**: 视觉SLAM、3D建图、环境感知 + +### 5. robot_pose_publisher_ros2 - 机器人位姿发布包 +**主要功能**: +- 基于TF变换的位姿计算 +- 实时机器人位姿发布 +- 支持多种消息格式 + +**核心节点**: +- `robot_pose_publisher`: 位姿发布节点 + +**应用场景**: 位姿监控、导航状态反馈、数据记录 + +### 6. laserscan_to_point_pulisher - 激光雷达点云转换包 +**主要功能**: +- 激光雷达数据转换为点云 +- 坐标系转换和数据处理 +- 实时数据可视化 + +**核心节点**: +- `laserscanToPointPublish`: 激光雷达点云转换节点 + +**应用场景**: 数据转换、可视化、环境感知 + +## 系统要求 + +### 硬件要求 +- 支持的机器人平台: X1、X3、R2系列 +- 传感器: 激光雷达、RGB-D相机、IMU +- 计算平台: 支持ROS2的Linux系统 + +### 软件依赖 +- **操作系统**: Ubuntu 20.04/22.04 +- **ROS版本**: ROS2 Foxy/Galactic/Humble +- **核心库**: + - OpenCV (计算机视觉) + - PCL (点云处理) + - Nav2 (导航功能) + - tf2_ros (坐标变换) + - Cartographer (SLAM) + - RTABMap (视觉SLAM) + +## 快速开始 + +### 1. 环境准备 +```bash +# 安装ROS2 +sudo apt update +sudo apt install ros-humble-desktop-full + +# 安装依赖 +sudo apt install python3-colcon-common-extensions +sudo apt install ros-humble-nav2-bringup +sudo apt install ros-humble-cartographer-ros +sudo apt install ros-humble-rtabmap-ros +``` + +### 2. 编译系统 +```bash +# 创建工作空间 +mkdir -p ~/robot_ws/src +cd ~/robot_ws/src + +# 克隆代码 +git clone + +# 编译 +cd ~/robot_ws +colcon build --symlink-install + +# 设置环境 +source install/setup.bash +``` + +### 3. 启动系统 + +#### 启动机器人底盘 +```bash +# X3机器人 +ros2 launch car_bringup car_bringup_X3_launch.py + +# R2机器人 +ros2 launch car_bringup car_bringup_R2_launch.py +``` + +#### 启动颜色追踪 +```bash +ros2 launch car_astra colorTracker_X3.launch.py +``` + +#### 启动导航系统 +```bash +# SLAM建图 +ros2 launch car_nav map_gmapping_launch.py + +# 自主导航 +ros2 launch car_nav navigation_dwa_launch.py +``` + +#### 启动SLAM系统 +```bash +# 点云建图 +ros2 launch car_slam orbslam_pcl_map_launch.py + +# 八叉树建图 +ros2 launch car_slam orbslam_pcl_octomap_launch.py +``` + +## 使用指南 + +### 1. 机器人控制 +```bash +# 手动控制 +ros2 run teleop_twist_keyboard teleop_twist_keyboard + +# 查看机器人状态 +ros2 topic echo /robot_pose +ros2 topic echo /voltage +``` + +### 2. 建图导航 +```bash +# 开始建图 +export RPLIDAR_TYPE=a1 +ros2 launch car_nav map_gmapping_launch.py + +# 保存地图 +ros2 launch car_nav save_map_launch.py + +# 开始导航 +ros2 launch car_nav navigation_dwa_launch.py +``` + +### 3. 视觉功能 +```bash +# 颜色识别 +ros2 run car_astra colorHSV + +# 目标追踪 +ros2 run car_astra colorTracker +``` + +### 4. 数据可视化 +```bash +# 启动RViz +ros2 run rviz2 rviz2 + +# 查看点云 +ros2 launch car_slam display_pcl_launch.py + +# 查看导航 +ros2 launch car_nav display_nav_launch.py +``` + +## 配置说明 + +### 1. 机器人参数配置 +根据实际机器人型号修改对应的参数文件: +- `car_bringup/param/`: 机器人底盘参数 +- `car_nav/params/`: 导航算法参数 +- `car_slam/params/`: SLAM算法参数 + +### 2. 传感器校准 +- 激光雷达: 使用`calibrate_*`节点进行校准 +- 相机: 修改`car_slam/params/`中的相机参数 +- IMU: 调整`car_bringup/param/imu_filter_param.yaml` + +### 3. 坐标系配置 +确保以下坐标系正确配置: +- `base_link`: 机器人基座 +- `base_footprint`: 机器人地面投影 +- `laser`: 激光雷达坐标系 +- `camera`: 相机坐标系 +- `map`: 全局地图坐标系 +- `odom`: 里程计坐标系 + +## 故障排除 + +### 1. 常见问题 +- **TF变换错误**: 检查坐标系配置和时间同步 +- **激光雷达无数据**: 检查设备连接和驱动 +- **相机图像异常**: 检查相机参数和USB连接 +- **导航失败**: 检查地图质量和参数配置 + +### 2. 调试工具 +```bash +# 查看TF树 +ros2 run tf2_tools view_frames + +# 检查话题 +ros2 topic list +ros2 topic echo /topic_name + +# 查看节点状态 +ros2 node list +ros2 node info /node_name + +# 参数调试 +ros2 param list /node_name +ros2 param get /node_name parameter_name +``` + +## 开发指南 + +### 1. 代码结构 +- 遵循ROS2标准包结构 +- 使用Python或C++开发节点 +- 添加适当的错误处理和日志 + +### 2. 测试验证 +- 单元测试: 使用pytest或gtest +- 集成测试: 验证节点间通信 +- 性能测试: 监控CPU和内存使用 + +### 3. 文档维护 +- 更新README文档 +- 添加代码注释 +- 记录参数说明 + +## 许可证 +本项目遵循开源许可证,具体请查看LICENSE文件。 + +## 贡献指南 +欢迎提交问题报告和改进建议。请遵循以下步骤: +1. Fork项目 +2. 创建特性分支 +3. 提交更改 +4. 发起Pull Request + +## 联系方式 +如有问题或建议,请通过以下方式联系: +- 邮箱: [维护者邮箱] +- 项目地址: [GitHub链接] +- 文档地址: [文档链接] \ No newline at end of file diff --git a/src/src/car_code/car_astra/README.md b/src/src/car_code/car_astra/README.md new file mode 100644 index 0000000..bf07bca --- /dev/null +++ b/src/src/car_code/car_astra/README.md @@ -0,0 +1,110 @@ +# ROS2 颜色追踪与识别包 README 文档 + +## 包名称 +**car_astra** + +## 功能描述 +该包主要用于基于深度摄像头的颜色目标追踪和识别功能。包含颜色识别、HSV颜色空间处理、目标追踪等功能,适用于机器人视觉导航和目标跟随应用。 + +## 节点列表 + +### 1. colorHSV (颜色识别节点) +- **节点名称**: `coloridentify` +- **可执行文件**: `colorHSV` +- **功能**: 实时颜色识别和HSV参数调整 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/Current_point` | `yahboomcar_msgs/msg/Position` | 发布检测到的颜色目标位置信息 | +| `/cmd_vel` | `geometry_msgs/msg/Twist` | 发布运动控制命令 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| 摄像头数据 | `sensor_msgs/msg/Image` | 通过OpenCV VideoCapture获取 | + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `Hmin` | int | 0 | HSV色彩空间H分量最小值 | +| `Smin` | int | 85 | HSV色彩空间S分量最小值 | +| `Vmin` | int | 126 | HSV色彩空间V分量最小值 | +| `Hmax` | int | 9 | HSV色彩空间H分量最大值 | +| `Smax` | int | 253 | HSV色彩空间S分量最大值 | +| `Vmax` | int | 253 | HSV色彩空间V分量最大值 | +| `refresh` | bool | false | 是否刷新HSV参数 | + +### 2. colorTracker (颜色追踪节点) +- **节点名称**: `color_tracker` +- **可执行文件**: `colorTracker` +- **功能**: 基于深度信息的颜色目标追踪和跟随 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/cmd_vel` | `geometry_msgs/msg/Twist` | 发布机器人运动控制命令 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 回调函数 | 描述 | +|:---------|:---------|:---------|:-----| +| `/camera/depth/image_raw` | `sensor_msgs/msg/Image` | `depth_img_Callback` | 订阅深度图像数据 | +| `/JoyState` | `std_msgs/msg/Bool` | `JoyStateCallback` | 订阅手柄状态 | +| `/Current_point` | `yahboomcar_msgs/msg/Position` | `positionCallback` | 订阅目标位置信息 | + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `linear_Kp` | double | 3.0 | 线性PID控制器比例系数 | +| `linear_Ki` | double | 0.0 | 线性PID控制器积分系数 | +| `linear_Kd` | double | 1.0 | 线性PID控制器微分系数 | +| `angular_Kp` | double | 0.5 | 角度PID控制器比例系数 | +| `angular_Ki` | double | 0.0 | 角度PID控制器积分系数 | +| `angular_Kd` | double | 2.0 | 角度PID控制器微分系数 | +| `scale` | int | 1000 | 距离缩放因子 | +| `minDistance` | double | 1.0 | 最小跟随距离(米) | + +## 启动文件 + +### colorTracker_X3.launch.py +启动颜色追踪系统,包含: +- 机器人底盘驱动节点 +- 颜色识别节点 + +## 使用说明 + +### 1. 颜色识别模式 +```bash +# 启动颜色识别节点 +ros2 run car_astra colorHSV +``` + +**操作说明**: +- 按 `i` 键进入识别模式 +- 按 `r` 键重置参数 +- 按空格键进入追踪模式 +- 按 `q` 键退出程序 + +### 2. 颜色追踪模式 +```bash +# 启动完整的颜色追踪系统 +ros2 launch car_astra colorTracker_X3.launch.py +``` + +### 3. HSV参数调整 +- 在识别模式下,可以通过鼠标选择区域来自动获取HSV参数 +- 参数会自动保存到 `colorHSV.text` 文件中 +- 可以通过ROS2参数服务器动态调整HSV参数 + +## 依赖项 +- OpenCV (计算机视觉库) +- PCL (点云处理库) +- ROS2 (机器人操作系统) +- yahboomcar_msgs (自定义消息类型) +- cv_bridge (OpenCV与ROS图像转换) + +## 注意事项 +1. 需要连接深度摄像头(如Astra系列) +2. 确保光照条件良好,避免颜色识别误差 +3. 根据实际环境调整HSV参数范围 +4. 追踪模式下请确保安全距离,避免碰撞 \ No newline at end of file diff --git a/src/src/car_code/car_bringup/README.md b/src/src/car_code/car_bringup/README.md new file mode 100644 index 0000000..00e8071 --- /dev/null +++ b/src/src/car_code/car_bringup/README.md @@ -0,0 +1,218 @@ +# ROS2 机器人底盘驱动包 README 文档 + +## 包名称 +**car_bringup** + +## 功能描述 +该包是机器人底盘驱动的核心包,提供不同型号机器人的底层控制接口,包括运动控制、IMU数据发布、RGB灯光控制、蜂鸣器控制等功能。支持多种机器人型号(X1、X3、R2)的驱动程序。 + +## 节点列表 + +### 1. Mcnamu_driver_X3 (X3型机器人驱动节点) +- **节点名称**: `driver_node` +- **可执行文件**: `Mcnamu_driver_X3` +- **功能**: X3型机器人底盘驱动控制 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/edition` | `std_msgs/msg/Float32` | 发布版本信息 | +| `/voltage` | `std_msgs/msg/Float32` | 发布电池电压信息 | +| `/joint_states` | `sensor_msgs/msg/JointState` | 发布关节状态信息 | +| `/vel_raw` | `geometry_msgs/msg/Twist` | 发布原始速度信息 | +| `/imu/data_raw` | `sensor_msgs/msg/Imu` | 发布IMU原始数据 | +| `/imu/mag` | `sensor_msgs/msg/MagneticField` | 发布磁力计数据 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 回调函数 | 描述 | +|:---------|:---------|:---------|:-----| +| `/cmd_vel` | `geometry_msgs/msg/Twist` | `cmd_vel_callback` | 订阅运动控制命令 | +| `/RGBLight` | `std_msgs/msg/Int32` | `RGBLightcallback` | 订阅RGB灯光控制 | +| `/Buzzer` | `std_msgs/msg/Bool` | `Buzzercallback` | 订阅蜂鸣器控制 | + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `car_type` | string | "X3" | 机器人型号 | +| `imu_link` | string | "imu_link" | IMU坐标系名称 | +| `Prefix` | string | "" | 话题前缀 | +| `xlinear_limit` | double | 1.0 | X轴线速度限制 | +| `ylinear_limit` | double | 1.0 | Y轴线速度限制 | +| `angular_limit` | double | 5.0 | 角速度限制 | + +### 2. Mcnamu_driver_x1 (X1型机器人驱动节点) +- **节点名称**: `driver_node` +- **可执行文件**: `Mcnamu_driver_x1` +- **功能**: X1型机器人底盘驱动控制 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/edition` | `std_msgs/msg/Float32` | 发布版本信息 | +| `/voltage` | `std_msgs/msg/Float32` | 发布电池电压信息 | +| `/joint_states` | `sensor_msgs/msg/JointState` | 发布关节状态信息 | +| `/vel_raw` | `geometry_msgs/msg/Twist` | 发布原始速度信息 | +| `/imu/data_raw` | `sensor_msgs/msg/Imu` | 发布IMU原始数据 | +| `/imu/mag` | `sensor_msgs/msg/MagneticField` | 发布磁力计数据 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 回调函数 | 描述 | +|:---------|:---------|:---------|:-----| +| `/cmd_vel` | `geometry_msgs/msg/Twist` | `cmd_vel_callback` | 订阅运动控制命令 | +| `/RGBLight` | `std_msgs/msg/Int32` | `RGBLightcallback` | 订阅RGB灯光控制 | +| `/Buzzer` | `std_msgs/msg/Bool` | `Buzzercallback` | 订阅蜂鸣器控制 | + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `car_type` | string | "X1" | 机器人型号 | +| `imu_link` | string | "imu_link" | IMU坐标系名称 | +| `nav_use_rotvel` | bool | false | 导航是否使用旋转速度 | + +### 3. Ackman_driver_R2 (R2型机器人驱动节点) +- **节点名称**: `driver_node` +- **可执行文件**: `Ackman_driver_R2` +- **功能**: R2型阿克曼转向机器人底盘驱动控制 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/edition` | `std_msgs/msg/Float32` | 发布版本信息 | +| `/voltage` | `std_msgs/msg/Float32` | 发布电池电压信息 | +| `/joint_states` | `sensor_msgs/msg/JointState` | 发布关节状态信息 | +| `/vel_raw` | `geometry_msgs/msg/Twist` | 发布原始速度信息 | +| `/imu/data_raw` | `sensor_msgs/msg/Imu` | 发布IMU原始数据 | +| `/imu/mag` | `sensor_msgs/msg/MagneticField` | 发布磁力计数据 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 回调函数 | 描述 | +|:---------|:---------|:---------|:-----| +| `/cmd_vel` | `geometry_msgs/msg/Twist` | `cmd_vel_callback` | 订阅运动控制命令 | +| `/RGBLight` | `std_msgs/msg/Int32` | `RGBLightcallback` | 订阅RGB灯光控制 | +| `/Buzzer` | `std_msgs/msg/Bool` | `Buzzercallback` | 订阅蜂鸣器控制 | + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `car_type` | string | "R2" | 机器人型号 | +| `imu_link` | string | "imu_link" | IMU坐标系名称 | +| `nav_use_rotvel` | bool | false | 导航是否使用旋转速度 | + +## 巡逻功能节点 + +### 4. patrol_4ROS (四轮机器人巡逻节点) +- **节点名称**: `patrol_node` +- **可执行文件**: `patrol_4ROS` +- **功能**: 基于激光雷达的自主巡逻功能 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/cmd_vel` | `geometry_msgs/msg/Twist` | 发布运动控制命令 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 回调函数 | 描述 | +|:---------|:---------|:---------|:-----| +| `/scan` | `sensor_msgs/msg/LaserScan` | `LaserScanCallback` | 订阅激光雷达数据 | + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `odom_frame` | string | "odom" | 里程计坐标系名称 | +| `base_frame` | string | "base_footprint" | 机器人基座坐标系名称 | + +### 5. patrol_a1_X3 (A1雷达X3机器人巡逻节点) +- **节点名称**: `patrol_node` +- **可执行文件**: `patrol_a1_X3` +- **功能**: 基于A1激光雷达的X3机器人巡逻 + +### 6. patrol_a1_R2 (A1雷达R2机器人巡逻节点) +- **节点名称**: `patrol_node` +- **可执行文件**: `patrol_a1_R2` +- **功能**: 基于A1激光雷达的R2机器人巡逻 + +### 7. patrol_4ROS_R2 (四轮雷达R2机器人巡逻节点) +- **节点名称**: `patrol_node` +- **可执行文件**: `patrol_4ROS_R2` +- **功能**: 基于四轮激光雷达的R2机器人巡逻 + +## 校准功能节点 + +### 8. calibrate_linear_X3 (X3线性校准节点) +- **可执行文件**: `calibrate_linear_X3` +- **功能**: X3机器人线性运动校准 + +### 9. calibrate_angular_X3 (X3角度校准节点) +- **可执行文件**: `calibrate_angular_X3` +- **功能**: X3机器人角度运动校准 + +### 10. calibrate_linear_R2 (R2线性校准节点) +- **可执行文件**: `calibrate_linear_R2` +- **功能**: R2机器人线性运动校准 + +### 11. calibrate_angular_R2 (R2角度校准节点) +- **可执行文件**: `calibrate_angular_R2` +- **功能**: R2机器人角度运动校准 + +## 启动文件 + +### car_bringup_R2_launch.py +启动R2机器人完整系统,包含: +- R2机器人驱动节点 +- 机器人状态发布节点 +- IMU滤波节点 +- EKF定位节点 +- 手柄控制节点 + +## 使用说明 + +### 1. 启动机器人驱动 +```bash +# 启动X3机器人 +ros2 run car_bringup Mcnamu_driver_X3 + +# 启动X1机器人 +ros2 run car_bringup Mcnamu_driver_x1 + +# 启动R2机器人 +ros2 run car_bringup Ackman_driver_R2 +``` + +### 2. 启动完整系统 +```bash +# 启动R2机器人完整系统 +ros2 launch car_bringup car_bringup_R2_launch.py +``` + +### 3. 控制机器人运动 +```bash +# 发布运动控制命令 +ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.5, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}" +``` + +### 4. 控制RGB灯光 +```bash +# 控制RGB灯光效果 (0-6不同效果) +ros2 topic pub /RGBLight std_msgs/msg/Int32 "data: 1" +``` + +### 5. 控制蜂鸣器 +```bash +# 打开蜂鸣器 +ros2 topic pub /Buzzer std_msgs/msg/Bool "data: true" +``` + +## 依赖项 +- ROS2 (机器人操作系统) +- Rosmaster_Lib (机器人底层控制库) +- tf2_ros (坐标变换库) +- sensor_msgs (传感器消息类型) +- geometry_msgs (几何消息类型) +- PyKDL (运动学和动力学库) + +## 注意事项 +1. 确保机器人硬件连接正确 +2. 根据实际机器人型号选择对应的驱动节点 +3. 校准功能需要在开阔平坦的环境中进行 +4. 巡逻功能需要激光雷达正常工作 +5. 注意电池电压监控,低电压时及时充电 \ No newline at end of file diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/Ackman_driver_R2.py b/src/src/car_code/car_bringup/car_bringup/Ackman_driver_R2.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/Ackman_driver_R2.py rename to src/src/car_code/car_bringup/car_bringup/Ackman_driver_R2.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/Mcnamu_driver_X3.py b/src/src/car_code/car_bringup/car_bringup/Mcnamu_driver_X3.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/Mcnamu_driver_X3.py rename to src/src/car_code/car_bringup/car_bringup/Mcnamu_driver_X3.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/Mcnamu_driver_x1.py b/src/src/car_code/car_bringup/car_bringup/Mcnamu_driver_x1.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/Mcnamu_driver_x1.py rename to src/src/car_code/car_bringup/car_bringup/Mcnamu_driver_x1.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/__init__.py b/src/src/car_code/car_bringup/car_bringup/__init__.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/__init__.py rename to src/src/car_code/car_bringup/car_bringup/__init__.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/calibrate_angular_R2.py b/src/src/car_code/car_bringup/car_bringup/calibrate_angular_R2.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/calibrate_angular_R2.py rename to src/src/car_code/car_bringup/car_bringup/calibrate_angular_R2.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/calibrate_angular_X3.py b/src/src/car_code/car_bringup/car_bringup/calibrate_angular_X3.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/calibrate_angular_X3.py rename to src/src/car_code/car_bringup/car_bringup/calibrate_angular_X3.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/calibrate_linear_R2.py b/src/src/car_code/car_bringup/car_bringup/calibrate_linear_R2.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/calibrate_linear_R2.py rename to src/src/car_code/car_bringup/car_bringup/calibrate_linear_R2.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/calibrate_linear_X3.py b/src/src/car_code/car_bringup/car_bringup/calibrate_linear_X3.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/calibrate_linear_X3.py rename to src/src/car_code/car_bringup/car_bringup/calibrate_linear_X3.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/patrol_4ROS.py b/src/src/car_code/car_bringup/car_bringup/patrol_4ROS.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/patrol_4ROS.py rename to src/src/car_code/car_bringup/car_bringup/patrol_4ROS.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/patrol_4ROS_R2.py b/src/src/car_code/car_bringup/car_bringup/patrol_4ROS_R2.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/patrol_4ROS_R2.py rename to src/src/car_code/car_bringup/car_bringup/patrol_4ROS_R2.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/patrol_a1_R2.py b/src/src/car_code/car_bringup/car_bringup/patrol_a1_R2.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/patrol_a1_R2.py rename to src/src/car_code/car_bringup/car_bringup/patrol_a1_R2.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/patrol_a1_X3.py b/src/src/car_code/car_bringup/car_bringup/patrol_a1_X3.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/patrol_a1_X3.py rename to src/src/car_code/car_bringup/car_bringup/patrol_a1_X3.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/test.py b/src/src/car_code/car_bringup/car_bringup/test.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/test.py rename to src/src/car_code/car_bringup/car_bringup/test.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/transform_utils.py b/src/src/car_code/car_bringup/car_bringup/transform_utils.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/transform_utils.py rename to src/src/car_code/car_bringup/car_bringup/transform_utils.py diff --git a/src/src/car_code/car_bringup/yahboomcar_bringup/untitled.py b/src/src/car_code/car_bringup/car_bringup/untitled.py similarity index 100% rename from src/src/car_code/car_bringup/yahboomcar_bringup/untitled.py rename to src/src/car_code/car_bringup/car_bringup/untitled.py diff --git a/src/src/car_code/car_nav/README.md b/src/src/car_code/car_nav/README.md new file mode 100644 index 0000000..a99b470 --- /dev/null +++ b/src/src/car_code/car_nav/README.md @@ -0,0 +1,249 @@ +# ROS2 机器人导航包 README 文档 + +## 包名称 +**car_nav** + +## 功能描述 +该包提供机器人导航和建图的完整解决方案,包括SLAM建图、路径规划、自主导航等功能。支持多种建图算法(Gmapping、Cartographer、RTABMap)和导航算法(DWA、TEB)。 + +## 节点列表 + +### 1. scan_filter (激光雷达滤波节点) +- **节点名称**: `scan_dilute` +- **可执行文件**: `scan_filter` +- **功能**: 激光雷达数据降采样和滤波处理 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/downsampled_scan` | `sensor_msgs/msg/LaserScan` | 发布降采样后的激光雷达数据 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 回调函数 | 描述 | +|:---------|:---------|:---------|:-----| +| `/scan` | `sensor_msgs/msg/LaserScan` | `laserCallback` | 订阅原始激光雷达数据 | + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `multiple` | int | 2 | 降采样倍数 | + +## 启动文件 + +### 建图相关启动文件 + +#### 1. map_gmapping_launch.py +- **功能**: 启动Gmapping SLAM建图 +- **支持的雷达类型**: A1、S2、4ROS +- **环境变量**: `RPLIDAR_TYPE` + +#### 2. map_gmapping_a1_launch.py +- **功能**: 基于A1激光雷达的Gmapping建图 + +#### 3. map_gmapping_4ros_s2_launch.py +- **功能**: 基于4ROS/S2激光雷达的Gmapping建图 + +#### 4. map_cartographer_launch.py +- **功能**: 启动Cartographer SLAM建图 +- **配置文件**: `lds_2d.lua` + +#### 5. map_rtabmap_launch.py +- **功能**: 启动RTABMap SLAM建图 +- **包含组件**: + - 激光雷达启动 + - RTABMap同步节点 + +#### 6. save_map_launch.py +- **功能**: 保存建图结果 +- **默认保存路径**: `maps/yahboomcar` + +### 导航相关启动文件 + +#### 7. navigation_dwa_launch.py +- **功能**: 基于DWA算法的自主导航 +- **参数文件**: `dwa_nav_params.yaml` +- **地图文件**: `maps/yahboomcar.yaml` + +#### 8. navigation_teb_launch.py +- **功能**: 基于TEB算法的自主导航 +- **参数文件**: `teb_nav_params.yaml` +- **地图文件**: `maps/yahboomcar.yaml` + +#### 9. navigation_rtabmap_launch.py +- **功能**: 基于RTABMap的自主导航 +- **包含组件**: + - 激光雷达启动 + - RTABMap定位 + - RTABMap导航 + +#### 10. rtabmap_nav_launch.py +- **功能**: RTABMap导航系统 +- **参数文件**: `rtabmap_nav_params.yaml` + +### 定位相关启动文件 + +#### 11. rtabmap_localization_launch.py +- **功能**: RTABMap定位系统 + +#### 12. rtabmap_sync_launch.py +- **功能**: RTABMap数据同步 + +### 显示相关启动文件 + +#### 13. display_map_launch.py +- **功能**: 显示建图结果 +- **RViz配置**: `rviz/map.rviz` + +#### 14. display_nav_launch.py +- **功能**: 显示导航过程 +- **RViz配置**: `rviz/nav.rviz` + +#### 15. display_rtabmap_map_launch.py +- **功能**: 显示RTABMap建图结果 +- **RViz配置**: `rviz/rtabmap_map.rviz` + +#### 16. display_rtabmap_nav_launch.py +- **功能**: 显示RTABMap导航过程 +- **RViz配置**: `rviz/rtabmap_nav.rviz` + +### 其他启动文件 + +#### 17. laser_bringup_launch.py +- **功能**: 启动激光雷达驱动 + +#### 18. occupancy_grid_launch.py +- **功能**: 启动占用栅格地图 + +#### 19. rtabmap_viz_launch.py +- **功能**: RTABMap可视化 + +## 配置文件 + +### 导航参数文件 + +#### 1. dwa_nav_params.yaml +- **功能**: DWA导航算法参数配置 +- **包含组件**: + - bt_navigator (行为树导航器) + - controller_server (控制器服务器) + - local_costmap (局部代价地图) + - global_costmap (全局代价地图) + - planner_server (路径规划器) + - map_server (地图服务器) + +#### 2. teb_nav_params.yaml +- **功能**: TEB导航算法参数配置 + +#### 3. rtabmap_nav_params.yaml +- **功能**: RTABMap导航参数配置 + +### 建图参数文件 + +#### 4. lds_2d.lua +- **功能**: Cartographer 2D建图参数配置 +- **主要参数**: + - `map_frame`: "map" + - `tracking_frame`: "base_footprint" + - `published_frame`: "odom" + - `use_odometry`: true + - `num_laser_scans`: 1 + +## 地图文件 + +### 1. yahboomcar.yaml/yahboomcar.pgm +- **分辨率**: 0.05m/pixel +- **原点**: [-2.3, -8.95, 0] +- **占用阈值**: 0.65 +- **空闲阈值**: 0.25 + +### 2. yahboomcar2.yaml/yahboomcar2.pgm +- **分辨率**: 0.05m/pixel +- **原点**: [-10, -10, 0] + +## 使用说明 + +### 1. SLAM建图 + +#### Gmapping建图 +```bash +# 设置激光雷达类型 +export RPLIDAR_TYPE=a1 + +# 启动建图 +ros2 launch car_nav map_gmapping_launch.py + +# 保存地图 +ros2 launch car_nav save_map_launch.py +``` + +#### Cartographer建图 +```bash +# 启动Cartographer建图 +ros2 launch car_nav map_cartographer_launch.py + +# 保存地图 +ros2 service call /write_state cartographer_ros_msgs/srv/WriteState "{filename: '/path/to/map.bag'}" +``` + +#### RTABMap建图 +```bash +# 启动RTABMap建图 +ros2 launch car_nav map_rtabmap_launch.py +``` + +### 2. 自主导航 + +#### DWA导航 +```bash +# 启动DWA导航 +ros2 launch car_nav navigation_dwa_launch.py +``` + +#### TEB导航 +```bash +# 启动TEB导航 +ros2 launch car_nav navigation_teb_launch.py +``` + +#### RTABMap导航 +```bash +# 启动RTABMap导航 +ros2 launch car_nav navigation_rtabmap_launch.py +``` + +### 3. 可视化 + +#### 查看建图过程 +```bash +# 显示建图结果 +ros2 launch car_nav display_map_launch.py +``` + +#### 查看导航过程 +```bash +# 显示导航过程 +ros2 launch car_nav display_nav_launch.py +``` + +### 4. 激光雷达滤波 +```bash +# 启动激光雷达滤波节点 +ros2 run car_nav scan_filter +``` + +## 依赖项 +- ROS2 (机器人操作系统) +- Nav2 (导航功能包) +- Cartographer (谷歌SLAM算法) +- RTABMap (实时外观建图) +- Gmapping (SLAM建图算法) +- RViz2 (可视化工具) +- TF2 (坐标变换库) + +## 注意事项 +1. 建图前确保激光雷达正常工作 +2. 建图过程中保持机器人缓慢移动 +3. 导航前确保已有准确的地图 +4. 根据环境特点选择合适的导航算法 +5. 定期校准里程计和IMU数据 +6. 注意机器人安全,避免碰撞障碍物 \ No newline at end of file diff --git a/src/src/car_code/car_slam/README.md b/src/src/car_code/car_slam/README.md new file mode 100644 index 0000000..aed8461 --- /dev/null +++ b/src/src/car_code/car_slam/README.md @@ -0,0 +1,234 @@ +# ROS2 SLAM与点云处理包 README 文档 + +## 包名称 +**car_slam** + +## 功能描述 +该包提供视觉SLAM和点云处理功能,主要基于ORB-SLAM和点云建图技术。包含点云生成、滤波、可视化、八叉树地图构建等功能,适用于机器人三维环境感知和建图。 + +## 节点列表 + +### 1. pointcloud_mapping (点云建图节点) +- **节点名称**: `pointcloud_mapping` +- **可执行文件**: `point_cloud_main` +- **功能**: 基于RGB-D相机的实时点云建图 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/Global/PointCloudOutput` | `sensor_msgs/msg/PointCloud2` | 发布全局点云地图 | +| `/Local/PointCloudOutput` | `sensor_msgs/msg/PointCloud2` | 发布局部点云地图 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 回调函数 | 描述 | +|:---------|:---------|:---------|:-----| +| `/RGBD/RGB/Image` | `sensor_msgs/msg/Image` | `callback` | 订阅RGB图像数据 | +| `/RGBD/Depth/Image` | `sensor_msgs/msg/Image` | `callback` | 订阅深度图像数据 | +| `/RGBD/CameraPose` | `geometry_msgs/msg/PoseStamped` | `callback` | 订阅相机位姿数据 | + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `topicColor` | string | "/RGBD/RGB/Image" | RGB图像话题名称 | +| `topicDepth` | string | "/RGBD/Depth/Image" | 深度图像话题名称 | +| `topicTcw` | string | "/RGBD/CameraPose" | 相机位姿话题名称 | +| `local_frame_id` | string | "camera" | 局部坐标系ID | +| `global_frame_id` | string | "camera" | 全局坐标系ID | +| `node_path` | string | "/root/yahboomcar_ros2_ws/yahboomcar_ws/src/yahboomcar_slam/pcl/" | 点云文件保存路径 | +| `use_viewer` | bool | false | 是否启用PCL可视化 | +| `fx` | float | 517.306408 | 相机内参fx | +| `fy` | float | 516.469215 | 相机内参fy | +| `cx` | float | 318.643040 | 相机内参cx | +| `cy` | float | 255.313989 | 相机内参cy | +| `resolution` | float | 0.05 | 点云分辨率 | +| `depthfactor` | float | 1000.0 | 深度图缩放因子 | +| `queueSize` | int | 10 | 消息队列大小 | +| `buseExact` | bool | false | 是否使用精确时间同步 | + +## 启动文件 + +### 1. orbslam_ros_launch.py +- **功能**: 启动ORB-SLAM系统 +- **包含组件**: + - ORB-SLAM节点 + - 相机驱动节点 + +### 2. orbslam_base_launch.py +- **功能**: 启动ORB-SLAM基础系统 + +### 3. orbslam_pose_launch.py +- **功能**: 启动ORB-SLAM位姿估计 + +### 4. orbslam_pcl_map_launch.py +- **功能**: 启动ORB-SLAM点云建图 +- **包含组件**: + - ORB-SLAM节点 + - 点云建图节点 + +### 5. orbslam_pcl_octomap_launch.py +- **功能**: 启动ORB-SLAM点云八叉树建图 +- **包含组件**: + - ORB-SLAM节点 + - 点云建图节点 + - 八叉树地图节点 + +### 6. camera_octomap_launch.py +- **功能**: 启动相机八叉树建图 + +### 7. octomap_server_launch.py +- **功能**: 启动八叉树地图服务器 + +### 8. display_octomap_launch.py +- **功能**: 显示八叉树地图 +- **RViz配置**: `rviz/octomap.rviz` + +### 9. display_pcl_launch.py +- **功能**: 显示点云地图 +- **RViz配置**: `rviz/orbslam_pcl.rviz` + +## 配置文件 + +### 相机参数文件 + +#### 1. mono.yaml +- **功能**: 单目相机参数配置 +- **包含参数**: + - 相机内参矩阵 + - 畸变系数 + - 图像尺寸 + +#### 2. rgbd.yaml +- **功能**: RGB-D相机参数配置 +- **包含参数**: + - RGB相机内参 + - 深度相机内参 + - 基线距离 + +#### 3. usb_cam_param.yaml +- **功能**: USB相机参数配置 + +### SLAM参数文件 + +#### 4. ORBvoc.txt +- **功能**: ORB特征词汇表 +- **用途**: 用于回环检测和重定位 + +### 点云处理参数文件 + +#### 5. pointcloud_map.yaml +- **功能**: 点云建图参数配置 +- **包含参数**: + - 点云分辨率 + - 滤波参数 + - 可视化参数 + +#### 6. pointcloud_octomap.yaml +- **功能**: 点云八叉树建图参数配置 +- **包含参数**: + - 八叉树分辨率 + - 占用概率阈值 + - 更新参数 + +## 点云数据文件 + +### resultPointCloudFile.pcd +- **功能**: 保存的点云地图文件 +- **格式**: PCD (Point Cloud Data) +- **用途**: 离线点云分析和可视化 + +## 使用说明 + +### 1. 启动ORB-SLAM系统 +```bash +# 启动基础ORB-SLAM系统 +ros2 launch car_slam orbslam_base_launch.py + +# 启动带位姿估计的ORB-SLAM +ros2 launch car_slam orbslam_pose_launch.py + +# 启动完整的ORB-SLAM系统 +ros2 launch car_slam orbslam_ros_launch.py +``` + +### 2. 启动点云建图 +```bash +# 启动点云建图 +ros2 launch car_slam orbslam_pcl_map_launch.py + +# 启动点云八叉树建图 +ros2 launch car_slam orbslam_pcl_octomap_launch.py +``` + +### 3. 启动八叉树地图 +```bash +# 启动八叉树地图服务器 +ros2 launch car_slam octomap_server_launch.py + +# 启动相机八叉树建图 +ros2 launch car_slam camera_octomap_launch.py +``` + +### 4. 可视化 +```bash +# 显示点云地图 +ros2 launch car_slam display_pcl_launch.py + +# 显示八叉树地图 +ros2 launch car_slam display_octomap_launch.py +``` + +### 5. 单独运行点云建图节点 +```bash +# 运行点云建图节点 +ros2 run car_slam point_cloud_main + +# 设置参数 +ros2 param set /pointcloud_mapping use_viewer true +ros2 param set /pointcloud_mapping resolution 0.05 +``` + +## 主要功能 + +### 1. 点云生成 +- 基于RGB-D图像生成彩色点云 +- 支持实时点云生成和处理 +- 自动去除无效点和噪声 + +### 2. 点云滤波 +- 体素滤波降低点云密度 +- 通过滤波器去除离群点 +- 支持自定义滤波参数 + +### 3. 点云变换 +- 基于相机位姿变换点云到全局坐标系 +- 支持多帧点云融合 +- 实时更新全局点云地图 + +### 4. 点云可视化 +- 支持PCL可视化器实时显示 +- 发布ROS点云消息供RViz显示 +- 支持点云保存和加载 + +### 5. 八叉树建图 +- 将点云转换为八叉树表示 +- 支持占用概率更新 +- 适用于路径规划和碰撞检测 + +## 依赖项 +- ROS2 (机器人操作系统) +- PCL (点云处理库) +- OpenCV (计算机视觉库) +- Eigen3 (线性代数库) +- ORB-SLAM2/3 (视觉SLAM算法) +- OctoMap (八叉树地图库) +- message_filters (消息同步) +- cv_bridge (OpenCV与ROS图像转换) + +## 注意事项 +1. 确保RGB-D相机正常工作并已校准 +2. 根据实际相机参数修改配置文件 +3. 点云建图需要足够的计算资源 +4. 建图过程中保持相机稳定移动 +5. 定期保存点云地图以防数据丢失 +6. 大规模点云建图时注意内存使用 +7. 八叉树分辨率影响地图精度和存储空间 \ No newline at end of file diff --git a/src/src/car_code/laserscan_to_point_pulisher/README.md b/src/src/car_code/laserscan_to_point_pulisher/README.md index ee4bb00..99d44d1 100644 --- a/src/src/car_code/laserscan_to_point_pulisher/README.md +++ b/src/src/car_code/laserscan_to_point_pulisher/README.md @@ -1,91 +1,202 @@ -# ROS2 节点 README 文档 +# ROS2 激光雷达点云转换包 README 文档 -## 节点名称 -**robot_pose_publisher** +## 包名称 +**laserscan_to_point_pulisher** ## 功能描述 -该节点的主要功能是将激光雷达的扫描数据(`LaserScan`)转换为点云数据(`Path`消息中的`poses`字段),并发布到`/scan_points`话题,供其他节点使用。通过订阅激光雷达的扫描数据,计算每个扫描点的坐标,并将这些坐标封装到`Path`消息中进行发布,以便在机器人导航、环境建模等应用中使用。 - -## 订阅接口 -| 话题名称 | 消息类型 | 回调函数 | 描述 | -|:------------|:--------------------------|:------------------|:-------------------------| -| `/scan` | `sensor_msgs/msg/LaserScan` | `laserscan_callback` | 订阅激光雷达的扫描数据 | - -## 发布接口 -| 话题名称 | 消息类型 | 发布内容描述 | -|:--------------|:--------------------------|:-------------------------------| -| `/scan_points`| `nav_msgs/msg/Path` | 发布由激光扫描数据转换而来的点云数据 | - -## 其他信息 - -### 代码中的问题 -1. **节点名称不准确**: - - 在代码中,节点名称被设置为`robot_pose_publisher`,但根据文件名和类名`laserscanToPointPublish`,这个名称可能不够准确,容易引起误解。 - -2. **变量名拼写错误**: - - 在`main`函数中,销毁节点时使用了`robot_pose_publisher.destroy_node()`,但变量名实际上是`robot_laser_scan_publisher`,这会导致程序运行时出现错误。 - -3. **消息类型和话题名称的注释不清晰**: - - 在代码中,部分注释和变量名的拼写存在错误,例如`sacn_point_publisher`(应为`scan_point_publisher`),这可能会影响代码的可读性和维护性。 - -4. **缺少错误处理和日志输出**: - - 在数据处理和消息发布过程中,缺少对异常情况的处理和日志输出,这在调试和运行时可能会导致问题难以发现。 - -### 修改建议 -1. **修正节点名称**: - - 将节点名称修改为更符合实际功能的名字,例如`laserscan_to_point_publisher`,以提高代码的可读性和准确性。 - - 修改代码: - ```python - class laserscanToPointPublish(Node): - def __init__(self): - super().__init__('laserscan_to_point_publisher') - # 其他初始化代码... - ``` - -2. **修正变量名拼写错误**: - - 在`main`函数中,确保变量名一致,避免拼写错误导致的运行时错误。 - - 修改代码: - ```python - def main(args=None): - rclpy.init(args=args) - robot_laser_scan_publisher = laserscanToPointPublish() - rclpy.spin(robot_laser_scan_publisher) - robot_laser_scan_publisher.destroy_node() - rclpy.shutdown() - ``` - -3. **添加日志输出和错误处理**: - - 在数据处理和消息发布过程中,添加日志输出,以便在运行时能够监控节点的状态和数据流动。 - - 示例修改: - ```python - def laserscan_callback(self, msg): - self.get_logger().info('Received laser scan data') - try: - # 数据处理逻辑 - laser_points = self.laserscan_to_points(msg.ranges, msg.angle_min, msg.angle_increment) - self.scan_point_publisher.publish(laser_points) - self.get_logger().info('Published scan points') - except Exception as e: - self.get_logger().error(f'Error processing laser scan data: {e}') - ``` - -4. **优化代码结构和注释**: - - 修正变量名和注释中的拼写错误,提高代码的可读性。 - - 添加详细的注释,解释每个函数和关键步骤的作用,方便后续维护和扩展。 - -### 使用说明 -1. **运行节点**: - - 确保ROS2环境已正确配置,并安装了必要的依赖项。 - - 使用以下命令运行节点: - ```bash - ros2 run laserscan_to_point_publisher - ``` - -2. **验证节点功能**: - - 使用`ros2 topic echo /scan_points`命令查看发布的点云数据。 - - 确保激光雷达的扫描数据能够正确订阅,并且转换后的点云数据能够正常发布。 - -3. **参数调整**: - - 根据实际应用场景,可以调整节点中的参数,如订阅的话题名称、发布的消息类型等。 - -通过以上修改和优化,可以提高节点的稳定性和可维护性,使其更好地适应实际应用需求。 \ No newline at end of file +该包的主要功能是将激光雷达的扫描数据(`LaserScan`)转换为路径点云数据(`Path`消息中的`poses`字段),并发布到指定话题,供其他节点使用。通过订阅激光雷达的扫描数据,计算每个扫描点的坐标,并将这些坐标封装到`Path`消息中进行发布,适用于机器人导航、环境建模等应用。 + +## 节点列表 + +### 1. laserscanToPointPublish (激光雷达点云转换节点) +- **节点名称**: `robot_pose_publisher` (注意:此处存在命名问题,建议修改为`laserscan_to_point_publisher`) +- **可执行文件**: `laserscan_to_point_publisher` +- **功能**: 将激光雷达扫描数据转换为路径点云数据 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/scan_points` | `nav_msgs/msg/Path` | 发布由激光扫描数据转换而来的点云数据 | + +#### 订阅接口 +| 话题名称 | 消息类型 | 回调函数 | 描述 | +|:---------|:---------|:---------|:-----| +| `/scan` | `sensor_msgs/msg/LaserScan` | `laserscan_callback` | 订阅激光雷达的扫描数据 | + +## 核心功能 + +### 1. 数据转换 +- 将激光雷达的极坐标数据转换为笛卡尔坐标 +- 基于激光雷达的角度信息计算每个点的位置 +- 支持实时数据转换和发布 + +### 2. 坐标计算 +- 使用三角函数计算点的x, y坐标 +- 公式:`x = distance * cos(angle)`, `y = distance * sin(angle)` +- 支持激光雷达的角度增量和最小角度参数 + +### 3. 消息封装 +- 将计算得到的点坐标封装为`PoseStamped`消息 +- 组装成`Path`消息进行发布 +- 保持数据的时序性和一致性 + +## 使用说明 + +### 1. 启动节点 +```bash +# 启动激光雷达点云转换节点 +ros2 run laserscan_to_point_pulisher laserscan_to_point_publisher +``` + +### 2. 查看转换结果 +```bash +# 查看发布的点云数据 +ros2 topic echo /scan_points + +# 查看话题信息 +ros2 topic info /scan_points +``` + +### 3. 监控数据流 +```bash +# 查看激光雷达数据 +ros2 topic echo /scan + +# 查看发布频率 +ros2 topic hz /scan_points +``` + +### 4. 可视化 +```bash +# 在RViz中可视化路径点云 +ros2 run rviz2 rviz2 +# 添加Path显示,话题选择/scan_points +``` + +## 代码中的问题及修改建议 + +### 1. 节点名称不一致 +**问题**: 节点名称设置为`robot_pose_publisher`,但实际功能是激光雷达点云转换 +**建议修改**: +```python +super().__init__('laserscan_to_point_publisher') +``` + +### 2. 变量名拼写错误 +**问题**: `sacn_point_publisher`应为`scan_point_publisher` +**建议修改**: +```python +self.scan_point_publisher = self.create_publisher( + Path, + '/scan_points', + 10) +``` + +### 3. main函数中的变量名错误 +**问题**: 销毁节点时使用了错误的变量名 +**建议修改**: +```python +def main(args=None): + rclpy.init(args=args) + robot_laser_scan_publisher = laserscanToPointPublish() + rclpy.spin(robot_laser_scan_publisher) + robot_laser_scan_publisher.destroy_node() # 修正变量名 + rclpy.shutdown() +``` + +### 4. 参数使用错误 +**问题**: `laserscan_to_points`函数的参数传递有误 +**建议修改**: +```python +def laserscan_callback(self, msg): + angle_min = msg.angle_min + angle_increment = msg.angle_increment + laserscan = msg.ranges + laser_points = self.laserscan_to_points(laserscan, angle_min, angle_increment) # 修正参数 + self.scan_point_publisher.publish(laser_points) +``` + +### 5. 添加错误处理和日志 +**建议添加**: +```python +def laserscan_callback(self, msg): + try: + self.get_logger().info('Received laser scan data') + angle_min = msg.angle_min + angle_increment = msg.angle_increment + laserscan = msg.ranges + laser_points = self.laserscan_to_points(laserscan, angle_min, angle_increment) + self.scan_point_publisher.publish(laser_points) + self.get_logger().debug('Published scan points') + except Exception as e: + self.get_logger().error(f'Error processing laser scan data: {e}') +``` + +### 6. 完善数据有效性检查 +**建议添加**: +```python +def laserscan_to_points(self, laserscan, angle_min, angle_increment): + points = [] + angle = angle_min + laser_points = Path() + laser_points.header.frame_id = "laser" # 添加坐标系信息 + laser_points.header.stamp = self.get_clock().now().to_msg() # 添加时间戳 + + for distance in laserscan: + # 检查数据有效性 + if not math.isnan(distance) and not math.isinf(distance) and distance > 0: + x = distance * math.cos(angle) + y = distance * math.sin(angle) + pose = PoseStamped() + pose.header.frame_id = "laser" + pose.header.stamp = laser_points.header.stamp + pose.pose.position.x = x + pose.pose.position.y = y + pose.pose.position.z = 0.0 + pose.pose.orientation.w = 1.0 # 设置默认姿态 + points.append(pose) + angle += angle_increment + + laser_points.poses = points + return laser_points +``` + +## 应用场景 + +### 1. 环境建模 +- 将激光雷达数据转换为点云用于环境重建 +- 支持2D环境地图构建 +- 用于障碍物检测和避障 + +### 2. 路径规划 +- 为路径规划算法提供环境点云数据 +- 支持动态障碍物检测 +- 用于局部路径规划 + +### 3. 数据可视化 +- 在RViz中可视化激光雷达扫描结果 +- 支持实时数据显示 +- 用于系统调试和监控 + +### 4. 数据记录 +- 记录激光雷达扫描轨迹 +- 用于离线分析和处理 +- 支持数据回放和分析 + +## 依赖项 +- ROS2 (机器人操作系统) +- rclpy (ROS2 Python客户端库) +- geometry_msgs (几何消息类型) +- nav_msgs (导航消息类型) +- sensor_msgs (传感器消息类型) +- tf2_ros (坐标变换库) +- math (数学计算库) + +## 注意事项 +1. 确保激光雷达正常工作并发布`/scan`话题 +2. 根据实际激光雷达参数调整坐标计算 +3. 注意数据的有效性检查,过滤无效点 +4. 合理设置发布频率,避免过高的CPU占用 +5. 在可视化时注意坐标系的一致性 +6. 定期检查和修复代码中的拼写错误 +7. 根据应用需求优化数据处理算法 \ No newline at end of file diff --git a/src/src/car_code/robot_pose_publisher_ros2/README.md b/src/src/car_code/robot_pose_publisher_ros2/README.md index b23cc43..e63f130 100644 --- a/src/src/car_code/robot_pose_publisher_ros2/README.md +++ b/src/src/car_code/robot_pose_publisher_ros2/README.md @@ -1,117 +1,188 @@ -# robot_pose_publisher_ros2 +# ROS2 机器人位姿发布包 README 文档 -This is ros2 verison of [robot_pose_publisher](https://github.com/GT-RAIL/robot_pose_publisher) - - -# ROS2 节点 README 文档 - -## 节点名称 -**robot_pose_publisher** +## 包名称 +**robot_pose_publisher_ros2** ## 功能描述 -该节点的主要功能是基于TF(变换框架)获取机器人在地图坐标系(`/map`)下的位姿,并将其发布为`geometry_msgs/msg/PoseStamped`或`geometry_msgs/msg/Pose`消息。节点通过定时器定期查询TF变换,获取机器人在地图坐标系中的位置和姿态,并将这些信息封装到相应的消息类型中进行发布,供其他节点使用。 - -## 订阅接口 -该节点没有显式的订阅接口,因为它依赖于TF(变换框架)来获取机器人位姿信息。节点通过`tf2_ros::TransformListener`监听TF变换,从`/map`到`/base_link`的变换被用于计算机器人位姿。 - -## 发布接口 -| 话题名称 | 消息类型 | 发布内容描述 | -|:--------------|:----------------------------------------------|:-------------------------------| -| `/robot_pose` | `geometry_msgs/msg/PoseStamped` 或 `geometry_msgs/msg/Pose` | 发布机器人在地图坐标系下的位姿数据 | - -## 其他信息 - -### 代码中的问题 -1. **异常处理不够完善**: - - 在`timer_callback`函数中,如果TF查询失败,异常被捕获但没有记录具体的错误信息,这在调试时可能会导致问题难以定位。 - -2. **参数验证不足**: - - 节点中使用了参数`map_frame`、`base_frame`和`is_stamped`,但在代码中没有对这些参数进行充分的验证,可能导致节点在运行时出现意外行为。 - -3. **资源管理问题**: - - 在节点构造函数中,创建了多个共享指针(如`tf_buffer_`、`tf_listener_`、`publisher_stamp`、`publisher_`),但在节点销毁时没有明确释放这些资源,可能导致资源泄漏。 - -4. **日志输出不够详细**: - - 节点在运行过程中缺乏足够的日志输出,无法方便地监控节点的状态和数据流动。 - -### 修改建议 -1. **完善异常处理**: - - 在异常处理中添加具体的错误信息,方便调试和问题定位。 - - 修改代码: - ```cpp - void timer_callback() - { - geometry_msgs::msg::TransformStamped transformStamped; - try - { - transformStamped = tf_buffer_->lookupTransform(map_frame, base_frame, this->now()); - } - catch (tf2::TransformException &ex) - { - RCLCPP_ERROR(this->get_logger(), "Failed to lookup transform: %s", ex.what()); - return; - } - // 其他逻辑... - } - ``` - -2. **添加参数验证**: - - 在设置参数后,添加验证逻辑,确保参数值合理。 - - 示例修改: - ```cpp - RobotPosePublisher() : Node("robot_pose_publisher") - { - // 参数声明和获取... - if (map_frame.empty() || base_frame.empty()) - { - RCLCPP_ERROR(this->get_logger(), "Invalid frame parameters"); - throw std::runtime_error("Invalid frame parameters"); - } - // 其他逻辑... - } - ``` - -3. **优化资源管理**: - - 在节点析构函数中明确释放共享资源,确保没有资源泄漏。 - - 修改代码: - ```cpp - ~RobotPosePublisher() - { - tf_buffer_.reset(); - tf_listener_.reset(); - publisher_stamp.reset(); - publisher_.reset(); - } - ``` - -4. **添加详细日志输出**: - - 在关键步骤添加日志输出,方便监控节点运行状态。 - - 示例修改: - ```cpp - void timer_callback() - { - // 查询TF变换... - RCLCPP_INFO(this->get_logger(), "Published robot pose: (%.2f, %.2f, %.2f)", - pose_stamped.pose.position.x, - pose_stamped.pose.position.y, - pose_stamped.pose.position.z); - // 发布消息... - } - ``` - -### 使用说明 -1. **运行节点**: - - 确保ROS2环境已正确配置,并安装了必要的依赖项。 - - 使用以下命令运行节点: - ```bash - ros2 run robot_pose_publisher - ``` - -2. **验证节点功能**: - - 使用`ros2 topic echo /robot_pose`命令查看发布的机器人位姿数据。 - - 确保TF变换框架中存在从`/map`到`/base_link`的变换,否则节点将无法正常工作。 - -3. **参数调整**: - - 根据实际应用场景,可以调整节点中的参数,如`map_frame`、`base_frame`和`is_stamped`,通过命令行或配置文件进行设置。 - -通过以上修改和优化,可以提高节点的稳定性和可维护性,使其更好地适应实际应用需求。 \ No newline at end of file +该包提供机器人位姿发布功能,基于TF变换计算机器人在地图坐标系中的位置和姿态,并以标准ROS消息格式发布。主要用于机器人定位、导航状态监控和上层应用的位姿获取。 + +## 节点列表 + +### 1. robot_pose_publisher (机器人位姿发布节点) +- **节点名称**: `robot_pose_publisher` +- **可执行文件**: `robot_pose_publisher` +- **功能**: 实时发布机器人在地图坐标系中的位姿信息 + +#### 发布接口 +| 话题名称 | 消息类型 | 描述 | +|:---------|:---------|:-----| +| `/robot_pose` | `geometry_msgs/msg/Pose` | 发布机器人位姿(非时间戳版本) | +| `/robot_pose` | `geometry_msgs/msg/PoseStamped` | 发布机器人位姿(时间戳版本) | + +#### 订阅接口 +无直接订阅接口,通过TF监听器获取坐标变换信息 + +#### 参数配置 +| 参数名称 | 类型 | 默认值 | 描述 | +|:---------|:-----|:-------|:-----| +| `map_frame` | string | "map" | 地图坐标系名称 | +| `base_frame` | string | "base_link" | 机器人基座坐标系名称 | +| `is_stamped` | bool | false | 是否发布带时间戳的位姿消息 | + +#### 定时器配置 +- **发布频率**: 20Hz (每50ms发布一次) +- **定时器类型**: Wall Timer (实时定时器) + +## 启动文件 + +### robot_pose_publisher_launch.py +- **功能**: 启动机器人位姿发布节点 +- **包含组件**: + - robot_pose_publisher节点 +- **可配置参数**: + - 地图坐标系名称 + - 基座坐标系名称 + - 输出消息类型 + +## 核心功能 + +### 1. TF监听 +- 监听地图坐标系到机器人基座坐标系的变换 +- 实时获取机器人位置和姿态信息 +- 自动处理坐标变换异常 + +### 2. 位姿计算 +- 基于TF变换计算机器人位姿 +- 支持位置(x, y, z)和姿态(四元数)信息 +- 提供高精度的位姿数据 + +### 3. 消息发布 +- 支持两种消息格式:Pose和PoseStamped +- 可配置发布频率和消息类型 +- 自动添加时间戳和坐标系信息 + +### 4. 异常处理 +- 处理TF变换查找失败的情况 +- 在坐标变换不可用时跳过发布 +- 提供稳定的位姿数据流 + +## 使用说明 + +### 1. 启动位姿发布节点 +```bash +# 使用默认参数启动 +ros2 run robot_pose_publisher_ros2 robot_pose_publisher + +# 使用启动文件启动 +ros2 launch robot_pose_publisher_ros2 robot_pose_publisher_launch.py +``` + +### 2. 配置参数启动 +```bash +# 自定义坐标系名称 +ros2 run robot_pose_publisher_ros2 robot_pose_publisher \ + --ros-args \ + -p map_frame:=map \ + -p base_frame:=base_footprint \ + -p is_stamped:=true +``` + +### 3. 查看位姿信息 +```bash +# 查看位姿消息(非时间戳版本) +ros2 topic echo /robot_pose + +# 查看位姿消息(时间戳版本) +ros2 topic echo /robot_pose +``` + +### 4. 监控发布频率 +```bash +# 查看话题发布频率 +ros2 topic hz /robot_pose + +# 查看话题信息 +ros2 topic info /robot_pose +``` + +### 5. 参数动态配置 +```bash +# 查看当前参数 +ros2 param list /robot_pose_publisher + +# 修改参数 +ros2 param set /robot_pose_publisher map_frame odom +ros2 param set /robot_pose_publisher base_frame base_link +ros2 param set /robot_pose_publisher is_stamped true +``` + +## 消息格式 + +### geometry_msgs/msg/Pose +```yaml +position: + x: 0.0 + y: 0.0 + z: 0.0 +orientation: + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 +``` + +### geometry_msgs/msg/PoseStamped +```yaml +header: + stamp: + sec: 0 + nanosec: 0 + frame_id: "map" +pose: + position: + x: 0.0 + y: 0.0 + z: 0.0 + orientation: + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 +``` + +## 应用场景 + +### 1. 机器人导航 +- 为导航系统提供实时位姿信息 +- 支持路径规划和轨迹跟踪 +- 用于导航状态监控 + +### 2. 定位系统 +- 与SLAM系统配合使用 +- 提供机器人定位结果 +- 支持多传感器融合定位 + +### 3. 数据记录 +- 记录机器人运动轨迹 +- 用于离线分析和评估 +- 支持数据可视化 + +### 4. 上层应用 +- 为应用层提供位姿接口 +- 支持远程监控和控制 +- 用于任务规划和执行 + +## 依赖项 +- ROS2 (机器人操作系统) +- tf2_ros (坐标变换库) +- geometry_msgs (几何消息类型) +- rclcpp (ROS2 C++客户端库) + +## 注意事项 +1. 确保TF树完整,map到base_link的变换可用 +2. 根据实际机器人配置修改坐标系名称 +3. 合理设置发布频率,避免过高的CPU占用 +4. 在SLAM或定位系统启动后再启动此节点 +5. 注意坐标系的一致性,避免混淆不同的坐标系 +6. 监控TF变换的时效性,避免使用过时的数据 +7. 根据应用需求选择合适的消息类型(Pose或PoseStamped) \ No newline at end of file