Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
O
Oil_Level_Recognition_System
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Oil_Level_Recognition_System
Commits
29a54b5f
Commit
29a54b5f
authored
Dec 01, 2025
by
Yuhaibo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1
parent
81c47ac5
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
431 additions
and
114 deletions
+431
-114
1.spec
1.spec
+1
-1
app.py
app.py
+1
-1
app.py
handlers/app.py
+63
-0
model_test_handler.py
handlers/modelpage/model_test_handler.py
+28
-10
model_training_handler.py
handlers/modelpage/model_training_handler.py
+100
-82
model_trainingworker_handler.py
handlers/modelpage/model_trainingworker_handler.py
+113
-8
view_handler.py
handlers/view_handler.py
+3
-2
args.yaml
runs/segment/train/args.yaml
+106
-0
modelset_page.py
widgets/modelpage/modelset_page.py
+7
-1
general_set.py
widgets/videopage/general_set.py
+9
-9
No files found.
1.spec
View file @
29a54b5f
...
@@ -306,7 +306,7 @@ exe = EXE(
...
@@ -306,7 +306,7 @@ exe = EXE(
bootloader_ignore_signals
=
False
,
bootloader_ignore_signals
=
False
,
strip
=
False
,
# 不strip二进制文件
strip
=
False
,
# 不strip二进制文件
upx
=
False
,
# 不使用UPX压缩(避免兼容性问题)
upx
=
False
,
# 不使用UPX压缩(避免兼容性问题)
console
=
Tru
e
,
# True表示显示控制台窗口(用于调试)
console
=
Fals
e
,
# True表示显示控制台窗口(用于调试)
disable_windowed_traceback
=
False
,
disable_windowed_traceback
=
False
,
target_arch
=
None
,
target_arch
=
None
,
codesign_identity
=
None
,
codesign_identity
=
None
,
...
...
app.py
View file @
29a54b5f
...
@@ -147,7 +147,7 @@ except ImportError:
...
@@ -147,7 +147,7 @@ except ImportError:
# 支持相对导入(作为模块运行)和绝对导入(独立运行)
# 支持相对导入(作为模块运行)和绝对导入(独立运行)
try
:
try
:
from
.widgets
import
MenuBar
from
.widgets
import
MenuBar
from
.widgets.font_manager
import
FontManager
# 导入所有处理器 (Mixin类)
# 导入所有处理器 (Mixin类)
from
.handlers
import
(
from
.handlers
import
(
ChannelPanelHandler
,
ChannelPanelHandler
,
...
...
handlers/app.py
View file @
29a54b5f
...
@@ -689,6 +689,9 @@ class MainWindow(
...
@@ -689,6 +689,9 @@ class MainWindow(
# 默认显示模式1
# 默认显示模式1
self
.
videoLayoutStack
.
setCurrentIndex
(
0
)
self
.
videoLayoutStack
.
setCurrentIndex
(
0
)
self
.
_video_layout_mode
=
0
# 0=默认模式, 1=曲线模式
self
.
_video_layout_mode
=
0
# 0=默认模式, 1=曲线模式
# 初次创建页面后刷新一次按钮状态(默认布局)
self
.
_updateCurveButtonsByMission
()
return
page
return
page
...
@@ -989,6 +992,12 @@ class MainWindow(
...
@@ -989,6 +992,12 @@ class MainWindow(
# ========== 曲线面板信号 ==========
# ========== 曲线面板信号 ==========
self
.
curvePanel
.
backClicked
.
connect
(
self
.
switchToRealTimeDetectionPage
)
# 返回实时检测管理页面
self
.
curvePanel
.
backClicked
.
connect
(
self
.
switchToRealTimeDetectionPage
)
# 返回实时检测管理页面
# 页面堆栈切换与视频子布局切换时,刷新曲线按钮状态
if
hasattr
(
self
,
'stackedWidget'
):
self
.
stackedWidget
.
currentChanged
.
connect
(
self
.
_onMainStackChanged
)
if
hasattr
(
self
,
'videoLayoutStack'
):
self
.
videoLayoutStack
.
currentChanged
.
connect
(
self
.
_onVideoLayoutChanged
)
def
_initMenuBar
(
self
):
def
_initMenuBar
(
self
):
"""初始化菜单栏(委托给MenuBarHandler处理)"""
"""初始化菜单栏(委托给MenuBarHandler处理)"""
...
@@ -1026,6 +1035,60 @@ class MainWindow(
...
@@ -1026,6 +1035,60 @@ class MainWindow(
"""显示实时检测管理页面"""
"""显示实时检测管理页面"""
self
.
stackedWidget
.
setCurrentWidget
(
self
.
videoPage
)
self
.
stackedWidget
.
setCurrentWidget
(
self
.
videoPage
)
self
.
statusBar
()
.
showMessage
(
self
.
tr
(
"当前页面: 实时检测管理"
))
self
.
statusBar
()
.
showMessage
(
self
.
tr
(
"当前页面: 实时检测管理"
))
# 切到视频页时刷新默认布局下的曲线按钮状态
self
.
_updateCurveButtonsByMission
()
def
_onMainStackChanged
(
self
,
index
:
int
):
"""主页面堆栈变化时,根据当前页面刷新按钮状态"""
try
:
if
index
==
self
.
PAGE_VIDEO
:
self
.
_updateCurveButtonsByMission
()
except
Exception
:
pass
def
_onVideoLayoutChanged
(
self
,
index
:
int
):
"""视频页面子布局变化时刷新按钮状态"""
try
:
self
.
_video_layout_mode
=
index
# 仅在默认模式下需要检查并启用曲线按钮
if
index
==
0
:
self
.
_updateCurveButtonsByMission
()
except
Exception
:
pass
def
_updateCurveButtonsByMission
(
self
):
"""
根据每个通道的任务标签是否有任务,启用或禁用“查看曲线”按钮。
仅在默认模式布局(任务表格 + 2x2通道面板)下生效。
"""
try
:
# 仅在视频页默认布局下处理
if
(
not
hasattr
(
self
,
'stackedWidget'
)
or
self
.
stackedWidget
.
currentIndex
()
!=
self
.
PAGE_VIDEO
or
not
hasattr
(
self
,
'videoLayoutStack'
)
or
self
.
videoLayoutStack
.
currentIndex
()
!=
0
):
return
# 遍历四个通道面板
for
panel
in
getattr
(
self
,
'channelPanels'
,
[]):
# 任务标签文本
mission_label
=
getattr
(
panel
,
'taskLabel'
,
None
)
text
=
''
if
mission_label
and
hasattr
(
mission_label
,
'text'
):
text
=
mission_label
.
text
()
.
strip
()
# 判断是否有有效任务
has_mission
=
bool
(
text
)
and
text
not
in
(
'请选择任务'
,
'未选择任务'
)
# 找到“查看曲线”按钮并设置可用性
curve_btn
=
getattr
(
panel
,
'btn_curve'
,
None
)
if
curve_btn
and
hasattr
(
curve_btn
,
'setEnabled'
):
curve_btn
.
setEnabled
(
has_mission
)
except
Exception
:
# 安静失败,不影响主流程
pass
def
showModelPage
(
self
):
def
showModelPage
(
self
):
"""显示模型管理页面"""
"""显示模型管理页面"""
...
...
handlers/modelpage/model_test_handler.py
View file @
29a54b5f
...
@@ -1207,7 +1207,7 @@ class ModelTestHandler:
...
@@ -1207,7 +1207,7 @@ class ModelTestHandler:
height
=
area_data
[
'liquid_height'
]
height
=
area_data
[
'liquid_height'
]
print
(
f
"[液位检测] {area_name}: {height}mm"
)
print
(
f
"[液位检测] {area_name}: {height}mm"
)
self
.
_showTestDetectionResult
(
test_frame
,
detection_result
,
boxes
,
bottoms
,
tops
)
self
.
_showTestDetectionResult
(
test_frame
,
detection_result
,
boxes
,
bottoms
,
tops
,
actual_heights
)
print
(
f
"[液位检测] 检测结果已显示在右侧面板中"
)
print
(
f
"[液位检测] 检测结果已显示在右侧面板中"
)
else
:
else
:
...
@@ -1217,7 +1217,7 @@ class ModelTestHandler:
...
@@ -1217,7 +1217,7 @@ class ModelTestHandler:
print
(
f
" 2. 检测区域设置不正确(位置偏移)"
)
print
(
f
" 2. 检测区域设置不正确(位置偏移)"
)
print
(
f
" 3. 图像质量或光照条件不佳"
)
print
(
f
" 3. 图像质量或光照条件不佳"
)
self
.
_showTestDetectionResult
(
test_frame
,
None
,
boxes
,
bottoms
,
tops
)
self
.
_showTestDetectionResult
(
test_frame
,
None
,
boxes
,
bottoms
,
tops
,
actual_heights
)
print
(
f
"[液位检测] 原始图像和标注信息已显示在右侧面板中"
)
print
(
f
"[液位检测] 原始图像和标注信息已显示在右侧面板中"
)
...
@@ -1227,7 +1227,7 @@ class ModelTestHandler:
...
@@ -1227,7 +1227,7 @@ class ModelTestHandler:
traceback
.
print_exc
()
traceback
.
print_exc
()
try
:
try
:
self
.
_showTestDetectionResult
(
test_frame
,
None
,
boxes
,
bottoms
,
tops
)
self
.
_showTestDetectionResult
(
test_frame
,
None
,
boxes
,
bottoms
,
tops
,
actual_heights
)
print
(
f
"[液位检测] 原始图像已显示(检测失败)"
)
print
(
f
"[液位检测] 原始图像已显示(检测失败)"
)
except
Exception
as
show_error
:
except
Exception
as
show_error
:
print
(
f
"[液位检测] 显示原始图像也失败: {show_error}"
)
print
(
f
"[液位检测] 显示原始图像也失败: {show_error}"
)
...
@@ -1518,8 +1518,17 @@ class ModelTestHandler:
...
@@ -1518,8 +1518,17 @@ class ModelTestHandler:
if
hasattr
(
self
,
'_detection_stopped'
):
if
hasattr
(
self
,
'_detection_stopped'
):
print
(
f
"[视频检测] 最终停止标志: {self._detection_stopped}"
)
print
(
f
"[视频检测] 最终停止标志: {self._detection_stopped}"
)
def
_showTestDetectionResult
(
self
,
original_frame
,
detection_result
,
boxes
,
bottoms
,
tops
):
def
_showTestDetectionResult
(
self
,
original_frame
,
detection_result
,
boxes
,
bottoms
,
tops
,
actual_heights
=
None
):
"""在显示面板中显示检测结果(带液位线的图像)"""
"""在显示面板中显示检测结果(带液位线的图像)
Args:
original_frame: 原始图像帧
detection_result: 检测结果
boxes: 检测框列表
bottoms: 底部点列表
tops: 顶部点列表
actual_heights: 实际容器高度列表(毫米),如果为None则使用默认值20mm
"""
try
:
try
:
from
datetime
import
datetime
from
datetime
import
datetime
...
@@ -1527,6 +1536,12 @@ class ModelTestHandler:
...
@@ -1527,6 +1536,12 @@ class ModelTestHandler:
print
(
f
"[检测结果显示] 原始帧尺寸: {original_frame.shape}"
)
print
(
f
"[检测结果显示] 原始帧尺寸: {original_frame.shape}"
)
print
(
f
"[检测结果显示] 检测区域数量: {len(boxes)}"
)
print
(
f
"[检测结果显示] 检测区域数量: {len(boxes)}"
)
print
(
f
"[检测结果显示] 检测结果: {detection_result}"
)
print
(
f
"[检测结果显示] 检测结果: {detection_result}"
)
print
(
f
"[检测结果显示] 容器实际高度: {actual_heights}"
)
# 如果没有提供actual_heights,使用默认值
if
actual_heights
is
None
:
actual_heights
=
[
20.0
]
*
len
(
boxes
)
print
(
f
"[检测结果显示] 未提供容器高度,使用默认值: {actual_heights}"
)
# 复制原始帧
# 复制原始帧
result_frame
=
original_frame
.
copy
()
result_frame
=
original_frame
.
copy
()
...
@@ -1541,6 +1556,9 @@ class ModelTestHandler:
...
@@ -1541,6 +1556,9 @@ class ModelTestHandler:
bottom_y
=
bottoms
[
i
][
1
]
bottom_y
=
bottoms
[
i
][
1
]
top_y
=
tops
[
i
][
1
]
top_y
=
tops
[
i
][
1
]
# 获取该区域的实际容器高度
actual_height
=
actual_heights
[
i
]
if
i
<
len
(
actual_heights
)
else
20.0
# 提取液位高度(默认为0)
# 提取液位高度(默认为0)
liquid_height
=
0.0
liquid_height
=
0.0
...
@@ -1550,7 +1568,7 @@ class ModelTestHandler:
...
@@ -1550,7 +1568,7 @@ class ModelTestHandler:
area_name
,
area_data
=
area_items
[
i
]
area_name
,
area_data
=
area_items
[
i
]
if
'liquid_height'
in
area_data
:
if
'liquid_height'
in
area_data
:
liquid_height
=
area_data
[
'liquid_height'
]
liquid_height
=
area_data
[
'liquid_height'
]
print
(
f
"[检测结果显示] 区域{i+1}: 检测到液位 {liquid_height}mm"
)
print
(
f
"[检测结果显示] 区域{i+1}: 检测到液位 {liquid_height}mm
(容器高度: {actual_height}mm)
"
)
else
:
else
:
print
(
f
"[检测结果显示] 区域{i+1}: 未检测到液位,使用默认值 0mm"
)
print
(
f
"[检测结果显示] 区域{i+1}: 未检测到液位,使用默认值 0mm"
)
else
:
else
:
...
@@ -1558,10 +1576,10 @@ class ModelTestHandler:
...
@@ -1558,10 +1576,10 @@ class ModelTestHandler:
else
:
else
:
print
(
f
"[检测结果显示] 区域{i+1}: 无检测结果,使用默认值 0mm"
)
print
(
f
"[检测结果显示] 区域{i+1}: 无检测结果,使用默认值 0mm"
)
# 计算液位线的Y坐标
# 计算液位线的Y坐标
- 使用实际容器高度
container_height
=
bottom_y
-
top_y
container_height
_px
=
bottom_y
-
top_y
liquid_ratio
=
min
(
1.0
,
max
(
0.0
,
liquid_height
/
100.0
))
liquid_ratio
=
min
(
1.0
,
max
(
0.0
,
liquid_height
/
actual_height
))
liquid_y
=
int
(
bottom_y
-
container_height
*
liquid_ratio
)
liquid_y
=
int
(
bottom_y
-
container_height
_px
*
liquid_ratio
)
# 绘制液位线(红色)
# 绘制液位线(红色)
cv2
.
line
(
result_frame
,
(
left
,
liquid_y
),
(
right
,
liquid_y
),
(
0
,
0
,
255
),
3
)
cv2
.
line
(
result_frame
,
(
left
,
liquid_y
),
(
right
,
liquid_y
),
(
0
,
0
,
255
),
3
)
...
...
handlers/modelpage/model_training_handler.py
View file @
29a54b5f
...
@@ -973,7 +973,7 @@ class ModelTrainingHandler(ModelTestHandler):
...
@@ -973,7 +973,7 @@ class ModelTrainingHandler(ModelTestHandler):
# 强制垃圾回收,释放可能的文件句柄
# 强制垃圾回收,释放可能的文件句柄
gc
.
collect
()
gc
.
collect
()
time
.
sleep
(
0.5
)
time
.
sleep
(
1.0
)
# 增加初始等待时间
success_count
=
0
success_count
=
0
fail_count
=
0
fail_count
=
0
...
@@ -983,8 +983,8 @@ class ModelTrainingHandler(ModelTestHandler):
...
@@ -983,8 +983,8 @@ class ModelTrainingHandler(ModelTestHandler):
deleted
=
False
deleted
=
False
last_error
=
None
last_error
=
None
for
attempt
in
range
(
5
):
# 最多重试5次
for
attempt
in
range
(
10
):
# 增加到10次重试
self
.
_appendLog
(
f
"[调试] 尝试删除 (第{attempt+1}/
5
次)...
\n
"
)
self
.
_appendLog
(
f
"[调试] 尝试删除 (第{attempt+1}/
10
次)...
\n
"
)
try
:
try
:
if
os
.
path
.
exists
(
pt_file
):
if
os
.
path
.
exists
(
pt_file
):
os
.
remove
(
pt_file
)
os
.
remove
(
pt_file
)
...
@@ -998,7 +998,9 @@ class ModelTrainingHandler(ModelTestHandler):
...
@@ -998,7 +998,9 @@ class ModelTrainingHandler(ModelTestHandler):
except
OSError
as
e
:
except
OSError
as
e
:
last_error
=
str
(
e
)
last_error
=
str
(
e
)
self
.
_appendLog
(
f
"[调试] ✗ 删除失败: {last_error}
\n
"
)
self
.
_appendLog
(
f
"[调试] ✗ 删除失败: {last_error}
\n
"
)
if
attempt
<
4
:
if
attempt
<
9
:
# 每次重试前强制垃圾回收
gc
.
collect
()
self
.
_appendLog
(
f
"[调试] 等待0.5秒后重试...
\n
"
)
self
.
_appendLog
(
f
"[调试] 等待0.5秒后重试...
\n
"
)
time
.
sleep
(
0.5
)
# 等待0.5秒后重试
time
.
sleep
(
0.5
)
# 等待0.5秒后重试
...
@@ -1886,7 +1888,7 @@ class ModelTrainingHandler(ModelTestHandler):
...
@@ -1886,7 +1888,7 @@ class ModelTrainingHandler(ModelTestHandler):
def
add_box
(
self
,
cx
,
cy
,
size
):
def
add_box
(
self
,
cx
,
cy
,
size
):
"""
"""
添加检测区域
,并自动计算顶部点和底部点
添加检测区域
(不自动生成顶部点和底部点)
Args:
Args:
cx: 框中心x坐标
cx: 框中心x坐标
...
@@ -1894,32 +1896,17 @@ class ModelTrainingHandler(ModelTestHandler):
...
@@ -1894,32 +1896,17 @@ class ModelTrainingHandler(ModelTestHandler):
size: 框的边长
size: 框的边长
"""
"""
self
.
boxes
.
append
((
cx
,
cy
,
size
))
self
.
boxes
.
append
((
cx
,
cy
,
size
))
# 自动计算并添加底部点和顶部点
# 底部点:box底边y坐标 - box高度的10%,x为中心
half_size
=
size
/
2
bottom_y
=
cy
+
half_size
-
(
size
*
0.1
)
# 底边y - 10%高度
bottom_x
=
cx
# x位置为box轴对称中心
self
.
bottom_points
.
append
((
int
(
bottom_x
),
int
(
bottom_y
)))
# 顶部点:box顶边y坐标 + box高度的10%,x为中心
top_y
=
cy
-
half_size
+
(
size
*
0.1
)
# 顶边y + 10%高度
top_x
=
cx
# x位置为box轴对称中心
self
.
top_points
.
append
((
int
(
top_x
),
int
(
top_y
)))
print
(
f
"添加框: 中心({cx}, {cy}), 边长{size}"
)
print
(
f
"添加框: 中心({cx}, {cy}), 边长{size}"
)
print
(
f
" 底部点: ({int(bottom_x)}, {int(bottom_y)})"
)
print
(
f
" 顶部点: ({int(top_x)}, {int(top_y)})"
)
def
add_bottom
(
self
,
x
,
y
):
def
add_bottom
(
self
,
x
,
y
):
"""添加底部标记点
(保留用于兼容性,但不再使用)
"""
"""添加底部标记点"""
# 此方法保留但不再使用,因为底部点会在add_box时自动添加
self
.
bottom_points
.
append
((
int
(
x
),
int
(
y
)))
p
ass
p
rint
(
f
"添加底部点: ({int(x)}, {int(y)})"
)
def
add_top
(
self
,
x
,
y
):
def
add_top
(
self
,
x
,
y
):
"""添加顶部标记点
(保留用于兼容性,但不再使用)
"""
"""添加顶部标记点"""
# 此方法保留但不再使用,因为顶部点会在add_box时自动添加
self
.
top_points
.
append
((
int
(
x
),
int
(
y
)))
p
ass
p
rint
(
f
"添加顶部点: ({int(x)}, {int(y)})"
)
def
get_mission_results
(
self
):
def
get_mission_results
(
self
):
"""获取标注结果"""
"""获取标注结果"""
...
@@ -2946,7 +2933,12 @@ class ModelTrainingHandler(ModelTestHandler):
...
@@ -2946,7 +2933,12 @@ class ModelTrainingHandler(ModelTestHandler):
device
=
model_params
.
get
(
'device'
,
'未知'
)
device
=
model_params
.
get
(
'device'
,
'未知'
)
# 读取训练结果(如果有results.csv)
# 读取训练结果(如果有results.csv)
results_csv
=
os
.
path
.
join
(
model_dir
,
'results.csv'
)
# 优先从training_results目录读取
results_csv
=
os
.
path
.
join
(
model_dir
,
'training_results'
,
'results.csv'
)
if
not
os
.
path
.
exists
(
results_csv
):
# 备用:从模型根目录读取
results_csv
=
os
.
path
.
join
(
model_dir
,
'results.csv'
)
final_epoch
=
""
final_epoch
=
""
train_loss
=
""
train_loss
=
""
val_loss
=
""
val_loss
=
""
...
@@ -3557,86 +3549,111 @@ class ModelTrainingHandler(ModelTestHandler):
...
@@ -3557,86 +3549,111 @@ class ModelTrainingHandler(ModelTestHandler):
str: data.yaml文件路径,失败返回None
str: data.yaml文件路径,失败返回None
"""
"""
try
:
try
:
self
.
_appendLog
(
f
"正在处理数据集目录: {dataset_folder}
\n
"
)
# 检查是否已经有data.yaml文件
# 检查是否已经有data.yaml文件
existing_yaml
=
os
.
path
.
join
(
dataset_folder
,
'data.yaml'
)
existing_yaml
=
os
.
path
.
join
(
dataset_folder
,
'data.yaml'
)
if
os
.
path
.
exists
(
existing_yaml
):
if
os
.
path
.
exists
(
existing_yaml
):
self
.
_appendLog
(
f
"使用现有配置文件: {existing_yaml}
\n
"
)
self
.
_appendLog
(
f
"使用现有配置文件: {existing_yaml}
\n
"
)
return
existing_yaml
return
existing_yaml
#
直接在数据集目录创建配置文件
#
创建data.yaml文件路径
data_yaml_path
=
os
.
path
.
join
(
dataset_folder
,
'data.yaml'
)
data_yaml_path
=
os
.
path
.
join
(
dataset_folder
,
'data.yaml'
)
# 智能检测数据集目录结构
# 支持的图片格式
image_extensions
=
[
'.jpg'
,
'.jpeg'
,
'.png'
,
'.bmp'
,
'.tif'
,
'.tiff'
]
# 检查目录中是否包含图片
def
has_images
(
folder
):
if
not
os
.
path
.
exists
(
folder
)
or
not
os
.
path
.
isdir
(
folder
):
return
False
try
:
for
f
in
os
.
listdir
(
folder
):
if
os
.
path
.
isfile
(
os
.
path
.
join
(
folder
,
f
))
and
\
any
(
f
.
lower
()
.
endswith
(
ext
)
for
ext
in
image_extensions
):
return
True
except
Exception
as
e
:
self
.
_appendLog
(
f
"检查图片时出错: {str(e)}
\n
"
)
return
False
return
False
# 检查常见的数据集结构
train_dir
=
None
train_dir
=
None
val_dir
=
None
val_dir
=
None
# 方案1: 标准YOLO格式 (dataset_folder/images/train)
# 1. 检查是否是标准YOLO格式: dataset/images/train, dataset/images/val
images_train_dir
=
os
.
path
.
join
(
dataset_folder
,
'images'
,
'train'
)
if
os
.
path
.
exists
(
os
.
path
.
join
(
dataset_folder
,
'images'
,
'train'
)):
images_val_dir
=
os
.
path
.
join
(
dataset_folder
,
'images'
,
'val'
)
train_dir
=
os
.
path
.
join
(
dataset_folder
,
'images'
,
'train'
)
val_dir
=
os
.
path
.
join
(
dataset_folder
,
'images'
,
'val'
)
if
os
.
path
.
exists
(
images_train_dir
):
self
.
_appendLog
(
"检测到标准YOLO数据集结构
\n
"
)
train_dir
=
images_train_dir
# 2. 检查是否直接包含图片
val_dir
=
images_val_dir
if
os
.
path
.
exists
(
images_val_dir
)
else
images_train_dir
elif
has_images
(
dataset_folder
):
train_dir
=
dataset_folder
# 方案2: dataset_folder本身就是images目录 (例如: dataset/train/images)
val_dir
=
dataset_folder
elif
os
.
path
.
basename
(
dataset_folder
)
==
'images'
:
self
.
_appendLog
(
"检测到包含图片的目录,将用于训练和验证
\n
"
)
parent_dir
=
os
.
path
.
dirname
(
dataset_folder
)
# 3. 检查是否包含images目录
# 检查是否在train或val目录下
elif
os
.
path
.
exists
(
os
.
path
.
join
(
dataset_folder
,
'images'
)):
if
os
.
path
.
basename
(
parent_dir
)
in
[
'train'
,
'val'
]:
images_dir
=
os
.
path
.
join
(
dataset_folder
,
'images'
)
# dataset_folder是 dataset/train/images
if
has_images
(
images_dir
):
train_dir
=
dataset_folder
train_dir
=
images_dir
val_dir
=
dataset_folder
val_dir
=
images_dir
else
:
self
.
_appendLog
(
"检测到包含图片的images目录
\n
"
)
# dataset_folder是简化格式的images目录 (dataset/images)
train_dir
=
dataset_folder
# 4. 如果以上都不匹配,尝试在子目录中查找
val_dir
=
dataset_folder
if
train_dir
is
None
:
for
root
,
dirs
,
files
in
os
.
walk
(
dataset_folder
):
# 方案3: 直接包含图片的文件夹(递归搜索)
if
has_images
(
root
):
else
:
train_dir
=
root
image_extensions
=
[
'.jpg'
,
'.jpeg'
,
'.png'
,
'.bmp'
,
'.tif'
,
'.tiff'
]
val_dir
=
root
self
.
_appendLog
(
f
"在子目录中找到图片: {root}
\n
"
)
# 先检查当前目录
break
files
=
os
.
listdir
(
dataset_folder
)
has_images
=
any
(
os
.
path
.
isfile
(
os
.
path
.
join
(
dataset_folder
,
f
))
and
f
.
lower
()
.
endswith
(
tuple
(
image_extensions
))
for
f
in
files
)
if
has_images
:
train_dir
=
dataset_folder
val_dir
=
dataset_folder
else
:
# 递归搜索子目录,找到第一个包含图片的目录
for
subdir
in
files
:
subdir_path
=
os
.
path
.
join
(
dataset_folder
,
subdir
)
if
os
.
path
.
isdir
(
subdir_path
):
subdir_files
=
os
.
listdir
(
subdir_path
)
has_sub_images
=
any
(
os
.
path
.
isfile
(
os
.
path
.
join
(
subdir_path
,
f
))
and
f
.
lower
()
.
endswith
(
tuple
(
image_extensions
))
for
f
in
subdir_files
)
if
has_sub_images
:
train_dir
=
subdir_path
val_dir
=
subdir_path
break
if
not
train_dir
:
# 如果还是没找到图片,返回错误
self
.
_appendLog
(
f
"错误: 无法确定训练图片目录
\n
"
)
if
train_dir
is
None
or
not
has_images
(
train_dir
):
error_msg
=
f
"错误: 未在 {dataset_folder} 及其子目录中找到有效的图片文件。
\n
"
error_msg
+=
f
"支持的图片格式: {', '.join(image_extensions)}
\n
"
error_msg
+=
"请确保选择的目录中包含图片文件。
\n
"
self
.
_appendLog
(
error_msg
)
QtWidgets
.
QMessageBox
.
critical
(
self
.
training_panel
,
"错误"
,
error_msg
)
return
None
return
None
# 创建data.yaml
配置
# 创建data.yaml
内容
data_config
=
{
data_config
=
{
'train'
:
train_dir
,
'path'
:
os
.
path
.
dirname
(
os
.
path
.
abspath
(
dataset_folder
)),
# 数据集根目录
'val'
:
val_dir
,
'train'
:
os
.
path
.
relpath
(
train_dir
,
os
.
path
.
dirname
(
dataset_folder
))
.
replace
(
'
\\
'
,
'/'
),
'nc'
:
1
,
# 类别数量,液位检测通常是单类别
'val'
:
os
.
path
.
relpath
(
val_dir
,
os
.
path
.
dirname
(
dataset_folder
))
.
replace
(
'
\\
'
,
'/'
),
'nc'
:
1
,
# 类别数量
'names'
:
[
'liquid_level'
]
# 类别名称
'names'
:
[
'liquid_level'
]
# 类别名称
}
}
# 确保验证集目录存在,如果不存在则使用训练集
if
not
os
.
path
.
exists
(
os
.
path
.
join
(
os
.
path
.
dirname
(
dataset_folder
),
data_config
[
'val'
])):
data_config
[
'val'
]
=
data_config
[
'train'
]
self
.
_appendLog
(
"警告: 验证集目录不存在,将使用训练集进行验证
\n
"
)
# 写入文件
with
open
(
data_yaml_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
with
open
(
data_yaml_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
yaml
.
dump
(
data_config
,
f
,
default_flow_style
=
False
,
allow_unicode
=
True
)
yaml
.
dump
(
data_config
,
f
,
default_flow_style
=
False
,
allow_unicode
=
True
)
self
.
_appendLog
(
f
"创建配置文件: {data_yaml_path}
\n
"
)
self
.
_appendLog
(
f
"成功创建配置文件: {data_yaml_path}
\n
"
)
self
.
_appendLog
(
f
"训练集目录: {data_config['train']}
\n
"
)
self
.
_appendLog
(
f
"验证集目录: {data_config['val']}
\n
"
)
return
data_yaml_path
return
data_yaml_path
except
Exception
as
e
:
except
Exception
as
e
:
self
.
_appendLog
(
f
"创建配置文件失败: {str(e)}
\n
"
)
error_msg
=
f
"创建配置文件失败: {str(e)}
\n
"
self
.
_appendLog
(
error_msg
)
import
traceback
import
traceback
self
.
_appendLog
(
traceback
.
format_exc
())
self
.
_appendLog
(
traceback
.
format_exc
())
QtWidgets
.
QMessageBox
.
critical
(
self
.
training_panel
,
"错误"
,
f
"创建数据集配置文件时出错:
\n
{str(e)}"
)
return
None
return
None
\ No newline at end of file
handlers/modelpage/model_trainingworker_handler.py
View file @
29a54b5f
...
@@ -738,6 +738,7 @@ class TrainingWorker(QThread):
...
@@ -738,6 +738,7 @@ class TrainingWorker(QThread):
train_dir
=
os
.
path
.
join
(
model_output_dir
,
"train"
)
train_dir
=
os
.
path
.
join
(
model_output_dir
,
"train"
)
weights_dir
=
os
.
path
.
join
(
train_dir
,
"weights"
)
weights_dir
=
os
.
path
.
join
(
train_dir
,
"weights"
)
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
training_report
[
"model_output_dir"
]
=
model_output_dir
# 保存模型输出目录
# 设置training_results目录用于存放训练结果文件
# 设置training_results目录用于存放训练结果文件
training_results_dir
=
os
.
path
.
join
(
model_output_dir
,
"training_results"
)
training_results_dir
=
os
.
path
.
join
(
model_output_dir
,
"training_results"
)
...
@@ -791,7 +792,11 @@ class TrainingWorker(QThread):
...
@@ -791,7 +792,11 @@ class TrainingWorker(QThread):
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
training_report
[
"weights_dir"
]
=
weights_dir
# 立即转换PT文件为DAT格式并删除PT文件
# 立即转换PT文件为DAT格式并删除PT文件
self
.
log_output
.
emit
(
f
"
\n
[调试] ========== 准备调用转换和删除方法 ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] 调用位置: 训练完成 - 正常路径
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] Weights目录: {weights_dir}
\n
"
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
log_output
.
emit
(
f
"[调试] 转换和删除方法调用完成
\n
"
)
# 整理训练结果文件
# 整理训练结果文件
self
.
_organizeTrainingResults
(
save_dir_abs
)
self
.
_organizeTrainingResults
(
save_dir_abs
)
...
@@ -808,7 +813,11 @@ class TrainingWorker(QThread):
...
@@ -808,7 +813,11 @@ class TrainingWorker(QThread):
if
os
.
path
.
exists
(
possible_dir
):
if
os
.
path
.
exists
(
possible_dir
):
weights_dir
=
possible_dir
weights_dir
=
possible_dir
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
log_output
.
emit
(
f
"
\n
[调试] ========== 准备调用转换和删除方法 ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] 调用位置: 训练完成 - 备用路径
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] Weights目录: {weights_dir}
\n
"
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
log_output
.
emit
(
f
"[调试] 转换和删除方法调用完成
\n
"
)
break
break
else
:
else
:
self
.
log_output
.
emit
(
f
"[ERROR] 未找到权重目录,跳过转换
\n
"
)
self
.
log_output
.
emit
(
f
"[ERROR] 未找到权重目录,跳过转换
\n
"
)
...
@@ -854,7 +863,11 @@ class TrainingWorker(QThread):
...
@@ -854,7 +863,11 @@ class TrainingWorker(QThread):
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
training_report
[
"weights_dir"
]
=
weights_dir
# 立即转换PT文件为DAT格式并删除PT文件
# 立即转换PT文件为DAT格式并删除PT文件
self
.
log_output
.
emit
(
f
"
\n
[调试] ========== 准备调用转换和删除方法 ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] 调用位置: 继续训练完成 - 正常路径
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] Weights目录: {weights_dir}
\n
"
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
log_output
.
emit
(
f
"[调试] 转换和删除方法调用完成
\n
"
)
# 整理训练结果文件
# 整理训练结果文件
self
.
_organizeTrainingResults
(
save_dir_abs
)
self
.
_organizeTrainingResults
(
save_dir_abs
)
...
@@ -871,7 +884,11 @@ class TrainingWorker(QThread):
...
@@ -871,7 +884,11 @@ class TrainingWorker(QThread):
if
os
.
path
.
exists
(
possible_dir
):
if
os
.
path
.
exists
(
possible_dir
):
weights_dir
=
possible_dir
weights_dir
=
possible_dir
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
log_output
.
emit
(
f
"
\n
[调试] ========== 准备调用转换和删除方法 ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] 调用位置: 继续训练完成 - 备用路径
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] Weights目录: {weights_dir}
\n
"
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
log_output
.
emit
(
f
"[调试] 转换和删除方法调用完成
\n
"
)
break
break
else
:
else
:
self
.
log_output
.
emit
(
f
"[ERROR] 未找到权重目录,跳过转换
\n
"
)
self
.
log_output
.
emit
(
f
"[ERROR] 未找到权重目录,跳过转换
\n
"
)
...
@@ -896,7 +913,11 @@ class TrainingWorker(QThread):
...
@@ -896,7 +913,11 @@ class TrainingWorker(QThread):
self
.
training_report
[
"weights_dir"
]
=
weights_dir
self
.
training_report
[
"weights_dir"
]
=
weights_dir
# 立即转换PT文件为DAT格式并删除PT文件
# 立即转换PT文件为DAT格式并删除PT文件
self
.
log_output
.
emit
(
f
"
\n
[调试] ========== 准备调用转换和删除方法 ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] 调用位置: 用户停止训练
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] Weights目录: {weights_dir}
\n
"
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
log_output
.
emit
(
f
"[调试] 转换和删除方法调用完成
\n
"
)
# 整理训练结果文件:将train目录下的其他文件移动到training_results目录
# 整理训练结果文件:将train目录下的其他文件移动到training_results目录
self
.
_organizeTrainingResults
(
save_dir_abs
)
self
.
_organizeTrainingResults
(
save_dir_abs
)
...
@@ -988,6 +1009,12 @@ class TrainingWorker(QThread):
...
@@ -988,6 +1009,12 @@ class TrainingWorker(QThread):
if
not
saved
:
if
not
saved
:
self
.
log_output
.
emit
(
"⚠ 所有保存方法均失败
\n
"
)
self
.
log_output
.
emit
(
"⚠ 所有保存方法均失败
\n
"
)
else
:
# 保存成功后,立即转换为DAT并删除PT
self
.
log_output
.
emit
(
"
\n
[调试] ========== 保存检查点后转换并删除PT ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] 检查点已保存,开始转换为DAT格式...
\n
"
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
log_output
.
emit
(
f
"[调试] 检查点转换和删除完成
\n
"
)
else
:
else
:
self
.
log_output
.
emit
(
f
"⚠ 权重目录不存在: {weights_dir}
\n
"
)
self
.
log_output
.
emit
(
f
"⚠ 权重目录不存在: {weights_dir}
\n
"
)
except
Exception
as
save_error
:
except
Exception
as
save_error
:
...
@@ -1063,6 +1090,12 @@ class TrainingWorker(QThread):
...
@@ -1063,6 +1090,12 @@ class TrainingWorker(QThread):
if
not
saved
:
if
not
saved
:
self
.
log_output
.
emit
(
"⚠ 所有保存方法均失败
\n
"
)
self
.
log_output
.
emit
(
"⚠ 所有保存方法均失败
\n
"
)
else
:
# 保存成功后,立即转换为DAT并删除PT
self
.
log_output
.
emit
(
"
\n
[调试] ========== 保存检查点后转换并删除PT ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] 检查点已保存,开始转换为DAT格式...
\n
"
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
log_output
.
emit
(
f
"[调试] 检查点转换和删除完成
\n
"
)
else
:
else
:
self
.
log_output
.
emit
(
f
"⚠ 权重目录不存在: {weights_dir}
\n
"
)
self
.
log_output
.
emit
(
f
"⚠ 权重目录不存在: {weights_dir}
\n
"
)
except
Exception
as
save_error
:
except
Exception
as
save_error
:
...
@@ -1113,6 +1146,12 @@ class TrainingWorker(QThread):
...
@@ -1113,6 +1146,12 @@ class TrainingWorker(QThread):
if
not
saved
:
if
not
saved
:
self
.
log_output
.
emit
(
"⚠ 所有保存方法均失败
\n
"
)
self
.
log_output
.
emit
(
"⚠ 所有保存方法均失败
\n
"
)
else
:
# 保存成功后,立即转换为DAT并删除PT
self
.
log_output
.
emit
(
"
\n
[调试] ========== 保存检查点后转换并删除PT ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试] 检查点已保存,开始转换为DAT格式...
\n
"
)
self
.
_convertPtToDatAndCleanup
(
weights_dir
)
self
.
log_output
.
emit
(
f
"[调试] 检查点转换和删除完成
\n
"
)
else
:
else
:
self
.
log_output
.
emit
(
f
"⚠ 权重目录不存在: {weights_dir}
\n
"
)
self
.
log_output
.
emit
(
f
"⚠ 权重目录不存在: {weights_dir}
\n
"
)
else
:
else
:
...
@@ -1194,32 +1233,49 @@ class TrainingWorker(QThread):
...
@@ -1194,32 +1233,49 @@ class TrainingWorker(QThread):
Args:
Args:
weights_dir: 权重目录路径
weights_dir: 权重目录路径
"""
"""
self
.
log_output
.
emit
(
"
\n
"
+
"="
*
70
+
"
\n
"
)
self
.
log_output
.
emit
(
"[调试-Worker] _convertPtToDatAndCleanup 方法被调用
\n
"
)
self
.
log_output
.
emit
(
f
"[调试-Worker] 权重目录: {weights_dir}
\n
"
)
self
.
log_output
.
emit
(
"="
*
70
+
"
\n
"
)
try
:
try
:
if
not
os
.
path
.
exists
(
weights_dir
):
if
not
os
.
path
.
exists
(
weights_dir
):
self
.
log_output
.
emit
(
"[调试-Worker] 权重目录不存在,退出
\n
"
)
return
return
self
.
log_output
.
emit
(
"[调试-Worker] 正在创建PT到DAT转换器...
\n
"
)
# 创建转换器
# 创建转换器
converter
=
PtToDatConverter
(
key
=
MODEL_ENCRYPTION_KEY
)
converter
=
PtToDatConverter
(
key
=
MODEL_ENCRYPTION_KEY
)
self
.
log_output
.
emit
(
"[调试-Worker] 转换器创建成功
\n
"
)
# 查找所有.pt文件
# 查找所有.pt文件
pt_files
=
[]
pt_files
=
[]
self
.
log_output
.
emit
(
"[调试-Worker] 开始扫描PT文件...
\n
"
)
for
filename
in
os
.
listdir
(
weights_dir
):
for
filename
in
os
.
listdir
(
weights_dir
):
if
filename
.
endswith
(
'.pt'
):
if
filename
.
endswith
(
'.pt'
):
pt_file_path
=
os
.
path
.
join
(
weights_dir
,
filename
)
pt_file_path
=
os
.
path
.
join
(
weights_dir
,
filename
)
pt_files
.
append
(
pt_file_path
)
pt_files
.
append
(
pt_file_path
)
self
.
log_output
.
emit
(
f
"[调试-Worker] 发现PT文件: {filename}
\n
"
)
if
not
pt_files
:
if
not
pt_files
:
self
.
log_output
.
emit
(
"[调试-Worker] 未发现PT文件,退出
\n
"
)
return
return
self
.
log_output
.
emit
(
f
"[调试-Worker] 共发现 {len(pt_files)} 个PT文件
\n
"
)
# 转换每个.pt文件
# 转换每个.pt文件
converted_files
=
[]
converted_files
=
[]
self
.
log_output
.
emit
(
"
\n
[调试-Worker] 开始转换PT文件...
\n
"
)
for
pt_file
in
pt_files
:
for
pt_file
in
pt_files
:
try
:
try
:
filename
=
os
.
path
.
basename
(
pt_file
)
filename
=
os
.
path
.
basename
(
pt_file
)
self
.
log_output
.
emit
(
f
"
\n
[调试-Worker] 处理文件: {filename}
\n
"
)
# 生成输出文件名(使用.dat扩展名)
# 生成输出文件名(使用.dat扩展名)
exp_name
=
self
.
training_params
.
get
(
'exp_name'
,
''
)
exp_name
=
self
.
training_params
.
get
(
'exp_name'
,
''
)
base_name
=
os
.
path
.
splitext
(
filename
)[
0
]
base_name
=
os
.
path
.
splitext
(
filename
)[
0
]
self
.
log_output
.
emit
(
f
"[调试-Worker] 实验名称: {exp_name}
\n
"
)
self
.
log_output
.
emit
(
f
"[调试-Worker] 基础名称: {base_name}
\n
"
)
if
exp_name
:
if
exp_name
:
# 例如: best.pt -> best.template_1234.dat
# 例如: best.pt -> best.template_1234.dat
...
@@ -1229,45 +1285,94 @@ class TrainingWorker(QThread):
...
@@ -1229,45 +1285,94 @@ class TrainingWorker(QThread):
output_filename
=
f
"{base_name}.dat"
output_filename
=
f
"{base_name}.dat"
output_path
=
os
.
path
.
join
(
weights_dir
,
output_filename
)
output_path
=
os
.
path
.
join
(
weights_dir
,
output_filename
)
self
.
log_output
.
emit
(
f
"[调试-Worker] 输出路径: {output_filename}
\n
"
)
# 执行转换
# 执行转换
self
.
log_output
.
emit
(
f
"[调试-Worker] 开始转换 {filename}...
\n
"
)
converted_path
=
converter
.
convert_file
(
pt_file
,
output_path
)
converted_path
=
converter
.
convert_file
(
pt_file
,
output_path
)
converted_files
.
append
(
converted_path
)
converted_files
.
append
(
converted_path
)
self
.
log_output
.
emit
(
f
"[调试-Worker] ✓ 转换成功: {output_filename}
\n
"
)
# 删除原始.pt文件(增强版:重试机制)
# 删除原始.pt文件(增强版:重试机制)
self
.
log_output
.
emit
(
f
"
\n
[调试-Worker] ========== 开始删除PT文件 ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[调试-Worker] 目标文件: {pt_file}
\n
"
)
import
time
import
time
import
gc
import
gc
# 先等待确保转换完成
self
.
log_output
.
emit
(
f
"[调试-Worker] 等待0.5秒确保转换完成...
\n
"
)
time
.
sleep
(
0.5
)
# 强制垃圾回收,释放可能的文件句柄
# 强制垃圾回收,释放可能的文件句柄
self
.
log_output
.
emit
(
f
"[调试-Worker] 执行垃圾回收...
\n
"
)
gc
.
collect
()
gc
.
collect
()
time
.
sleep
(
0.3
)
deleted
=
False
deleted
=
False
for
attempt
in
range
(
5
):
# 最多重试5次
last_error
=
None
self
.
log_output
.
emit
(
f
"[调试-Worker] 开始10次删除重试...
\n
"
)
for
attempt
in
range
(
10
):
# 增加到10次重试
self
.
log_output
.
emit
(
f
"[调试-Worker] 第 {attempt+1}/10 次删除尝试...
\n
"
)
try
:
try
:
if
os
.
path
.
exists
(
pt_file
):
if
os
.
path
.
exists
(
pt_file
):
self
.
log_output
.
emit
(
f
"[调试-Worker] 文件存在,尝试删除...
\n
"
)
os
.
remove
(
pt_file
)
os
.
remove
(
pt_file
)
deleted
=
True
deleted
=
True
self
.
log_output
.
emit
(
f
"[调试-Worker] ✓✓✓ 删除成功!
\n
"
)
self
.
log_output
.
emit
(
f
"[转换] 已删除原始文件: {filename}
\n
"
)
break
else
:
# 文件已不存在
self
.
log_output
.
emit
(
f
"[调试-Worker] 文件不存在(可能已被删除)
\n
"
)
deleted
=
True
break
break
except
Exception
as
del_error
:
except
Exception
as
del_error
:
if
attempt
<
4
:
last_error
=
str
(
del_error
)
time
.
sleep
(
0.3
)
# 等待0.3秒后重试
self
.
log_output
.
emit
(
f
"[调试-Worker] ✗ 删除失败: {last_error}
\n
"
)
if
attempt
<
9
:
# 每次重试前强制垃圾回收
self
.
log_output
.
emit
(
f
"[调试-Worker] 执行垃圾回收并等待0.5秒...
\n
"
)
gc
.
collect
()
time
.
sleep
(
0.5
)
# 增加等待时间到0.5秒
else
:
else
:
# 最后一次失败,输出错误日志
# 最后一次失败,输出错误日志
self
.
log_output
.
emit
(
f
"[警告] 无法删除PT文件: {filename} - {del_error}
\n
"
)
self
.
log_output
.
emit
(
f
"
\n
[警告] ========== 删除失败 ==========
\n
"
)
self
.
log_output
.
emit
(
f
"[提示] 请手动删除该文件: {pt_file}
\n
"
)
self
.
log_output
.
emit
(
f
"[警告] 无法删除PT文件: {filename}
\n
"
)
self
.
log_output
.
emit
(
f
" 错误: {last_error}
\n
"
)
self
.
log_output
.
emit
(
f
" 路径: {pt_file}
\n
"
)
self
.
log_output
.
emit
(
f
" 请手动删除该文件
\n
"
)
self
.
log_output
.
emit
(
f
"[警告] ====================================
\n
"
)
if
deleted
:
if
not
deleted
:
self
.
log_output
.
emit
(
f
"[成功] 已删除PT文件: {filename}
\n
"
)
self
.
log_output
.
emit
(
f
"[调试-Worker] ========== PT文件删除失败 ==========
\n
"
)
# 记录删除失败的文件
if
"failed_deletions"
not
in
self
.
training_report
:
self
.
training_report
[
"failed_deletions"
]
=
[]
self
.
training_report
[
"failed_deletions"
]
.
append
({
"file"
:
pt_file
,
"error"
:
last_error
})
else
:
self
.
log_output
.
emit
(
f
"[调试-Worker] ========== PT文件删除成功 ==========
\n
"
)
except
Exception
as
convert_error
:
except
Exception
as
convert_error
:
self
.
log_output
.
emit
(
f
"[调试-Worker] ✗ 转换异常: {str(convert_error)}
\n
"
)
import
traceback
self
.
log_output
.
emit
(
f
"[调试-Worker] 详细错误:
\n
{traceback.format_exc()}
\n
"
)
continue
continue
# 更新训练报告
# 更新训练报告
self
.
training_report
[
"converted_dat_files"
]
=
converted_files
self
.
training_report
[
"converted_dat_files"
]
=
converted_files
self
.
log_output
.
emit
(
f
"
\n
[调试-Worker] _convertPtToDatAndCleanup 执行完成
\n
"
)
self
.
log_output
.
emit
(
f
"[调试-Worker] 共转换 {len(converted_files)} 个文件
\n
"
)
self
.
log_output
.
emit
(
"="
*
70
+
"
\n\n
"
)
except
Exception
as
e
:
except
Exception
as
e
:
self
.
log_output
.
emit
(
f
"
\n
[调试-Worker] ✗✗✗ _convertPtToDatAndCleanup 发生异常: {str(e)}
\n
"
)
import
traceback
import
traceback
traceback
.
print_exc
()
self
.
log_output
.
emit
(
f
"[调试-Worker] 详细错误:
\n
{traceback.format_exc()}
\n
"
)
self
.
log_output
.
emit
(
"="
*
70
+
"
\n\n
"
)
def
_organizeTrainingResults
(
self
,
train_dir
):
def
_organizeTrainingResults
(
self
,
train_dir
):
"""
"""
...
...
handlers/view_handler.py
View file @
29a54b5f
...
@@ -439,9 +439,10 @@ class ViewHandler:
...
@@ -439,9 +439,10 @@ class ViewHandler:
for
panel
in
self
.
channelPanels
:
for
panel
in
self
.
channelPanels
:
if
hasattr
(
panel
,
'btnCurve'
):
if
hasattr
(
panel
,
'btnCurve'
):
if
target_index
==
0
:
if
target_index
==
0
:
print
(
1
)
# 曲线模式的同步布局:禁用查看曲线按钮
# 曲线模式的同步布局:禁用查看曲线按钮
panel
.
btnCurve
.
setEnabled
(
False
)
#
panel.btnCurve.setEnabled(False)
panel
.
btnCurve
.
setToolTip
(
"同步布局下无法查看曲线"
)
#
panel.btnCurve.setToolTip("同步布局下无法查看曲线")
else
:
else
:
# 曲线模式的历史回放布局:检查通道是否有任务
# 曲线模式的历史回放布局:检查通道是否有任务
has_task
=
False
has_task
=
False
...
...
runs/segment/train/args.yaml
0 → 100644
View file @
29a54b5f
task
:
segment
mode
:
train
model
:
C:\Users\admin\AppData\Local\Temp\yolo_train_wobn62b4.pt
data
:
D:/restructure/liquid_level_line_detection_system/dataset1/train\data.yaml
epochs
:
100
time
:
null
patience
:
100
batch
:
3
imgsz
:
640
save
:
true
save_period
:
1
cache
:
false
device
:
'
0'
workers
:
0
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
multi_scale
:
false
compile
:
false
overlap_mask
:
true
mask_ratio
:
4
dropout
:
0.0
val
:
true
split
:
val
save_json
:
false
conf
:
null
iou
:
0.7
max_det
:
300
half
:
false
dnn
:
false
plots
:
true
source
:
null
vid_stride
:
1
stream_buffer
:
false
visualize
:
false
augment
:
false
agnostic_nms
:
false
classes
:
null
retina_masks
:
false
embed
:
null
show
:
false
save_frames
:
false
save_txt
:
false
save_conf
:
false
save_crop
:
false
show_labels
:
true
show_conf
:
true
show_boxes
:
true
line_width
:
null
format
:
torchscript
keras
:
false
optimize
:
false
int8
:
false
dynamic
:
false
simplify
:
true
opset
:
null
workspace
:
null
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
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
bgr
:
0.0
mosaic
:
1.0
mixup
:
0.0
cutmix
:
0.0
copy_paste
:
0.0
copy_paste_mode
:
flip
auto_augment
:
randaugment
erasing
:
0.4
cfg
:
null
tracker
:
botsort.yaml
save_dir
:
D:\restructure\liquid_level_line_detection_system\runs\segment\train
widgets/modelpage/modelset_page.py
View file @
29a54b5f
...
@@ -1706,7 +1706,13 @@ class ModelSetPage(QtWidgets.QWidget):
...
@@ -1706,7 +1706,13 @@ class ModelSetPage(QtWidgets.QWidget):
# 查找.dat文件(优先在根目录,兼容旧的weights子目录)
# 查找.dat文件(优先在根目录,兼容旧的weights子目录)
dat_files
=
list
(
subdir
.
glob
(
"*.dat"
))
dat_files
=
list
(
subdir
.
glob
(
"*.dat"
))
# 如果根目录没有.dat文件,检查weights子目录(兼容旧结构)
# 如果根目录没有.dat文件,检查train/weights子目录(训练后的模型)
if
not
dat_files
:
train_weights_dir
=
subdir
/
"train"
/
"weights"
if
train_weights_dir
.
exists
():
dat_files
=
list
(
train_weights_dir
.
glob
(
"*.dat"
))
# 如果train/weights也没有,检查weights子目录(兼容旧结构)
if
not
dat_files
:
if
not
dat_files
:
weights_dir
=
subdir
/
"weights"
weights_dir
=
subdir
/
"weights"
if
weights_dir
.
exists
():
if
weights_dir
.
exists
():
...
...
widgets/videopage/general_set.py
View file @
29a54b5f
...
@@ -1418,8 +1418,8 @@ class AnnotationWidget(QtWidgets.QWidget):
...
@@ -1418,8 +1418,8 @@ class AnnotationWidget(QtWidgets.QWidget):
instructions
=
[
instructions
=
[
"标注操作指南"
,
"标注操作指南"
,
"1. 左键拖动放置检测区域框"
,
"1. 左键拖动放置检测区域框"
,
"2.
拖动设置容器底
部点"
,
"2.
点击设置容器顶
部点"
,
"3.
拖动设置容器顶
部点"
,
"3.
点击设置容器底
部点"
,
"4. 双击编辑名称/高度,Enter确认"
,
"4. 双击编辑名称/高度,Enter确认"
,
"5. 双击状态标签切换状态
\n
(初始空满状态逻辑优化中,敬请期待)"
,
"5. 双击状态标签切换状态
\n
(初始空满状态逻辑优化中,敬请期待)"
,
"
\n
6. 双击空白区域完成标注"
,
"
\n
6. 双击空白区域完成标注"
,
...
@@ -1480,8 +1480,8 @@ class AnnotationWidget(QtWidgets.QWidget):
...
@@ -1480,8 +1480,8 @@ class AnnotationWidget(QtWidgets.QWidget):
instructions_en
=
[
instructions_en
=
[
"Annotation Guide"
,
"Annotation Guide"
,
"1. Drag detection box"
,
"1. Drag detection box"
,
"2. Click
bottom line
"
,
"2. Click
top point
"
,
"3. Click
top line
"
,
"3. Click
bottom point
"
,
"4. Double-click label to edit"
,
"4. Double-click label to edit"
,
"5. Enter=Confirm, ESC=Cancel"
,
"5. Enter=Confirm, ESC=Cancel"
,
"6. Repeat steps"
,
"6. Repeat steps"
,
...
@@ -1589,13 +1589,13 @@ class AnnotationWidget(QtWidgets.QWidget):
...
@@ -1589,13 +1589,13 @@ class AnnotationWidget(QtWidgets.QWidget):
# 检查点击位置是否在最后一个检测框内
# 检查点击位置是否在最后一个检测框内
if
self
.
_isPointInLastBox
(
image_x
,
image_y
):
if
self
.
_isPointInLastBox
(
image_x
,
image_y
):
self
.
annotation_engine
.
bottom_points
.
append
((
image_x
,
image_y
))
self
.
annotation_engine
.
bottom_points
.
append
((
image_x
,
image_y
))
self
.
annotation_engine
.
step
=
2
self
.
annotation_engine
.
step
=
0
# 完成一组标注,回到画框模式
self
.
_updateDisplay
()
self
.
_updateDisplay
()
elif
self
.
annotation_engine
.
step
==
2
:
# 点击顶部模式
elif
self
.
annotation_engine
.
step
==
2
:
# 点击顶部模式
# 检查点击位置是否在最后一个检测框内
# 检查点击位置是否在最后一个检测框内
if
self
.
_isPointInLastBox
(
image_x
,
image_y
):
if
self
.
_isPointInLastBox
(
image_x
,
image_y
):
self
.
annotation_engine
.
top_points
.
append
((
image_x
,
image_y
))
self
.
annotation_engine
.
top_points
.
append
((
image_x
,
image_y
))
self
.
annotation_engine
.
step
=
0
self
.
annotation_engine
.
step
=
1
# 切换到标注底部点模式
self
.
_updateDisplay
()
self
.
_updateDisplay
()
def
_onMouseMove
(
self
,
event
):
def
_onMouseMove
(
self
,
event
):
...
@@ -1644,11 +1644,11 @@ class AnnotationWidget(QtWidgets.QWidget):
...
@@ -1644,11 +1644,11 @@ class AnnotationWidget(QtWidgets.QWidget):
cy
=
(
self
.
box_start
[
1
]
+
y2
)
//
2
cy
=
(
self
.
box_start
[
1
]
+
y2
)
//
2
size
=
length
size
=
length
#
🔥 调用 add_box 方法,自动生成顶部点和底部点
#
调用 add_box 方法添加检测框
self
.
annotation_engine
.
add_box
(
cx
,
cy
,
size
)
self
.
annotation_engine
.
add_box
(
cx
,
cy
,
size
)
#
🔥 保持 step = 0(画框模式),不再需要手动点击设置顶部和底部点
#
切换到标注顶部点模式(先标顶点)
# self.annotation_engine.step = 1 # 旧逻辑:进入点击底
部点模式
self
.
annotation_engine
.
step
=
2
# step=2 是标注顶
部点模式
self
.
_updateDisplay
()
self
.
_updateDisplay
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment