跳转至

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:28self.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-173ModuleManagerstart_gas_module / stop_gas_module / is_gas_running。仿 main.py:625-651toggle_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

if sys.platform == 'win32':
    hiddenimports += ['pywintypes', 'win32api', 'win32ctypes']

否则 Linux 打包会找不到 pywintypes 而失败。


9.5 如何升级 PyTorch

当前代码假定 PyTorch < 2.6(torch.load 默认 weights_only=False)。升级到 2.6+ 之前:

  1. 改所有 torch.load(...) 显式加 weights_only=True
  2. model1.py:40
  3. model2.py:41
  4. model3.py:40
  5. model4.py:52

  6. 如果加载失败(旧 .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)
  1. 测试每个模型都能加载 + 推理一次。

  2. 如果升级 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 自动化」

优先级最高的"国创亮点"建议

如果时间有限,按以下顺序优先做(每项都是国创申报书的好素材):

  1. 修 P0-1/P0-2/P0-3(共 ~3h)→ 「工程鲁棒性提升」
  2. 加 logging 系统(~1h)→ 「可观测性建设」
  3. 加 pytest 测试(~2h)→ 「质量保障体系」
  4. 跨平台改造(~2h)→ 「部署范围扩展」
  5. 新加一个检测模块(如果数据集有的话,~5h)→ 「功能扩展」

加起来约 13 小时——一个学期周末就能搞定,写进国创就是漂亮的工作量。


9.8 推荐的工程提升清单

如果你想把这个项目推到真正的"研究级"水平,下列改造可以考虑(按学术含量从低到高):

工程层(低学术含量但好做)

  • [ ] 把 cv2.imwrite + Image.open 改成内存转换(P0-2)
  • [ ] 摄像头索引可配置(P0-3)
  • [ ] 停止机制修好(P0-1)
  • [ ] 接口统一(P1-1/2)
  • [ ] 加 pytest 测试,至少覆盖模型加载和单次推理
  • [ ] 加 GitHub Actions CI(pytest + ruff lint)
  • [ ] 加 logging
  • [ ] 加 mypy 类型注解

算法层(中等学术含量)

  • [ ] 把三模型投票改成端到端三分类(一个 EfficientNet 出 3 个 logit),对比指标
  • [ ] 引入数据增强(旋转/翻转/CutMix),重训 model4 提升膨胀检测准确率
  • [ ] 加 Grad-CAM 可视化,显示模型关注哪些区域
  • [ ] 引入半监督学习(FixMatch)让没标注的工厂图片也能利用
  • [ ] 模型量化(INT8 / FP16)+ ONNX Runtime 部署,对比加速效果

系统层(中高学术含量)

  • [ ] 把单进程多线程改成 多进程 + 共享内存(解决 GIL)
  • [ ] 加摄像头硬件触发(GPIO 信号 → 工业相机),脱离 OpenCV 软触发
  • [ ] 实现 RTSP 推流,把检测结果同步到工厂监控系统
  • [ ] 加 WebSocket 远程监控接口

学术层(高学术含量,适合国创结题论文)

  • [ ] 跨域自适应:在实验室数据训练的模型如何迁移到真实工厂环境(DANNCDAN
  • [ ] 不确定性估计:用 MC DropoutDeep 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 个问题,记下答案存档:

  1. swellcamera 的 label_mapping(标签对调)是因为训练时标签搞错了,还是别的原因?
    → 决定 P0-4 怎么修
  2. model1/2/3 分类头里的 Identity() 占位,原训练时这些位置是什么层?
    → 决定 P2-14 怎么修(是否要恢复 ReLU)
  3. 训练数据集在哪?多少张?标注方式?
    → 国创申报书要写
  4. 训练脚本(train.py)在哪?
    → 国创结题可能需要复现
  5. 各模型的验证集准确率/F1 分数?
    → 国创申报书要写
  6. 三模型投票权重 0.91/0.95/0.90 是怎么定的?拍脑袋还是数据?
    → 算法层论证
  7. efficientnet_b3_pytorch.pth 这个 48MB 文件是不是初始化用的预训练权重?
    → 决定是否可以删除
  8. 「圆盘干燥机」具体是哪个工厂的设备?项目立项时的合作单位?
    → 国创申报书"研究背景"章节

9.12 我希望你记住的三件事

写给后续接手者:

  1. 修 bug 之前先理解为什么这样写。比如 label_mapping 看起来是 bug,但删了可能让结果反转——先确认。
  2. 不要重训模型,除非数据集和训练脚本都齐全。当前 4 个 .pth 是黑盒,但能用。
  3. 优先做能让"演示效果"变好的修复:P0-1/P0-2/P0-3 修完,演示场景就稳了。其他的慢慢来。