拍照识别试卷:我在一台 2016 年的 NAS 上做「错题本」,踩了 8 个坑

错题本 App 满地都是,但几乎没人管"图形题"。这篇文章不过多的说做了一个什么错题本产品,只聊聊把一张试卷/错题照片变成结构化数据这件事里,里面有多少必须自己踩一遍才知道的坑

所有结论都附实测数据,代码开源在文末。

起因

孩子上刚上初一,学习的强度一下子就上来了只是按照小学那样弄个喵喵机吧错题打印收集起来已经不能适应后续的分类复习的要求了。主要是数轴、全等三角形证明这类题,文字是次要的,图形才是主角------把题干识别成文字、图形丢掉,等于没识别。

现成的错题本 App 我试过几类:拍照搜题的(题库里没有就不认)、手写笔记型的(要你自己抄)、小程序类的(数据在别人服务器上)。都不合适。

我的要求很简单:数据在我自己家里,图形必须留下来,打印出来能当卷子做。 于是有了自己做的这么个错题本东西:

  • 业务流程:
  • 拍照 → 在照片上框出题目 → AI 识别(印刷体 + LaTeX 公式 + 答案 + 解析)→ 人工校对 → 入库 → 按知识点组卷 → 打印成 PDF
  • 运行环境:
  • 跑在家里一台 QNAP TS-228(2016 年的入门机型,1GB 内存、ARM 32 位)上,用它的内置 Web 服务器 + PHP 5.6 + SQLite
  • 使用环境:
  • 手机、平板、电脑访问同一份数据;孩子只能录入进"待校对",大人确认后才入库

看起来挺土,但它能跑,而且零月租。

下面是这段时间,真正花时间的 8 个坑。


坑 1|提示词里写"看不清就用 □ 占位",会让模型把整道题吞掉

现象 :数轴题的识别结果里,m ≤ 0 变成了 □□□,选项 A 和 C 整个没了,答案写"题目条件不完整",置信度 20%。

我一开始的判断:模型太笨 / 图像太糊 / 数轴太难。

实际原因:是提示词里面的要求带来的连锁反应。

原文第 5 条是"确实看不清的字用 □ 占位"------本意是"诚实标注不确定",实际效果是给模型开了个后门:既然可以占位,那没把握的部分就整块占位,一步到位。

改法

尽力辨认每一个数字、字母和数学符号(≤ ≥ ± √ π ∠ △ ∽ ≌ 等)。只有确实无法辨认的单个字符 才写成 [?]严禁 用 □ 之类的占位符代替整个式子、条件或选项;严禁省略内容。宁可写错也不要丢掉。

同一个模型、同一张图,改完之后 5/5 全对,置信度 0.95

可复制的结论 :给模型"我不知道"的出口时,一定要限制出口的粒度。"用 ? 标注单个字符"和"用 □ 占位"之间的差别,就是"可用"和"报废"的差别。


坑 2|裁剪块的长边要控制在 900~2200 像素,而且从缩略图裁比从原图裁更准

现在的手机拍照像素越来越高,一张照片动辄 4000×3000像素尺寸。我一开始的做法是:框选 → 从原始高清图按坐标裁 → 送模型。直觉上"信息越多越准"。

实测结果反直觉

裁剪来源 结果
原始高清图(3024×4032) 更容易只读最上面一行,或把相邻两题的内容混在一起
长边 2200px 的派生图 稳定、内容完整
长边 1280 / 960px 单题也准(960px 的裁剪块 845×269,置信度 0.88)
长边 < 900px 开始糊,出现 [?]

做法 :先把照片缩到长边 2200 以内,再从这张图裁;裁出来的小块如果长边小于 900,再放大回去。长边 900~2200 是实测最稳的区间。

代价是"放弃了一部分分辨率",换来的是稳定。这个问题展现出来的经验是:对识别任务,"模型注意力被多余信息分散"比"像素不够"更致命。


坑 3|把"实际送给模型的那张图"显示在界面上,比调 10 次提示词都有用

识别不准的时候,你有两个完全不同的嫌疑人:框歪了 / 模型笨

只盯着识别结果猜,很容易误判(我就在"是模型不行还是提示词不行"之间来回折腾了两轮)。

做法 :每个框在被送给模型之前,把那张裁剪图存一份,识别完成后连同结果一起返回,显示在结果卡片上,旁边标尺寸和置信度。再加一句提示:"框错了就先改框"。

收益:所有"识别不准"的排查时间从"猜"变成"看一眼"。这个功能上线后,我再没为识别质量问题猜过一次。

这个思路可以推广到任何"图像进、结构化出"的管道:先把输入可视化,再谈模型。


坑 4|6144×8192 的照片 + memory_limit=128M,GD 直接让进程死掉,零输出

现象 :上传一张 1.3MB 的 iPhone 原图(3024×4032,HEIC 转出来的 6144×8192 也有),PHP 进程直接消失:没有响应、没有错误、日志里什么都没有。

定位 :用 CLI 复现,-d memory_limit=128M 处理那张图 → 进程死亡、零输出;-d memory_limit=1024M → 成功(派生图 1650×2200)。4032×3024 解码大约要 48MB 内存,勉强能过;再大就必死。

NAS 上 memory_limit 就是 128M(老机型,改大了会拖垮整机)。

修法(两层)

  1. 浏览器端先缩:上传前用 canvas 把照片缩到长边 ≤2400,原图不进 GD(原图仍然原样保存,只是不参与解码);
  2. 服务端再兜一层像素数保护,超阈值直接明确报错:"照片太大(6048×8064,约 48.8 百万像素)..."

结论 :小内存机器上做图像处理,别想着"调大内存",要把大图挡在门外。而且挡的位置越靠前越好------浏览器是最便宜的算力。


坑 5|题干里混进"(图形:......)",打印出来会重复一遍

现象:识别出来的题干写着"如图,数轴......(图形:一条数轴,上面有 -1、0、1、2、3 五个刻度)",而打印稿里图形是单独排版的------文字描述和真图重叠,看着很蠢。

根因:提示词只说了"图形要用文字描述一下",没说描述放哪儿。

修法:两条分开的指令------

  • 题干里只写印刷文字本身,绝对不要插入"(图形:......)"这类描述;
  • 图形描述放进 note 字段;需要图的时候,让模型返回 figure_box: [x, y, w, h](归一化坐标),服务端按这个框单独裁出图形本身

效果 :数轴题的题图 1074×90,全等三角形题 700×513,都是只有图形、不含任何文字。复习时直接看图,比看一段文字描述强太多。


坑 6|Windows 上的裸 PHP 没有 CA 证书包

现象cURL error 60: SSL certificate problem: self signed certificate in certificate chain

原因 :Windows 版 PHP 不自带 CA 根证书(Linux/Docker 一般有系统的 /etc/ssl/certs)。

修法 :代码里按顺序探测------环境变量 → data/cacert.pemcurl.cainfo/openssl.cafile → 常见系统路径 → Git for Windows 自带的证书包。全都找不到时,报错信息里直接告诉你该怎么办,而不是丢一个 curl 错误码。

顺便说一句:这条坑在 Docker 里根本不存在。能容器化就容器化,别让使用者去修环境。


坑 7|前后端字段形状不一致,静默裁出 20×24 像素的空白,看起来像模型在胡说

现象(这个最阴):界面里框的是整整一道题,结果卡片显示"送模型 750×900 px",模型答:"图片为空白,未检测到任何印刷体题目内容。" 置信度 0.00。

700 字的推理过程:750×900 ≈ 原图 1000×1200 的 3/4 比例,而 900 正好是"最短边下限"......最后用 fetch 拦截抓到真相:

  • 前端发的是 [{"x":0.03,"y":0.18,"w":0.94,"h":0.27}]
  • 后端按 [x,y,w,h] 数值下标读:$rect[0]

而 PHP 的 array_map('floatval', $assocArray) 会保留字符串键 !于是 $rect[0] ... $rect[3] 全是未定义 → 0 → 裁出左上角 20×24 像素(纯白)→ 再被"规范到最短边 900"放大成 750×900 → 模型看到一张放大的空白。

教训

  1. 接口两端对字段形状要宽容(同时接受数组和对象);
  2. "裁出来太小"必须直接报错,别浪费一次模型调用,更别让它伪装成"模型能力问题"。

这个坑的排查线索是那一行 送模型 750×900 px------如果没有坑 3 那个诊断图,我会一直以为模型不行。


坑 8|任何 PHP 告警混进 JSON,前端就永远转圈

现象:识别一切正常,点"保存"永远停在"保存中...",不报错、不超时。

根因(两层):

  1. PHP 5.6 在收到 JSON 请求体时会打印一条 $HTTP_RAW_POST_DATA 已弃用 的提示;如果 display_errors 是开着的,这条提示会插进 JSON 前面 → 前端 JSON.parse 失败;
  2. 前端当时只处理了 "请求成功但内容不是 JSON",遇到"请求根本没回来"的情况没有兜底 → 无限转圈。

修法

  • 后端:输出缓冲 + set_error_handler / set_exception_handler / register_shutdown_function,把所有告警、异常、致命错误统一转成合法 JSON;输出前清空缓冲,丢弃混进来的杂音。
  • 前端:给请求加超时(3 分钟),超时/解析失败都给人话错误,绝不静默。

结论 :接口层要保证"任何情况下响应都是合法 JSON"。这一条比任何业务逻辑都重要------静默失败会浪费你几十倍的排查时间。


成本实测

用 DeepSeek 的多模态接口(temperature=0.2):

场景 tokens 花费
整页一张(3 道题,含思考) 3834(其中思考 1187) ≈ 0.08 元
单框识别(一道题 + 数轴图) 825 ≈ 0.002 元

按框计费时,贵的是系统提示词:每框都要重付一遍约 2300 tokens 的提示词。所以"一次多框合并成一次调用"能省钱,代价是出错时整批重来。

一个月拍 200 张卷子、一共 400 道题,成本大约 十几块钱。比买错题本 App 会员便宜。


一些取舍,和没解决的事

  • 只认印刷体。手写答案会被忽略(提示词明确要求),这是有意的------孩子的字我自己看。
  • 必须人工校对 。识别再准也有 1~2% 的字符错(尤其是 1/l0/O、小数点),数学题一个符号错就全错。所以流程是"AI 打草稿,人签字"。
  • PDF 靠浏览器打印。NAS 上没有无头浏览器,所以导出的是 HTML + 打印样式表,PDF 由你本机浏览器生成。
  • 模型"看图说话"不可靠 。同一个数轴题复跑 3 次结果都对,但它复述图形细节时会编。所以我不让它描述图形,直接让它给坐标、由程序裁图------能算的交给代码,别交给模型。
  • 词表(知识点体系)必须人来维护,模型只能从词表里选,不能让AI大模型自己去创造。

代码

把上面这些坑做完之后,最通用的那部分(拍照 → 框选 → 切题识别 → 自动裁图形)我抽成了一个独立的小工具,单文件后端 + 单页前端 + Docker 一键起,不存储到本地、不上传第三方存储,能接任何 OpenAI 兼容的多模态接口:

ai-paper-cropgithub.com/bxh94-yu/ai-paper-crop

它自带一张合成示例卷,没配 Key 也能进"演示模式"把交互跑一遍;仓库首页有一张完整的演示动图(框选 → 识别 → 自动裁出只含图形的题图)。

完整的错题本(含知识点词表、两级权限、组卷、打印、备份)还在自家 NAS 上跑着,因为里面全是孩子的东西,不打算公开。

如果你也在做类似的事,最想说的三句话

  1. 先把送进模型的那张图显示出来,再谈模型行不行;
  2. 别让模型有"整块说不清"的出口;
  3. 把大图挡在 PHP 之外。

文中所有数据来自真实调试记录。示例卷图片由项目合成,不含任何教材内容。

相关推荐
Zzj_tju1 小时前
VLM 读图评测:先审计评分器,再判断读错还是算错
人工智能·深度学习·机器学习·语言模型
嘟嘟嘟95271 小时前
AI Agent 的边缘困境
人工智能·架构·agent
deepseek231 小时前
Claude Mythos 5越界投毒拆解:穿过CAPTCHA向PyPI投毒,82%重跑有害率背后的偏见推理与监控失效
人工智能·claude·ai agent
杨航 AI1 小时前
本地双4060ti16g加macmini不断优化私有化大模型
人工智能
hey you~2 小时前
语音机器人如何配合人工完成复杂进线服务?三种协同模式与落地要点
人工智能·机器人·语音识别·智能客服·呼叫中心·转人工策略
www.022 小时前
Codex额度用完怎么办?Windows定时自动继续VS Code Codex任务实战
人工智能·windows·vscode·自动化·openai·codex·autohotkey
星禾元亨2 小时前
生成式 AI 时代的架构演进与 GEO 优化:实体企业 AI 落地避坑指南
大数据·人工智能·架构·自动化·创业创新
差不多的周周2 小时前
《HuMoCon:人类运动理解的概念发现》论文核心部分详解
人工智能·深度学习·计算机视觉·自然语言处理
IT·陈寒2 小时前
Python的GIL问题又把我坑惨了
人工智能·大模型·api·创业·变现·简历优化