博主介绍:✌全网粉丝50W+,前互联网大厂软件研发、集结硕博英豪成立软件开发工作室,专注于计算机相关专业项目实战6年之久,累计开发项目作品上万套。凭借丰富的经验与专业实力,已帮助成千上万的学生顺利毕业,选择我们,就是选择放心、选择安心毕业✌
> 🍅想要获取完整文章或者源码,或者代做,拉到文章底部即可与我联系了。🍅

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

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

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

2、最全计算机专业毕业设计选题大全(建议收藏)✅

1、项目介绍

技术栈: Python语言、Django框架、pyecharts可视化、opencv、matplotlib 、答题卡识别及判分系统+可视化+文档

摘 要

相比传统的纸质阅卷模式,答题卡的出现帮助教师缓解了试卷批阅的压力,答题卡配合光标阅读机的阅卷模式也逐渐普及应用,然而光标阅读机购买及维护费用较高不利于普通学校的使用。随着计算机视觉研究的不断发展,答题卡识别也成为了计算机视觉研究的重要内容之一,随着图像识别技术及人工智能系统的快速推广和应用,可以利用图像识别技术对答题卡进行图像处理,同时通过信息的识别比对来实现成绩的计算和存储,借助答题卡识别技术可以实现更加便捷的答题卡得分统计,进一步提升教学阅卷的效率同时对普通学校及个人也可以实现灵活的应用。

关键词:Python语言;Django;OpenCV;答题卡识别;题卡管理;

2、项目界面

(1)答题卡数据可视化分析

在这里插入图片描述

(2)答题卡识别功能

在这里插入图片描述

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

(4)答题卡识别记录管理

在这里插入图片描述

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

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

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

3、项目说明

4.3.1 系统登录页面
此次答题卡识别及判分系统的登录界面呈现内容如下,通过用户名及密码信息的录入以及拼图滑动验证码的滑动才可以实现系统平台的登录,考虑到答题卡及得分数据信息的安全性,在登录环节加入了拼图验证的方式,具体系统登录界面内容如下:

4.3.2系统首页界面
答题卡识别及判分系统的首页界面主要呈现了功能栏以及统计信息,用户登录首页就可以直观的查看到当前系统用户数量及答题卡识别数量,通过左侧的功能栏可以试下题卡识别、题卡管理及题卡分析等功能的应用,同时可以对用户的个人信息通过修改密码及用户管理进行内容的维护和管理,具体系统首页界面主要功能模块内容如下:

图4.3.2系统首页界面
4.3.3题卡识别界面
用户通过题卡识别的新增答题卡识别可以实现在线的答题卡内容上传,同时可以通过题卡识别界面查看到已上传的答题卡图片信息,通过图片内容的识别和信息的比对实现题卡数据信息的提取以及得分的统计,具体题卡识别界面的主要内容如下:

4.3.4题卡管理界面
用户上传的题卡信息会集中呈现在题卡管理界面,主要包括了答题卡的图片名称、得分信息、操作人员及上传时间,用户可以通过操作栏的删除按钮实现题卡信息的删除操作,也可以按照图片文档名称进行在线的图片搜索,题卡管理界面的主要信息内容呈现如下:

4.3.5 题卡分析界面
题卡分析模块主要集中展示了用户上传的答题卡数量以及得分分布情况,结合答题卡得分分布柱状图可以获取到得数数量较高的分段,同时可以结合用户答题卡识别次数来通过系统多个用户上传答题卡的数量信息,通过题卡分析界面可以实现更加直观的答题信息的统计和得分分布区间查看,具体分析界面内容如下图所示:

4.3.6 用户管理界面
通过用户管理模块可以实现系统用户的新增,答题卡识别及判分系统的新增用户信息只需要填列对应的姓名、密码及手机号即可,可以通过该界面实现用户信息的搜索以及用户信息的修改,具体系统新增用户信息录入界面内容呈现如下:

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

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐