课程定位: 围棋打谱辅助系统系列课程的 Part 1.1,衔接 Part 0(基础视觉系统)和 Part 2(游戏引擎)。本章聚焦棋子检测的模型训练全流程:从标注管线设计、YOLO 星位检测方案,到模型训练实战与性能调优。

前置知识: Part 0 棋盘矫正管线、Python 基础、PyTorch 基础

预计阅读: 40 分钟 | 实战耗时: 标注 5-7h + 训练 1-2h


目录

  1. 概述:为什么需要模型训练
  2. 标注管线设计
  3. YOLO 星位检测方案
  4. 模型训练实战
  5. C++ 推理集成
  6. 小结与下一步

1. 概述:为什么需要模型训练

Part 0 完成了棋盘矫正——从倾斜的摄像头画面中提取 600×600 的标准俯视图。下一步是识别棋盘上 361 个交点是否有棋子,以及棋子的颜色。

我们可以用传统 CV 方法(ROI 采样 + 双阈值)做初步判断,但准确率受光照影响大。为了达到生产级可靠性,需要训练一个轻量神经网络来做 361 分类。

同时,手工点击 4 角星位的标定流程也可以用 YOLO 自动检测替代——9 个星位标记点的检测不仅能自动标定,还能提供 RANSAC 鲁棒性。

本章覆盖这两个模型的完整生命周期:数据采集 → 标注 → 训练 → ONNX 导出 → C++ 集成。


2. 标注管线设计

依赖: Phase 0 棋盘矫正已完成 | 目标: 产出 YOLO 格式训练数据集

2.1 整体流程

                    Magic_GT 代码实现          手工操作
                    ════════════════          ══════════════

① 采集端改造    →  CalibWindow 加采集模式
                                         →  ② 连接 ESP32-CAM + 标定
                                         →  ③ 摆放棋子 + 变化光照
                                         →  ④ 逐批拍摄 500 张

⑤ auto_label.py →  ROI 初判生成 stone_state
                                         →  ⑥ 用 review_labels.py 逐张纠正

⑦ synthetic_data.py → 空棋盘合成 2000 张(自动)

⑧ batch_label.py → 转 YOLO 格式 → dataset/

                                         →  ⑨ 抽查 10% 标注质量
                                         →  ⑩ 确认 dataset 就绪

2.2 手工操作步骤

按执行顺序列出所有需要手动操作的步骤:

序号 操作 预估耗时 说明
连接 ESP32-CAM,标定棋盘 2 分钟 打开 go-board-calib.exe,Connect,标记 4 角星位
摆棋子,布置不同局面 分批进行 至少包含:空盘、开局、中盘、官子四种阶段
在 5 种光照下拍摄 500 张 2-3 小时 每换一种光照 → 摆 3-4 种局面 → 每种局面拍约 25 张
人工纠正 ROI 误判 2-3 小时 用 review_labels.py 逐张检查,键盘快捷键修改
抽查标注质量 30 分钟 从 dataset 随机抽 25-50 张,肉眼确认 bbox 贴合棋子
确认 dataset 就绪 5 分钟 运行训练脚本前的最后一次检查

总计约耗时 6-8 小时,可分多次完成。其余步骤(①⑤⑦⑧)由脚本自动实现,全自动。

2.3 采集端改造

CalibWindow 新增采集模式:

工具栏新增 checkbox: [☐ Collect]  [📸 Capture]

开启 Collect 模式后:
  - onFrame() 中矫正图自动保存到 pic/collect/
  - 文件名: raw_YYYYMMDD_HHmmss_sss.jpg
  - 同时调用 StoneDetector 生成初判 stone_state
  - 初判写入: raw_xxx_state.txt
  - 底部状态栏显示: "Collected: 47/500"

节流控制: 每 500ms 最多保存一帧(约 2fps),避免同一局面产生大量重复帧。

2.4 采集矩阵

覆盖 5 种光照 × 4 种局面阶段:

空盘 开局(20手) 中盘(80手) 官子(150手)
自然光(白天窗边) 25 张 25 张 25 张 25 张
台灯顶部直射 25 张 25 张 25 张 25 张
侧光 25 张 25 张 25 张 25 张
暗光/傍晚 20 张 20 张 20 张 20 张
顶灯(室内) 25 张 25 张 25 张 25 张

每格 20-25 张不是重复拍摄同一画面——每张之间稍微移动棋盘位置(±5cm)或轻微旋转摄像头角度(±3°),让矫正管线产生不同的像素分布。

2.5 标注生成

auto_label.py

# 核心逻辑
for each 交点 i:
    if stone_state[i] != '.':
        class_id = 0 if 'B' else 1
        bbox =  gridPixel[i] 为中心, 半径 = cell_size * 0.35
         归一化为 YOLO 格式: class cx/w cy/h bw/w bh/h

review_labels.py 交互界面

┌─────────────────────┬──────────────────┐
│                     │  File: 12 / 500  │
│   矫正图 +          │  Progress: ██░░   │
│   检测叠加          │                  │
│   (黑子蓝圈         │  当前交点: (3,3)  │
│    白子红圈         │  ROI 判断: B      │
│    空位灰点)        │                  │
│                     │  修改:            │
│   ← 鼠标悬停交点    │  [B] [W] [空]    │
│     → 高亮          │                  │
│                     │  快捷键:          │
│                     │  B=黑 W=白        │
│                     │  Space=空         │
│                     │  S 保存并翻页     │
└─────────────────────┴──────────────────┘

纠正优先级: 不用每张都仔细检查 361 个点。重点看:

  1. 棋盘边缘(四边第一/二行)— 光线衰减,ROI 容易误判
  2. 有手指/手的区域 — 遮挡污染 ROI
  3. 强反光区域 — 空位误判为白子
  4. 棋子密集区(中盘)— 相邻棋子互相影响

2.6 合成增强数据

python scripts/synthetic_data.py --count 2000 --output dataset/

逻辑:空棋盘矫正图 → 随机放置 10-200 个黑白棋子 → 绘制带位置噪声的圆(σ=2px)→ 随机亮度 ±20%、对比度 ±15% → 随机高斯噪声/模糊 → 输出 YOLO 格式标注。

优势:不限量、标注完美(自己放子 → stone_state 精确已知)、覆盖棋子密度全范围。

2.7 最终数据集目录

go-board-cpp/
└── dataset/
    ├── data.yaml
    ├── images/
    │   ├── train/          # ~1750 张
    │   ├── val/            # ~375 张
    │   └── test/           # ~375 张
    └── labels/
        ├── train/          # 每张对应一个 .txt
        ├── val/
        └── test/

3. YOLO 星位检测方案

3.1 目标

用 YOLO 检测 19 路棋盘上的 9 个星位标记点,自动计算单应矩阵完成棋盘矫正,替代当前手工点击 4 角的流程。

九星位坐标(19 路棋盘):

(3,3)   (3,9)   (3,15)
(9,3)   (9,9)   (9,15)
(15,3)  (15,9)  (15,15)

3.2 整体流程

ESP32-CAM 原始帧
  → YOLO 推理 (ONNX)
    → 9 个星位 bounding box + 置信度
      → NMS + 置信度过滤 (≥0.5)
        ├─ ≥4 个点 → findHomography(RANSAC) → 自动标定 ✅
        └─ <4 个点 → 退化为手动点击标定

3.3 模型选择

选项 参数量 推理速度 (CPU) 适用
YOLOv8-n 3.2M ~50ms ✅ 推荐
YOLOv11-n 2.6M ~40ms 更轻量
YOLOv8-s 11.2M ~120ms 精度更高

推荐 YOLOv8-n:单类检测(星位),目标小且特征一致,nano 足够。

训练配置:

参数
输入 640×640
类别 1 (star_point)
Epochs 100
增强 mosaic, hsv, flip, translate
输出 ONNX (opset 12, OpenCV dnn 兼容)

3.4 自动/手动对比

对比 当前 (4 点手工) 新方案 (YOLO 9 星位)
操作 连接 → 手动依次点击 4 角 连接 → 点击"自动标定”
时间 ~15-30 秒 ~100ms
鲁棒性 4 点精确解 9 点 RANSAC,容忍遮挡/误检
复用性 星位检测可用于后续落子定位

3.5 实施计划

阶段 内容 工时估计
P1 数据标注 扩展 MarkWidget 支持 9 星位标注 + YOLO 格式导出 2-3h
P2 采集标注 拍摄 50-100 张棋盘图 + 标注 30min
P3 训练 Python 训练 YOLOv8-n + 导出 ONNX 1-2h
P4 C++ 集成 StarPointDetector + 自动标定逻辑 2-3h
P5 测试 实机测试 + 精度调优 1h

4. 模型训练实战

4.1 V1 问题诊断

  • 白子误识别: 棋盘网格线被错误分类为白色棋子
  • 根因分析: 矫正图中棋盘网格线始终在固定像素位置。模型过度拟合了网格纹理特征

4.2 V2 改进:随机平移增强

核心思路: 对图片做 ±12px 随机平移 → 网格线不再固定在像素位置 → 模型被迫学习石子本身的形状/颜色特征

4.3 数据集

来源 数量 说明
ESP32-CAM 实拍 180 张 手工采集,AI 模型预标注
格式 600×600 JPEG + 361 字符标签

标签分布 (180 张原始图):

  • 空位 .: 81.3%
  • 白子 W: 10.4%
  • 黑子 B: 8.3%

4.4 增强策略 (5x → 900 张)

序号 增强类型 参数 说明
1 shift 平移 ±8px + 亮度 ±10 新增: 破坏网格位置锚定
2 bc 对比度 0.85-1.15, 亮度 ±20 光照变化
3 hsv H±5, S±25, V±20 色调抖动
4 noise 高斯噪声 σ=2-6 传感器噪声
5 combo shift + bc + hsv + noise + blur 组合增强

4.5 训练配置

参数
架构 轻量 FCN (435K 参数)
输入 304×304 RGB → 19×19×3 logits
优化器 AdamW, lr=1e-3, weight_decay=1e-4
调度器 CosineAnnealingLR (15 epochs, eta_min=1e-5)
损失函数 CrossEntropyLoss + Class Weights
批大小 32
Epochs 15
训练/验证 720/180 (按原始图分组拆分)
计算设备 CPU

类别权重(处理样本不平衡):

Empty (.): 0.412
White (W): 3.175
Black (B): 3.883

4.6 训练结果

Epoch   1  val_acc = 0.8259  *
Epoch   2  val_acc = 0.9491  *
Epoch   3  val_acc = 0.9954  *
Epoch   4  val_acc = 0.9943
Epoch   5  val_acc = 0.9895
Epoch   6  val_acc = 0.9921
Epoch   7  val_acc = 0.9949
Epoch   8  val_acc = 0.9949
Epoch   9  val_acc = 0.9960  *
Epoch  10  val_acc = 0.9955
Epoch  11  val_acc = 0.9956
Epoch  12  val_acc = 0.9977  *
Epoch  13  val_acc = 0.9984  *  ← 最佳
Epoch  14  val_acc = 0.9975
Epoch  15  val_acc = 0.9982

最佳模型 (Epoch 13) 分类准确率:

类别 准确率 样本数
空位 . 99.81% 53,665
白子 W 100.00% 6,505
黑子 B 99.90% 4,810
整体 99.84% 64,980

4.7 V1 vs V2 对比

指标 V1 (之前) V2 (现在)
输入图 164 张 180 张
增强数 820 张 900 张
白子准确率 97.5% 100.0%
整体准确率 99.2% 99.8%
平移增强 离线 + 在线 ✅

4.8 模型文件

文件 大小 用途
models/go-stone-classifier.pth 1.7 MB PyTorch checkpoint
models/go-stone-classifier.onnx 1.7 MB OpenCV dnn 推理 (单文件, opset 12)

模型接口:

输入: image (1, 3, 304, 304) float32 [0,1] RGB
输出: logits (1, 3, 19, 19) float32
推理: argmax(dim=1) → 19×19 网格 → 映射 {0:'.', 1:'W', 2:'B'}

5. C++ 推理集成

5.1 棋子检测 (StoneDetector)

C++ 源码无需修改,仅替换 ONNX 模型文件:

models/go-stone-classifier.onnx  ← 自动加载 (main_calib.cpp 启动时)

StoneDetector 类支持双模式:

  • AI 模式 (默认): ONNX 推理 (304×304 → 19×19)
  • 阈值模式 (fallback): ROI 采样 + 双阈值

5.2 星位检测 (StarPointDetector)

新增类:StarPointDetector

class StarPointDetector {
    bool loadModel(const std::string &onnxPath);
    std::vector<cv::Point2f> detect(const cv::Mat &rawBgr, float confThresh = 0.5);
private:
    cv::dnn::Net m_net;
    static constexpr int INPUT_SIZE = 640;
};

推理流程:

// 1. YOLO 前向
blobFromImage(rgb, 1/255.0, 640, 640, Scalar(), true, false);
m_net.setInput(blob);
cv::Mat output = m_net.forward();  // [1, 8400, 6]  (x,y,w,h,conf,cls)

// 2. 解析 + NMS
for each detection with cls==0 && conf >= 0.5:
    boxes.push_back(xywh2rect)
    confs.push_back(conf)
cv::dnn::NMSBoxes(boxes, confs, 0.5, 0.4, indices);

// 3. 取 box 中心作为星位坐标 → 匹配到棋盘坐标

// 4. RANSAC 单应矩阵
if (starPoints.size() >= 4) {
    cv::Mat H = cv::findHomography(boardCoords, starPoints, cv::RANSAC, 3.0);
    m_calibrator->setHomography(H);  // 自动标定完成 ✅
}

5.3 文件清单

go-board-cpp/
├── models/
│   ├── go-stone-classifier.onnx  (棋子分类)
│   └── go-star-yolo.onnx         (星位检测)
├── scripts/
│   ├── auto_label.py
│   ├── review_labels.py
│   ├── batch_label.py
│   ├── synthetic_data.py
│   ├── augment_dataset.py
│   ├── retrain.py
│   └── train_star_yolo.py
├── src/detect/
│   ├── StoneDetector.h/cpp
│   ├── StarPointDetector.h/cpp   (新增)
│   └── PersistentCalibrator.h    (新增 setHomography)
└── src/main_calib.cpp            (集成自动标定按钮)

6. 小结与下一步

这一章我们完成了从数据采集到模型推理的完整闭环:

  1. 标注管线 — 定义了采集-标注-纠正-合成-导出的五阶段流水线,约 6-8h 手工操作即可产出高质量训练集
  2. YOLO 星位检测 — 用 9 点 RANSAC 替代 4 点手工标定,省时且鲁棒
  3. 模型训练 — 435K 参数的轻量 FCN,在 900 张增强数据上达到 99.84% 整体准确率
  4. OpenCV DNN 推理 — 1.7MB ONNX 模型,304×304 输入,CPU 实时推理

关键设计原则:

  • 数据与代码分离: 标注管线完全独立于训练脚本,数据集可复用、可版本管理
  • 双模式推理: AI 模式优先,阈值模式兜底 — 没有模型也能跑
  • 渐进式增强: 平移增强是解决网格线过拟合的最小化干预

下一步 → Part 2: 将检测结果接入 GoGame 游戏引擎,实现命令模式 + AI 对弈 + SGF 打谱的完整交互系统。


相关文档: