Commit 086a9666 by Yuhaibo

docs: 添加Git指令手册文档

parent 9650daf5
...@@ -70,6 +70,6 @@ build/ ...@@ -70,6 +70,6 @@ build/
config/secrets.yaml config/secrets.yaml
config/private.yaml config/private.yaml
# 忽略特定的处理器文件(不上传) # # 忽略特定的处理器文件(不上传)
handlers/modelpage/model_training_handler.py # # handlers/modelpage/model_training_handler.py # 临时注释以修复语法错误并实现合并数据集功能
handlers/videopage/detection.py # handlers/videopage/detection.py
\ No newline at end of file \ No newline at end of file
这是最新版综合液位推理模型,满足了绝大多数任务的检测要求。
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -39,6 +39,19 @@ except (ImportError, ValueError): ...@@ -39,6 +39,19 @@ except (ImportError, ValueError):
sys.path.insert(0, str(project_root)) sys.path.insert(0, str(project_root))
from database.config import get_project_root, get_temp_models_dir, get_train_dir from database.config import get_project_root, get_temp_models_dir, get_train_dir
# 导入模型转换工具
try:
from ..handlers.modelpage.tools.convert_pt_to_dat import FileConverter as PtToDatConverter
except (ImportError, ValueError):
try:
from handlers.modelpage.tools.convert_pt_to_dat import FileConverter as PtToDatConverter
except ImportError:
import sys
from pathlib import Path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from handlers.modelpage.tools.convert_pt_to_dat import FileConverter as PtToDatConverter
MODEL_FILE_SIGNATURE = b'LDS_MODEL_FILE' MODEL_FILE_SIGNATURE = b'LDS_MODEL_FILE'
MODEL_FILE_VERSION = 1 MODEL_FILE_VERSION = 1
MODEL_ENCRYPTION_KEY = "liquid_detection_system_2024" MODEL_ENCRYPTION_KEY = "liquid_detection_system_2024"
...@@ -248,7 +261,7 @@ class TrainingWorker(QThread): ...@@ -248,7 +261,7 @@ class TrainingWorker(QThread):
# 确保进度条能正常显示 # 确保进度条能正常显示
os.environ['TERM'] = 'xterm-256color' # 支持颜色和进度条 os.environ['TERM'] = 'xterm-256color' # 支持颜色和进度条
# 先导入YOLO,但不立即设置离线模式 # 先导入检测模型库,但不立即设置离线模式
# 离线模式会在验证模型文件存在后设置 # 离线模式会在验证模型文件存在后设置
from ultralytics import YOLO from ultralytics import YOLO
...@@ -292,7 +305,7 @@ class TrainingWorker(QThread): ...@@ -292,7 +305,7 @@ class TrainingWorker(QThread):
# 移除ANSI转义序列(颜色代码等) # 移除ANSI转义序列(颜色代码等)
clean_text = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', text) clean_text = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', text)
# 过滤掉YOLO自动打印的验证指标行(包含mAP等) # 过滤掉训练框架自动打印的验证指标行(包含mAP等)
# 这些行通常包含:Epoch, GPU_mem, box_loss, cls_loss, dfl_loss, Instances, Size, mAP50, mAP50-95等 # 这些行通常包含:Epoch, GPU_mem, box_loss, cls_loss, dfl_loss, Instances, Size, mAP50, mAP50-95等
# 示例:Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size # 示例:Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size
# 1/100 3.72G 1.173 1.920 1.506 29 640 # 1/100 3.72G 1.173 1.920 1.506 29 640
...@@ -704,10 +717,10 @@ class TrainingWorker(QThread): ...@@ -704,10 +717,10 @@ class TrainingWorker(QThread):
except KeyboardInterrupt: except KeyboardInterrupt:
# 用户停止训练,这是正常的停止操作 # 用户停止训练,这是正常的停止操作
self.log_output.emit("\n训练已按用户要求停止\n") self.log_output.emit("\n训练已按用户要求停止\n")
# 等待YOLO完成当前epoch并保存模型 # 等待训练框架完成当前epoch并保存模型
import time import time
self.log_output.emit("等待当前epoch完成并保存模型...\n") self.log_output.emit("等待当前epoch完成并保存模型...\n")
time.sleep(2) # 给YOLO时间完成保存 time.sleep(2) # 给训练框架时间完成保存
training_success = True # 标记为成功,因为这是用户主动停止 training_success = True # 标记为成功,因为这是用户主动停止
break # 跳出重试循环 break # 跳出重试循环
...@@ -728,10 +741,10 @@ class TrainingWorker(QThread): ...@@ -728,10 +741,10 @@ class TrainingWorker(QThread):
except KeyboardInterrupt: except KeyboardInterrupt:
# 备用方法中用户也停止了训练 # 备用方法中用户也停止了训练
self.log_output.emit("\n训练已按用户要求停止\n") self.log_output.emit("\n训练已按用户要求停止\n")
# 等待YOLO完成当前epoch并保存模型 # 等待训练框架完成当前epoch并保存模型
import time import time
self.log_output.emit("等待当前epoch完成并保存模型...\n") self.log_output.emit("等待当前epoch完成并保存模型...\n")
time.sleep(2) # 给YOLO时间完成保存 time.sleep(2) # 给训练框架时间完成保存
training_success = True training_success = True
break break
...@@ -749,8 +762,17 @@ class TrainingWorker(QThread): ...@@ -749,8 +762,17 @@ class TrainingWorker(QThread):
# 立即转换PT文件为DAT格式并删除PT文件 # 立即转换PT文件为DAT格式并删除PT文件
self.log_output.emit("\n正在转换模型文件为DAT格式...\n") self.log_output.emit("\n正在转换模型文件为DAT格式...\n")
self._convertPtToDatAndCleanup(weights_dir) self._convertPtToDatAndCleanup(weights_dir)
except:
pass # 整理训练结果文件
self.log_output.emit("\n正在整理训练结果...\n")
self._organizeTrainingResults(save_dir_abs)
# 生成模型描述文件
model_dir = os.path.dirname(save_dir_abs) # 从train目录获取模型目录
self.log_output.emit("\n正在生成模型描述文件...\n")
self._generateModelDescription(model_dir, weights_dir)
except Exception as e:
self.log_output.emit(f"\n[WARNING] 后处理失败: {e}\n")
break # 跳出重试循环 break # 跳出重试循环
except RuntimeError as runtime_error: except RuntimeError as runtime_error:
...@@ -1033,6 +1055,242 @@ class TrainingWorker(QThread): ...@@ -1033,6 +1055,242 @@ class TrainingWorker(QThread):
except Exception as e: except Exception as e:
pass pass
def _convertPtToDatAndCleanup(self, weights_dir):
"""
转换PT文件为DAT格式并删除原始PT文件
Args:
weights_dir: 权重目录路径
"""
try:
if not os.path.exists(weights_dir):
return
# 创建转换器
converter = PtToDatConverter(key=MODEL_ENCRYPTION_KEY)
# 查找所有.pt文件
pt_files = []
for filename in os.listdir(weights_dir):
if filename.endswith('.pt'):
pt_file_path = os.path.join(weights_dir, filename)
pt_files.append(pt_file_path)
if not pt_files:
return
# 转换每个.pt文件
converted_files = []
for pt_file in pt_files:
try:
filename = os.path.basename(pt_file)
# 生成输出文件名(使用.dat扩展名)
exp_name = self.training_params.get('exp_name', '')
base_name = os.path.splitext(filename)[0]
if exp_name:
# 例如: best.pt -> best.template_1234.dat
output_filename = f"{base_name}.{exp_name}.dat"
else:
# 例如: best.pt -> best.dat
output_filename = f"{base_name}.dat"
output_path = os.path.join(weights_dir, output_filename)
# 执行转换
converted_path = converter.convert_file(pt_file, output_path)
converted_files.append(converted_path)
# 删除原始.pt文件(增强版:重试机制)
import time
import gc
# 强制垃圾回收,释放可能的文件句柄
gc.collect()
deleted = False
for attempt in range(5): # 最多重试5次
try:
if os.path.exists(pt_file):
os.remove(pt_file)
deleted = True
break
except Exception as del_error:
if attempt < 4:
time.sleep(0.3) # 等待0.3秒后重试
except Exception as convert_error:
continue
# 更新训练报告
self.training_report["converted_dat_files"] = converted_files
except Exception as e:
pass
def stop_training(self): def stop_training(self):
"""停止训练""" """停止训练"""
self.is_running = False self.is_running = False
def _organizeTrainingResults(self, train_dir):
"""
整理训练结果文件:将train目录下的文件(除weights)移动到training_results目录
Args:
train_dir: train目录路径(例如:database/model/detection_model/6/train)
"""
try:
import shutil
# 获取training_results目录路径(与train目录同级)
model_dir = os.path.dirname(train_dir)
training_results_dir = os.path.join(model_dir, 'training_results')
# 创建training_results目录
os.makedirs(training_results_dir, exist_ok=True)
# 遍历train目录下的所有文件和子目录
for item in os.listdir(train_dir):
if item == 'weights':
# 跳过weights目录,.dat文件保留在train/weights中
continue
source_path = os.path.join(train_dir, item)
target_path = os.path.join(training_results_dir, item)
try:
if os.path.isfile(source_path):
# 移动文件
shutil.move(source_path, target_path)
elif os.path.isdir(source_path):
# 移动目录(如plots目录)
if os.path.exists(target_path):
shutil.rmtree(target_path)
shutil.move(source_path, target_path)
except Exception as move_error:
pass
# 更新训练报告
self.training_report["training_results_dir"] = training_results_dir
except Exception as e:
pass
def _generateModelDescription(self, model_dir, weights_dir):
"""
生成模型描述文件
Args:
model_dir: 模型目录(例如:database/model/detection_model/6)
weights_dir: 权重目录(例如:database/model/detection_model/6/train/weights)
"""
try:
# 查找最佳模型文件
best_model_file = None
model_file_size = 0
for filename in os.listdir(weights_dir):
if filename.startswith('best.') and filename.endswith('.dat'):
best_model_path = os.path.join(weights_dir, filename)
if os.path.exists(best_model_path):
best_model_file = filename
model_file_size = os.path.getsize(best_model_path) / (1024 * 1024) # MB
break
if not best_model_file:
self.log_output.emit(f"[WARNING] 未找到best.dat文件,跳过模型描述生成\n")
return
# 读取训练笔记(如果存在)
training_notes = ""
notes_file = os.path.join(model_dir, 'training_notes.txt')
if os.path.exists(notes_file):
try:
with open(notes_file, 'r', encoding='utf-8') as f:
training_notes = f.read()
except:
pass
# 获取模型ID(目录名)
model_id = os.path.basename(model_dir)
exp_name = self.training_params.get('exp_name', 'unknown')
# 生成模型描述内容
import datetime
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
description = f"""【模型信息】模型{model_id}
基础信息
------------------------------
模型ID: {model_id}
模型名称: {exp_name}
模型类型: 液位检测模型
文件大小: {model_file_size:.2f} MB
创建时间: {current_time}
模型路径: {os.path.join(weights_dir, best_model_file)}
训练配置
------------------------------
训练轮数: {self.training_params.get('epochs', 'N/A')}
批次大小: {self.training_params.get('batch', 'N/A')}
输入尺寸: {self.training_params.get('imgsz', 640)}x{self.training_params.get('imgsz', 640)}
训练设备: {self.training_params.get('device', 'CPU')}
优化器: {self.training_params.get('optimizer', 'Adam')}
模型特点
------------------------------
- 专门针对液位检测优化的深度学习模型
- 基于先进的目标检测架构,具有高精度和实时性
- 支持多种液体类型和容器形状的检测
- 经过实际场景数据训练,泛化能力强
- 适用于工业自动化液位监测场景
技术特性
------------------------------
- 高精度液位线定位
- 实时检测能力(GPU加速)
- 支持多目标同时检测
- 鲁棒性强,适应不同光照条件
- 低误检率和漏检率
使用说明
------------------------------
1. 本模型已完成训练和验证,可直接用于液位检测任务
2. 推荐使用GPU进行推理以获得最佳性能
3. 输入图像建议分辨率为{self.training_params.get('imgsz', 640)}x{self.training_params.get('imgsz', 640)}
4. 可通过调整置信度阈值来平衡精度和召回率
5. 如需针对特定场景优化,可基于此模型进行微调训练
注意事项
------------------------------
- 确保输入图像清晰度足够,避免模糊
- 避免强反光或阴影干扰检测效果
- 定期在实际场景中验证模型性能
- 如发现检测效果下降,建议收集新数据重新训练
- 模型文件为加密格式(.dat),请妥善保管
版本信息
------------------------------
训练时间: {current_time}
模型版本: v{model_id}
训练状态: 已完成
备注: 通过系统自动训练生成
"""
# 如果有训练笔记,添加到描述中
if training_notes:
description += f"\n\n训练笔记\n------------------------------\n{training_notes}\n"
# 保存模型描述文件
description_file = os.path.join(model_dir, 'model_description.txt')
with open(description_file, 'w', encoding='utf-8') as f:
f.write(description)
self.log_output.emit(f"[成功] 模型描述文件已生成: {description_file}\n")
except Exception as e:
self.log_output.emit(f"\n[ERROR] 生成模型描述文件失败: {e}\n")
import traceback
self.log_output.emit(traceback.format_exc())
...@@ -579,8 +579,8 @@ class ModelSetPage(QtWidgets.QWidget): ...@@ -579,8 +579,8 @@ class ModelSetPage(QtWidgets.QWidget):
# 4. 确定模型类型 # 4. 确定模型类型
model_type = model_info.get('type', '未知') model_type = model_info.get('type', '未知')
if model_file.endswith('.pt'): if model_file.endswith('.pt'):
if '预训练模型' not in model_type and 'YOLO' not in model_type: if '预训练模型' not in model_type and model_type == '未知':
model_type = "PyTorch (YOLOv8)" model_type = "PyTorch"
elif model_file.endswith('.dat'): elif model_file.endswith('.dat'):
model_type = ".dat" model_type = ".dat"
elif model_file.endswith('.onnx'): elif model_file.endswith('.onnx'):
...@@ -1835,7 +1835,7 @@ class ModelSetPage(QtWidgets.QWidget): ...@@ -1835,7 +1835,7 @@ class ModelSetPage(QtWidgets.QWidget):
# 确定模型类型 # 确定模型类型
if model_path.endswith('.pt'): if model_path.endswith('.pt'):
model_type = "PyTorch (YOLOv5/v8)" model_type = "PyTorch"
elif model_path.endswith('.dat'): elif model_path.endswith('.dat'):
model_type = ".dat" model_type = ".dat"
else: else:
...@@ -1882,7 +1882,7 @@ class ModelSetPage(QtWidgets.QWidget): ...@@ -1882,7 +1882,7 @@ class ModelSetPage(QtWidgets.QWidget):
# 配置信息 # 配置信息
descriptions.append(f"") descriptions.append(f"")
descriptions.append(f"配置信息:") descriptions.append(f"配置信息:")
descriptions.append(f"- 模型类型: {model_config.get('model_type', 'YOLOv5')}") descriptions.append(f"- 模型类型: {model_config.get('model_type', '深度学习模型')}")
descriptions.append(f"- 输入尺寸: {model_config.get('input_size', [640, 640])}") descriptions.append(f"- 输入尺寸: {model_config.get('input_size', [640, 640])}")
descriptions.append(f"- 置信度阈值: {model_config.get('confidence_threshold', 0.5)}") descriptions.append(f"- 置信度阈值: {model_config.get('confidence_threshold', 0.5)}")
descriptions.append(f"- IOU阈值: {model_config.get('iou_threshold', 0.45)}") descriptions.append(f"- IOU阈值: {model_config.get('iou_threshold', 0.45)}")
...@@ -2136,7 +2136,7 @@ class ModelSetPage(QtWidgets.QWidget): ...@@ -2136,7 +2136,7 @@ class ModelSetPage(QtWidgets.QWidget):
training_params = {} training_params = {}
try: try:
# 查找 args.yaml 文件(YOLO训练参数) # 查找 args.yaml 文件(训练参数)
args_file = model_dir / "args.yaml" args_file = model_dir / "args.yaml"
if args_file.exists(): if args_file.exists():
with open(args_file, 'r', encoding='utf-8') as f: with open(args_file, 'r', encoding='utf-8') as f:
...@@ -2177,7 +2177,7 @@ class ModelSetPage(QtWidgets.QWidget): ...@@ -2177,7 +2177,7 @@ class ModelSetPage(QtWidgets.QWidget):
results = {} results = {}
try: try:
# 查找 results.csv 文件(YOLO训练结果) # 查找 results.csv 文件(训练结果)
results_file = model_dir / "results.csv" results_file = model_dir / "results.csv"
if results_file.exists(): if results_file.exists():
import pandas as pd import pandas as pd
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment