博主介绍:✌全网粉丝10W+,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业项目实战6年之久,选择我们就是选择放心、选择安心毕业✌
> 🍅想要获取完整文章或者源码,或者代做,拉到文章底部即可与我联系了。🍅

点击查看作者主页,了解更多项目!

🍅感兴趣的可以先收藏起来,点赞、关注不迷路,大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助同学们顺利毕业 。🍅

1、毕业设计:2026年计算机专业毕业设计选题汇总(建议收藏)✅

2、大数据毕业设计:2026年选题大全 深度学习 python语言 JAVA语言 hadoop和spark(建议收藏)✅

1、项目介绍

  • 技术栈:Python语言、Django框架、pyecharts可视化、OpenCV(计算机视觉)、matplotlib、MySQL数据库(数据存储)、HTML(前端界面)、拼图滑动验证码(安全验证)
  • 核心功能:答题卡图像识别与自动判分、识别记录管理、得分数据可视化分析(得分分布、识别数量统计)、用户权限管理、后台系统运维
  • 研究背景:传统纸质阅卷依赖人工或光标阅读机,存在显著痛点——人工阅卷效率低、易出错,光标阅读机购买与维护成本高,普通学校与个人难以负担;随着计算机视觉技术发展,亟需基于OpenCV的低成本答题卡识别方案,解决“阅卷效率低、成本高”的问题,满足教学场景中快速判分与数据统计需求。
  • 研究意义:技术层面,整合OpenCV图像处理、DjangoWeb架构与pyecharts可视化,构建“识别-判分-分析-管理”完整技术闭环;用户层面,为教师/学校降低阅卷成本(无需采购专业设备),提升判分效率(秒级识别),支持得分数据可视化;行业层面,推动计算机视觉技术在教育场景落地,为“智慧阅卷”提供轻量化解决方案,具备实际应用与学习价值(如毕业设计、教学工具开发)。

2、项目界面

  1. 答题卡数据可视化分析
    在这里插入图片描述

  2. 答题卡识别功能
    在这里插入图片描述

  3. 上传图片进行识别
    在这里插入图片描述

  4. 答题卡识别记录管理
    在这里插入图片描述

  5. 后台管理
    在这里插入图片描述

  6. 数据面板
    在这里插入图片描述

  7. 注册登录
    在这里插入图片描述

3、项目说明

本项目是基于Python+Django框架开发的答题卡识别及自动判分系统,核心集成OpenCV计算机视觉技术、pyecharts可视化工具与MySQL数据库,实现“答题卡上传-图像识别-自动判分-记录管理-数据分析”的全流程功能,旨在解决传统阅卷效率低、成本高的问题,适配普通学校与个人教学场景。

(1)核心技术与算法实现(OpenCV识别判分)

系统核心的答题卡识别与判分功能基于OpenCV构建,技术流程标准化:

  1. 图像预处理:用户上传答题卡图片后,OpenCV自动进行图像优化——灰度化(降低色彩干扰)、二值化(区分答题区域与背景,如黑色选项框与白色背景)、降噪处理(去除图片模糊或污渍导致的干扰像素);
  2. 关键区域定位:通过轮廓检测算法,识别答题卡的答题区域(如选择题选项框、题号位置)、准考证号区域,确保仅对有效区域进行识别,避免边缘无关内容干扰;
  3. 答案识别与比对
    • 识别:通过像素值分析,判断用户选择的选项(如黑色填充的选项框即为所选答案);
    • 比对:系统预设标准答案(管理员在后台录入),将识别结果与标准答案逐题比对,自动计算得分(如100题满分,每题1分,错1题扣1分);
  4. 结果存储:识别结果(得分、答题情况、上传时间)与原图路径同步存入MySQL数据库,为后续管理与分析提供数据支撑。

(2)核心功能模块详解

① 用户认证与安全模块
  • 注册登录
    • 功能:用户通过“注册登录界面”完成账号注册(填写姓名、密码、手机号)与登录,登录时需完成拼图滑动验证码验证,防止恶意登录与数据泄露;
    • 权限划分:系统区分普通用户(如教师,仅可上传识别、查看个人记录)与管理员(可管理所有用户、录入标准答案、监控系统数据),保障数据安全。
  • 个人中心:支持普通用户修改密码、维护个人信息,管理员可通过“用户管理界面”新增/修改/删除用户账号,分配操作权限。
② 答题卡识别与判分模块
  • 上传识别:用户进入“上传图片进行识别”界面,支持单张/批量上传答题卡图片(格式如JPG、PNG),点击“开始识别”后,系统调用OpenCV接口完成自动判分,1-3秒内返回得分结果;
  • 识别记录查看:“答题卡识别功能”界面展示个人所有识别记录,包含原图预览、得分、识别时间,支持按“识别时间”“得分区间”筛选,快速定位目标记录;
  • 结果校验:若识别结果存在异议(如因图片模糊导致识别错误),用户可查看“答题详情”(逐题比对识别答案与标准答案),管理员可手动修正得分并重新存储。
③ 答题卡管理模块
  • 记录管理:“答题卡识别记录管理”界面集中展示所有用户的识别记录(含图片名称、得分、操作人员、上传时间),支持:
    • 搜索:按图片名称关键词查找特定记录;
    • 删除:普通用户仅可删除个人记录,管理员可删除所有无效记录(如重复上传、识别失败的记录);
  • 标准答案管理:管理员专属功能,通过后台录入不同试卷的标准答案(如“试卷A选择题标准答案:1.A、2.B、3.C…”),确保判分标准统一。
④ 数据可视化分析模块
  • 数据面板:“数据面板”界面展示系统宏观数据,如总用户数、总识别次数、平均得分、今日新增识别量,帮助管理员快速把握系统运营状态;
  • 可视化分析:“答题卡数据可视化分析”界面通过pyecharts生成多维度图表:
    • 得分分布柱状图:展示不同得分区间的记录数量(如“80-90分区间有20条记录,90-100分区间有15条记录”);
    • 识别趋势折线图:按日期展示每日识别次数变化(如“周一识别50次,周二识别30次”);
    • 用户识别量排行:对比不同用户的累计识别次数,辅助管理员评估系统使用频率。
⑤ 后台管理模块(管理员专属)
  • 系统监控:查看系统日志(如用户登录日志、识别失败日志),排查异常问题(如某时段识别失败率高,可能因图片格式不兼容);
  • 数据维护:备份/恢复MySQL数据库,防止数据丢失;管理图片存储路径,定期清理无效图片文件,节省存储空间;
  • 功能配置:调整识别参数(如OpenCV二值化阈值、识别精度),优化识别效果(如降低模糊图片的识别错误率)。

(3)系统价值与应用场景

  • 目标用户:中小学教师(日常作业/小测验判分)、培训机构(模拟考试快速阅卷)、个人学习者(自测后自动核对答案);
  • 核心价值:相比传统人工阅卷,效率提升80%以上;相比光标阅读机,零设备采购成本,普通学校与个人均可免费使用;
  • 扩展性:支持后续新增“批量导出报表”功能(将得分数据导出为Excel)、“错题统计”功能(分析高频错题,辅助教学重点突破)。

4、核心代码


# 导入工具包
import numpy as np
import argparse
import imutils
import cv2

# 正确答案
ANSWER_KEY = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}


def order_points(pts):
    # 一共4个坐标点
    rect = np.zeros((4, 2), dtype="float32")

    # 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
    # 计算左上,右下
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]

    # 计算右上和左下
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    return rect


def four_point_transform(image, pts):
    # 获取输入坐标点
    rect = order_points(pts)
    (tl, tr, br, bl) = rect

    # 计算输入的w和h值
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    maxWidth = max(int(widthA), int(widthB))

    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    maxHeight = max(int(heightA), int(heightB))

    # 变换后对应坐标位置
    dst = np.array([
        [0, 0],
        [maxWidth - 1, 0],
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype="float32")

    # 计算变换矩阵
    M = cv2.getPerspectiveTransform(rect, dst)
    warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))

    # 返回变换后结果
    return warped


def sort_contours(cnts, method="left-to-right"):
    reverse = False
    i = 0
    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True
    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    return cnts, boundingBoxes

def main(path):
    # 预处理
    image = cv2.imread(path)
    contours_img = image.copy()
    # print(contours_img)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    edged = cv2.Canny(blurred, 75, 200)

    # 轮廓检测
    cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_SIMPLE)[0]
    # print(contours_img)
    cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
    # cv2.drawContours(contours_img,cnts.reshape(-1,1,2),-1,(0,0,255),3)

    # cv_show('contours_img', contours_img)
    docCnt = None

    # 确保检测到了
    if len(cnts) > 0:
        # 根据轮廓大小进行排序
        cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

        # 遍历每一个轮廓
        for c in cnts:
            # 近似
            peri = cv2.arcLength(c, True)
            approx = cv2.approxPolyDP(c, 0.02 * peri, True)

            # 准备做透视变换
            if len(approx) == 4:
                docCnt = approx
                break

    # 执行透视变换

    warped = four_point_transform(gray, docCnt.reshape(4, 2))
    # cv_show('warped', warped)
    # Otsu's 阈值处理
    thresh = cv2.threshold(warped, 0, 255,
                           cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
    # cv_show('thresh', thresh)
    thresh_Contours = thresh.copy()
    # 找到每一个圆圈轮廓
    cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_SIMPLE)[0]
    cv2.drawContours(thresh_Contours, cnts, -1, (0, 0, 255), 3)
    # cv_show('thresh_Contours', thresh_Contours)
    questionCnts = []

    # 遍历
    for c in cnts:
        # 计算比例和大小
        (x, y, w, h) = cv2.boundingRect(c)
        ar = w / float(h)

        # 根据实际情况指定标准
        if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
            questionCnts.append(c)

    # 按照从上到下进行排序
    questionCnts = sort_contours(questionCnts,
                                 method="top-to-bottom")[0]
    correct = 0

    # 每排有5个选项
    for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
        # 排序
        cnts = sort_contours(questionCnts[i:i + 5])[0]
        bubbled = None

        # 遍历每一个结果
        for (j, c) in enumerate(cnts):
            # 使用mask来判断结果
            mask = np.zeros(thresh.shape, dtype="uint8")
            cv2.drawContours(mask, [c], -1, 255, -1)  # -1表示填充
            # cv_show('mask', mask)
            # 通过计算非零点数量来算是否选择这个答案
            mask = cv2.bitwise_and(thresh, thresh, mask=mask)
            total = cv2.countNonZero(mask)

            # 通过阈值判断
            if bubbled is None or total > bubbled[0]:
                bubbled = (total, j)

        # 对比正确答案
        color = (0, 0, 255)
        k = ANSWER_KEY[q]

        # 判断正确
        if k == bubbled[1]:
            color = (0, 255, 0)
            correct += 1

        # 绘图
        cv2.drawContours(warped, [cnts[k]], -1, color, 3)

    score = (correct / 5.0) * 100
    print("--------------------------------------------")
    print("答题卡得分为: {:.2f}分".format(score))
    print("正确率: {:.2f}%".format(score))
    return score

if __name__ == '__main__':
    main()


5、源码获取方式

🍅由于篇幅限制,获取完整文章或源码、代做项目的,查看我的【用户名】、【专栏名称】、【顶部选题链接】就可以找到我啦🍅

感兴趣的可以先收藏起来,点赞、关注不迷路,下方查看👇🏻获取联系方式👇🏻

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐