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