AI辅导系统如何实现视觉接地?拍照讲题Demo全解析
在 AI 辅导系统的开发中,有一个很常见的需求:学生上传一道几何题图片,AI 能给出答案,却说不清图上哪一条边对应哪一步。这个场景背后,正是 Visual Grounding(视觉接地)要解决的问题。本文会从概念讲起,结合完整可运行的“拍照讲题”Demo,拆解如何把一个带视觉接地能力的 AI 辅导原型落地。无论你是做教育产品的后端开发,还是刚接触多模态大模型的学生,都能按这套流程把项目跑起来。
1. 背景与核心概念:什么是 AI Tutoring 与 Visual Grounding
1.1 从“能做题”到“能讲题”的 AI 辅导系统
传统意义上的 AI 辅导,通常指知识库问答、题目搜索、答案解析这类能力。学生输入一道文字题,AI 返回解题步骤和答案。这类系统对纯文字题目已经比较成熟,但一旦进入数学几何、物理受力分析、化学实验图谱、生物结构图等场景,问题就来了:AI 知道答案是“两条边相等”,但它没有告诉学生,图上的哪两条边是相等的。
用户需要的不是一句冷冰冰的答案,而是像老师那样“指着图讲”。AI 要能圈出底边、标出高、框住关键条件,再把讲解和图像位置对应起来。这种能力被称为“视觉接地”,英文叫 Visual Grounding,是 AI 辅导从“能做题”升级到“能讲题”的关键一步。
如果拆开看,一个带视觉接地能力的 AI 辅导系统至少包含三层能力:
- 看懂图像内容:包括图形结构、文字区域、手写公式、图表坐标等。
- 理解自然语言提问:例如“哪条边是底边”“哪一步化简错了”“这个实验现象发生在哪个位置”。
- 将语言描述映射到图像中的具体区域:最终输出一个或多个坐标框,让前端可以高亮标注。
这三层能力分别涉及视觉理解、语义理解、跨模态对齐。近几年多模态大模型和开放词汇目标检测模型发展很快,上述能力已经可以从原型走向工程化落地。
1.2 Visual Grounding 是什么
Visual Grounding,中文常翻译为“视觉接地”或“视觉定位”。它的核心任务是:给定一张图片和一句自然语言描述,模型需要找出图片中与该描述对应的区域,通常输出一个矩形框 bbox 或分割掩码 mask。
举例来说:
- 输入图片:一张三角形几何图。
- 输入文本:“三角形底边”。
- 期望输出:底边附近的坐标框
[x1, y1, x2, y2]。
这个任务在学术上也被称为 Referring Expression Comprehension,即“指代表达理解”。它和我们常说的目标检测不太一样。传统目标检测是“类别限定”,模型只会查找训练集中出现过的物体类别,例如 person、car、cat;而 Visual Grounding 的文本描述是开放式的,可以是任意自然语言,例如“图片左下角的红色三角形”“从 A 点到 B 点的辅助线”“第二行第三个选项”,模型需要把语言和视觉区域进行对齐。
它也不是 OCR。OCR 只负责从图像里识别文字内容,而 Visual Grounding 可以定位“不是文字”的区域,比如一条边、一个角、一个实验容器的位置。
简单理解就是:OCR 告诉 AI“图上写了什么字”,Visual Grounding 告诉 AI“学生问的东西在图上的哪个位置”。
1.3 为什么辅导场景必须引入视觉接地
在 AI 辅导场景中,引入视觉接地主要有四个价值:
第一,提升讲解可理解性。人类教师讲题时,天然会配合“指着图说话”的动作。AI 如果只会输出文字,学生很难把抽象描述和具体图像对应起来;一旦 AI 给出位置框,讲解就能从“虚空描述”变成“视觉证据”。
第二,提高解题推理质量。很多几何、物理题目,推理过程依赖图形中的位置关系。AI 若能先定位“关键区域”,再针对该区域做推理,逻辑会更加严谨,也更容易发现学生的错误点。
第三,支撑自动批改和错因分析。比如学生手写作业拍照上传,AI 需要定位到“某一道题”“某个步骤”“某个答案”才能判断对错,而不是把整张图片当成一个整体。
第四,为 AI Agent 操作界面打基础。如果 AI 要进一步实现“点击演示”“框选重点”“在图上画辅助线”,就必须具备视觉接地能力,让模型知道往哪里点、往哪里画。
所以,Visual Grounding 不是炫技,而是 AI 教育产品走向实用化的基础设施。
2. AI 辅导 + 视觉接地的整体架构
2.1 一条典型的“拍照-定位-讲解”链路
一个最简单的 AI 拍照讲题系统,可以按照下面的数据流来设计:
上传图片 + 题目文本 ↓ 视觉接地模型 → 得到候选区域 bbox ↓ 多模态大模型 → 基于 bbox 和图像生成讲解 ↓ 前端可视化 → 在原图标注框 + 步骤文字第一层是输入层,负责接收用户上传的图片、题目文本以及可选的历史对话信息。第二层是定位层,由视觉接地模型完成关键词或问题句到图像坐标的映射。第三层是讲解层,一般由一个多模态大模型或教育领域模型完成,它会把图像内容、定位结果、用户问题一起组织成自然语言讲解。最后一层是输出层,返回给前端的不仅是文字,还包括可视化标注数据,例如框的坐标、标签、置信度。
这套链路看起来简单,但每个环节都有不少细节。实际项目中,图片可能模糊、倾斜、反光,题目可能是手写体,用户提问也可能包含口语化表达。因此需要把视觉接地模型和讲解模型分开设计,或者使用一个统一的多模态大模型同时完成定位和讲解。
2.2 两种主流实现路线
目前主流实现路线有两种:两阶段路线和统一模型路线。
两阶段路线是“视觉接地模型 + 多模态大模型”。先用 Grounding DINO、SAM、Florence-2 这类模型定位目标区域,再把定位结果和图片一起交给 Qwen-VL、GPT-4V 等模型生成讲解。这种路线的好处是定位环节独立可控,方便评估检测效果;缺点是链路较长,中间坐标如果处理不好,讲解模型拿到的上下文会不够准确。
统一模型路线是直接用一个大模型完成视觉、定位和文本生成。比如 Florence-2 把目标检测、区域描述、视觉问答等任务统一成序列生成任务;Qwen2-VL、GPT-4V 这类多模态大模型也支持在对话中输出坐标框。这种路线开发效率高,省去多模型拼接的复杂度;缺点是模型更重,部分场景下坐标精度可能不如专业检测模型。
选型时主要看三点:硬件条件、延迟要求、精度要求。如果只需要快速出 Demo,优先选统一模型;如果要上线到高并发生产环境,且对坐标精度要求很高,建议走两阶段路线,把“定位”和“讲解”解耦,分别优化。
2.3 模块与数据流设计
从工程角度看,整个系统可以拆成四个模块:
输入模块负责图片上传、格式校验、压缩、方向校正。定位模块负责执行视觉接地任务,绑定具体模型。讲解模块负责把定位结果转成教学语言,可以选择本地模型或云端 API。输出模块负责封装统一 JSON 结构、保存标注图片、返回给前端。
为了降低前后端耦合,建议统一约定一个返回结构:
{ "explanation": "讲解文本", "marked_image": "/static/uploads/marked_result.png", "boxes": [ { "label": "底边", "bbox": [0.10, 0.80, 0.90, 0.95], "score": 0.97 } ] }这里的 bbox 建议统一使用 0 到 1 的归一化坐标,前端拿到原图宽高后再换算成像素坐标,避免图片缩放导致标注偏移。
3. 环境准备与依赖说明
3.1 运行环境与硬件要求
本文的示例代码以 Python 为主,需要 Python 3.9 及以上版本。你可以在 Ubuntu 22.04、Windows WSL2 或 macOS 上运行,但建议优先使用 Linux 环境,因为很多视觉模型依赖的 CUDA 生态在 Linux 下最稳定。
如果只是跑通 Mock 模式熟悉流程,CPU 环境就足够。如果要加载 Florence-2、Grounding DINO、SAM 这类真实模型,建议准备一块至少 16GB 显存的 GPU,例如 RTX 4090、A10、A100 等。模型量化后可以降低显存需求,但不建议新手一开始就做量化,先把链路跑通更重要。
3.2 依赖安装
先创建项目目录和虚拟环境:
mkdir ai-tutor-grounding cd ai-tutor-grounding python -m venv venv source venv/bin/activate # Windows 使用 venv\Scripts\activate在项目目录下新建requirements.txt:
torch transformers accelerate opencv-python pillow flask requests然后在终端里执行安装:
pip install -r requirements.txt这里没有锁具体版本,因为视觉模型相关的库更新非常快,版本差异可能导致 API 不兼容。建议你在自己的环境里安装当前稳定版本,如果遇到 API 变动,以官方文档为准。
3.3 项目结构
本文示例项目结构如下:
ai-tutor-grounding/ ├── app.py # Flask 服务 ├── tutor.py # 核心逻辑:接地引擎、讲解、画框 ├── requirements.txt ├── static/ │ └── uploads/ # 上传图片和标注图片存放目录 └── templates/ └── index.html # 前端演示页面这样划分后,tutor.py可以独立测试,app.py只负责 HTTP 接口,后续替换模型时不需要大改服务层。
4. 核心原理拆解:视觉接地模型怎么工作
4.1 目标检测、引用表达理解与视觉接地的边界
理解 Visual Grounding 之前,需要先分清几个概念:
目标检测(Object Detection)解决的是“图里有什么”,输出类别和位置,类别通常来自固定集合。引用表达理解(Referring Expression Comprehension)解决的是“用户说的东西在图哪里”,输入是一句自然语言,输出位置。Visual Grounding 在实际项目中经常等同于后者,但它的范围更广,可以包含指代表达、视觉定位、文本到区域匹配等子任务。
在 AI 辅导场景中,我们真正需要的是“开放式的指代表达理解”。因为学生可能会说“右边那个角的辅助线画错了”“第二问的化简结果”“图上标了红点的位置”,这些表达无法靠固定类别覆盖。
视觉接地模型通常包含三个模块:图像编码器提取视觉特征,文本编码器提取语言特征,跨模态融合模块在图像和文本之间做对齐,最终通过检测头或分割头输出坐标框或 mask。
4.2 主流模型能力对比
下面列几个常见的模型方向,帮助大家快速认识选型空间:
| 模型 / 系列 | 类型 | 输入 | 输出 | 适用场景 |
|---|---|---|---|---|
| Grounding DINO | 开放词汇目标检测 | 图像 + 文本描述 | bbox + 类别 | 区域定位,常用两阶段路线 |
| SAM | 分割模型 | 图像 + 点/框/文本 prompt | mask | 精确分割目标区域 |
| Florence-2 | 统一多模态模型 | 图像 + 任务指令 | 文本 / bbox / mask | 多任务统一,适合快速 Demo |
| Qwen2-VL | 多模态 LLM | 图像 + 对话文本 | 文本 / bbox | 对话式讲解 + 定位 |
| GPT-4V | 闭源多模态 LLM | 图像 + 文本 | 文本 / 位置引用 | 效果强,但要注意数据合规 |
需要注意的是,不同模型对“视觉接地”的表达形式差异很大。有的直接输出像素坐标,有的输出归一化坐标,有的输出带有自然语言的描述。工程落地时,建议在“模型接入层”做统一封装,无论底层用哪个模型,对外都返回标准 JSON。
4.3 坐标体系与归一化
视觉接地模型最常见的输出格式是矩形框坐标。不同模型的坐标体系并不一致:
- 坐标范围可能是 0~1 的浮点数。
- 坐标范围可能是 0~1000 的整数。
- 坐标可能是相对原图的像素值。
- 坐标可能经过了模型内部的输入分辨率缩放。
如果前端展示时没有做坐标换算,就会看到“框偏移”“框太小”“框位置错误”等问题。这里提供一个通用的归一化坐标转换函数:
def normalize_bbox(box, img_w, img_h, scale=1000): """将模型输出的坐标统一为 0~1 归一化坐标。 实际使用时,scale 要根据模型输出范围调整。 """ if scale and all(c <= scale for c in box): x_min, y_min, x_max, y_max = [c / scale for c in box] else: x_min, y_min, x_max, y_max = box x_min = max(0, min(1, x_min)) y_min = max(0, min(1, y_min)) x_max = max(0, min(1, x_max)) y_max = max(0, min(1, y_max)) return [x_min, y_min, x_max, y_max]这段代码只是演示思路。实际模型接入时,一定要先打印模型原始输出,明确坐标含义后再做转换,否则很容易在坐标环节踩坑。
5. 完整实战:实现一个“拍照讲题”Demo
5.1 场景定义与功能拆解
我们要实现一个最小可运行的 AI 拍照讲题 Demo。用户上传一张图片,输入一个问题,例如“哪条边是底边?”,系统返回:
- 一段讲解文本。
- 一张画好红框的图片。
- 定位框的标准化数据。
为了确保没有任何 GPU 的读者也能跑通完整流程,我会先实现一个 Mock 接地引擎,用规则返回模拟定位结果。Mock 模式跑通后,再给出替换成真实模型的接入思路。
功能拆解如下:
- 图片上传接口:接收图片和问题文本。
- 视觉接地引擎:根据问题返回目标框。
- 讲解生成:根据问题、定位框生成讲解文字。
- 图片标注:在原始图片上绘制红色矩形框。
- 前端页面:上传图片、显示讲解、显示标注结果。
5.2 先跑通 Mock 模式
推荐大家先用 mock 模式把整条链路跑通。这样做的好处是:可以先验证前后端交互、图片处理、坐标归一化、标注绘制这些工程逻辑是否正确,再接入真实模型时,只需要替换接地引擎内部实现。
启动服务时通过环境变量控制:
export USE_MOCK=true python app.py如果设置了USE_MOCK=false,则会尝试加载真实模型。
5.3 核心逻辑 tutor.py
创建tutor.py,代码如下:
# 文件路径:tutor.py import os from PIL import Image, ImageDraw USE_MOCK = os.getenv("USE_MOCK", "true").lower() in ("1", "true", "yes") class GroundingEngine: """视觉接地引擎。 USE_MOCK=true 时返回模拟定位结果,用于先跑通完整链路; USE_MOCK=false 时加载 Florence-2 等真实模型,替换下方真实模型调用即可。 """ def __init__(self, use_mock: bool = True): self.use_mock = use_mock self.model = None self.processor = None if not self.use_mock: from transformers import AutoProcessor, AutoModelForCausalLM self.processor = AutoProcessor