09 · 开发指南¶
本篇是给 24 级冲国创同学的开发参考:如何调试、如何加新功能、如何在不重训的前提下迭代、如何把这个项目继续推上去。 前置:建议把 02 ~ 08 都过一遍。 上一篇:08 · 问题与改进
9.1 本地调试环境¶
推荐 IDE 配置¶
| IDE | 配置 |
|---|---|
| VS Code | 推荐。装 Python 扩展 + Pylance + Mermaid Preview |
| PyCharm | 也可。注意把 models/.idea/ 删掉避免冲突 |
.vscode/launch.json 示例¶
{
"version": "0.2.0",
"configurations": [
{
"name": "Run main GUI",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/main.py",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "Run swellcamera standalone",
"type": "python",
"request": "launch",
"module": "swellcamera",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "Run watercamera standalone",
"type": "python",
"request": "launch",
"module": "watercamera",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
}
]
}
注意 cwd 必须是项目根目录——否则 watercamera.py:113 的相对路径 simhei.ttf 找不到、各 model 的 get_resource_path 也会算错。
推荐 .gitignore¶
仓库当前没有 .gitignore,所以 .idea/、__pycache__/、tempCodeRunnerFile.py 都被跟踪了。强烈建议加一个:
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
*.egg-info/
# IDE
.idea/
.vscode/launch.json
*.swp
# OS
.DS_Store
Thumbs.db
# 临时文件
tempCodeRunnerFile.py
temp_frame.jpg
test.jpg
*.log
# PyInstaller 构建产物
build/
dist/
*.spec.bak
# 测试输出
test_output/
9.2 如何在不重训的前提下替换模型¶
如果想升级到更好的 backbone(如 EfficientNet-B5、ConvNeXt-Tiny),前提是你拿到了对应的 .pth 权重。流程:
步骤 1:保持接口约定¶
新模型必须:
- 输入是 [1, 3, H, W] 的张量
- 输出单一 logit(经 sigmoid 后是二分类概率)
- 加载权重的方式与现在兼容(load_state_dict(torch.load(path)))
步骤 2:替换 backbone¶
以替换 model4 为 ConvNeXt-Tiny 为例:
# 改 models/model4.py:_build_model
import torchvision.models as tv
def _build_model(self):
model = tv.convnext_tiny(weights=None)
# 替换分类头(输入维度对应 ConvNeXt-Tiny 的特征维 768)
num_features = 768
model.classifier[2] = nn.Sequential(
nn.Dropout(0.45),
nn.Linear(num_features, 256),
nn.ReLU(),
nn.BatchNorm1d(256),
nn.Dropout(0.55),
nn.Linear(256, 1)
)
model_weight_path = get_resource_path(os.path.join('models', os.path.basename(self.model_path)))
model.load_state_dict(torch.load(model_weight_path, map_location=self.device))
model.to(self.device).eval()
return model
步骤 3:调整输入尺寸¶
不同 backbone 的最佳输入尺寸不同:
| Backbone | 推荐输入 | 特征维 |
|---|---|---|
| EfficientNet-B3(当前) | 300×300 | 1536 |
| EfficientNet-B5 | 456×456 | 2048 |
| ConvNeXt-Tiny | 224×224 | 768 |
| ResNet50 | 224×224 | 2048 |
| ViT-B/16 | 224×224 | 768 |
改 model4.py:28 的 self.img_size。
步骤 4:保持 class_names 不变¶
class_names = ["膨胀", "不膨胀"] 这种语义不变。否则 swellcamera 的 label_mapping 也要改。
9.3 如何加新的检测模块¶
如果将来想加"气味浓度检测"等第三个模块,按以下步骤:
步骤 1:建子模块目录¶
gascamera/
├── __init__.py # from .gascamera import main
├── __main__.py # python -m gascamera 入口
└── gascamera.py # 主逻辑,仿 swellcamera 写
步骤 2:写主逻辑¶
复制 swellcamera.py 作为模板,改:
1. 类名:RealTimeGasClassifier
2. import 的 model:from models.model5 import AgeClassifier as GasClassifier
3. 窗口标题:cv2.imshow('Real-time Gas Detection', frame)
4. 文本前缀:"气味: ..."
步骤 3:在 main.py 注册¶
仿 main.py:122-173 在 ModuleManager 加 start_gas_module / stop_gas_module / is_gas_running。仿 main.py:625-651 加 toggle_gas_detection / start_gas_detection / stop_gas_detection。
步骤 4:加 UI 按钮¶
在 main.py:284-312 的 button_frame 里加:
self.gas_button = tk.Button(
button_frame,
text="启动气味检测",
font=("Microsoft YaHei", 14, "bold"),
bg="#3E8E41", # 绿色
fg="white",
relief=tk.FLAT,
cursor="hand2",
command=self.toggle_gas_detection,
height=2,
width=15
)
self.gas_button.pack(side=tk.LEFT, padx=(20, 0))
注意 GUI 宽度(main.py:231 window_width=600)可能放不下 3 个按钮,要相应调整。
步骤 5:在 spec 里加¶
# CameraMonitorSystem.spec:39-43 后加
for f in glob.glob('gascamera/**/*', recursive=True):
if os.path.isfile(f):
datas.append((f, os.path.join('gascamera', os.path.relpath(f, 'gascamera'))))
# hiddenimports 加
'gascamera', 'gascamera.gascamera',
9.4 如何修复跨平台问题¶
让项目能在 Linux/macOS 跑:
9.4.1 字体路径¶
# utils/fonts.py(新建)
import os
import sys
from pathlib import Path
def find_chinese_font():
"""跨平台中文字体查找"""
project_root = Path(__file__).parent.parent
candidates = [
# 项目内
project_root / "fonts" / "simhei.ttf",
# Windows
Path("C:/Windows/Fonts/simhei.ttf"),
Path("C:/Windows/Fonts/msyh.ttc"),
Path("C:/Windows/Fonts/simsun.ttc"),
# Linux(Ubuntu/Debian)
Path("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc"),
Path("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"),
# macOS
Path("/System/Library/Fonts/PingFang.ttc"),
Path("/Library/Fonts/Songti.ttc"),
]
for c in candidates:
if c.exists():
return str(c)
return None
然后让 swellcamera/watercamera 都改用这个函数。
9.4.2 摄像头索引¶
参见 08 · 问题与改进 § P0-3 的 fallback 方案。
9.4.3 PyInstaller hook¶
hook-tkinter.py:17 只处理 Windows,要加 Linux/macOS 分支:
elif sys.platform.startswith('linux'):
# Linux 的 Tcl/Tk 通常在 /usr/lib/tcltk/ 或 /usr/share/tcltk/
pass
elif sys.platform == 'darwin':
# macOS 的 Tcl/Tk 在 /usr/local/opt/tcl-tk/ 或 framework 里
pass
9.4.4 win32 隐藏导入要条件化¶
CameraMonitorSystem.spec:99-101:
否则 Linux 打包会找不到 pywintypes 而失败。
9.5 如何升级 PyTorch¶
当前代码假定 PyTorch < 2.6(torch.load 默认 weights_only=False)。升级到 2.6+ 之前:
- 改所有
torch.load(...)显式加weights_only=True: model1.py:40model2.py:41model3.py:40-
model4.py:52 -
如果加载失败(旧 .pth 含 Optimizer state),fallback 到
weights_only=False:
try:
state = torch.load(path, map_location=device, weights_only=True)
except (RuntimeError, AttributeError, _pickle.UnpicklingError):
state = torch.load(path, map_location=device, weights_only=False)
-
测试每个模型都能加载 + 推理一次。
-
如果升级 PyTorch 还伴随升级 torchvision,注意 EfficientNet 类导入路径可能也会变(
efficientnet_pytorch这个第三方库可能不再维护,可考虑迁移到torchvision.models.efficientnet_b3)。
9.6 跨平台打包¶
PyInstaller 不支持交叉打包——要 Linux 版必须在 Linux 上跑 pyinstaller,要 macOS 版必须在 macOS 上跑。
Linux 打包步骤¶
# 在 Linux 上
sudo apt install python3-tk libgl1 libglib2.0-0
python3 -m venv venv
source venv/bin/activate
pip install torch torchvision efficientnet_pytorch opencv-python Pillow numpy pyinstaller
# 改 spec 文件先:
# - 去掉 win32 相关 hiddenimports
# - 把 console=True 保留(终端运行)
# - 把 icon='logo.ico' 改成 icon=None 或 .icns
pyinstaller --clean CameraMonitorSystem.spec
./dist/CameraMonitorSystem/CameraMonitorSystem2.1
macOS 打包步骤¶
# 在 macOS 上
brew install python-tk
python3 -m venv venv
source venv/bin/activate
pip install torch torchvision efficientnet_pytorch opencv-python Pillow numpy pyinstaller
# spec 文件需要:
# - 改 icon 为 .icns
# - 加 codesign_identity=... 才能在 macOS 上分发
# - 可能要加 entitlements_file= 申请摄像头权限
pyinstaller --clean CameraMonitorSystem.spec
9.7 给冲国创同学的话¶
为什么做这些改进对你有用:
国创评审看的是「学生在项目中做了什么」而不是「项目本身有多牛」。 把一坨能跑的代码改成工程化的代码,正是体现"系统思维"和"工程能力"的最佳载体。 P0-1/P0-2/P0-3 这类 bug 修复属于"调试与设计"能力,写进申报书里非常有说服力。
国创申报书建议的"工作内容"措辞¶
| 实际做了 | 申报书措辞 |
|---|---|
| 修 P0-1 停止机制 bug | 「重构线程协作机制,引入双向 Event 协议解决多线程状态同步问题」 |
| 修 P0-2 临时文件 | 「优化推理 I/O 路径,将磁盘读写改为内存转换,单帧推理性能提升约 N 倍」 |
| 修 P0-3 摄像头索引 | 「实现摄像头自动发现与 fallback 机制,提升跨硬件兼容性」 |
| 统一 P1-1/P1-2 接口 | 「抽象模型层接口,统一 4 个模型类的命名与返回值结构」 |
| 加 logging | 「引入分级日志系统替代裸 print,支持文件持久化与可观测性」 |
| 跨平台改造 | 「实现 Windows/Linux/macOS 三平台支持,扩展项目部署范围」 |
| 加新检测模块 | 「设计可扩展的模块管理框架,新增 X 检测模块仅需 N 行代码」 |
| 加测试 | 「为推理流程编写单元测试,覆盖率达 N%,支持 CI 自动化」 |
优先级最高的"国创亮点"建议¶
如果时间有限,按以下顺序优先做(每项都是国创申报书的好素材):
- 修 P0-1/P0-2/P0-3(共 ~3h)→ 「工程鲁棒性提升」
- 加 logging 系统(~1h)→ 「可观测性建设」
- 加 pytest 测试(~2h)→ 「质量保障体系」
- 跨平台改造(~2h)→ 「部署范围扩展」
- 新加一个检测模块(如果数据集有的话,~5h)→ 「功能扩展」
加起来约 13 小时——一个学期周末就能搞定,写进国创就是漂亮的工作量。
9.8 推荐的工程提升清单¶
如果你想把这个项目推到真正的"研究级"水平,下列改造可以考虑(按学术含量从低到高):
工程层(低学术含量但好做)¶
- [ ] 把
cv2.imwrite+Image.open改成内存转换(P0-2) - [ ] 摄像头索引可配置(P0-3)
- [ ] 停止机制修好(P0-1)
- [ ] 接口统一(P1-1/2)
- [ ] 加
pytest测试,至少覆盖模型加载和单次推理 - [ ] 加
GitHub ActionsCI(pytest + ruff lint) - [ ] 加 logging
- [ ] 加 mypy 类型注解
算法层(中等学术含量)¶
- [ ] 把三模型投票改成端到端三分类(一个 EfficientNet 出 3 个 logit),对比指标
- [ ] 引入数据增强(旋转/翻转/CutMix),重训 model4 提升膨胀检测准确率
- [ ] 加 Grad-CAM 可视化,显示模型关注哪些区域
- [ ] 引入半监督学习(FixMatch)让没标注的工厂图片也能利用
- [ ] 模型量化(INT8 / FP16)+ ONNX Runtime 部署,对比加速效果
系统层(中高学术含量)¶
- [ ] 把单进程多线程改成 多进程 + 共享内存(解决 GIL)
- [ ] 加摄像头硬件触发(GPIO 信号 → 工业相机),脱离 OpenCV 软触发
- [ ] 实现 RTSP 推流,把检测结果同步到工厂监控系统
- [ ] 加 WebSocket 远程监控接口
学术层(高学术含量,适合国创结题论文)¶
- [ ] 跨域自适应:在实验室数据训练的模型如何迁移到真实工厂环境(DANN、CDAN)
- [ ] 不确定性估计:用 MC Dropout 或 Deep Ensembles 给出预测置信区间
- [ ] 持续学习:工厂数据持续到来,怎么让模型不灾难遗忘(EWC)
- [ ] 多模态融合:摄像头 + 温度/湿度传感器联合预测(这与本项目"圆盘干燥机"语境契合)
9.9 关键文件速查¶
| 任务 | 改哪 | 测试方式 |
|---|---|---|
| 加新检测模块 | 仿 swellcamera/ 建目录 + 改 main.py + 改 spec |
python -m newmodule |
| 改推理逻辑 | models/model*.py 的 _predict |
跑 models/model*.py 的 __main__ 测试 |
| 改 GUI 布局 | main.py:254-400 create_interface |
python main.py |
| 改打包 | CameraMonitorSystem.spec |
pyinstaller --clean CameraMonitorSystem.spec |
| 调投票算法 | watercamera.py:31-77 predict_single_image |
对 30 张已知含水量的图跑一遍统计 |
| 调阈值 | watercamera.py:46 的 >= 0.1 / 各模型 > 0.5 |
看混淆矩阵 |
| 改字体 | swellcamera.py:55-74 + watercamera.py:113 |
在 Linux 上跑看是否乱码 |
| 改停止机制 | main.py:112-120 + swellcamera.py:179 |
点停止按钮 |
9.10 找资源的指南¶
| 想了解 | 去哪 |
|---|---|
| EfficientNet 原理 | 原论文 |
| efficientnet_pytorch 库 | GitHub 仓库 |
| Tkinter 教程 | TkDocs |
| OpenCV-Python | 官方文档 |
| PyInstaller 配置 | 官方手册 |
| 模型部署最佳实践 | PyTorch deployment guide |
| 国创申报书模板 | 校 OA 上的「大学生创新训练计划」专题 |
9.11 该问 gxl 的问题清单¶
向 gxl 当面或微信确认下列必须澄清的 8 个问题,记下答案存档:
- swellcamera 的
label_mapping(标签对调)是因为训练时标签搞错了,还是别的原因?
→ 决定 P0-4 怎么修 - model1/2/3 分类头里的
Identity()占位,原训练时这些位置是什么层?
→ 决定 P2-14 怎么修(是否要恢复 ReLU) - 训练数据集在哪?多少张?标注方式?
→ 国创申报书要写 - 训练脚本(train.py)在哪?
→ 国创结题可能需要复现 - 各模型的验证集准确率/F1 分数?
→ 国创申报书要写 - 三模型投票权重 0.91/0.95/0.90 是怎么定的?拍脑袋还是数据?
→ 算法层论证 efficientnet_b3_pytorch.pth这个 48MB 文件是不是初始化用的预训练权重?
→ 决定是否可以删除- 「圆盘干燥机」具体是哪个工厂的设备?项目立项时的合作单位?
→ 国创申报书"研究背景"章节
9.12 我希望你记住的三件事¶
写给后续接手者:
- 修 bug 之前先理解为什么这样写。比如
label_mapping看起来是 bug,但删了可能让结果反转——先确认。 - 不要重训模型,除非数据集和训练脚本都齐全。当前 4 个 .pth 是黑盒,但能用。
- 优先做能让"演示效果"变好的修复:P0-1/P0-2/P0-3 修完,演示场景就稳了。其他的慢慢来。