早上好,我是まさきん。
我家有好几个孩子,各自上的练习题和教材都不一样。每次都要手动把一堆讲义分类整理,其实是个不小的负担。
于是我做了一个机制:一次性扫描完,剩下的分类工作交给AI处理。这次把实际用到的代码和规则,都具体分享一下。
起因是「光分类就耗掉大把时间」
做完的练习题和讲义,种类跟孩子数量一样多,科目和年级也各不相同。堆在一起放的话,最后根本分不清哪份是谁的。
一张一张翻出来确认,虽然是单纯的体力活,但很耗时间。所以我在想,能不能只把「分类」这一步自动化。
机制的整体结构
流程很简单,分成以下3个阶段。
- 把做完的讲义一起扫描,做成PDF
- 把PDF逐页转换成图片,让AI根据页眉和排版特征判断「是谁的、哪个科目」的讲义
- 根据判断结果,反映到按孩子、按科目区分的工具里
关键在于,扫描的时候完全不需要考虑分类,先整批读取进来,判断交给AI事后处理。
把PDF转换成逐页的图片
扫描出来的PDF,直接让AI读取有时识别不准。尤其是带手写批改结果的扫描PDF,很多时候只是一堆没有文字层的纯图片。
所以我先用PyMuPDF,也就是fitz,把每一页转换成PNG图片。
import fitz
doc = fitz.open("scan.pdf")
for i in range(doc.page_count):
pix = doc[i].get_pixmap(matrix=fitz.Matrix(2, 2)) # 放大到2倍分辨率
pix.save(f"page_{i+1}.png")
用Matrix(2, 2)把分辨率提高到2倍之后再转成图片,AI识别文字和勾选栏的精度会提升。手写的批改结果尤其如此,分辨率低的话漏看的情况会增多。
容易踩的坑:坐标单位
只想放大确认勾选栏时,可以给get_pixmap传入clip参数指定范围。这里有一个陷阱。clip的坐标要用PDF的点坐标,也就是原始页面尺寸来指定,而不是放大后的像素坐标。
# 即使放大到2倍、4倍,clip的数值也要按页面的实际尺寸(A4大约590×836pt)来指定
pix = doc[i].get_pixmap(matrix=fitz.Matrix(4, 4), clip=fitz.Rect(0, 140, 589, 600))
如果因为加了倍率就把clip的数值也放大,范围会跑到图片外面,结果什么都截不到。这里如果搞混,就会遇到「裁切出来的图片为什么是一片空白」这种问题,提前知道能省不少时间。
判断逻辑
把发给AI的指示通用化后介绍一下。
以下是扫描后的学习讲义图片。
请根据以下线索,判断这是哪个孩子、哪个科目的讲义。
- 页面上部的Logo或标题设计
- 格线和排版形式,例如方格大小、行间距等
- 记载的单元名称或题型
- 姓名栏中填写的名字
判断结果请用「孩子ID」「科目」「单元」这3项返回。
无法判断时请回答「不明」,不要勉强下结论。
实际分类时,我会先把标题文字、排版特征、姓名栏这3项组合起来做一份「识别对照表」,让AI对照这份表来判断。比如「汉字抄写练习,右上角印有年级名称」和「计算练习题,每题左下角印有表示题型的小符号」,光看外观就几乎能唯一区分。做成表格之后,AI的判断就不容易出现偏差。
用机制来减少分类的负担,这个思路或许不只能用在讲义上。放着不管的话,容易被忽略的手机资费方案审视,感觉也是类似的事情。
批改规则统一成「只看勾选」
处理多个孩子的教材时,还有一件事我特别坚持。就是不论孩子是谁,批改标准都统一。
具体来说,只根据每道题左侧或左上方的勾选栏是否被涂黑,来判断对错。
- 勾选栏被涂黑 → 错误,需要复习
- 勾选栏保持空白 → 正确
- 答案附近的红色圆圈是「确认正确的标记」,不是错误的标记
- 用红笔改写过的答案是家长批改留下的,不是孩子自己的答案。不能根据有没有红字来判断对错
字写得工整与否、用橡皮擦改过的痕迹,都不纳入判断。理由很简单。有没有勾选可以机械化地读取,但如果把手写的工整度也混进评价标准,判断基准马上就会变得不稳定。
所以我最初就把「对错的判断只依据勾选栏是否涂黑,字写得好不好、有没有红字订正都不作为判断材料」这条规则,明确写进给AI的指示里。有没有这一句话,判断出现偏差的程度差别相当大。
防止漏看勾选栏的放大步骤
如果把整张扫描图缩小着看,勾选栏的涂黑部分有时候会糊成一团看不清楚。为了避免「看起来都写完了所以全对」这种草率的判断,我一定会执行以下步骤。
- 先看整页,把握大致的排版
- 只把勾选栏那一列裁切出来放大,用到前面提过的
clip - 在放大后的图片上,逐题确认是否涂黑
- 判断不了的勾选栏,或者本来就看不清文字的地方,不猜测,交给人来确认
我实际感受到,如果只用缩略图大小的缩小图片来判断,很容易漏看涂黑的部分。
按科目改变「记录错误的单位」
还有一个我特别下功夫的地方。就是按科目改变记录错误的单位。
| 科目 | 记录单位 | 具体例子 |
|---|---|---|
| 汉字、平假名练习 | 单字为单位 | 把「校」「ぬ」这类文字本身作为关键字 |
| 数学 | 题型模式为单位 | 把「单位换算」「带余数的除法」「数位」这类题型作为关键字 |
| 理科、社会 | 主题加小节为单位 | 用「主题名:小节:编号」这种分层的关键字 |
同样是「做错的题」,按科目不同,该回顾的粒度也不一样。如果把汉字按单元汇总,下一次复习的精准度就会下降。反过来,如果把数学的「题型模式」拆到每一题的粒度,复习对象就很难聚焦。
数学方面,我会在每题角落印上表示题型的小符号,读取批改结果时就按这个符号来汇总错题。规则很简单:同一类型的题只要有一题做错,整个类型就标记为「需要复习」。
理科和社会,因为讲义末尾的复习专栏有时会出自跟当天主题不同的单元,所以我会先把题目文字和主题清单对照一遍,再确定属于哪个主题的复习内容。不做武断的判断,而是一定先核对再记录。
这个机制,其实是把原本为另一个用途做的「记录错题并重复出题」的机制挪用过来的。用途变了,但底层逻辑原样可以继续用。
为什么做成跨执行环境的结构
这个机制,跟AI之间的交互本身放在云端的沙盒环境里执行,而实际的文件操作和状态管理,则交给放在家里电脑上的常驻进程负责,做成了这样的两段式结构。
理由很简单,保存批改结果的数据库和生成练习题用的脚本,只放在家里的电脑上。从云端访问家里这一侧,要经过一个只负责发送简单指令的轻量客户端脚本。
# 示例:根据判断结果,把做错的关键字反映进去
python3 tools/kanji_drill/kanji_bridge_client.py mark-miss 校庭 音楽
python3 tools/kanji_drill/kanji_bridge_client.py mark-correct 教室
# 反映之前,先确认家里那边的常驻进程是否还活着
python3 tools/kanji_drill/kanji_bridge_client.py health
我一定会在正式反映之前执行这个health命令。如果没注意到家里那边的进程已经挂掉,还继续发送mark-miss,命令表面上会显示成功,但实际上什么都没反映进去,这种事故我想避免。
尝试后的感受
最省心的一点,是不再需要确认「这是谁的、哪个科目」了。整批扫描完之后,只需要看结果就行。
不过,我并不是完全信任AI的判断。判断为「不明」的部分,以及勾选栏判断犹豫不决的部分,我一定会自己再确认一遍。与勉强让AI分类相比,让它老实说出「不知道」,结果反而是一个更值得信赖的机制。
适合这样的人
- 好几个孩子的讲义和练习题混在一起,分类要花不少时间的人
- 想把批改标准从「看字迹工整度」统一成机械化规则的人
- 对写PyMuPDF、搭一个简单的桥接脚本这类实现不抵触的人
总结
多个孩子的教材,数量越多,分类的负担就越大。把扫描和AI分类组合起来,就能把这部分单独切出来自动化。
坐标单位、批改标准的明文化,这些不起眼的细节,其实才是保证精度稳定的关键。考虑机制化的时候,「先定好判断基准本身」这项工作,或许意外地重要。
机制化能减少的负担,或许不只是讲义分类。接下来我打算用同样的方式,重新看看手机资费的方案。
本文包含联盟营销广告。如果您通过本站链接申请相关商品或服务,我们可能会从合作企业获得报酬。此外,运营者是乐天集团的员工,也可能通过员工推荐计划获得报酬。文章内容与评价均基于运营者的真实体验与调查完成,与是否存在广告无关,但请您在阅读前了解上述关系。详情请参阅免责声明与联盟营销说明。 免责声明与联盟营销说明
本文包含机器翻译内容。正式条件请以乐天移动官方网站的多语言页面为准。
如果您对乐天移动的通信区域·信号状况有疑问,可以通过官方的 电波改善调查申请表 进行咨询。