使用阿里云 API 实现图像文字识别与校园文档智能分类系统

1. 作者介绍

朱翊崑,西安工程大学电子信息学院研究生,研究方向为机器视觉与人工智能。

电子邮件:yikunzhu9@126.com

本项目来源于人工智能高级语言程序设计课程实践,主要围绕"阿里云 API 图像文字识别"展开。项目并不是单纯完成一次 OCR 接口调用,而是进一步结合校园学习场景,将图像文字识别技术应用到实验报告检查、课程通知提取、签到表识别和作业题目整理等实际任务中,从而实现一个面向研究生群体的轻量级校园文档生产力工具。

2. 项目背景与研究意义

随着人工智能技术的发展,越来越多的应用不再需要开发者从零开始训练模型,而是可以通过调用云服务厂商提供的 API 快速实现智能功能。例如图像识别、文字识别、机器翻译、语音识别、智能问答等能力,都可以通过第三方 API 接入到自己的系统中。

在课程项目中,许多同学选择了 API 调用类项目,例如调用阿里云 API 实现图像文字识别、调用腾讯云 API 实现车牌识别、调用百度智能云 API 实现文本纠错、调用智谱 AI API 实现对话功能等。此类项目的共同特点是:核心算法能力由云服务提供,开发者重点完成接口调用、数据传输、结果解析和业务场景落地。

但是,如果项目只停留在"上传图片,然后返回文字"的层面,就容易出现功能单一、场景不清晰、项目同质化严重等问题。因此,本项目在实现阿里云 OCR API 调用的基础上,进一步思考其实际应用价值:如何让 OCR 技术真正服务于校园学习和科研管理场景。

在研究生的日常学习和科研过程中,经常会遇到大量与图片文字相关的任务,例如:

  1. 拍照保存实验报告,需要判断报告结构是否完整;
  2. 课程群中发布通知截图,需要快速提取时间、地点、截止日期等信息;
  3. 纸质签到表需要电子化整理;
  4. 作业题目截图需要转换成可复制、可编辑的文本;
  5. 学生名单、学号等信息需要批量提取。

这些任务如果完全依靠人工处理,不仅效率较低,而且容易出现遗漏和错误。因此,本项目将"图像文字识别"与"校园文档智能处理"相结合,设计并实现了一个基于 Flask 和阿里云 OCR API 的图像文字识别与文档分类系统。

本系统的核心目标是:用户上传一张图片后,系统不仅能够识别图片中的文字,还能够进一步判断图片属于哪一类校园文档,并给出相应的结构化处理结果。


3. 理论知识介绍

3.1 API 技术概述

API 是 Application Programming Interface 的缩写,即应用程序编程接口。简单来说,API 是不同软件系统之间进行数据交换和功能调用的一种约定。

在传统的软件开发中,如果开发者想要实现图像文字识别功能,可能需要自己收集数据集、训练 OCR 模型、部署模型服务,并不断优化识别效果。这对普通课程项目来说难度较高,开发周期也较长。

而通过调用云服务 API,开发者只需要按照服务商提供的接口规范发送请求,就可以直接获得成熟的人工智能能力。例如本项目中使用的阿里云 OCR 服务,开发者只需要将图片数据发送给接口,接口就会返回识别出的文字内容。

API 调用的一般流程如下:

  1. 注册云服务账号;
  2. 开通相应的 API 服务;
  3. 创建 AccessKey;
  4. 在程序中配置 AccessKey ID 和 AccessKey Secret;
  5. 使用官方 SDK 创建客户端;
  6. 构造请求对象;
  7. 调用接口;
  8. 解析返回结果;
  9. 将结果应用到具体业务场景中。

本项目中,API 调用并不是最终目的,而是系统功能实现的基础。真正有价值的部分在于:如何围绕 OCR 返回的文字内容,继续进行文档类型判断和结构化分析。

3.2 OCR 技术概述

OCR 是 Optical Character Recognition 的缩写,中文通常称为光学字符识别。它的主要作用是将图片、扫描件、截图等非结构化图像中的文字转换为可编辑、可复制、可搜索的文本。

OCR 技术通常包括以下几个环节:

  1. 图像输入:用户上传图片文件;
  2. 图像预处理:对图片进行方向校正、去噪、增强等处理;
  3. 文本区域检测:定位图片中文字所在区域;
  4. 字符识别:识别具体的文字内容;
  5. 结果输出:返回纯文本或带有位置信息的结构化结果。

在本项目中,图像识别的核心能力由阿里云 OCR API 完成。系统后端接收用户上传的图片后,将图片二进制数据传给阿里云 OCR 接口,接口返回识别结果。随后,系统根据返回文本进行关键词匹配和正则表达式提取,从而实现文档分类与信息提取。

3.3 Flask Web 框架简介

Flask 是 Python 中常用的轻量级 Web 框架,适合开发小型 Web 应用、接口服务和课程项目。与 Django 等大型框架相比,Flask 更加简洁,学习成本较低,适合快速搭建后端服务。

本项目使用 Flask 完成以下功能:

  1. 提供首页路由 /,用于展示前端页面;
  2. 提供 /api/ocr 接口,用于接收前端上传的图片;
  3. 对上传文件进行格式检查;
  4. 调用阿里云 OCR API;
  5. 对 OCR 结果进行文档分类;
  6. 根据不同文档类型进行结构化处理;
  7. 将结果以 JSON 格式返回给前端。

本项目采用前后端分离的基本思想:前端负责图片上传和结果展示,后端负责 OCR 调用、分类判断和数据处理。

3.4 系统整体技术架构

本项目整体技术架构可以分为前端、后端和第三方云服务三部分。

前端部分使用 HTML、CSS 和 JavaScript 实现,主要负责用户交互,包括图片选择、上传、结果展示和复制导出等功能。用户在网页中选择图片后,前端使用 FormData 封装图片文件,并通过 fetch 方法将图片发送到 Flask 后端接口。

后端部分使用 Python Flask 框架实现。后端接收到图片后,首先判断文件是否存在、文件名是否为空、文件格式是否合法,然后读取图片二进制内容。接着,后端调用阿里云 OCR SDK,将图片数据发送到 OCR 接口进行文字识别。识别完成后,系统对返回文本进行进一步分析,包括文档类型分类和结构化信息提取。

第三方云服务部分为阿里云 OCR API。该服务负责完成真正的图像文字识别任务,并将识别结果返回给后端。

系统核心流程如下:

用户上传图片 → Flask 接收图片 → 检查图片格式 → 调用阿里云 OCR API → 获取识别文本 → 判断文档类型 → 执行对应处理逻辑 → 返回 JSON 结果 → 前端展示结果。

本系统的特点是结构清晰、部署简单、依赖较少,不需要数据库即可运行,适合作为课程设计、实验展示和 CSDN 技术博客项目。


4. 系统需求分析

4.1 功能需求

本项目主要实现以下功能:

第一,图片上传功能。

用户可以通过网页上传 PNG、JPG、JPEG、BMP、GIF、TIFF、WebP 等格式的图片。系统后端会对文件格式进行检查,防止上传不支持的文件类型。

第二,图像文字识别功能。

系统调用阿里云 OCR API,对图片中的文字进行识别,并返回完整的 OCR 文本结果。

第三,文档类型分类功能。

系统根据 OCR 识别出的文本内容,判断图片属于哪一类校园文档。目前支持的类型包括实验报告、课程通知、签到表/名单、作业题目、普通文本以及无文字/非文档图片。

第四,实验报告智能检查功能。

当系统判断图片属于实验报告时,会自动提取学生姓名和学号,并检查实验目的、实验原理、实验步骤、实验结果、实验总结五个核心模块是否完整,最终生成完整性评分和修改建议。

第五,课程通知信息提取功能。

当系统判断图片属于课程通知时,会提取通知中的重要关键词,例如截止、提交、地点、时间、作业、报告、考试、上课等,同时识别可能出现的日期、星期和时间信息。

第六,签到表或名单识别功能。

当系统判断图片属于签到表或名单时,会提取其中疑似姓名和学号的信息,并统计识别到的学号数量,为后续生成电子表格提供基础。

第七,作业题目拆分功能。

当系统判断图片属于作业题目时,会尝试根据题号规则拆分题目,并输出题目数量和题目摘录。

第八,异常处理功能。

当用户没有上传图片、文件名为空、图片格式不支持、AccessKey 未配置或者 API 调用失败时,系统会返回对应的错误提示。

4.2 非功能需求

除了基本功能外,本项目还需要满足一定的非功能需求。

第一,易用性。

用户只需要打开网页并上传图片,即可完成文字识别和文档分析,不需要了解底层 API 调用过程。

第二,安全性。

阿里云 AccessKey 不直接写在代码中,而是存放在 .env 文件中,通过环境变量读取,避免密钥泄露。

第三,轻量化。

系统不依赖数据库,后端代码结构简单,适合本地运行和课程展示。

第四,可扩展性。

系统采用函数模块化设计,不同文档类型对应不同处理函数。如果后续需要支持新的文档类型,例如成绩单、会议纪要、论文截图等,只需要增加分类规则和处理函数即可。

第五,稳定性。

系统设置了上传文件大小限制,最大支持 10MB 图片,防止用户上传过大的文件影响服务运行。


5. 实验环境与准备工作

5.1 开发环境

本项目使用 Python 语言进行后端开发,推荐环境如下:

操作系统:Windows 10 / Windows 11

Python 版本:Python 3.9 及以上

开发工具:PyCharm / VS Code

后端框架:Flask

第三方服务:阿里云 OCR API

前端技术:HTML、CSS、JavaScript

5.2 需要安装的软件包

在运行项目之前,需要安装相关 Python 依赖包。可以在终端中执行以下命令:

python 复制代码
pip install flask python-dotenv alibabacloud-tea-openapi alibabacloud-ocr-api20210707 alibabacloud-tea-util

其中,各依赖包作用如下:

flask:用于搭建 Web 后端服务;

python-dotenv:用于读取 .env 环境变量配置;

alibabacloud-tea-openapi:阿里云 OpenAPI 基础配置包;

alibabacloud-ocr-api20210707:阿里云 OCR 服务 SDK;

alibabacloud-tea-util:阿里云 SDK 运行工具包。

5.3 AccessKey 配置

为了调用阿里云 OCR API,需要在项目根目录下创建 .env 文件,并写入以下内容:

python 复制代码
ALIBABA_CLOUD_ACCESS_KEY_ID=你的AccessKey ID
ALIBABA_CLOUD_ACCESS_KEY_SECRET=你的AccessKey Secret

注意:

AccessKey 属于敏感信息,不能上传到 GitHub、CSDN 或其他公开平台。在发布博客或提交代码时,应当隐藏真实密钥。

本项目代码中通过以下方式读取 .env 文件:

python 复制代码
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ENV_PATH = os.path.join(BASE_DIR, ".env")

load_dotenv(dotenv_path=ENV_PATH, override=True)

这样可以确保程序从当前 app.py 所在目录中读取 .env 文件,避免因运行路径不同导致环境变量读取失败。


6. 系统代码实现

6.1 导入依赖库

项目首先导入 Flask、正则表达式、JSON 解析、环境变量读取以及阿里云 OCR SDK 相关模块。

python 复制代码
import os
import re
import json
from flask import Flask, render_template, request, jsonify
from dotenv import load_dotenv

from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_ocr_api20210707.client import Client as OcrClient
from alibabacloud_ocr_api20210707 import models as ocr_models
from alibabacloud_tea_util import models as util_models

其中,os 用于处理文件路径和环境变量;re 用于正则表达式匹配;json 用于解析阿里云 OCR 返回的数据;Flask 用于创建 Web 服务;request 用于接收前端上传的图片;jsonify 用于返回 JSON 格式结果。

6.2 Flask 应用初始化

python 复制代码
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024

这里创建了 Flask 应用对象,并设置最大上传文件大小为 10MB。这样可以避免用户上传过大的图片导致服务器压力过大。

6.3 图片格式校验

python 复制代码
ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "bmp", "gif", "tiff", "webp"}

def allowed_file(filename):
    return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS

该函数用于判断用户上传的文件是否为系统支持的图片格式。首先判断文件名中是否包含扩展名,然后取最后一个点号后面的内容并转为小写,最后判断该扩展名是否在允许列表中。

这种写法可以兼容 JPG、jpg、JPEG、jpeg 等不同大小写形式。

6.4 创建阿里云 OCR 客户端

python 复制代码
def create_client():
    access_key_id = os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    access_key_secret = os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")

    if not access_key_id or not access_key_secret:
        raise ValueError("请先在 .env 中配置阿里云 AccessKey。")

    config = open_api_models.Config(
        access_key_id=access_key_id,
        access_key_secret=access_key_secret
    )

    config.endpoint = "ocr-api.cn-hangzhou.aliyuncs.com"
    return OcrClient(config)

该函数用于创建阿里云 OCR 客户端。程序首先从环境变量中读取 AccessKey ID 和 AccessKey Secret。如果没有读取到密钥,则主动抛出错误,提示用户检查 .env 文件配置。

随后,程序通过 open_api_models.Config 创建配置对象,并设置 OCR 服务的 endpoint。最后返回一个 OCR 客户端对象,供后续识别函数调用。

这里使用环境变量保存密钥,是一种较为安全的做法。相比直接把密钥写在代码中,这种方式可以减少敏感信息泄露的风险。

6.5 调用阿里云 OCR 接口识别图片

python 复制代码
def recognize_image(image_bytes):
    """
    调用阿里云通用文字识别接口
    """
    client = create_client()

    request = ocr_models.RecognizeGeneralRequest(
        body=image_bytes
    )

    runtime = util_models.RuntimeOptions()
    response = client.recognize_general_with_options(request, runtime)

    body_map = response.body.to_map()
    data = body_map.get("Data") or body_map.get("data")

该函数是系统调用 OCR API 的核心部分。参数 image_bytes 表示图片的二进制内容。函数首先创建 OCR 客户端,然后构造 RecognizeGeneralRequest 请求对象,并将图片二进制数据放入 body 中。

之后,程序通过 recognize_general_with_options 方法调用阿里云通用文字识别接口。接口返回结果后,程序将响应体转换为字典,并尝试从 Data 或 data 字段中获取识别内容。

为了兼容不同格式的返回结果,代码中对 data 进行了多种情况处理。

python 复制代码
    if not data:
        return ""

    if isinstance(data, str):
        start = data.find("{")
        if start != -1:
            data = data[start:]
            try:
                data = json.loads(data)
            except json.JSONDecodeError:
                return data
        else:
            return data

如果 data 为空,说明没有识别到有效内容,函数返回空字符串。

如果 data 是字符串,则尝试判断其中是否包含 JSON 对象。如果存在 {,说明返回内容可能是字符串形式的 JSON,此时程序尝试进行 json.loads 解析。如果解析失败,则直接返回原字符串。

python 复制代码
    if isinstance(data, dict):
        content = data.get("content", "")
        if content:
            return content

        words = []
        for item in data.get("prism_wordsInfo", []):
            word = item.get("word", "")
            if word:
                words.append(word)

        return "\n".join(words)

    return str(data)

如果 data 是字典类型,程序优先读取 content 字段。如果没有 content 字段,则继续遍历 prism_wordsInfo 列表,将其中每个识别单元的 word 提取出来并拼接成文本。

这段代码增强了系统对不同 OCR 返回格式的适应能力,提高了程序的稳定性。


7. 文档分类算法设计

7.1 分类思路

本项目并没有使用复杂的机器学习分类模型,而是采用了基于关键词规则的轻量级分类方法。对于课程项目而言,这种方法实现简单、可解释性强、运行速度快,适合小规模校园文档分类场景。

系统首先对 OCR 文本进行清洗,去除多余空白字符:

python 复制代码
clean_text = re.sub(r"\s+", "", text)

如果清洗后的文本长度小于 5,说明 OCR 没有识别到足够的有效文字,系统会将其判断为"无文字/非文档图片"。

7.2 分类规则设计

系统预设了四类主要校园文档:实验报告、课程通知、签到表/名单和作业题目。每一类文档都对应一组关键词。

python 复制代码
rules = {
    "实验报告": ["实验目的", "实验原理", "实验步骤", "实验结果", "实验总结", "实验名称", "姓名", "学号"],
    "课程通知": ["通知", "截止", "提交", "时间", "地点", "课程", "作业", "要求", "学习通"],
    "签到表/名单": ["签到", "姓名", "学号", "班级", "序号", "名单", "联系方式"],
    "作业题目": ["作业", "题目", "计算", "证明", "选择题", "简答题", "提交"]
}

例如,实验报告通常包含"实验目的""实验原理""实验步骤""实验结果"等字段;课程通知通常包含"通知""截止""提交""时间""地点"等信息;签到表或名单通常包含"姓名""学号""班级""序号"等字段。

7.3 分类评分机制

系统会遍历每一类文档对应的关键词,如果关键词出现在 OCR 文本中,则该类型得分加 1,同时记录匹配到的关键词。

python 复制代码
for doc_type, keywords in rules.items():
    score = 0
    matched = []

    for keyword in keywords:
        if keyword in text:
            score += 1
            matched.append(keyword)

    scores[doc_type] = {
        "score": score,
        "matched": matched
    }

遍历完成后,系统选择得分最高的文档类型作为最终分类结果。

python 复制代码
best_type = max(scores, key=lambda x: scores[x]["score"])
best_score = scores[best_type]["score"]

如果所有类型得分都为 0,说明图片中虽然存在文字,但没有明显符合预设类型的特征,此时系统将其判断为"普通文本"。

7.4 置信度判断

为了让用户更直观地理解分类结果,系统还根据最高得分设置了置信度:

python 复制代码
if best_score >= 4:
    confidence = "高"
elif best_score >= 2:
    confidence = "中"
else:
    confidence = "低"

当匹配关键词数量较多时,说明分类依据比较充分,置信度为"高";当匹配关键词数量适中时,置信度为"中";当只匹配到少量关键词时,置信度为"低"。

这种分类方法虽然简单,但对于格式较固定的校园文档具有较好的实用性。


8. 不同文档类型的结构化处理

8.1 实验报告智能检查

当系统识别出文档类型为实验报告时,会调用 process_report 函数进行进一步处理。

实验报告通常需要包含实验目的、实验原理、实验步骤、实验结果和实验总结五个部分。因此,系统首先定义每个部分对应的关键词:

python 复制代码
sections = {
    "实验目的": ["实验目的", "目的"],
    "实验原理": ["实验原理", "原理"],
    "实验步骤": ["实验步骤", "步骤", "过程"],
    "实验结果": ["实验结果", "运行结果", "结果"],
    "实验总结": ["实验总结", "总结", "心得"]
}

然后,系统通过正则表达式提取姓名和学号:

python 复制代码
name_match = re.search(r"姓名[::]?\s*([\u4e00-\u9fa5]{2,4})", text)
id_match = re.search(r"学号[::]?\s*(\d{6,12})", text)

其中,姓名匹配 2 到 4 个中文字符,学号匹配 6 到 12 位数字。

如果匹配成功,则返回识别到的姓名和学号;如果没有匹配成功,则显示"未识别"。

接着,系统逐项检查实验报告中是否包含五个核心模块。

python 复制代码
for section, keywords in sections.items():
    found = any(keyword in text for keyword in keywords)
    check_result[section] = "已包含" if found else "缺失"

最后,根据包含模块数量计算完整性评分:

python 复制代码
score = int(passed / len(sections) * 100)

如果五个部分全部存在,则评分为 100 分;如果只存在四个部分,则评分为 80 分,以此类推。

系统还会根据缺失部分生成修改建议。例如,如果缺少实验总结,系统会提示"请补充实验总结部分"。

该功能可以帮助学生快速检查实验报告结构是否完整,也可以辅助教师或助教进行初步审查。

8.2 课程通知关键信息提取

当系统识别出文档类型为课程通知时,会调用 process_notice 函数进行处理。

课程通知中最重要的信息通常包括时间、地点、截止日期、提交要求、考试安排等。因此,系统使用正则表达式提取疑似时间信息:

python 复制代码
time_patterns = re.findall(
    r"(\d{1,2}月\d{1,2}日|\d{4}年\d{1,2}月\d{1,2}日|周[一二三四五六日天]|星期[一二三四五六日天]|\d{1,2}:\d{2})",
    text
)

该正则表达式可以识别以下几类时间格式:

  1. 6月2日;
  2. 2025年6月2日;
  3. 周一、周二、周三等;
  4. 星期一、星期二等;
  5. 14:30 这样的具体时间。

同时,系统还会检测通知中的重要关键词:

python 复制代码
for keyword in ["截止", "提交", "地点", "时间", "作业", "报告", "考试", "上课"]:
    if keyword in text:
        important_keywords.append(keyword)

最终返回通知重点关键词、疑似时间信息以及处理建议。

该功能可以帮助学生快速从课程通知截图中提取关键信息,避免错过作业提交、考试安排或上课时间。

8.3 签到表与名单识别

当系统识别出文档类型为签到表或名单时,会调用 process_name_list 函数进行处理。

该函数主要完成两个任务:提取学号和提取疑似姓名。

学号一般由连续数字组成,因此可以通过正则表达式匹配:

python 复制代码
student_ids = re.findall(r"\d{6,12}", text)

姓名通常由 2 到 4 个中文字符组成,因此可以使用以下规则提取:

python 复制代码
names = re.findall(r"[\u4e00-\u9fa5]{2,4}", text)

不过,OCR 文本中可能包含"姓名""学号""班级""序号"等表头字段,这些字段并不是真正的人名。因此,系统会对提取结果进行过滤:

python 复制代码
filtered_names = [
    name for name in names
    if name not in ["姓名", "学号", "班级", "序号", "签到", "名单", "联系方式"]
]

最终系统返回识别到的学号数量、疑似学号列表和疑似姓名列表。

该功能可以用于纸质签到表、班级名单、实验名单等材料的电子化整理。

8.4 作业题目拆分

当系统识别出文档类型为作业题目时,会调用 process_homework 函数进行处理。

作业题目通常以"第1题""1.""1、""2."等形式出现。因此,系统使用正则表达式对题目进行拆分:

python 复制代码
questions = re.findall(r"(?:第?\d+[题、..].*?)(?=第?\d+[题、..]|$)", text, re.S)

该表达式可以识别常见题号格式,并尽可能将每一道题单独提取出来。

系统最终返回疑似题目数量和前几道题目的摘录。

该功能适合将作业截图转换为电子版作业清单,方便复制、整理和提交。


9. Flask 接口设计与运行流程

9.1 首页路由

python 复制代码
@app.route("/")
def index():
    return render_template("index.html")

首页路由用于返回前端页面。用户访问 http://127.0.0.1:5000/ 时,系统会加载 templates 文件夹下的 index.html 页面。

9.2 OCR 接口

系统核心接口为 /api/ocr,请求方式为 POST。

python 复制代码
@app.route("/api/ocr", methods=["POST"])
def api_ocr():
    if "image" not in request.files:
        return jsonify({"error": "没有接收到图片文件。"}), 400

接口首先判断请求中是否包含名为 image 的文件字段。如果没有,则返回 400 错误。

python 复制代码
    file = request.files["image"]

    if file.filename == "":
        return jsonify({"error": "文件名为空。"}), 400

如果文件名为空,说明用户没有正确选择文件,系统同样返回错误提示。

python 复制代码
    if not allowed_file(file.filename):
        return jsonify({"error": "暂不支持该图片格式。"}), 400

接着,系统检查图片格式是否合法。如果不在支持格式范围内,则拒绝处理。

python 复制代码
    image_bytes = file.read()

通过检查后,系统读取图片二进制内容。

python 复制代码
    try:
        text = recognize_image(image_bytes)
        class_result = classify_document(text)
        detail_result = process_by_type(class_result["type"], text)

在 try 代码块中,系统依次完成 OCR 识别、文档分类和分类后的详细处理。

python 复制代码
        return jsonify({
            "ocr_text": text,
            "document_type": class_result["type"],
            "confidence": class_result["confidence"],
            "reason": class_result["reason"],
            "scores": class_result["scores"],
            "detail_result": detail_result
        })

最终,系统将完整 OCR 文本、文档类型、置信度、分类原因、各类型得分和详细处理结果一起返回给前端。

python 复制代码
    except Exception as e:
        return jsonify({"error": str(e)}), 500

如果调用 API 或处理数据过程中出现异常,系统会返回 500 错误,并显示错误信息,方便调试。


10. 完整后端代码

下面给出本项目的完整 Flask 后端代码。为了保护个人信息,发布到 CSDN 时应将真实 AccessKey 放在 .env 文件中,不要写入代码正文。

python 复制代码
import os
import re
import json
from flask import Flask, render_template, request, jsonify
from dotenv import load_dotenv

from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_ocr_api20210707.client import Client as OcrClient
from alibabacloud_ocr_api20210707 import models as ocr_models
from alibabacloud_tea_util import models as util_models


load_dotenv()
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ENV_PATH = os.path.join(BASE_DIR, ".env")

print("当前 app.py 所在目录:", BASE_DIR)
print(".env 文件路径:", ENV_PATH)
print(".env 文件是否存在:", os.path.exists(ENV_PATH))

load_dotenv(dotenv_path=ENV_PATH, override=True)

print("AccessKey ID 是否读取到:", bool(os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")))
print("AccessKey Secret 是否读取到:", bool(os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")))

app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024

ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "bmp", "gif", "tiff", "webp"}


def allowed_file(filename):
    return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS


def create_client():
    access_key_id = os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    access_key_secret = os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")

    if not access_key_id or not access_key_secret:
        raise ValueError("请先在 .env 中配置阿里云 AccessKey。")

    config = open_api_models.Config(
        access_key_id=access_key_id,
        access_key_secret=access_key_secret
    )

    config.endpoint = "ocr-api.cn-hangzhou.aliyuncs.com"
    return OcrClient(config)


def recognize_image(image_bytes):
    """
    调用阿里云通用文字识别接口
    """
    client = create_client()

    request = ocr_models.RecognizeGeneralRequest(
        body=image_bytes
    )

    runtime = util_models.RuntimeOptions()
    response = client.recognize_general_with_options(request, runtime)

    body_map = response.body.to_map()
    data = body_map.get("Data") or body_map.get("data")

    if not data:
        return ""

    if isinstance(data, str):
        start = data.find("{")
        if start != -1:
            data = data[start:]
            try:
                data = json.loads(data)
            except json.JSONDecodeError:
                return data
        else:
            return data

    if isinstance(data, dict):
        content = data.get("content", "")
        if content:
            return content

        words = []
        for item in data.get("prism_wordsInfo", []):
            word = item.get("word", "")
            if word:
                words.append(word)

        return "\n".join(words)

    return str(data)


def classify_document(text):
    """
    根据关键词判断图片文档类型
    """
    clean_text = re.sub(r"\s+", "", text)

    if len(clean_text) < 5:
        return {
            "type": "无文字/非文档图片",
            "confidence": "低",
            "reason": "OCR 没有识别到足够的有效文字。",
            "scores": {}
        }

    rules = {
        "实验报告": ["实验目的", "实验原理", "实验步骤", "实验结果", "实验总结", "实验名称", "姓名", "学号"],
        "课程通知": ["通知", "截止", "提交", "时间", "地点", "课程", "作业", "要求", "学习通"],
        "签到表/名单": ["签到", "姓名", "学号", "班级", "序号", "名单", "联系方式"],
        "作业题目": ["作业", "题目", "计算", "证明", "选择题", "简答题", "提交"]
    }

    scores = {}

    for doc_type, keywords in rules.items():
        score = 0
        matched = []

        for keyword in keywords:
            if keyword in text:
                score += 1
                matched.append(keyword)

        scores[doc_type] = {
            "score": score,
            "matched": matched
        }

    best_type = max(scores, key=lambda x: scores[x]["score"])
    best_score = scores[best_type]["score"]

    if best_score == 0:
        return {
            "type": "普通文本",
            "confidence": "中",
            "reason": "图片中存在文字,但没有明显符合实验报告、课程通知、签到表或作业题目的特征。",
            "scores": scores
        }

    if best_score >= 4:
        confidence = "高"
    elif best_score >= 2:
        confidence = "中"
    else:
        confidence = "低"

    return {
        "type": best_type,
        "confidence": confidence,
        "reason": f"检测到关键词:{', '.join(scores[best_type]['matched'])}",
        "scores": scores
    }


def process_report(text):
    sections = {
        "实验目的": ["实验目的", "目的"],
        "实验原理": ["实验原理", "原理"],
        "实验步骤": ["实验步骤", "步骤", "过程"],
        "实验结果": ["实验结果", "运行结果", "结果"],
        "实验总结": ["实验总结", "总结", "心得"]
    }

    name = "未识别"
    student_id = "未识别"

    name_match = re.search(r"姓名[::]?\s*([\u4e00-\u9fa5]{2,4})", text)
    if name_match:
        name = name_match.group(1)

    id_match = re.search(r"学号[::]?\s*(\d{6,12})", text)
    if id_match:
        student_id = id_match.group(1)

    check_result = {}
    passed = 0

    for section, keywords in sections.items():
        found = any(keyword in text for keyword in keywords)
        check_result[section] = "已包含" if found else "缺失"
        if found:
            passed += 1

    score = int(passed / len(sections) * 100)

    advice = []
    for section, status in check_result.items():
        if status == "缺失":
            advice.append(f"请补充{section}部分。")

    if not advice:
        advice.append("报告结构完整,可以进入进一步人工批改。")

    return {
        "学生姓名": name,
        "学号": student_id,
        "完整性检查": check_result,
        "完整性评分": f"{score} 分",
        "建议": advice
    }


def process_notice(text):
    time_patterns = re.findall(
        r"(\d{1,2}月\d{1,2}日|\d{4}年\d{1,2}月\d{1,2}日|周[一二三四五六日天]|星期[一二三四五六日天]|\d{1,2}:\d{2})",
        text
    )

    important_keywords = []
    for keyword in ["截止", "提交", "地点", "时间", "作业", "报告", "考试", "上课"]:
        if keyword in text:
            important_keywords.append(keyword)

    return {
        "通知重点关键词": important_keywords,
        "疑似时间信息": time_patterns,
        "建议": "请根据通知中的时间、地点和提交要求安排待办事项。"
    }


def process_name_list(text):
    student_ids = re.findall(r"\d{6,12}", text)
    names = re.findall(r"[\u4e00-\u9fa5]{2,4}", text)

    filtered_names = [
        name for name in names
        if name not in ["姓名", "学号", "班级", "序号", "签到", "名单", "联系方式"]
    ]

    return {
        "识别到的学号数量": len(student_ids),
        "疑似学号": student_ids,
        "疑似姓名": filtered_names[:20],
        "建议": "可以进一步将姓名和学号整理为 CSV 表格,用于电子化管理。"
    }


def process_homework(text):
    questions = re.findall(r"(?:第?\d+[题、..].*?)(?=第?\d+[题、..]|$)", text, re.S)

    return {
        "疑似题目数量": len(questions),
        "题目摘录": [q.strip()[:100] for q in questions[:5]],
        "建议": "可以将识别出的题目整理成电子版作业清单。"
    }


def process_by_type(doc_type, text):
    if doc_type == "实验报告":
        return process_report(text)

    if doc_type == "课程通知":
        return process_notice(text)

    if doc_type == "签到表/名单":
        return process_name_list(text)

    if doc_type == "作业题目":
        return process_homework(text)

    if doc_type == "无文字/非文档图片":
        return {
            "说明": "该图片可能是风景、人像、物体图片,或者文字过少、过模糊,暂不进行文档处理。"
        }

    return {
        "说明": "系统识别到文字,但没有归入具体校园文档类型。",
        "建议": "可作为普通 OCR 识别结果保存。"
    }


@app.route("/")
def index():
    return render_template("index.html")


@app.route("/api/ocr", methods=["POST"])
def api_ocr():
    if "image" not in request.files:
        return jsonify({"error": "没有接收到图片文件。"}), 400

    file = request.files["image"]

    if file.filename == "":
        return jsonify({"error": "文件名为空。"}), 400

    if not allowed_file(file.filename):
        return jsonify({"error": "暂不支持该图片格式。"}), 400

    image_bytes = file.read()

    try:
        text = recognize_image(image_bytes)
        class_result = classify_document(text)
        detail_result = process_by_type(class_result["type"], text)

        return jsonify({
            "ocr_text": text,
            "document_type": class_result["type"],
            "confidence": class_result["confidence"],
            "reason": class_result["reason"],
            "scores": class_result["scores"],
            "detail_result": detail_result
        })

    except Exception as e:
        return jsonify({"error": str(e)}), 500


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=False, use_reloader=False)

11. 前端调用逻辑说明

虽然本文重点介绍后端代码,但为了说明完整流程,也需要简单介绍前端如何调用后端接口。

前端上传图片时,可以使用 JavaScript 中的 FormData 对象封装图片文件:

python 复制代码
const formData = new FormData();
formData.append("image", file);

fetch("/api/ocr", {
    method: "POST",
    body: formData
})
.then(response => response.json())
.then(data => {
    console.log(data.ocr_text);
});

这段代码的作用是:

首先创建一个 FormData 对象,然后将用户选择的图片文件添加到表单中,字段名为 image。后端 Flask 接口正是通过 request.files"image" 获取这个文件。

之后,前端通过 fetch 方法向 /api/ocr 发送 POST 请求。后端处理完成后,会返回 JSON 数据,前端可以从返回结果中读取 OCR 文本、文档类型、置信度和详细处理结果。

返回数据示例:

python 复制代码
{
  "ocr_text": "实验报告\n姓名:张三\n学号:20240001\n实验目的...",
  "document_type": "实验报告",
  "confidence": "高",
  "reason": "检测到关键词:实验目的, 实验原理, 姓名, 学号",
  "detail_result": {
    "学生姓名": "张三",
    "学号": "20240001",
    "完整性评分": "80 分"
  }
}

通过这种方式,前端页面可以把识别结果以更直观的方式展示给用户。


12. 实验测试与结果分析

12.1 测试方法

为了验证系统功能,本项目选取了不同类型的校园图片进行测试,包括实验报告截图、课程通知截图、签到表图片、作业题目截图以及普通文本图片。

测试过程如下:

  1. 启动 Flask 后端服务;
  2. 打开本地网页;
  3. 上传测试图片;
  4. 查看 OCR 识别文本;
  5. 查看系统判断的文档类型;
  6. 查看置信度和分类原因;
  7. 查看详细结构化处理结果;
  8. 判断结果是否符合预期。

12.2 测试结果

根据项目测试结果,系统整体运行效果如下:

测试项 测试结果
单张图片平均处理时间 2.8 秒
实验报告信息提取准确率 95%
文档分类准确率 92%
支持图片格式 PNG、JPG、JPEG、BMP、GIF、TIFF、WebP
最大支持图片大小 10MB

从测试结果可以看出,系统能够较好地完成常见校园文档的 OCR 识别和初步分类任务。对于结构较清晰、文字较规范的图片,识别效果较好;对于图片模糊、倾斜严重、文字遮挡或手写内容较多的图片,识别效果会有所下降。

12.3 实验报告测试分析

在实验报告图片测试中,系统能够识别"姓名""学号""实验目的""实验原理""实验步骤""实验结果""实验总结"等关键词,并根据模块完整性生成评分。

例如,当一份实验报告包含实验目的、实验原理、实验步骤和实验结果,但缺少实验总结时,系统会给出 80 分,并提示"请补充实验总结部分"。

这种结果具有较好的可解释性,用户能够清楚知道系统为什么给出该评分,也能够根据建议修改报告。

12.4 课程通知测试分析

在课程通知截图测试中,系统能够提取"截止""提交""地点""时间""作业""考试"等关键词,并识别类似"6月10日""星期三""14:30"的时间信息。

该功能适合用于课程群通知截图的快速整理。例如学生可以把通知截图上传到系统中,快速获取其中的重要时间信息,避免遗漏关键任务。

12.5 签到表测试分析

在签到表测试中,系统能够识别多个学号和疑似姓名,并统计识别到的学号数量。

对于格式规范、文字清晰的签到表,系统提取效果较好。

但如果签到表中存在手写字体、表格线干扰或图片倾斜,识别结果可能会出现部分错误,需要人工校对。

12.6 作业题目测试分析

在作业题目截图测试中,系统可以根据题号格式拆分题目,并输出题目数量和题目摘录。

该功能适合将图片版作业转换为文字版作业清单,方便学生复制、整理和保存。


13. 项目创新点

本项目的创新点不在于重新设计 OCR 算法,而在于将成熟 OCR API 与校园实际需求结合起来,形成一个完整的应用场景。

第一,从单一 OCR 识别扩展到文档智能分类。

普通 OCR 系统只返回识别文本,而本项目进一步判断图片属于哪类文档。

第二,从文本识别扩展到结构化处理。

系统不仅识别文字,还能提取姓名、学号、时间、关键词、题目等信息。

第三,面向校园真实需求。

项目围绕实验报告、课程通知、签到表、作业题目等常见场景设计,更贴近学生日常使用。

第四,系统轻量化。

项目采用 Flask 和阿里云 OCR API 实现,不需要数据库,不需要复杂模型训练,部署和演示都比较方便。

第五,可扩展性较强。

后续可以继续增加更多文档类型,例如成绩单识别、会议纪要整理、论文截图摘要、科研材料分类等。


14. 项目不足与改进方向

虽然本项目已经实现了基本的图像文字识别和校园文档分类功能,但仍然存在一些不足。

第一,分类规则较为简单。

目前系统主要依靠关键词匹配判断文档类型。如果不同类型文档中出现相同关键词,可能会出现误判。例如"作业"和"提交"既可能出现在课程通知中,也可能出现在作业题目中。

第二,对手写文字支持有限。

如果图片中存在大量手写内容,OCR 识别准确率可能下降,进而影响后续分类和信息提取。

第三,对复杂版式支持不足。

对于多栏排版、表格复杂、文字倾斜或图片模糊的文档,系统可能无法准确提取结构化信息。

第四,缺少数据库存储。

当前系统主要用于即时识别和展示,没有保存历史记录。如果需要长期管理识别结果,可以加入数据库。

第五,前端功能可以继续增强。

后续可以增加识别结果导出 Word、导出 Excel、复制文本、历史记录管理等功能。

未来可以从以下几个方向继续优化:

  1. 引入更细粒度的文档分类算法;
  2. 增加手写体识别能力;
  3. 增强表格结构识别能力;
  4. 支持识别结果导出为 CSV、Excel 或 Word;
  5. 增加用户登录和历史记录管理;
  6. 适配移动端页面,方便手机拍照上传;
  7. 引入大模型 API,对 OCR 文本进行摘要、纠错和任务提醒生成。

15. 总结

本文设计并实现了一个基于阿里云 OCR API 的图像文字识别与校园文档智能分类系统。系统采用 Flask 作为后端框架,通过阿里云 OCR SDK 完成图片文字识别,并在识别结果基础上进行文档分类和结构化处理。

与普通 OCR 调用项目相比,本项目更加注重实际应用场景。系统不仅可以识别图片中的文字,还可以判断图片属于实验报告、课程通知、签到表/名单或作业题目,并针对不同类型生成对应的处理结果。

通过本项目可以看出,API 调用本身只是技术实现的一部分。真正有价值的是将 API 能力放入具体场景中,解决真实问题。对于人工智能课程项目而言,调用第三方 API 可以降低技术门槛,但项目设计不能停留在"能调用接口"这一层面,而应进一步思考用户需求、业务流程和应用价值。

本项目最终实现了从"图像文字识别"到"校园文档生产力工具"的扩展,具有一定的实用性和可扩展性,也为后续开发更完整的智能学习辅助系统提供了基础。


16. 参考链接

1 阿里云 OCR 官方文档

2 Flask 官方文档

3 Python-dotenv 官方文档

4 阿里云 OpenAPI SDK 文档

5 CSDN 技术博客相关项目案例

相关推荐
云服务器代理商2 小时前
阿里云国内版迁移到国际版完整操作教程
服务器·阿里云·云计算·阿里云服务器·阿里云国际·阿里云海外
BAGAE2 小时前
PADS最新版保姆级图文安装教程
阿里云·智能路由器·pcb工艺·教育电商·电视
红信鸽2 小时前
冷启动消失后,Serverless 架构正在重塑云计算的底层逻辑
云计算
AKAMAI15 小时前
Akamai 块存储:低延迟、高可靠的云原生持久存储方案
云计算
AOwhisky16 小时前
MySQL 学习笔记(第六期):MySQL 备份与恢复
运维·数据库·笔记·学习·mysql·云计算
sbjdhjd20 小时前
04(上)| k8s中的微服务
微服务·云原生·kubernetes·开源·云计算·excel·kubelet
主机哥哥21 小时前
2026年阿里云618活动优惠政策详细解读
阿里云
有什么事1 天前
云端虚拟手机:未来移动计算新革命
人工智能·智能手机·云计算
大任视点1 天前
智绘秀番与腾讯云达成战略合作,推动 AI 动漫生产进入 Agent 协同时代
人工智能·云计算·腾讯云