<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>先木</title><description>Personal site</description><link>https://crxm.top/</link><templateTheme>Firefly</templateTheme><templateThemeVersion>6.9.1</templateThemeVersion><templateThemeUrl>https://github.com/CuteLeaf/Firefly</templateThemeUrl><lastBuildDate>2026年6月3日 18:37:38</lastBuildDate><item><title>用微信小程序 + Flask 做一个党费管理系统</title><link>https://crxm.top/posts/party-fee-manager/</link><guid isPermaLink="true">https://crxm.top/posts/party-fee-manager/</guid><description>从&quot;每次收党费都得挨个发消息追&quot;到&quot;党员自己打开微信就缴了&quot;，基于微信小程序 + Flask + SQLite 实现的党费管理系统，支持党员自助缴费、两级管理员看板、微信支付（含 mock 模式），已开源。</description><pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;从”每次收党费都得挨个发消息追”到”党员自己打开微信就缴了”，做了这个小工具，顺手开源出来。&lt;/p&gt;&lt;p&gt;GitHub：&lt;a href=&quot;https://github.com/cairangxianmu/party-fee-manager&quot; target=&quot;_blank&quot;&gt;party-fee-manager&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;section&gt;&lt;h2&gt;背景&lt;a href=&quot;#背景&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;每到收党费，流程大概是这样：管理员群里发通知，等一圈，再发一遍，有人现金、有人转账、有人说”我上次交了吧”，月底对账发现台账和实际金额对不上。&lt;/p&gt;&lt;p&gt;解决方案其实不复杂——做个小程序，党员自己进来缴费，记录自动生成，管理员实时看进度。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;界面预览&lt;a href=&quot;#界面预览&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;党员端&lt;a href=&quot;#党员端&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;img alt=&quot;首页&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;1204&quot; src=&quot;/_astro/1.DmZe9Sma_fE0UT.webp&quot; srcset=&quot;/_astro/1.DmZe9Sma_fE0UT.webp 580w&quot; /&gt;&lt;figcaption&gt;首页&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt=&quot;身份绑定&quot; loading=&quot;lazy&quot; width=&quot;834&quot; height=&quot;1566&quot; src=&quot;/_astro/6.CzabHHHX_1b6KhY.webp&quot; srcset=&quot;/_astro/6.CzabHHHX_ZFlADa.webp 640w, /_astro/6.CzabHHHX_19GUpE.webp 750w, /_astro/6.CzabHHHX_Z1iKj5J.webp 828w, /_astro/6.CzabHHHX_1b6KhY.webp 834w&quot; /&gt;&lt;figcaption&gt;身份绑定&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt=&quot;缴费记录&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;1206&quot; src=&quot;/_astro/4.E0XDglRQ_2ngBXl.webp&quot; srcset=&quot;/_astro/4.E0XDglRQ_2ngBXl.webp 592w&quot; /&gt;&lt;figcaption&gt;缴费记录&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt=&quot;修改个人信息&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;1210&quot; src=&quot;/_astro/7.CZr7NrNv_Z1N5SKB.webp&quot; srcset=&quot;/_astro/7.CZr7NrNv_Z1N5SKB.webp 592w&quot; /&gt;&lt;figcaption&gt;修改个人信息&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;管理端&lt;a href=&quot;#管理端&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;img alt=&quot;超级管理员看板&quot; loading=&quot;lazy&quot; width=&quot;584&quot; height=&quot;1206&quot; src=&quot;/_astro/2.DgrcLiB8_Z1xMoc4.webp&quot; srcset=&quot;/_astro/2.DgrcLiB8_Z1xMoc4.webp 584w&quot; /&gt;&lt;figcaption&gt;超级管理员看板&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt=&quot;支部管理员看板&quot; loading=&quot;lazy&quot; width=&quot;586&quot; height=&quot;1190&quot; src=&quot;/_astro/3.CIbxMPFb_Z1hCklk.webp&quot; srcset=&quot;/_astro/3.CIbxMPFb_Z1hCklk.webp 586w&quot; /&gt;&lt;figcaption&gt;支部管理员看板&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt=&quot;成员管理&quot; loading=&quot;lazy&quot; width=&quot;588&quot; height=&quot;1212&quot; src=&quot;/_astro/5.BOYV82Ty_1rXHwx.webp&quot; srcset=&quot;/_astro/5.BOYV82Ty_1rXHwx.webp 588w&quot; /&gt;&lt;figcaption&gt;成员管理&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt=&quot;信息审核&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;1210&quot; src=&quot;/_astro/8.FPSOufiA_1uF6Ld.webp&quot; srcset=&quot;/_astro/8.FPSOufiA_1uF6Ld.webp 590w&quot; /&gt;&lt;figcaption&gt;信息审核&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;功能&lt;a href=&quot;#功能&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;党员端&lt;a href=&quot;#党员端-1&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;首次使用需要输入工号 / 学号 + 手机号完成身份绑定，系统将微信 OpenID 与党员信息关联，之后打开直接识别身份，不用重复操作。每个微信只能绑一个人，每个人也只能绑一个微信。&lt;/p&gt;&lt;p&gt;绑定后可以看到自己所有期次的缴费状态，未缴的高亮提示，点击直接跳到支付页面。支持微信支付，同时内置了 mock 模式，不需要真实商户号也能跑通完整缴费流程，方便演示。&lt;/p&gt;&lt;p&gt;如果需要修改手机号、党员身份、所在支部等信息，可以在小程序内提交变更申请，管理员审核通过后生效，防止信息被随意修改。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;管理端&lt;a href=&quot;#管理端-1&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;系统设计了两级角色：&lt;strong&gt;超级管理员&lt;/strong&gt;管全院，&lt;strong&gt;支部管理员&lt;/strong&gt;只看自己支部，两者数据严格隔离。&lt;/p&gt;&lt;p&gt;超管看板用环形图展示全院整体缴费比例，下方按支部分组显示进度条，支持按期次和支部筛选。“一键确认到账”可以批量将已支付记录标记为到账，不用逐条处理。成员管理支持单条录入和 Excel 批量导入。&lt;/p&gt;&lt;p&gt;信息审核页面以变更对比的方式展示每条申请，旧值划线、新值加粗标红，管理员能直观看出改了什么，点击批准或驳回即可。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;技术实现&lt;a href=&quot;#技术实现&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;整体结构&lt;a href=&quot;#整体结构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;微信小程序（WXML / WXSS / JS）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↕ wx.request / HTTPS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Flask REST API（Python）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↕&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;SQLite 数据库（单文件）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;前后端完全分离，小程序只负责渲染和交互，业务逻辑全在后端。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;鉴权：两套并行&lt;a href=&quot;#鉴权两套并行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;管理员&lt;/strong&gt;走用户名 + 密码，登录后后端签发 JWT，payload 里带 &lt;code&gt;role&lt;/code&gt;（super / branch）和 &lt;code&gt;branch_id&lt;/code&gt;，后续请求通过 &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt; 鉴权。权限装饰器从 token 中读取角色，支部管理员调全院接口直接返回 403。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;require_super&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@wraps&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decorated&lt;/span&gt;&lt;span&gt;(*&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;kwargs&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;token &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;Bearer &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jwt.&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(token, &lt;/span&gt;&lt;span&gt;JWT_SECRET&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;algorithms&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;HS256&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;span&gt;&quot;role&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;super&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;jsonify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;code&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;msg&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;权限不足&quot;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;g.admin_id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;span&gt;&quot;sub&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;(*args, **kwargs)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; decorated&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;党员&lt;/strong&gt;走微信 OpenID 体系：小程序调 &lt;code&gt;wx.login()&lt;/code&gt; 拿到临时 code，后端用 APPID + AppSecret 向微信换取 OpenID，查库匹配党员记录后签发党员专用 JWT。OpenID 是微信给每个用户在特定小程序下的唯一标识，不会变、不会失效，用来做持久化身份绑定很合适。&lt;/p&gt;&lt;p&gt;数据隔离同样在后端保证：支部管理员的所有查询都自动追加 &lt;code&gt;WHERE branch_id = ?&lt;/code&gt;，从 SQL 层面隔开，不依赖前端传参。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;路由按角色拆文件&lt;a href=&quot;#路由按角色拆文件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;routes/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── admin_login.py    # 管理员登录（flask-limiter 频率限制，防爆破）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── super_admin.py    # 超管：成员 CRUD、期数、支部、账号、导出 Excel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── branch_admin.py   # 支部管理员：成员查询、审核&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── payment.py        # 微信支付回调 &amp;amp; 确认到账&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└── user.py           # 党员端：登录、查记录、提交申请&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;数据库选型&lt;a href=&quot;#数据库选型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;用户量有限（数十到数百人），SQLite 的几个特点很适合这个场景：零配置、单文件便于备份、开启 WAL 模式后支持读写并发。如果未来规模扩大，把 &lt;code&gt;get_db()&lt;/code&gt; 换成 PostgreSQL 连接，其余代码基本不用动。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;前端请求封装&lt;a href=&quot;#前端请求封装&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;小程序用原生开发，没有引入 UI 框架。&lt;code&gt;utils/api.js&lt;/code&gt; 统一处理鉴权头、401 过期跳转和错误提示，各页面只需调用 &lt;code&gt;api.post / api.get&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/admin/login&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;password&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;请求头里带了 &lt;code&gt;ngrok-skip-browser-warning: true&lt;/code&gt;，跳过 ngrok 免费版在浏览器侧插入的提示页，真机调试不受影响。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;部署&lt;a href=&quot;#部署&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;环境要求&lt;a href=&quot;#环境要求&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;工具&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Python 3.10+&lt;/td&gt;&lt;td&gt;后端运行环境&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;微信开发者工具&lt;/td&gt;&lt;td&gt;最新稳定版，用于小程序调试&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;微信小程序 AppID&lt;/td&gt;&lt;td&gt;在&lt;a href=&quot;https://mp.weixin.qq.com&quot; target=&quot;_blank&quot;&gt;微信公众平台&lt;/a&gt; → 开发管理 → 开发设置中获取，个人测试号即可&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://ngrok.com/download&quot; target=&quot;_blank&quot;&gt;ngrok&lt;/a&gt;&lt;/td&gt;&lt;td&gt;真机调试时做内网穿透（可选）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;后端&lt;a href=&quot;#后端&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://github.com/cairangxianmu/party-fee-manager.git&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;party-fee-manager/backend&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;requirements.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 初始化数据库&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;from database import init_db; init_db()&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 写入演示数据（可选，会清空现有数据）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;seed_demo.py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# → http://localhost:5000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;演示数据包含 2 个支部、5 名在册党员（张三 / 李四 / 王五等匿名示例）和 2 个缴费期次，部分已缴、部分未缴，可以直接看到看板上的统计效果。&lt;/p&gt;&lt;p&gt;演示账号：&lt;/p&gt;



















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;账号&lt;/th&gt;&lt;th&gt;密码&lt;/th&gt;&lt;th&gt;角色&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;admin&lt;/td&gt;&lt;td&gt;admin123&lt;/td&gt;&lt;td&gt;超级管理员&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;branch01&lt;/td&gt;&lt;td&gt;branch123&lt;/td&gt;&lt;td&gt;支部管理员（第一支部）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;凭证推荐通过环境变量注入，避免写入代码：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;APPID&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;wxxxxxxxxxxx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;APP_SECRET&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;xxxxxxxxxxxxxxxx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;也可以直接在 &lt;code&gt;backend/config.py&lt;/code&gt; 填写默认值，仅限本地调试。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;微信开发者工具&lt;a href=&quot;#微信开发者工具&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;打开微信开发者工具，选择「小程序」→「导入项目」，目录选择 &lt;code&gt;miniprogram/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;miniprogram/project.config.json&lt;/code&gt; 填入你的 AppID&lt;/li&gt;
&lt;li&gt;详情 → 本地设置 → 勾选**「不校验合法域名」**，否则 localhost 请求会被拦截&lt;/li&gt;
&lt;li&gt;点击&lt;strong&gt;编译（Ctrl+B）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;code&gt;miniprogram/app.js&lt;/code&gt; 里的 &lt;code&gt;baseUrl&lt;/code&gt; 默认指向 &lt;code&gt;http://localhost:5000&lt;/code&gt;，模拟器调试直接可用。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;真机调试&lt;a href=&quot;#真机调试&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;微信真机预览要求后端使用 HTTPS，本地可以用 &lt;a href=&quot;https://ngrok.com/download&quot; target=&quot;_blank&quot;&gt;ngrok&lt;/a&gt; 做内网穿透。注册账号、下载安装后完成 authtoken 配置，然后：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ngrok&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 输出类似：Forwarding  https://xxxx.ngrok-free.app -&amp;gt; http://localhost:5000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;将该地址填入 &lt;code&gt;miniprogram/app.js&lt;/code&gt; 的 &lt;code&gt;baseUrl&lt;/code&gt;，重新编译后点击「预览」扫码，即可在真机上体验完整流程，包括微信授权登录和支付。&lt;/p&gt;&lt;p&gt;注意 ngrok 免费版每次重启地址会变，重启后需要更新 &lt;code&gt;baseUrl&lt;/code&gt; 并重新编译。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;正式上线&lt;a href=&quot;#正式上线&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;日常演示和开发不需要关注这部分，有实际上线需求时参考：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;微信支付&lt;/strong&gt;：&lt;code&gt;config.py&lt;/code&gt; 中 &lt;code&gt;PAY_MODE&lt;/code&gt; 默认为 &lt;code&gt;mock&lt;/code&gt;。接入真实支付需要开通微信商户号，获取 &lt;code&gt;MCHID&lt;/code&gt;、&lt;code&gt;APIV3_KEY&lt;/code&gt;、商户 API 证书（&lt;code&gt;apiclient_key.pem&lt;/code&gt;）和 &lt;code&gt;SERIAL_NO&lt;/code&gt;，填入配置后将 &lt;code&gt;PAY_MODE&lt;/code&gt; 改为 &lt;code&gt;real&lt;/code&gt;，并配置公网 HTTPS 回调地址 &lt;code&gt;NOTIFY_URL&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;JWT 密钥&lt;/strong&gt;：将 &lt;code&gt;JWT_SECRET&lt;/code&gt; 替换为随机字符串：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;import secrets; print(secrets.token_hex(32))&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;服务器部署&lt;/strong&gt;：用 &lt;code&gt;gunicorn&lt;/code&gt; + &lt;code&gt;nginx&lt;/code&gt; 替代 Flask 开发服务器，配置 Let’s Encrypt SSL 证书（微信小程序正式版强制要求 HTTPS）。SQLite 适合中小规模，数据量大时可迁移到 PostgreSQL。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>TibetanHWR 系列三：CNN 训练与 Web 部署——手写识别在线演示实现</title><link>https://crxm.top/posts/tibetan-character-recognition/tibetan-character-model-train/</link><guid isPermaLink="true">https://crxm.top/posts/tibetan-character-recognition/tibetan-character-model-train/</guid><description>藏文手写识别系列三：基于 TibetanCharacter 数据集，详解 DigitCNN / LetterCNN 两款模型的设计与训练方案，以及 FastAPI + Canvas 的 Web 演示应用搭建过程。</description><pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 TibetanCharacter 系列的第三篇，也是终章。前两篇分别解决了「数据从哪来」和「数据怎么清洗」的问题，本篇把数据喂进模型，再把模型搬进浏览器。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;项目资源&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;📊 数据集：&lt;a href=&quot;https://pan.baidu.com/s/1TnM9Rxue9ae0bhPJ2EUP8g?pwd=4ata&quot; target=&quot;_blank&quot;&gt;百度网盘&lt;/a&gt;（提取码：&lt;code&gt;4ata&lt;/code&gt;）&lt;/p&gt;&lt;p&gt;💻 项目代码：&lt;a href=&quot;https://github.com/cairangxianmu/tibetan-hwr&quot; target=&quot;_blank&quot;&gt;cairangxianmu/tibetan-hwr&lt;/a&gt;&lt;/p&gt;&lt;p&gt;🌐 Web 演示：&lt;a href=&quot;https://cairangxianmu-tibetan-hwr.hf.space&quot; target=&quot;_blank&quot;&gt;cairangxianmu-tibetan-hwr.hf.space&lt;/a&gt;&lt;/p&gt;&lt;p&gt;📄 论文：&lt;a href=&quot;https://doi.org/10.16229/j.cnki.issn1001-7542.2019.04.006&quot; target=&quot;_blank&quot;&gt;DOI 10.16229/j.cnki.issn1001-7542.2019.04.006&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;一、整体架构&lt;a href=&quot;#一整体架构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;把「手写识别」拆成可以独立迭代的三个模块：数据、模型、服务。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;dataset/                 ImageFolder 组织的 PNG 集合&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;recognition/             PyTorch 训练管线&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├── dataset.py        DataLoader 工厂 + 字符映射表&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├── model.py          DigitCNN / LetterCNN&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└── train.py          训练入口（日志 / 曲线 / 权重保存）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;checkpoint/{mode}_best.pth&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;web/                     FastAPI + Canvas&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├── app.py            懒加载模型 · /predict 接口&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└── static/           Canvas 画板 + 逐笔撤销&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;两个任务走&lt;strong&gt;完全相同的管线&lt;/strong&gt;，只由 &lt;code&gt;--mode digit|letter&lt;/code&gt; 这一个开关切换数据源、模型结构和类别数。这让代码可以大部分共用，同时各自有针对性的设计。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;二、模型设计&lt;a href=&quot;#二模型设计&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;2.1 为什么要两个网络&lt;a href=&quot;#21-为什么要两个网络&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;数字&lt;/th&gt;&lt;th&gt;字母&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;输入尺寸&lt;/td&gt;&lt;td&gt;28×28&lt;/td&gt;&lt;td&gt;64×64&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;类别数&lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;30&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;图像面积&lt;/td&gt;&lt;td&gt;1×&lt;/td&gt;&lt;td&gt;5.2×&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;字形复杂度&lt;/td&gt;&lt;td&gt;简单&lt;/td&gt;&lt;td&gt;较复杂&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;字母任务的&lt;strong&gt;信息量&lt;/strong&gt;（面积 × 类别）是数字任务的十几倍，共用一套小网络会欠拟合，共用一套大网络对数字又是浪费。因此按任务复杂度分别设计。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.2 DigitCNN（10 类）&lt;a href=&quot;#22-digitcnn10-类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;LeNet 风格，两层卷积 + 两层全连接，参数量 &lt;strong&gt;~420K&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;输入 1×28×28&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Conv(1→32, 3×3, pad=1) → BN → ReLU → MaxPool(2)   →  32×14×14&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Conv(32→64, 3×3, pad=1) → BN → ReLU → MaxPool(2)  →  64×7×7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Flatten → FC(3136→128) → ReLU → Dropout(0.5)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;FC(128→10)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;CPU 上单张推理 &amp;lt; 5 ms，训练 30 epoch 在笔记本上约 5 分钟。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.3 LetterCNN（30 类）&lt;a href=&quot;#23-lettercnn30-类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 DigitCNN 基础上增加第三个卷积块，并在每层卷积后加 &lt;strong&gt;BatchNorm&lt;/strong&gt;，参数量 &lt;strong&gt;~2.3M&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;输入 1×64×64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Conv(1→32,  3×3) → BN → ReLU → MaxPool(2)   →  32×32×32&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Conv(32→64, 3×3) → BN → ReLU → MaxPool(2)   →  64×16×16&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Conv(64→128,3×3) → BN → ReLU → MaxPool(2)   →  128×8×8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Flatten → FC(8192→256) → ReLU → Dropout(0.5)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;FC(256→30)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;BatchNorm 在这里做两件事：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;加速收敛&lt;/strong&gt;——实测达到相同验证准确率所需 epoch 数减少约 30%；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;稳定训练&lt;/strong&gt;——缓解内部协变量偏移，缩小训练 / 验证准确率差距。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;两个模型都通过 &lt;code&gt;get_model(mode)&lt;/code&gt; 工厂函数统一调用，训练、推理、权重加载全部复用同一套入口。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三、训练管线&lt;a href=&quot;#三训练管线&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;3.1 数据加载&lt;a href=&quot;#31-数据加载&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;数据集按 &lt;code&gt;ImageFolder&lt;/code&gt; 约定组织（子目录名即类别），通过 &lt;code&gt;get_dataloaders()&lt;/code&gt; 一行获取 train/val DataLoader：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;train_loader, val_loader, num_classes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_dataloaders&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;mode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;letter&quot;&lt;/span&gt;&lt;span&gt;,   &lt;/span&gt;&lt;span&gt;# 或 &quot;digit&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;batch_size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;64&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;val_split&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;,   &lt;/span&gt;&lt;span&gt;# 固定随机种子 42，保证划分可复现&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.2 预处理与数据增强&lt;a href=&quot;#32-预处理与数据增强&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;二值化预处理&lt;/strong&gt;是训练和推理共用的第一步：对灰度图先做高斯模糊（σ=1），再用 Otsu 算法自动确定阈值进行二值化。相比直接 Otsu，模糊步骤先消除抗锯齿噪点，让笔画边缘更连续，实测效果明显优于多种其他方案（纯 Otsu、自适应阈值、形态学闭运算等）。&lt;/p&gt;&lt;p&gt;训练集在二值化前还施加几何增强，顺序很关键：&lt;strong&gt;几何变换必须在二值化之前做&lt;/strong&gt;。若先二值化再做仿射变换，双线性插值会在纯 0/255 的图上产生灰色污染像素，破坏二值一致性。正确的流水线：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Grayscale → Resize&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ RandomRotation(±15°)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ RandomAffine(translate=8%, scale=±15%, shear=±8°)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ RandomPerspective(distortion=0.2, p=0.4)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ GaussianBinarize（σ=1 + Otsu）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ ToTensor → Normalize(0.5, 0.5)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ RandomErasing(p=0.3)          ← Tensor 级别，模拟笔画断裂&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;fill=255&lt;/code&gt; 保证所有几何变换露出的区域为白色背景，与训练数据一致。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;不做翻转&lt;/strong&gt;。这一点专门针对藏文：多个字母互为镜像（如 &lt;code&gt;ག / ད&lt;/code&gt;），水平或垂直翻转会直接污染标签。这是「通用视觉增强」在专门领域需要让位于先验知识的典型案例。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.3 优化与调度&lt;a href=&quot;#33-优化与调度&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;优化器：Adam，lr=1e-3，weight_decay=1e-4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;调度器：CosineAnnealingLR，T_max=epochs，eta_min=1e-6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;损失：  CrossEntropyLoss(label_smoothing=0.1)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;早停：  patience=10，验证准确率连续无改善时恢复最优权重并停止&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;建议：  digit → 30 epoch，letter → 50 epoch（batch 128）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;标签平滑&lt;/strong&gt;（&lt;code&gt;label_smoothing=0.1&lt;/code&gt;）让模型对自己的预测不过度自信，缓解在小数据集上的过拟合。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;早停&lt;/strong&gt;监控验证准确率，连续 &lt;code&gt;patience&lt;/code&gt; 个 epoch 无改善时触发，并自动恢复精度最高时的权重——既防止过拟合，又省去手动盯训练曲线的麻烦。训练结束或早停后，每隔 5 个 epoch 的周期检查点也会保留最近 3 个，便于回退。&lt;/p&gt;&lt;p&gt;Cosine 退火的直觉：前期大 lr 快速下降到低损失盆地，后期小 lr 精细探索盆底，避免在最优解附近震荡。相比 StepLR，在同等 epoch 下通常能多挤出 0.5–1 个百分点。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.4 日志与可视化&lt;a href=&quot;#34-日志与可视化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;每次训练在 &lt;code&gt;runs/{mode}_{timestamp}/&lt;/code&gt; 下自动生成四件套：&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;文件&lt;/th&gt;&lt;th&gt;用途&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;args.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;超参数快照 + 原始命令，复现实验&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;metrics.csv&lt;/code&gt;&lt;/td&gt;&lt;td&gt;逐 epoch 指标，便于后处理分析&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;events.out.*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;TensorBoard 事件文件&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;training_curves.png&lt;/code&gt;&lt;/td&gt;&lt;td&gt;训练结束时的总览图&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;训练结束后自动生成训练曲线（Loss / Accuracy）：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;数字模型&lt;/strong&gt;（30 epoch）&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;数字模型训练曲线&quot; loading=&quot;lazy&quot; width=&quot;1784&quot; height=&quot;593&quot; src=&quot;/_astro/digit_training_curves.C9Ry-91S_Z2hWYdl.webp&quot; srcset=&quot;/_astro/digit_training_curves.C9Ry-91S_ZLB3j.webp 640w, /_astro/digit_training_curves.C9Ry-91S_nnTjh.webp 750w, /_astro/digit_training_curves.C9Ry-91S_sowRh.webp 828w, /_astro/digit_training_curves.C9Ry-91S_VzM5d.webp 1080w, /_astro/digit_training_curves.C9Ry-91S_1fbBbp.webp 1280w, /_astro/digit_training_curves.C9Ry-91S_Z15jabJ.webp 1668w, /_astro/digit_training_curves.C9Ry-91S_Z2hWYdl.webp 1784w&quot; /&gt;&lt;figcaption&gt;数字模型训练曲线&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;字母模型&lt;/strong&gt;（50 epoch）&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;字母模型训练曲线&quot; loading=&quot;lazy&quot; width=&quot;1784&quot; height=&quot;593&quot; src=&quot;/_astro/letter_training_curves.CwXSfIN4_2dhDyU.webp&quot; srcset=&quot;/_astro/letter_training_curves.CwXSfIN4_2c95bN.webp 640w, /_astro/letter_training_curves.CwXSfIN4_2hhDtl.webp 750w, /_astro/letter_training_curves.CwXSfIN4_1vI8cA.webp 828w, /_astro/letter_training_curves.CwXSfIN4_Z22uVwP.webp 1080w, /_astro/letter_training_curves.CwXSfIN4_is05d.webp 1280w, /_astro/letter_training_curves.CwXSfIN4_1RKEAP.webp 1668w, /_astro/letter_training_curves.CwXSfIN4_2dhDyU.webp 1784w&quot; /&gt;&lt;figcaption&gt;字母模型训练曲线&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.5 权重保存&lt;a href=&quot;#35-权重保存&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不止保存权重，还把元数据一起存进 checkpoint，让推理侧加载时无需再传参：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;torch.&lt;/span&gt;&lt;span&gt;save&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;epoch&quot;&lt;/span&gt;&lt;span&gt;:            epoch,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;mode&quot;&lt;/span&gt;&lt;span&gt;:             args.mode,      &lt;/span&gt;&lt;span&gt;# &quot;digit&quot; / &quot;letter&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;num_classes&quot;&lt;/span&gt;&lt;span&gt;:      num_classes,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;model_state_dict&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: model.&lt;/span&gt;&lt;span&gt;state_dict&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;val_acc&quot;&lt;/span&gt;&lt;span&gt;:          val_acc,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;&quot;checkpoint/&lt;/span&gt;&lt;span&gt;{mode}&lt;/span&gt;&lt;span&gt;_best.pth&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四、Web 在线演示&lt;a href=&quot;#四web-在线演示&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;4.1 后端：FastAPI + 懒加载&lt;a href=&quot;#41-后端fastapi--懒加载&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;三个路由就够了：&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;路由&lt;/th&gt;&lt;th&gt;方法&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;GET&lt;/td&gt;&lt;td&gt;返回前端页面&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/health&lt;/code&gt;&lt;/td&gt;&lt;td&gt;GET&lt;/td&gt;&lt;td&gt;健康检查，返回推理设备&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/predict&lt;/code&gt;&lt;/td&gt;&lt;td&gt;POST&lt;/td&gt;&lt;td&gt;接收 base64 图像 + 模式，返回识别结果&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;strong&gt;模型懒加载&lt;/strong&gt;是一个值得单独提的设计：服务启动不触碰权重文件，首次收到某模式请求时才读盘加载，之后缓存在内存中。好处是启动秒级响应、内存占用按需增长；如果只识别数字，字母模型永远不会被加载。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;_model_cache: &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_load_model&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mode&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; torch.nn.Module:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; mode &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; _model_cache:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; _model_cache[mode]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkpoint &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; torch.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;(ckpt_path, &lt;/span&gt;&lt;span&gt;map_location&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;_device)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;model &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_model&lt;/span&gt;&lt;span&gt;(mode, &lt;/span&gt;&lt;span&gt;num_classes&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;checkpoint[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;num_classes&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;model.&lt;/span&gt;&lt;span&gt;load_state_dict&lt;/span&gt;&lt;span&gt;(checkpoint[&lt;/span&gt;&lt;span&gt;&quot;model_state_dict&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;model.&lt;/span&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_model_cache[mode] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.2 前端：Canvas + 逐笔撤销&lt;a href=&quot;#42-前端canvas--逐笔撤销&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;前端不依赖任何框架，核心是一个 400×400 的 Canvas。鼠标和触屏统一处理，真正想讲的是 &lt;strong&gt;逐笔撤销&lt;/strong&gt;的实现——不存像素，而是存&lt;strong&gt;每一笔落笔前的快照&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;startDraw&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 落笔前拍快照&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;currentStroke&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getImageData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;canvas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;canvas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;beginPath&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;moveTo&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;endDraw&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;strokeHistory&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;currentStroke&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 抬笔后入栈&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;undoBtn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;click&quot;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;strokeHistory&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;putImageData&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 还原到上一笔之前&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这样任意笔画数都能无损撤销，且实现比维护笔画向量列表简单得多。代价是内存占用随撤销栈线性增长，对小画板无伤大雅。&lt;/p&gt;&lt;p&gt;其它交互细节：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;图片上传（点击 / 拖放），自动居中缩放填充画板；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+Z&lt;/code&gt; 撤销、&lt;code&gt;Enter&lt;/code&gt; 识别；&lt;/li&gt;
&lt;li&gt;切换数字 / 字母模式时清空画板。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;识别请求就是把 Canvas &lt;code&gt;toDataURL&lt;/code&gt; 编码为 base64 发过去，响应体携带 Top-5 候选：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;:      &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;character&quot;&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;&quot;༣&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;confidence&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;97.42&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;top5&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;character&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;༣&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;confidence&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;97.42&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;character&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;༨&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;confidence&quot;&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;1.83&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.3 关键细节：训练—推理分布对齐&lt;a href=&quot;#43-关键细节训练推理分布对齐&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是本项目调试中&lt;strong&gt;收益最高的一步&lt;/strong&gt;，值得单独拎出来讲。&lt;/p&gt;&lt;p&gt;上线后首轮体验识别率很低。原因是：训练样本里字符几乎&lt;strong&gt;铺满整帧&lt;/strong&gt;（28×28 或 64×64 都是紧贴字符边缘），但推理时用户在 400×400 画板上写一个字，字符只占中心一小块，周围大片白边。直接缩放，字符就被压成了一小坨，模型从未见过这种尺度。&lt;/p&gt;&lt;p&gt;解决办法：在推理预处理里&lt;strong&gt;先做紧边界框裁剪&lt;/strong&gt;，再缩放。流程图：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Canvas 400×400（白底 + 一小块字符）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼  _tight_crop：找暗像素边界框 → 扩 15% padding → 裁剪 → 填充为正方形&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;字符铺满帧的图像&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼  Grayscale → Resize（28×28 或 64×64）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼  GaussianBinarize：高斯模糊（σ=1）→ Otsu 二值化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼  ToTensor → Normalize(0.5, 0.5)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;模型输入（与训练验证集分布一致）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;核心代码：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_tight_crop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;img_gray&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pad_ratio&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0.15&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;arr  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;(img_gray)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; arr &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;                      &lt;/span&gt;&lt;span&gt;# 暗像素掩码&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows, cols &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;(mask, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;), np.&lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;(mask, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt;&lt;span&gt; rows.&lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;():                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 画板为空&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; img_gray&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rmin, rmax &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt;(rows)[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;][[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cmin, cmax &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt;(cols)[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;][[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pad &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;&lt;span&gt;(rmax &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; rmin, cmax &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; cmin) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; pad_ratio))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 外扩 padding 后裁剪，不越界&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cropped &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img_gray.&lt;/span&gt;&lt;span&gt;crop&lt;/span&gt;&lt;span&gt;((cmin, rmin, cmax &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, rmax &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 填充为正方形白背景，避免后续 Resize 拉伸&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;side   &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(cropped.size)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;square &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Image.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;L&quot;&lt;/span&gt;&lt;span&gt;, (side, side), &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;square.&lt;/span&gt;&lt;span&gt;paste&lt;/span&gt;&lt;span&gt;(cropped, ((side &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; cw) &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;, (side &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; ch) &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; square&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;加入这一步后，识别率显著提升。&lt;strong&gt;经验&lt;/strong&gt;：训练和推理的数据分布不一致，即使模型本身没问题也会表现得「模型很差」——定位这类问题比调模型本身更重要。手写画板和上传图片走同一条预处理路径，保证两种输入的表现一致。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;五、快速上手&lt;a href=&quot;#五快速上手&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 1. 依赖&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;requirements.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 2. 下载预训练模型（推荐）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#    从 GitHub Releases 下载 digit_best.pth 和 letter_best.pth，放入 checkpoint/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#    https://github.com/cairangxianmu/tibetan-hwr/releases/latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 3. 启动 Web 服务&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;web&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;uvicorn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app:app&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--port&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 浏览器打开 http://localhost:8000，书写或上传图片后点击「识别」&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如需自行训练（需先准备数据集，见系列一）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recognition&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;train.py&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--mode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;digit&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--epochs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;                              &lt;/span&gt;&lt;span&gt;# ~5 min CPU&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;train.py&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--mode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;letter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--epochs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--batch-size&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;128&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--patience&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# ~5 min GPU&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;六、小结&lt;a href=&quot;#六小结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;本篇把数据变成了一个可交互的 Demo，沿途的关键选择：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;按任务复杂度差异化设计模型&lt;/strong&gt;：DigitCNN 与 LetterCNN 均含 BatchNorm，LetterCNN 额外多一层卷积块，避免一刀切；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预处理统一二值化&lt;/strong&gt;：高斯模糊（σ=1）+ Otsu，训练与推理完全一致；几何增强必须在二值化之前做，否则插值会污染二值图；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增强策略服从先验&lt;/strong&gt;：藏文镜像字禁用翻转；增加透视变换和随机擦除以拟合手写板的书写风格；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签平滑 + 早停&lt;/strong&gt;：防止小数据集过拟合，无需手动盯训练曲线；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;懒加载服务&lt;/strong&gt;：启动快、内存按需增长，适合多模型共享后端的场景；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;训练—推理分布对齐&lt;/strong&gt;：Canvas 空白多、训练样本铺满帧，靠 &lt;code&gt;_tight_crop&lt;/code&gt; 拉平差距，实测收益最高。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;三篇合在一起，完整覆盖了这套系统&lt;strong&gt;数据采集（系列一）→ 图像预处理（系列二）→ 模型训练与在线演示（本篇）&lt;/strong&gt; 的全链路。没有用到任何预训练模型，从零开始、轻量部署，可作为理解手写识别端到端链路的入门实践。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>TibetanHWR 系列一：TibetanCharacter 数据集——藏文手写数字与字母图像集</title><link>https://crxm.top/posts/tibetan-character-recognition/tibetan-character-data/</link><guid isPermaLink="true">https://crxm.top/posts/tibetan-character-recognition/tibetan-character-data/</guid><description>开源藏文手写图像数据集：TibetanMNIST（17768 张手写数字）与 TibetanLetter（77636 张字母图像），涵盖数据背景、格式说明、分布统计。</description><pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;项目资源&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;📊 数据集：&lt;a href=&quot;https://pan.baidu.com/s/1TnM9Rxue9ae0bhPJ2EUP8g?pwd=4ata&quot; target=&quot;_blank&quot;&gt;百度网盘&lt;/a&gt;（提取码：&lt;code&gt;4ata&lt;/code&gt;）&lt;/p&gt;&lt;p&gt;💻 项目代码：&lt;a href=&quot;https://github.com/cairangxianmu/tibetan-hwr&quot; target=&quot;_blank&quot;&gt;cairangxianmu/tibetan-hwr&lt;/a&gt;&lt;/p&gt;&lt;p&gt;🌐 Web 演示：&lt;a href=&quot;https://cairangxianmu-tibetan-hwr.hf.space&quot; target=&quot;_blank&quot;&gt;cairangxianmu-tibetan-hwr.hf.space&lt;/a&gt;&lt;/p&gt;&lt;p&gt;📄 论文：&lt;a href=&quot;https://doi.org/10.16229/j.cnki.issn1001-7542.2019.04.006&quot; target=&quot;_blank&quot;&gt;DOI 10.16229/j.cnki.issn1001-7542.2019.04.006&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;一、项目简介&lt;a href=&quot;#一项目简介&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;本文介绍两个专为藏文手写体识别任务构建的开源数据集：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TibetanMNIST&lt;/strong&gt;：藏文手写数字数据集，共 &lt;strong&gt;17768&lt;/strong&gt; 张图像，涵盖藏文 0–9 共 10 个数字类别&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TibetanLetter&lt;/strong&gt;：藏文手写字母数据集，共 &lt;strong&gt;77636&lt;/strong&gt; 张图像，覆盖 30 个藏文辅音字母&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;两个数据集均由本人在本科期间带队制作，是目前为数不多的藏文手写体公开数据集，填补了该领域数据空白，适合用于藏文识别相关的机器学习入门任务。&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;TibetanMNIST&lt;/th&gt;&lt;th&gt;TibetanLetter&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;类别数&lt;/td&gt;&lt;td&gt;10（数字 0–9）&lt;/td&gt;&lt;td&gt;30（辅音字母）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;样本数&lt;/td&gt;&lt;td&gt;17,768&lt;/td&gt;&lt;td&gt;77,636&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;图像尺寸&lt;/td&gt;&lt;td&gt;28×28&lt;/td&gt;&lt;td&gt;64×64&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;通道&lt;/td&gt;&lt;td&gt;灰度图&lt;/td&gt;&lt;td&gt;灰度图&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;二、背景与起源&lt;a href=&quot;#二背景与起源&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;2.1 从 MNIST 到 TibetanMNIST&lt;a href=&quot;#21-从-mnist-到-tibetanmnist&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;MNIST 数据集是机器学习领域的里程碑——由美国国家标准与技术研究所（NIST）发起，收录了 250 位书写者的手写数字，自发布以来被广泛用于检验各类算法，推动了机器学习领域的长足发展，当之无愧是历史上最具影响力的数据集之一。&lt;/p&gt;&lt;p&gt;然而，现有手写体数据集几乎清一色面向英文、中文、阿拉伯文等强势语言，少数民族语言的数据资源极度匮乏。藏文作为拥有数百万使用者的语言，在公开数据集层面几乎是空白。这一现状不仅限制了藏文智能输入、文档数字化等实际应用的发展，也使藏文相关的学术研究举步维艰。&lt;/p&gt;&lt;p&gt;TibetanMNIST 正是在这一背景下诞生的。数据集按照 MNIST 的格式规范制作，图像尺寸同为 28×28 像素灰度图，可直接套用为 MNIST 设计的模型框架进行训练，大大降低了研究者的上手门槛。书写者为本校藏族同学，经过严格的质量筛选，最终整理出 17768 张高清图像——&lt;strong&gt;据我们所知，这是全球第一个公开的藏文手写数字图像数据集&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.2 从数字到字母的扩展&lt;a href=&quot;#22-从数字到字母的扩展&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;数字数据集的顺利完成，让团队看到了进一步拓展的可能。藏文数字仅有 10 个类别，而藏文字母有 30 个辅音字母，是构成藏文文本的核心单元，其识别难度与应用价值都更高。&lt;/p&gt;&lt;p&gt;为此，我们组织了更大规模的数据采集：邀请本校 &lt;strong&gt;150 名藏族大学生&lt;/strong&gt;参与手写，在统一规范的方格纸上书写全部 30 个辅音字母，原始采集量约 100000 例。经人工逐一核查清洗后，最终形成 &lt;strong&gt;TibetanLetter&lt;/strong&gt; 数据集，包含 77636 张 64×64 像素的标准化字母图像。&lt;/p&gt;&lt;p&gt;两个数据集合并发布，统称 &lt;strong&gt;TibetanCharacter&lt;/strong&gt;，希望为藏文手写体识别研究提供一个坚实的数据基础。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三、TibetanMNIST 数字数据集&lt;a href=&quot;#三tibetanmnist-数字数据集&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;3.1 藏文数字介绍&lt;a href=&quot;#31-藏文数字介绍&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;藏文数字是藏语书写体系中独立使用的计数符号，与阿拉伯数字一一对应，共 10 个，分别表示 0 到 9。它们并非藏文字母的一部分，而是在语言中单独成体，形态简洁、笔画清晰，具有较高的可辨识度。从字形上看，藏文数字自成风格，与汉字数字或阿拉伯数字均无直接渊源，是藏族文化中独特的符号系统：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;阿拉伯数字&lt;/th&gt;&lt;th&gt;藏文数字&lt;/th&gt;&lt;th&gt;阿拉伯数字&lt;/th&gt;&lt;th&gt;藏文数字&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;༠&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;༥&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;༡&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;༦&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;༢&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;༧&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;༣&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;༨&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;༤&lt;/td&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;༩&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.2 数据采集&lt;a href=&quot;#32-数据采集&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;数据采集以纸质手写为主，书写者涵盖学生与研究人员。为保证样本质量，我们对书写规范做了统一要求：字体大小须适中、笔画完整清晰、书写在规定区域内，避免连笔或涂改。&lt;/p&gt;&lt;p&gt;采集完成后，所有原始图像均经过扫描数字化，再由团队成员对每张图像进行人工复核，经过 300 余次人工筛选，剔除书写不规范、字迹模糊、尺寸异常等无效样本，最终保留 &lt;strong&gt;17768&lt;/strong&gt; 例有效图像，平均每个类别约 1777 张。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.3 类别分布&lt;a href=&quot;#33-类别分布&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;10 个数字类别整体分布较为均衡，每类约 1300–2100 张，无明显类别失衡问题，可直接用于有监督学习训练，无需额外做类别重采样：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;TibetanMNIST 类别分布&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;393&quot; src=&quot;/_astro/digit-distribution.C_akS6WW_1QwOxo.webp&quot; srcset=&quot;/_astro/digit-distribution.C_akS6WW_1QwOxo.webp 572w&quot; /&gt;&lt;figcaption&gt;TibetanMNIST 类别分布&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.4 数据示例&lt;a href=&quot;#34-数据示例&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;下图展示了每个类别的若干手写样本，可以看到不同书写者在笔画风格、粗细、倾斜程度上存在自然差异，这正是手写体识别的挑战所在：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;TibetanMNIST 数据示例&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;547&quot; src=&quot;/_astro/digit-samples.q4vDSEgw_1a2750.webp&quot; srcset=&quot;/_astro/digit-samples.q4vDSEgw_1a2750.webp 626w&quot; /&gt;&lt;figcaption&gt;TibetanMNIST 数据示例&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;原始图像命名规则：&lt;/strong&gt; &lt;code&gt;{类别标签}_{纸张编号}_{纸张内序号}.png&lt;/code&gt;，例如 &lt;code&gt;3_12_05.png&lt;/code&gt; 表示数字 3、第 12 张纸、第 5 个样本。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四、TibetanLetter 字母数据集&lt;a href=&quot;#四tibetanletter-字母数据集&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;4.1 藏文字母介绍&lt;a href=&quot;#41-藏文字母介绍&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;藏文是一种拼音文字，其书写体系由&lt;strong&gt;辅音字母&lt;/strong&gt;与&lt;strong&gt;元音符号&lt;/strong&gt;组合而成。现代藏文共有 30 个辅音字母和 4 个元音符号，书写时以辅音字母为基础，通过垂直方向的叠加（上加字、下加字）和水平方向的拼接（前加字、后加字、再后加字），加上元音符号构成一个完整的藏文音节。&lt;/p&gt;&lt;p&gt;每个音节在视觉上呈现为一个紧凑的字块，结构层次丰富，书写风格因人而异，这也使得藏文手写体识别在技术上颇具挑战性。本数据集聚焦于最基本的构成单元，收录了手写形式的 &lt;strong&gt;30 个辅音字母&lt;/strong&gt;：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;30 个藏文辅音字母&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;800&quot; src=&quot;/_astro/letter-intro.BmWlIj4q_Z1JlHn9.webp&quot; srcset=&quot;/_astro/letter-intro.BmWlIj4q_Z2gYNWB.webp 640w, /_astro/letter-intro.BmWlIj4q_TYcCM.webp 750w, /_astro/letter-intro.BmWlIj4q_Z1JlHn9.webp 776w&quot; /&gt;&lt;figcaption&gt;30 个藏文辅音字母&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.2 数据采集&lt;a href=&quot;#42-数据采集&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;为保证数据一致性，我们统一制定了书写规范：书写载体为&lt;strong&gt;红色格线方格纸&lt;/strong&gt;（8 行 × 12 列），每格书写一个字母，通过格线约束个体书写差异，保证字母大小和位置相对一致。&lt;/p&gt;&lt;p&gt;共有本校 &lt;strong&gt;150 名藏族大学生&lt;/strong&gt;参与书写，每人完成全部 30 个字母的书写任务，采集原始样本约 100000 例。所有纸张经高精度扫描后，由团队成员对每个字母图像进行逐一核查，删除字迹不清、书写越格、字母混淆等无效样本，最终保留有效数据 &lt;strong&gt;77636&lt;/strong&gt; 例。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.3 类别分布&lt;a href=&quot;#43-类别分布&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;30 个字母类别分布相对均衡，每类约 2000 余个样本。少数字母因字形复杂、书写难度较高，有效率略低于其他类别，但整体样本量仍可满足模型训练需求：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;TibetanLetter 类别分布&quot; loading=&quot;lazy&quot; width=&quot;446&quot; height=&quot;353&quot; src=&quot;/_astro/letter-distribution.DZ4jxo_J_Z2fhWKA.webp&quot; srcset=&quot;/_astro/letter-distribution.DZ4jxo_J_Z2fhWKA.webp 446w&quot; /&gt;&lt;figcaption&gt;TibetanLetter 类别分布&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.4 数据示例&lt;a href=&quot;#44-数据示例&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;下图为部分藏文辅音字母的手写样本展示，每行对应一个字母类别，可以直观看到同一字母在不同书写者手下的风格差异：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;TibetanLetter 数据示例&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;544&quot; src=&quot;/_astro/letter-samples.Behyzqnp_1zhnHp.webp&quot; srcset=&quot;/_astro/letter-samples.Behyzqnp_zgxEi.webp 640w, /_astro/letter-samples.Behyzqnp_1zhnHp.webp 690w&quot; /&gt;&lt;figcaption&gt;TibetanLetter 数据示例&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.5 图像预处理&lt;a href=&quot;#45-图像预处理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;由于书写纸张使用红色格线，原始扫描图像中格线与字母内容混叠，无法直接使用。我们设计了如下自动化预处理流程，将原始整张纸面图像切分并标准化为单张字母图像：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;原始扫描图像&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓ HSV 颜色提取（分离红色格线）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓ 中值滤波（去除噪声线条）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓ 轮廓检测（定位字母区域）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓ 字母提取（裁剪单个字母）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓ 统一尺寸（归一化图像大小）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;最终输出：标准化字母图像（64×64 灰度图）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;其中，HSV 颜色空间下对红色通道的精准提取是关键步骤——红色在 HSV 空间中分布在色调（H）的两端，需要分段掩膜合并处理。轮廓检测基于 OpenCV 的外轮廓提取，结合面积与长宽比过滤，排除杂点干扰，准确定位每个字母单元格。&lt;/p&gt;&lt;p&gt;具体实现细节见：&lt;a href=&quot;/posts/tibetan-character-recognition/tibetan-character-data-process/index&quot;&gt;TibetanHWR 系列二：图像预处理&lt;/a&gt;&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;五、小结&lt;a href=&quot;#五小结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;TibetanCharacter 数据集由 TibetanMNIST（17,768 张手写数字）与 TibetanLetter（77,636 张手写字母）两部分组成，填补了藏文手写体公开数据集的空白。系列后续两篇将分别介绍如何用 Python + OpenCV 对原始扫描图进行自动化预处理（系列二），以及如何训练 CNN 模型并部署为 Web 在线演示（系列三）。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;六、引用&lt;a href=&quot;#六引用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如在研究中使用本数据集，请引用以下论文：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;周毛克,才让先木,龙从军,等.基于卷积神经网络的藏文手写数字和字母识别研究[J].青海师范大学学报(自然科学版),2019,35(04):34-39.DOI:10.16229/j.cnki.issn1001-7542.2019.04.006.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@article&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;zhou2019tibetan&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;   = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;基于卷积神经网络的藏文手写数字和字母识别研究&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;  = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;周毛克 and 才让先木 and 龙从军 and others&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;journal&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;青海师范大学学报(自然科学版)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;volume&lt;/span&gt;&lt;span&gt;  = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;35&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;  = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;04&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;pages&lt;/span&gt;&lt;span&gt;   = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;34--39&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;    = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;2019&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;doi&lt;/span&gt;&lt;span&gt;     = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;10.16229/j.cnki.issn1001-7542.2019.04.006&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;</content:encoded></item><item><title>TibetanHWR 系列二：图像预处理——从红格纸扫描图到单字母图像</title><link>https://crxm.top/posts/tibetan-character-recognition/tibetan-character-data-process/</link><guid isPermaLink="true">https://crxm.top/posts/tibetan-character-recognition/tibetan-character-data-process/</guid><description>基于 Python 与 OpenCV 实现手写藏文字母图像的自动提取与裁剪，涵盖 HSV 颜色提取、中值滤波、霍夫直线检测、轮廓检测、图像旋转与裁剪等核心处理流程。</description><pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;项目资源&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;📊 数据集：&lt;a href=&quot;https://pan.baidu.com/s/1TnM9Rxue9ae0bhPJ2EUP8g?pwd=4ata&quot; target=&quot;_blank&quot;&gt;百度网盘&lt;/a&gt;（提取码：&lt;code&gt;4ata&lt;/code&gt;）&lt;/p&gt;&lt;p&gt;💻 项目代码：&lt;a href=&quot;https://github.com/cairangxianmu/tibetan-hwr&quot; target=&quot;_blank&quot;&gt;cairangxianmu/tibetan-hwr&lt;/a&gt;&lt;/p&gt;&lt;p&gt;🌐 Web 演示：&lt;a href=&quot;https://cairangxianmu-tibetan-hwr.hf.space&quot; target=&quot;_blank&quot;&gt;cairangxianmu-tibetan-hwr.hf.space&lt;/a&gt;&lt;/p&gt;&lt;p&gt;📄 论文：&lt;a href=&quot;https://doi.org/10.16229/j.cnki.issn1001-7542.2019.04.006&quot; target=&quot;_blank&quot;&gt;DOI 10.16229/j.cnki.issn1001-7542.2019.04.006&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;一、项目概述&lt;a href=&quot;#一项目概述&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;手写藏文字母识别是一项具有民族特色的任务，藏文字母笔画复杂、书写风格多样，目前没有相关公开数据集。本项目是藏文字母识别系统的图像预处理模块，负责将志愿者手写并扫描的表格型文字图像自动处理为可供模型训练的单字母图像。&lt;/p&gt;&lt;p&gt;表格采用红色框线划分格子，每个格子内手写一个藏文字母。图像预处理需要完成以下工作：提取红色框线定位表格区域、矫正扫描时产生的倾斜偏差、按格子裁剪并批量保存各字母图像，最终消除残余的红色噪声。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;项目构成：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main.py&lt;/code&gt;：主流程，读取扫描图像，依次执行颜色提取、滤波、直线检测、轮廓定位、旋转矫正、裁剪保存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replace.py&lt;/code&gt;：后处理，逐像素替换残余红色框线为白色背景&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;二、依赖安装&lt;a href=&quot;#二依赖安装&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencv-python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pillow&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三、图像处理流程&lt;a href=&quot;#三图像处理流程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;原始图像如下，为带有红色格线的手写表格：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;原始图像&quot; loading=&quot;lazy&quot; width=&quot;2480&quot; height=&quot;3507&quot; src=&quot;/_astro/01-original.BQJOct9i_glp0.webp&quot; srcset=&quot;/_astro/01-original.BQJOct9i_L8FNE.webp 640w, /_astro/01-original.BQJOct9i_O73hY.webp 750w, /_astro/01-original.BQJOct9i_21NXiE.webp 828w, /_astro/01-original.BQJOct9i_Uz23f.webp 1080w, /_astro/01-original.BQJOct9i_uvIc3.webp 1280w, /_astro/01-original.BQJOct9i_ZizGMy.webp 1668w, /_astro/01-original.BQJOct9i_294Sc7.webp 2048w, /_astro/01-original.BQJOct9i_glp0.webp 2480w&quot; /&gt;&lt;figcaption&gt;原始图像&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;hr /&gt;&lt;section&gt;&lt;h3&gt;1. HSV 颜色提取&lt;a href=&quot;#1-hsv-颜色提取&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;separate_color_red&lt;/code&gt; 函数提取图像中的红色框线。将图像从 BGR 转换为 HSV 色彩空间后，通过 &lt;code&gt;cv2.inRange&lt;/code&gt; 指定颜色范围进行掩膜提取。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;HSV 颜色范围参考&lt;/strong&gt;：&lt;a href=&quot;https://blog.csdn.net/u013270326/article/details/80704754&quot; target=&quot;_blank&quot;&gt;点击查看各颜色 HSV 分量范围&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;函数说明：&lt;code&gt;cv2.inRange(hsv, lowerb, upperb)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;hsv&lt;/code&gt;&lt;/td&gt;&lt;td&gt;输入图像，需先转换为 HSV 格式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;lowerb&lt;/code&gt;&lt;/td&gt;&lt;td&gt;H、S、V 分量的最低值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;upperb&lt;/code&gt;&lt;/td&gt;&lt;td&gt;H、S、V 分量的最高值&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;hsv &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;cvtColor&lt;/span&gt;&lt;span&gt;(img, cv2.&lt;/span&gt;&lt;span&gt;COLOR_BGR2HSV&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 转换为 HSV 色彩空间&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;lower_hsv &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;43&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;46&lt;/span&gt;&lt;span&gt;])            &lt;/span&gt;&lt;span&gt;# 红色 HSV 下界&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;high_hsv  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;])         &lt;/span&gt;&lt;span&gt;# 红色 HSV 上界&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;mask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;inRange&lt;/span&gt;&lt;span&gt;(hsv, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lowerb&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;lower_hsv, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;upperb&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;high_hsv)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;提取效果：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;HSV 颜色提取结果&quot; loading=&quot;lazy&quot; width=&quot;584&quot; height=&quot;829&quot; src=&quot;/_astro/02-hsv.Do1o-psX_1k8rFN.webp&quot; srcset=&quot;/_astro/02-hsv.Do1o-psX_1k8rFN.webp 584w&quot; /&gt;&lt;figcaption&gt;HSV 颜色提取结果&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 中值滤波&lt;a href=&quot;#2-中值滤波&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;medianBlur&lt;/code&gt; 用于过滤掉除最外层框线以外的噪声线条，保留主体轮廓。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;函数说明：&lt;code&gt;cv2.medianBlur(img, ksize)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;img&lt;/code&gt;&lt;/td&gt;&lt;td&gt;输入图像&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ksize&lt;/code&gt;&lt;/td&gt;&lt;td&gt;滤波核大小，必须为大于 1 的奇数（如 3、5、7、19）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;mediu &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;medianBlur&lt;/span&gt;&lt;span&gt;(img, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;19&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;滤波效果：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;中值滤波结果&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;839&quot; src=&quot;/_astro/03-median.DVzUSAYU_Z1LJA8b.webp&quot; srcset=&quot;/_astro/03-median.DVzUSAYU_Z1LJA8b.webp 604w&quot; /&gt;&lt;figcaption&gt;中值滤波结果&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 概率霍夫直线检测&lt;a href=&quot;#3-概率霍夫直线检测&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;调用 &lt;code&gt;HoughLinesP&lt;/code&gt; 前需先执行 Canny 边缘检测，将图像二值化。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;cv2.Canny(img, threshold1, threshold2)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;threshold1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;低阈值，用于连接间断边缘&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;threshold2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;高阈值，用于检测明显边缘&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;cv2.HoughLinesP(img, rho, theta, threshold, lines, minLineLength, maxLineGap)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;img&lt;/code&gt;&lt;/td&gt;&lt;td&gt;输入图像，须为 Canny 边缘检测后的二值图&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rho&lt;/code&gt;&lt;/td&gt;&lt;td&gt;直线半径精度，建议设为 1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;theta&lt;/code&gt;&lt;/td&gt;&lt;td&gt;角度步长，通常为 &lt;code&gt;np.pi / 180&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;threshold&lt;/code&gt;&lt;/td&gt;&lt;td&gt;累加器阈值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;minLineLength&lt;/code&gt;&lt;/td&gt;&lt;td&gt;最短直线长度，短于此值的直线被忽略&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;maxLineGap&lt;/code&gt;&lt;/td&gt;&lt;td&gt;同一直线上点的最大间隔，超过则视为两条线&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;img_canny &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;Canny&lt;/span&gt;&lt;span&gt;(img, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;250&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;HoughLinesP&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img_canny, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, np.pi &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;180&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;minLineLength&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;maxLineGap&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;lines1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; lines[:, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, :]  &lt;/span&gt;&lt;span&gt;# 降维处理&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; x1, y1, x2, y2 &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; lines1:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cv2.&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;(img, (x1, y1), (x2, y2), (&lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4. 轮廓检测&lt;a href=&quot;#4-轮廓检测&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;findContours&lt;/code&gt; 用于检测最外层矩形框，并获取其偏转角度，供后续旋转矫正使用。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;cv2.findContours(img, mode, method)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;可选值&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cv2.RETR_EXTERNAL&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只检测最外层轮廓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cv2.RETR_LIST&lt;/code&gt;&lt;/td&gt;&lt;td&gt;检测所有轮廓，不建立层级关系&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cv2.RETR_CCOMP&lt;/code&gt;&lt;/td&gt;&lt;td&gt;建立两层轮廓（外边界 + 内孔）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cv2.RETR_TREE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;建立完整层级树&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;method&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cv2.CHAIN_APPROX_NONE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;存储所有轮廓点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cv2.CHAIN_APPROX_SIMPLE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只保留方向端点，压缩冗余点&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;image, contours, hier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;findContours&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img, cv2.&lt;/span&gt;&lt;span&gt;RETR_EXTERNAL&lt;/span&gt;&lt;span&gt;, cv2.&lt;/span&gt;&lt;span&gt;CHAIN_APPROX_SIMPLE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; contours:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rect  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;minAreaRect&lt;/span&gt;&lt;span&gt;(c)   &lt;/span&gt;&lt;span&gt;# 最小外接矩形&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;box_  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;boxPoints&lt;/span&gt;&lt;span&gt;(rect)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;h &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;(box_[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; box_[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;w &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;(box_[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; box_[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 过滤掉不符合尺寸的轮廓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; w &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2200&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2500&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; w &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1500&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;box   &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;int0&lt;/span&gt;&lt;span&gt;(cv2.&lt;/span&gt;&lt;span&gt;boxPoints&lt;/span&gt;&lt;span&gt;(rect))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;angle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rect[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 角度归一化到 [0, 45] 范围&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;&lt;span&gt;(angle) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;45&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;angle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;90&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;(angle)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;5. 旋转矫正&lt;a href=&quot;#5-旋转矫正&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;将倾斜的图像旋转至水平方向。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;cv2.getRotationMatrix2D(center, angle, scale)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;center&lt;/code&gt;&lt;/td&gt;&lt;td&gt;旋转基点（通常为图像中心）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;angle&lt;/code&gt;&lt;/td&gt;&lt;td&gt;旋转角度&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;scale&lt;/code&gt;&lt;/td&gt;&lt;td&gt;缩放因子，1 表示不缩放&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;cv2.warpAffine(img, M, dsize)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;M&lt;/code&gt;&lt;/td&gt;&lt;td&gt;由 &lt;code&gt;getRotationMatrix2D&lt;/code&gt; 得到的变换矩阵&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;dsize&lt;/code&gt;&lt;/td&gt;&lt;td&gt;输出图像尺寸 &lt;code&gt;(width, height)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;(h, w) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img.shape[:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;center &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (w &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;, h &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;M &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;getRotationMatrix2D&lt;/span&gt;&lt;span&gt;(center, angle, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;rotated &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;warpAffine&lt;/span&gt;&lt;span&gt;(img, M, (w, h))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;矫正效果：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;旋转矫正结果&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;844&quot; src=&quot;/_astro/04-rotate.CSRg3MPN_XJG2e.webp&quot; srcset=&quot;/_astro/04-rotate.CSRg3MPN_XJG2e.webp 602w&quot; /&gt;&lt;figcaption&gt;旋转矫正结果&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;6. 图像裁剪&lt;a href=&quot;#6-图像裁剪&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;沿外层矩形框裁剪：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;沿边框裁剪&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;798&quot; src=&quot;/_astro/05-cut1.J9Rru57G_2cdkLf.webp&quot; srcset=&quot;/_astro/05-cut1.J9Rru57G_2cdkLf.webp 533w&quot; /&gt;&lt;figcaption&gt;沿边框裁剪&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 利用轮廓坐标裁剪&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;x1, y1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; box[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;x2, y2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; box[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;img_cut &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img[y1 &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;:y2 &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;, x1 &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;:x2 &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;按格子裁剪并批量保存：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 数组切片裁剪（需用 cv2.imread 加载图像）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;img[y1:y2, x1:x2]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;裁剪效果：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;按格子裁剪结果&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;680&quot; src=&quot;/_astro/06-cut2.Ci9Wz0iY_18HkBp.webp&quot; srcset=&quot;/_astro/06-cut2.Ci9Wz0iY_PopkW.webp 640w, /_astro/06-cut2.Ci9Wz0iY_18HkBp.webp 724w&quot; /&gt;&lt;figcaption&gt;按格子裁剪结果&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;7. 消除多余红色框线&lt;a href=&quot;#7-消除多余红色框线&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;使用 PIL 逐像素替换，将红色像素替换为白色。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：此操作须使用 &lt;code&gt;Image.open()&lt;/code&gt; 加载图像，而非 &lt;code&gt;cv2.imread()&lt;/code&gt;。&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PIL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;img2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Image.&lt;/span&gt;&lt;span&gt;open&lt;/span&gt;&lt;span&gt;(path)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;img2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img2.&lt;/span&gt;&lt;span&gt;convert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;RGBA&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;pixdata &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img2.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; y &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(img2.size[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(img2.size[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; pixdata[x, y][&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;220&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;# 判断为红色像素&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pixdata[x, y] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;span&gt;# 替换为白色&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;img2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img2.&lt;/span&gt;&lt;span&gt;convert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;RGB&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;img2.&lt;/span&gt;&lt;span&gt;save&lt;/span&gt;&lt;span&gt;(path)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;处理效果：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;消除多余框线结果&quot; loading=&quot;lazy&quot; width=&quot;792&quot; height=&quot;784&quot; src=&quot;/_astro/07-replace.B1wXHV86_1uQIYQ.webp&quot; srcset=&quot;/_astro/07-replace.B1wXHV86_2wPP7i.webp 640w, /_astro/07-replace.B1wXHV86_Z2oa9wc.webp 750w, /_astro/07-replace.B1wXHV86_1uQIYQ.webp 792w&quot; /&gt;&lt;figcaption&gt;消除多余框线结果&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四、完整代码&lt;a href=&quot;#四完整代码&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;1. main.py&lt;a href=&quot;#1-mainpy&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; cv2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; numpy &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; os&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; replace&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;separate_color_red&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;提取图像中的红色区域（HSV 颜色提取）&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;hsv &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;cvtColor&lt;/span&gt;&lt;span&gt;(img, cv2.&lt;/span&gt;&lt;span&gt;COLOR_BGR2HSV&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lower_hsv &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;43&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;46&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;high_hsv  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;inRange&lt;/span&gt;&lt;span&gt;(hsv, &lt;/span&gt;&lt;span&gt;lowerb&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;lower_hsv, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;upperb&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;high_hsv)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;颜色提取完成&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; mask&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;salt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;椒盐噪声滤波&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; k &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(n):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(np.random.&lt;/span&gt;&lt;span&gt;random&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; img.shape[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;j &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(np.random.&lt;/span&gt;&lt;span&gt;random&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; img.shape[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; img.ndim &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img[j, i] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt;&lt;span&gt; img.ndim &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img[j, i, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img[j, i, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img[j, i, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; img&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cut&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;box&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;save_path&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;按格子裁剪并保存图像&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x1, y1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; box[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x2, y2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; box[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img_cut &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img[y1 &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;:y2 &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;, x1 &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;:x2 &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;row &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 行数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;col &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# 列数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;h, w &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img_cut.shape[:&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell_h &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; row&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell_w &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; w &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; col&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;os.&lt;/span&gt;&lt;span&gt;makedirs&lt;/span&gt;&lt;span&gt;(save_path, &lt;/span&gt;&lt;span&gt;exist_ok&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(row):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(col):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img_cut[r &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; cell_h:(r &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; cell_h,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                           &lt;/span&gt;&lt;/span&gt;&lt;span&gt;c &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; cell_w:(c &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; cell_w]&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;filename &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; os.path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(save_path, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.png&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cv2.&lt;/span&gt;&lt;span&gt;imwrite&lt;/span&gt;&lt;span&gt;(filename, cell)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;replace.&lt;/span&gt;&lt;span&gt;replace_red&lt;/span&gt;&lt;span&gt;(filename)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;img_path&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;save_path&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;imread&lt;/span&gt;&lt;span&gt;(img_path)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 1. 颜色提取&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;separate_color_red&lt;/span&gt;&lt;span&gt;(img)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 2. 中值滤波&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mediu &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;medianBlur&lt;/span&gt;&lt;span&gt;(mask, &lt;/span&gt;&lt;span&gt;19&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 3. 霍夫直线检测&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img_canny &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;Canny&lt;/span&gt;&lt;span&gt;(mediu, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;250&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;61&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;HoughLinesP&lt;/span&gt;&lt;span&gt;(img_canny, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, np.pi &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;180&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;62&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                            &lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;minLineLength&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;maxLineGap&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;63&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; lines &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;64&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;[警告] 未检测到直线：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;img_path&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;65&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;66&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lines1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; lines[:, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, :]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;67&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; x1, y1, x2, y2 &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; lines1:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;68&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cv2.&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;(mediu, (x1, y1), (x2, y2), (&lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;69&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 4. 轮廓检测&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;70&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_, contours, _ &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;findContours&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;71&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mediu, cv2.&lt;/span&gt;&lt;span&gt;RETR_EXTERNAL&lt;/span&gt;&lt;span&gt;, cv2.&lt;/span&gt;&lt;span&gt;CHAIN_APPROX_SIMPLE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;72&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; contours:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;73&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rect  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;minAreaRect&lt;/span&gt;&lt;span&gt;(c)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;74&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;box_  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;boxPoints&lt;/span&gt;&lt;span&gt;(rect)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;75&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;h &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;(box_[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; box_[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;76&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;w &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;(box_[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; box_[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;77&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; w &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2200&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;78&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;79&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2500&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; w &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1500&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;80&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;81&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;box   &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.&lt;/span&gt;&lt;span&gt;int0&lt;/span&gt;&lt;span&gt;(cv2.&lt;/span&gt;&lt;span&gt;boxPoints&lt;/span&gt;&lt;span&gt;(rect))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;82&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;angle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rect[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;83&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;&lt;span&gt;(angle) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;45&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;84&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;angle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;90&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;(angle)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;85&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# 5. 旋转矫正&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;86&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(ih, iw) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img.shape[:&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;87&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;center &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (iw &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;, ih &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;88&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;M &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;getRotationMatrix2D&lt;/span&gt;&lt;span&gt;(center, angle, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;89&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rotated &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv2.&lt;/span&gt;&lt;span&gt;warpAffine&lt;/span&gt;&lt;span&gt;(img, M, (iw, ih))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;90&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# 6. 裁剪并保存&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;91&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cut&lt;/span&gt;&lt;span&gt;(rotated, box, label, save_path)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;92&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;93&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;94&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;95&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; sys&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;96&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img_path  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sys.argv[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;97&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;label     &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sys.argv[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;98&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;save_path &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sys.argv[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;99&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;(img_path, label, save_path)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. replace.py&lt;a href=&quot;#2-replacepy&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PIL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;replace_red&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;将图像中的红色像素替换为白色&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Image.&lt;/span&gt;&lt;span&gt;open&lt;/span&gt;&lt;span&gt;(path).&lt;/span&gt;&lt;span&gt;convert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;RGBA&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pixdata &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; img.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; y &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(img.size[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(img.size[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;r, g, b, a &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pixdata[x, y]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;220&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt;&lt;span&gt; g &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pixdata[x, y] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;img.&lt;/span&gt;&lt;span&gt;convert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;RGB&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;save&lt;/span&gt;&lt;span&gt;(path)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;五、小结&lt;a href=&quot;#五小结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;通过七步流水线——HSV 颜色提取、中值滤波、霍夫直线检测、轮廓检测、旋转矫正、图像裁剪、红色框线消除——将志愿者手写扫描表格图像自动转化为标准化单字母图像（64×64 灰度图），构成了 TibetanLetter 数据集的基础。这批图像可直接用于卷积神经网络训练，详见系列三：CNN 训练与 Web 部署。&lt;/p&gt;&lt;/section&gt;</content:encoded></item></channel></rss>