返回顶部
首页 > 资讯 > 后端开发 > Python >使用 OpenCV-Python 识别答题卡判卷功能
  • 958
分享到

使用 OpenCV-Python 识别答题卡判卷功能

2024-04-02 19:04:59 958人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

任务 识别用相机拍下来的答题卡,并判断最终得分(假设正确答案是B, E, A, D, B) 主要步骤 轮廓识别——答题卡边缘识别 透视变换——提取答题卡主体

任务

识别用相机拍下来的答题卡,并判断最终得分(假设正确答案是B, E, A, D, B)

主要步骤

  1. 轮廓识别——答题卡边缘识别
  2. 透视变换——提取答题卡主体
  3. 轮廓识别——识别出所有圆形选项,剔除无关轮廓
  4. 检测每一行选择的是哪一项,并将结果储存起来,记录正确的个数
  5. 计算最终得分并在图中标注

分步实现

轮廓识别——答题卡边缘识别

输入图像


import cv2 as cv
import numpy as np
 
# 正确答案
right_key = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
 
# 输入图像
img = cv.imread('./images/test_01.jpg')
img_copy = img.copy()
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
cvshow('img-gray', img_gray)

图像预处理


# 图像预处理
# 高斯降噪
img_gaussian = cv.GaussianBlur(img_gray, (5, 5), 1)
cvshow('gaussianblur', img_gaussian)
# canny边缘检测
img_canny = cv.Canny(img_gaussian, 80, 150)
cvshow('canny', img_canny)

 

 

轮廓识别——答题卡边缘识别


# 轮廓识别——答题卡边缘识别
cnts, hierarchy = cv.findContours(img_canny, cv.RETR_EXTERNAL, cv.CHaiN_APPROX_SIMPLE)
cv.drawContours(img_copy, cnts, -1, (0, 0, 255), 3)
cvshow('contours-show', img_copy)

透视变换——提取答题卡主体

对每个轮廓进行拟合,将多边形轮廓变为四边形


docCnt = None
 
# 确保检测到了
if len(cnts) > 0:
    # 根据轮廓大小进行排序
    cnts = sorted(cnts, key=cv.contourArea, reverse=True)
 
    # 遍历每一个轮廓
    for c in cnts:
        # 近似
        peri = cv.arcLength(c, True)
        # arclength 计算一段曲线的长度或者闭合曲线的周长;
        # 第一个参数输入一个二维向量,第二个参数表示计算曲线是否闭合
 
        approx = cv.approxPolyDP(c, 0.02 * peri, True)
        # 用一条顶点较少的曲线/多边形来近似曲线/多边形,以使它们之间的距离<=指定的精度;
        # c是需要近似的曲线,0.02*peri是精度的最大值,True表示曲线是闭合的
 
        # 准备做透视变换
        if len(approx) == 4:
            docCnt = approx
            break

透视变换——提取答题卡主体


# 透视变换——提取答题卡主体
docCnt = docCnt.reshape(4, 2)
warped = four_point_transfORM(img_gray, docCnt)
cvshow('warped', warped)

def four_point_transform(img, four_points):
    rect = order_points(four_points)
    (tl, tr, br, bl) = rect
 
    # 计算输入的w和h的值
    widthA = np.sqrt((tr[0] - tl[0]) ** 2 + (tr[1] - tl[1]) ** 2)
    widthB = np.sqrt((br[0] - bl[0]) ** 2 + (br[1] - bl[1]) ** 2)
    maxWidth = max(int(widthA), int(widthB))
 
    heightA = np.sqrt((tl[0] - bl[0]) ** 2 + (tl[1] - bl[1]) ** 2)
    heightB = np.sqrt((tr[0] - br[0]) ** 2 + (tr[1] - br[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')
 
    # 最主要的函数就是 cv2.getPerspectiveTransform(rect, dst) 和 cv2.warpPerspective(image, M, (maxWidth, maxHeight))
    M = cv.getPerspectiveTransform(rect, dst)
    warped = cv.warpPerspective(img, M, (maxWidth, maxHeight))
    return warped
 
 
def order_points(points):
    res = np.zeros((4, 2), dtype='float32')
    # 按照从前往后0,1,2,3分别表示左上、右上、右下、左下的顺序将points中的数填入res中
 
    # 将四个坐标x与y相加,和最大的那个是右下角的坐标,最小的那个是左上角的坐标
    sum_hang = points.sum(axis=1)
    res[0] = points[np.argmin(sum_hang)]
    res[2] = points[np.argmax(sum_hang)]
 
    # 计算坐标x与y的离散插值np.diff()
    diff = np.diff(points, axis=1)
    res[1] = points[np.argmin(diff)]
    res[3] = points[np.argmax(diff)]
 
    # 返回result
    return res

轮廓识别——识别出选项


# 轮廓识别——识别出选项
thresh = cv.threshold(warped, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)[1]
cvshow('thresh', thresh)
thresh_cnts, _ = cv.findContours(thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
w_copy = warped.copy()
cv.drawContours(w_copy, thresh_cnts, -1, (0, 0, 255), 2)
cvshow('warped_contours', w_copy)
 
questionCnts = []
# 遍历,挑出选项的cnts
for c in thresh_cnts:
    (x, y, w, h) = cv.boundingRect(c)
    ar = w / float(h)
    # 根据实际情况指定标准
    if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
        questionCnts.append(c)
 
# 检查是否挑出了选项
w_copy2 = warped.copy()
cv.drawContours(w_copy2, questionCnts, -1, (0, 0, 255), 2)
cvshow('questionCnts', w_copy2)

成功将无关轮廓剔除

检测每一行选择的是哪一项,并将结果储存起来,记录正确的个数


# 检测每一行选择的是哪一项,并将结果储存在元组bubble中,记录正确的个数correct
# 按照从上到下t2b对轮廓进行排序
questionCnts = sort_contours(questionCnts, method="t2b")[0]
correct = 0
# 每行有5个选项
for (i, q) in enumerate(np.arange(0, len(questionCnts), 5)):
    # 排序
    cnts = sort_contours(questionCnts[q:q+5])[0]
 
    bubble = None
    # 得到每一个选项的mask并填充,与正确答案进行按位与操作获得重合点数
    for (j, c) in enumerate(cnts):
        mask = np.zeros(thresh.shape, dtype='uint8')
        cv.drawContours(mask, [c], -1, 255, -1)
        # cvshow('mask', mask)
 
        # 通过按位与操作得到thresh与mask重合部分的像素数量
        bitand = cv.bitwise_and(thresh, thresh, mask=mask)
        totalPixel = cv.countNonZero(bitand)
 
        if bubble is None or bubble[0] < totalPixel:
            bubble = (totalPixel, j)
 
    k = bubble[1]
    color = (0, 0, 255)
    if k == right_key[i]:
        correct += 1
        color = (0, 255, 0)
 
    # 绘图
    cv.drawContours(warped, [cnts[right_key[i]]], -1, color, 3)
    cvshow('final', warped)

def sort_contours(contours, method="l2r"):
    # 用于给轮廓排序,l2r, r2l, t2b, b2t
    reverse = False
    i = 0
    if method == "r2l" or method == "b2t":
        reverse = True
    if method == "t2b" or method == "b2t":
        i = 1
 
    boundingBoxes = [cv.boundingRect(c) for c in contours]
    (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda a: a[1][i], reverse=reverse))
    return contours, boundingBoxes

 

用透过mask的像素的个数来判断考生选择的是哪个选项

计算最终得分并在图中标注


# 计算最终得分并在图中标注
score = (correct / 5.0) * 100
print(f"Score: {score}%")
cv.putText(warped, f"Score: {score}%", (10, 30), cv.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv.imshow("Original", img)
cv.imshow("Exam", warped)
cv.waiTKEy(0)

完整代码


import cv2 as cv
import numpy as np
 
 
def cvshow(name, img):
    cv.imshow(name, img)
    cv.waitKey(0)
    cv.destroyAllwindows()
 
def four_point_transform(img, four_points):
    rect = order_points(four_points)
    (tl, tr, br, bl) = rect
 
    # 计算输入的w和h的值
    widthA = np.sqrt((tr[0] - tl[0]) ** 2 + (tr[1] - tl[1]) ** 2)
    widthB = np.sqrt((br[0] - bl[0]) ** 2 + (br[1] - bl[1]) ** 2)
    maxWidth = max(int(widthA), int(widthB))
 
    heightA = np.sqrt((tl[0] - bl[0]) ** 2 + (tl[1] - bl[1]) ** 2)
    heightB = np.sqrt((tr[0] - br[0]) ** 2 + (tr[1] - br[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')
 
    # 最主要的函数就是 cv2.getPerspectiveTransform(rect, dst) 和 cv2.warpPerspective(image, M, (maxWidth, maxHeight))
    M = cv.getPerspectiveTransform(rect, dst)
    warped = cv.warpPerspective(img, M, (maxWidth, maxHeight))
    return warped
 
 
def order_points(points):
    res = np.zeros((4, 2), dtype='float32')
    # 按照从前往后0,1,2,3分别表示左上、右上、右下、左下的顺序将points中的数填入res中
 
    # 将四个坐标x与y相加,和最大的那个是右下角的坐标,最小的那个是左上角的坐标
    sum_hang = points.sum(axis=1)
    res[0] = points[np.argmin(sum_hang)]
    res[2] = points[np.argmax(sum_hang)]
 
    # 计算坐标x与y的离散插值np.diff()
    diff = np.diff(points, axis=1)
    res[1] = points[np.argmin(diff)]
    res[3] = points[np.argmax(diff)]
 
    # 返回result
    return res
 
 
def sort_contours(contours, method="l2r"):
    # 用于给轮廓排序,l2r, r2l, t2b, b2t
    reverse = False
    i = 0
    if method == "r2l" or method == "b2t":
        reverse = True
    if method == "t2b" or method == "b2t":
        i = 1
 
    boundingBoxes = [cv.boundingRect(c) for c in contours]
    (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda a: a[1][i], reverse=reverse))
    return contours, boundingBoxes
 
# 正确答案
right_key = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
 
# 输入图像
img = cv.imread('./images/test_01.jpg')
img_copy = img.copy()
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
cvshow('img-gray', img_gray)
 
# 图像预处理
# 高斯降噪
img_gaussian = cv.GaussianBlur(img_gray, (5, 5), 1)
cvshow('gaussianblur', img_gaussian)
# canny边缘检测
img_canny = cv.Canny(img_gaussian, 80, 150)
cvshow('canny', img_canny)
 
# 轮廓识别——答题卡边缘识别
cnts, hierarchy = cv.findContours(img_canny, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img_copy, cnts, -1, (0, 0, 255), 3)
cvshow('contours-show', img_copy)
 
docCnt = None
 
# 确保检测到了
if len(cnts) > 0:
    # 根据轮廓大小进行排序
    cnts = sorted(cnts, key=cv.contourArea, reverse=True)
 
    # 遍历每一个轮廓
    for c in cnts:
        # 近似
        peri = cv.arcLength(c, True)  # arclength 计算一段曲线的长度或者闭合曲线的周长;
        # 第一个参数输入一个二维向量,第二个参数表示计算曲线是否闭合
 
        approx = cv.approxPolyDP(c, 0.02 * peri, True)
        # 用一条顶点较少的曲线/多边形来近似曲线/多边形,以使它们之间的距离<=指定的精度;
        # c是需要近似的曲线,0.02*peri是精度的最大值,True表示曲线是闭合的
 
        # 准备做透视变换
        if len(approx) == 4:
            docCnt = approx
            break
 
 
# 透视变换——提取答题卡主体
docCnt = docCnt.reshape(4, 2)
warped = four_point_transform(img_gray, docCnt)
cvshow('warped', warped)
 
 
# 轮廓识别——识别出选项
thresh = cv.threshold(warped, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)[1]
cvshow('thresh', thresh)
thresh_cnts, _ = cv.findContours(thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
w_copy = warped.copy()
cv.drawContours(w_copy, thresh_cnts, -1, (0, 0, 255), 2)
cvshow('warped_contours', w_copy)
 
questionCnts = []
# 遍历,挑出选项的cnts
for c in thresh_cnts:
    (x, y, w, h) = cv.boundingRect(c)
    ar = w / float(h)
    # 根据实际情况指定标准
    if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
        questionCnts.append(c)
 
# 检查是否挑出了选项
w_copy2 = warped.copy()
cv.drawContours(w_copy2, questionCnts, -1, (0, 0, 255), 2)
cvshow('questionCnts', w_copy2)
 
 
# 检测每一行选择的是哪一项,并将结果储存在元组bubble中,记录正确的个数correct
# 按照从上到下t2b对轮廓进行排序
questionCnts = sort_contours(questionCnts, method="t2b")[0]
correct = 0
# 每行有5个选项
for (i, q) in enumerate(np.arange(0, len(questionCnts), 5)):
    # 排序
    cnts = sort_contours(questionCnts[q:q+5])[0]
 
    bubble = None
    # 得到每一个选项的mask并填充,与正确答案进行按位与操作获得重合点数
    for (j, c) in enumerate(cnts):
        mask = np.zeros(thresh.shape, dtype='uint8')
        cv.drawContours(mask, [c], -1, 255, -1)
        cvshow('mask', mask)
 
        # 通过按位与操作得到thresh与mask重合部分的像素数量
        bitand = cv.bitwise_and(thresh, thresh, mask=mask)
        totalPixel = cv.countNonZero(bitand)
 
        if bubble is None or bubble[0] < totalPixel:
            bubble = (totalPixel, j)
 
    k = bubble[1]
    color = (0, 0, 255)
    if k == right_key[i]:
        correct += 1
        color = (0, 255, 0)
 
    # 绘图
    cv.drawContours(warped, [cnts[right_key[i]]], -1, color, 3)
    cvshow('final', warped)
 
 
# 计算最终得分并在图中标注
score = (correct / 5.0) * 100
print(f"Score: {score}%")
cv.putText(warped, f"Score: {score}%", (10, 30), cv.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv.imshow("Original", img)
cv.imshow("Exam", warped)
cv.waitKey(0)
 
 

到此这篇关于使用 OpenCV-python 识别答题卡判卷的文章就介绍到这了,更多相关OpenCV Python 识别答题卡判卷内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 使用 OpenCV-Python 识别答题卡判卷功能

本文链接: https://lsjlt.com/news/160483.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 使用 OpenCV-Python 识别答题卡判卷功能
    任务 识别用相机拍下来的答题卡,并判断最终得分(假设正确答案是B, E, A, D, B) 主要步骤 轮廓识别——答题卡边缘识别 透视变换——提取答题卡主体 ...
    99+
    2024-04-02
  • 如何使用OpenCV-Python实现识别答题卡判卷功能
    这篇文章主要为大家展示了“如何使用OpenCV-Python实现识别答题卡判卷功能”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何使用OpenCV-Python实现识别答题卡判卷功能”这篇文章...
    99+
    2023-06-22
  • python OpenCV实现答题卡识别判卷
    本文实例为大家分享了python OpenCV实现答题卡识别判卷的具体代码,供大家参考,具体内容如下 完整代码: #导入工具包 import numpy as np import argparse import ...
    99+
    2022-06-02
    python OpenCV答题卡识别判卷 python答题卡识别判卷 python答题卡识别
  • Python+Opencv答题卡识别用例详解
    使用Python3和Opencv识别一张标准的答题卡。大致的过程如下: 1.读取图片 2.利用霍夫圆检测,检测出四个角的黑圆位置,从确定四个角的位置 3.利用透视变换和四个角的位置,...
    99+
    2024-04-02
  • python利用opencv如何实现答题卡自动判卷
    目录1、设定答题卡模板2、读取答题卡图像并对图像进行灰度化处理3、高斯模糊图像去噪点4、使用大津法二值分割图像5、使用开运算去噪点6、使用canny边缘检测算法7、筛选答题区域轮廓,透视变换矫正目标区域使用摄像头实时...
    99+
    2022-06-02
    python opencv答题卡 python答题卡识别 python答题系统
  • opencv实现答题卡识别
    本文实例为大家分享了opencv实现答题卡识别的具体代码,供大家参考,具体内容如下 """ 识别答题卡 """   import cv2 import numpy as np   d...
    99+
    2024-04-02
  • 基于Opencv图像识别实现答题卡识别示例详解
    目录1. 项目分析2.项目实验3.项目结果总结在观看唐宇迪老师图像处理的课程中,其中有一个答题卡识别的小项目,在此结合自己理解做一个简单的总结。 1. 项目分析 首先在拿到项目时候,...
    99+
    2024-04-02
  • C++OpenCV实现银行卡号识别功能
    目录前言一、获取模板图像1.1 功能效果1.2 功能源码二、银行卡号定位2.1 将银行卡号切割成四块2.2 字符切割三、字符识别3.1.读取文件3.2.字符匹配3.3.功能源码四、效...
    99+
    2024-04-02
  • C++ OpenCV如何实现银行卡号识别功能
    这篇文章主要介绍了C++ OpenCV如何实现银行卡号识别功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、获取模板图像如图所示,这是我们的模板图像。我们需要将...
    99+
    2023-06-28
  • 怎么使用Python+OpenCV实现图像识别替换功能
    本文小编为大家详细介绍“怎么使用Python+OpenCV实现图像识别替换功能”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么使用Python+OpenCV实现图像识别替换功能”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来...
    99+
    2023-07-02
  • Python答题卡识别并给出分数的实现代码
      哈喽大家好,这里是滑稽研究所。看过我们图像处理系列的朋友,应该知道识别答题卡那期文章。其中利用opencv框架,完美的实现了答题卡填涂区域的识别。在后台有...
    99+
    2024-04-02
  • Python OpenCV招商银行信用卡卡号识别的方法
    学在前面 从本篇博客起,我们将实际完成几个小案例,第一个就是银行卡号识别,预计本案例将写 5 篇左右的博客才可以完成,一起加油吧。 本文的目标是最终获取一套招商银行卡,0~9 数字的...
    99+
    2024-04-02
  • Python+OpenCV实现图像识别替换功能详解
    OpenCV-Python是一个Python库,旨在解决计算机视觉问题。 OpenCV是一个开源的计算机视觉库,1999年由英特尔的Gary Bradski启动。Bradski在访学...
    99+
    2024-04-02
  • python使用opencv进行人脸识别
    环境 ubuntu 12.04 LTS python 2.7.3 opencv 2.3.1-7 安装依赖 sudo apt-get install libopencv-* sudo apt-get in...
    99+
    2022-06-04
    python opencv
  • Python OpenCV实现识别信用卡号教程详解
    目录通过与 OpenCV 模板匹配的 OCR信用卡 OCR 结果总结今天的博文分为三个部分。 在第一部分中,我们将讨论 OCR-A 字体,这是一种专为辅助光学字符识别算法而创建的字体...
    99+
    2024-04-02
  • python+opencv实现文字颜色识别与标定功能
            最近接了一个比较简单的图像处理的单子,花了一点时间随便写了一下:  数据集客户没有是自...
    99+
    2024-04-02
  • Python+OpenCV实现信用卡数字识别的方法详解
    目录一、模板图像处理 二、信用卡图片预处理一、模板图像处理 (1)灰度图、二值图转化 template = cv2.imread('C:/Users/bwy/Desktop...
    99+
    2024-04-02
  • 使用Python+OpenCV进行卡类型及16位卡号数字的OCR功能
    目录1.效果图2.原理2.1OCR-A字体2.2检测过程步骤2.3优化3.源代码这篇博客将介绍如何通过OpenCV和Python使用模板匹配执行光学字符识别(OCR)。具体来说,将使...
    99+
    2024-04-02
  • Python怎么使用opencv进行手势识别
    这篇文章将为大家详细讲解有关Python怎么使用opencv进行手势识别,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。原理首先先进行手部的检测,找到之后会做Hand Landmarks。将手掌的21个点找...
    99+
    2023-06-26
  • 怎么在C++中使用opencv实现一个车道线识别功能
    本篇文章为大家展示了怎么在C++中使用opencv实现一个车道线识别功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。(一)目前国内外广泛使用的车道线检测方法主要分为两大类:(1) 基于道路特征的车...
    99+
    2023-06-06
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作