Skip to content

About

学习个人笔迹风格,生成带真实时序的笔画轨迹,导出 GRBL G 代码驱动写字机器人。

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

HandwritingRobot

学习个人笔迹风格,生成带真实书写时序的笔画轨迹,导出 GRBL G 代码驱动写字机器人。

与现有写字软件的本质区别:传统工具只是描图片轮廓,没有笔顺、没有时序,写出来僵硬如机器描图。
本项目处理的是联机笔迹(笔尖坐标 + 笔序 + 时序 + 抬落笔 + 压感),复刻的是人的书写动作。

手写样本 ──▶ 风格模型 ──▶ 运动约束优化 ──▶ G 代码 ──▶ 写字机器人
              (Python)        (C++ core)      (C++ core)

任意图形

不只是写字:表格、思维导图、导入的 SVG 在管线里都只是 StrokeSet,
和「一个汉字」走同一条版面 → G 代码 → 机器链路。

表格 / 思维导图 / SVG 导入示例

handwriting compose table   --rows 3 --cols 4 -o table.nc
handwriting compose mindmap --nodes "a:0.2,0.5,0.24,0.16;b:0.7,0.3,0.2,0.14" --edges "a,b"
handwriting compose svg     --file drawing.svg --svg preview.svg

当前状态

Pre-Alpha。 采集、字形 → G 代码、风格模型 v0、整行风格版面层、骨架对齐、
序列模型骨架均已跑通;真实个人风格训练待数位板行数据。

模块 状态
笔迹采集器(网页 / App 原生) 可用
风格特征提取 可用
主动选字(覆盖度驱动) 可用
字形骨架 → 轨迹 → G 代码 可用
横条本版面(字压横线) 可用
Qt 桌面预览工具 可用
SVG 预览 可用
任意图形(表格 / 思维导图 / SVG 导入) 可用
轨迹数据结构(C++ / Python) 可用
数据集读取与质检 可用
Qt 桌面 UI 可用(常用面板 + 选项弹窗 + 横条本富文本编辑 + 实时预览 + 导出 + 串口直连书写 + 预览内点字编辑)
项目保存/加载 + 设置持久化 可用(.hrproj、最近打开、未保存提示、QSettings)
长文分页(按页预览 / 单页或全部页导出) 可用
串口 / GRBL 通信(C++ 核心) 可用(流控、状态机、安全检查,118 项断言覆盖)
工作区标定(软限位) 可用(4 步向导 + 示意图 + 越界保护)
笔迹风格模型 v0(训练 PyTorch / 推理 C++,App 已接入) 可用
整行风格 · 版面层(字大小 / 字距 / 基线) 可用
骨架对齐(1.0 / 2.0 → 标准骨架) 可用
序列模型骨架(GRU + MDN,Δ点/抬落笔/压感) 可用(骨架)
开发者模式(文章实时推理 + 排版 + 预览内编辑) 可用
运动约束优化(论文核心) 未开始

界面

主界面只保留高频项:文本、常用版面、整行风格;低频设置收进「选项」弹窗。

主界面

选项弹窗 横条本文本编辑
选项弹窗 横条本文本编辑

「整行风格」开启后,字大小与基线会有统计波动,横条本仍压在横线上:

整行风格

采集与训练(App 内,不依赖网页)

工具 → 采集与训练… 打开原生窗口。

采集 训练
采集 训练
  • 采集:单字 / 整行,数位板或鼠标书写;「添加当前样本」累积多条,「导出 JSON」生成
    1.0 / 2.0 采集文件。整行模式按笔画停顿自动切字(charRanges);
    样本列表支持「删除选中 / 清空已采集」,写坏一条不用重录全部。
  • 训练:选数据 → 勾选「对齐到标准骨架」→ 开始训练;日志/进度实时显示。
    「对齐阈值」过滤匹配代价过高的笔画对;训练完自动 export-weights 并启用模型。
  • 体检数据:训练前先点「体检数据」,日志会列出压感/采样率/缺字等问题;
    发现问题时,点「开始训练」会二次确认。
  • 训练仍由 Python/PyTorch 完成(ADR-0007),App 通过 QProcess 调用;不再需要网页采集器。

项目与分页

  • 文件 → 新建 / 打开 / 保存 / 另存为,Ctrl+S 保存;最近打开保留 8 个;关闭前提示未保存。
  • .hrproj 保存文本、版面、整行风格、字形参数、风格模型开关/路径/强度。
  • 窗口几何、停靠布局、显示选项、最近文件用 QSettings 持久化,重开自动恢复。
  • 长文按「每行字数 × 每页行数」自动分页;工具栏「上一页 / 下一页」,状态栏显示「第 X / Y 页」。
  • 导出 G 代码支持当前页或全部页;全部页自动生成 name-p1.nc、name-p2.nc…

长文分页

风格模型 v0(可选,需要 torch)

默认输出是标准骨架,不是你本人的笔迹。风格模型 v0 是一个可训练的基线:
把每一笔拆成「中心线(结构)+ 残差(风格)」,用混合密度网络(MDN)
学习残差与笔压的分布,再作用到任意中心线上。

pip install -e ".[train]"

# 没有数位板时:用本地字形骨架 + 合成风格自测
handwriting train --chars "不八啼处字春晓永法眠觉闻鸟" --offline \
  -o data/exports/style-model.pt

# 用模型生成(默认取混合均值;加 --sample 可逐点随机)
handwriting stylize "永" --model data/exports/style-model.pt --offline \
  -o out.nc --svg out.svg

# 任意图形也能加风格
handwriting stylize --svg-file drawing.svg --model data/exports/style-model.pt --svg out.svg

App 里勾选左栏「使用风格模型」,在「选项 → 风格模型」里选权重 JSON、调强度;
预览和导出的 G 代码会走同一条 C++ 推理。加载失败会自动回退标准骨架,并在状态栏提示。

App 内启用风格模型

边界:v0 学的是「相对中心线的局部风格」(倾斜、提按、抖动),
不是完整的字形风格迁移;真实个人风格需要数位板数据。骨架对齐已实现
(train --align),真实数据训练待数位板。设计与限制见 docs/model.md。

语言分工:训练用 Python(PyTorch),推理在产品运行时用 C++
(core/src/style_model.cpp,零 Python 依赖)。训练完导出权重:

handwriting export-weights --model data/exports/style-model.pt \
  -o data/exports/style-model.json

跨语言对拍工具 hr-style-dump,当前最大误差 1.1e-07。规则见
ADR-0007。

开发者模式:粘贴文章,右侧实时推理 + 排版,并可直接点字编辑。

python scripts/serve_dev.py --model data/exports/style-model.pt \
  --weights data/exports/style-model.json
	# 打开 http://127.0.0.1:8765

默认走 C++ hr-style-batch;找不到二进制时回退 Python 参考实现。

快速开始

1. Qt 预览工具(开发期主力)

cmake -S . -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_PREFIX_PATH="C:/Qt/6.9.3/mingw_64"
cmake --build build -j
./build/app/handwriting-studio.exe

左侧改参数、右侧实时出预览。预览与导出的 G 代码共用同一份版面数据,
所以看到的位置就是机器实际走的位置。

默认版面就是横条本(150×213 mm / 22 行,字压在横线上),
字宽默认等于行距、每行字数自动算 —— 正是作业本去掉页边距后的书写区。

1.1 串口直连书写(替代奎享雕刻)

底部「机器控制 · 串口直连」面板提供了与奎享雕刻类似的接口,软件内直接连 GRBL 控制板:

  1. 插上控制板(Arduino Uno + CNC Shield / ESP32 等跑 GRBL 的板子),系统会枚举出 COM 口
  2. 下拉选对应的串口,点 连接(默认 115200)
  3. 连上后自动读 $$ 拿行程与最大进给,做上机前安全检查
  4. 先用 回零 $H 把笔归零,用 点动 把笔移到纸面左上角,再 设零点 G10 L20 P1
  5. 左边调好文字与版面后,点 开始书写 —— 程序会带行号流控、按 GRBL 的 128 B 串口缓冲限速发送

手动指令(回零 / 点动 / 设零点 / 抬落笔)与书写程序严格分离:载入程序后没点「开始」之前,随手点回零不会把整个程序偷发下去——这是个真实的安全隐患,已有回归测试守着。

机器参数(本机实测):「机器」分组里抬笔 Z / 落笔 Z / 书写进给默认取自奎享雕刻
settings/默认Grbl机器/Grbl.json(抬笔 Z=0、落笔 Z=6、进给 8000)。注意这台机器
Z 越大越往下压,与「Z 向上为正」相反。另外它 Y+ 朝上(纸上向下走是 −Y),
需要勾选「Y 轴翻转」,否则整页会写到纸外。参数按自己机器实测为准。

1.2 工作区标定(软限位,保护传动皮带)

步进机没有位置反馈,上电后手推机头不会更新 GRBL 坐标——所以通电后必须重新标定「设备能走到哪里」,否则 G 代码发下去机头可能冲到机械端点扯断皮带。

标定流程(底部「工作区标定(软限位)」分组):

  1. 断电手推机头到最左上角(物理起点,X 最小、Y 向上最大)
  2. 通电点「开始标定」,按向导走 4 步:
    • ① 记录最左上角(若通电前已手推到位,直接点「记录此角」)
    • ② 用点动把机头移到最右上角,点「记录此角」
    • ③ 用点动把机头移到最右下角,点「记录此角」
    • ④ 用点动把机头移到最左下角,点「记录此角」
  3. 点「保存边界」—— 写入 data/workspace-bounds.json,重启软件后自动加载
  4. 之后普通点动会被限制在边界内(越界自动贴边);发送 G 代码前会自动对照矩形做安全检查
  5. 示意图实时显示机头位置与边界线,机头越界立刻变红警告

记录顺序打乱也没关系——内部会自动取 min/max 归一化,所以无论你的坐标系方向如何都能得到正确矩形。

MinGW 构建注意:必须静态链接 libstdc++(app/CMakeLists.txt 已配置)。
Qt 的 mingw_64 包自带一个用较旧 MinGW 编译的 libstdc++-6.dll,
运行时会优先加载它,构造 std::ifstream 这类触及 std::locale 的对象
就会在该 DLL 内崩溃,而且栈回溯只指向 libstdc++,完全看不出是自己的代码问题。

1.3 打包与直接运行(无需在系统里装 Qt)

构建完成后,可执行文件位于:

build/app/handwriting-studio.exe

app/CMakeLists.txt 已在 POST_BUILD 阶段自动把运行所需的 DLL 与插件
复制到该目录,所以整个 build/app/ 文件夹拷到任意没有装 Qt 的 Windows 机器上,
双击 handwriting-studio.exe 就能跑
,不会再报“找不到 xxx.dll”。

自动跟随 exe 一起部署的文件(来自对 PE 导入表的完整闭包分析):

类别 文件
Qt 运行时 Qt6Core.dll Qt6Gui.dll Qt6Widgets.dll
MinGW 运行时 libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll
平台插件 platforms/qwindows.dll(普通 GUI 必选)、platforms/qoffscreen.dll(无头快照/演示必选)
图像插件 imageformats/qjpeg.dll qico.dll qgif.dll qsvg.dll(导出 PNG 等需要)

分发要点:务必把 build/app/ 整个目录一起拷走,不要只拷 handwriting-studio.exe——
上面这些 DLL 和 platforms/、imageformats/ 子目录必须跟 exe 待在同一目录。

连机前驱动:控制板(CH340 / CP2102 等 USB 转串口芯片)首次插上,Windows 可能没枚举出
COM 口。请先安装对应厂家的 VCP 虚拟串口驱动,设备管理器里出现 COMx 后软件才能连上。

2. 命令行

pip install -e ".[dev]"

# 预取字形(需要联网,之后可离线使用)
python -m handwriting.cli preload "春眠不觉晓"

# 按横条本排版(默认)
python -m handwriting.cli render "春眠不觉晓" -o chun.nc --svg chun.svg

# 自由版面,导到空白纸
python -m handwriting.cli render "春眠不觉晓" --mode grid --char-size 20

# 体检采集数据(自动识别单字 1.0 / 整行 2.0),打印质量与风格特征
python -m handwriting.cli inspect capture.json

3. 采集你的笔迹

用浏览器打开 capture/index.html,按提示写 150 个汉字,导出 JSON。
零依赖、可离线,手机 / 平板 / 数位板 / 电脑都能用。

采集顺序不是字表原序,而是按「结构 × 复杂度」覆盖度排的。
前 8 个字是人工策展的冷启动集(永西叶好冬花国风),覆盖 8 个不同的结构-复杂度格子;
写完 40 个字,每一类都至少有 2 个样本。这样在同样的字数预算下,
风格覆盖比按使用频率顺序写好得多。

页面顶部实时显示压感是否支持与实际采样率,侧栏有质量检查面板——
采样率不足、压感缺失、频繁出格会在写的当下就提示,不必等采完 150 个字才发现整批报废。

推荐的输入设备(不需要自己造硬件):

方案 成本 采样率 压感
入门数位板(绘王 / 高漫 6×4 英寸) 100–200 元 133–200 Hz 有
iPad + Apple Pencil 已有则 0 120–240 Hz 有
手机触屏 + 电容笔 20–50 元 60–120 Hz 无
鼠标 0 60–125 Hz 无(仅能验证链路)

两种采集模式(页面左上角切换):

  • 单字:一个字一格,坐标归一化到字格 —— 用于笔画骨架 / 单字形态,
    按上面的覆盖度顺序写 150 字。
  • 整行:按横线连续书写整行(可连笔),坐标归一化到整行方框、strokeStartT 以整行起笔为基准 ——
    用于位置飘逸 / 字距 / 行气 / 连笔潦草,这些是单字采不到的。
    导出 handwriting-capture/2.0;加载时用 LineSample.segment()
    按「抬笔间隔 + 已知字数」自动分段,无需参考字形。

建议两条腿走:整行写句子(拿行级风格)+ 单字按字表补覆盖(拿笔画结构)。
整行若记录了分段,还能切出单字样本(split_chars()),一份数据两用。

4. 构建 C++ 核心库

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure

只构建核心库与测试(不需要 Qt):

cmake -S . -B build -DHR_BUILD_APP=OFF

Qt 前端优先使用 Qt6;找不到时自动回退 Qt5.15,便于本地开发机快速构建。

5. Python 侧(数据集与训练)

pip install -e ".[dev]"
pytest                      # 262 个用例,联网用例默认跳过
pytest --run-network -m network   # 验证真实字形数据
ruff check src tests

目录结构

HandwritingRobot/
├── core/                 C++17 核心库(无 Qt、无 Python 依赖)
│   ├── include/handwriting/
│   ├── src/
│   └── tests/
├── app/                  Qt 桌面前端(直接链接 core)
├── src/handwriting/      Python:字形、渲染、风格、数据集、G 代码、CLI
├── capture/index.html    笔迹采集器
├── benchmarks/           性能基准(C++ 与 Python 同负载对比)
├── scripts/              开发期工具(生成字表顺序等)
├── docs/
│   ├── architecture.md    系统架构与语言分工
│   ├── glyph-pipeline.md  字形 → 轨迹 → G 代码管线
│   ├── data-format.md     笔迹数据格式规范
│   └── adr/               架构决策记录
├── data/                 本地采集数据与导出(**不入库**)
└── tests/                Python 测试

为什么是 C++ 核心 + Python 训练

用相同合成负载(100 字 × 8 笔 × 60 点)实测:

环节 Python C++
轨迹归一化 65.15 ms 0.13 ms
G 代码生成 65.67 ms 37.42 ms
串口传输 130.5 s 130.5 s
机床实际运行 ~600 s ~600 s
计算占端到端 0.018% 0.005%

换语言把端到端从 730.8 秒降到 730.6 秒。 批处理吞吐不是选 C++ 的理由——
瓶颈是串口带宽与机械运动。真正的理由是交互延迟:拖动参数滑块要实时重算预览,
Python 的 65 ms 是 15 fps,C++ 的 0.13 ms 无感。加上发布版可彻底摆脱 Python 运行时
(安装包 2~3 GB → 几十 MB)。

模型训练仍留在 Python:PyTorch 的自动微分与 CUDA kernel 库只服务 Python,
用 C 重写训练代码等于放弃整个生态。

详见 docs/architecture.md 与 ADR-0003。

文档

许可

代码采用 Apache-2.0。

第三方数据与模型权重单独声明许可,不适用仓库根目录的 LICENSE。
详见 ADR-0004。

data/ 下的用户笔迹数据不会进入版本库——它是生物特征数据,隐私敏感。

环境要求

  • C++:CMake ≥ 3.21,支持 C++17 的编译器(MSVC 2019+ / GCC 11+ / Clang 14+)
  • Qt:Qt 6.4+(推荐)或 Qt 5.15
  • Python:≥ 3.10
  • 本地推理显卡:任意支持 CUDA 的 NVIDIA 显卡。开发机为 RTX 5060 8GB(Blackwell,sm_120),
    该架构必须使用 PyTorch ≥ 2.7 + CUDA 12.8,旧版 wheel 没有对应 kernel。
    不要使用 TensorFlow 1.x 时代的代码,它在新卡上无法运行。

About

学习个人笔迹风格,生成带真实时序的笔画轨迹,导出 GRBL G 代码驱动写字机器人。

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages