答题卡实战

2023-10-30


import cv2
import matplotlib.pyplot as plt
import numpy as np
import myutils
import argparse
import imutils.contours

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


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


# 读取输入
image = cv2.imread("D:\\images\\text.jpg")
contours_img = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
cv_show('blurred', blurred)
edged = cv2.Canny(blurred, 75, 200)
cv_show('edged', edged)
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, \
                        cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
cv_show('contours_img', contours_img)


def order_points(pts):
    # 一共四个坐标点
    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)]

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


def four_point_transform(img, 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(img, 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


cnt = cnts
dotCnt = None
if len(cnt) > 0:
    cnt = sorted(cnt, key=cv2.contourArea, reverse=True)
    for c in cnt:
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.02 * peri, True)
        if len(approx) == 4:
            dotCnt = approx
warp = four_point_transform(gray, dotCnt.reshape(4, 2))
# otsu's 阈值处理
thresh = cv2.threshold(warp, 0, 255, cv2.THRESH_BINARY_INV |
                       cv2.THRESH_OTSU)[1]
thresh_contours = thresh.copy()
# 找到每一个圆圈轮廓
cnt = cv2.findContours(thresh_contours, cv2.RETR_EXTERNAL,
                       cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(thresh_contours, cnt, -1, (0, 255, 0), 2)

questionCnts = []

cv_show('cnt', thresh_contours)

# 遍历
for c in cnt:
    #     计算比例和大小
    (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]
# cv2.drawContours(warp,questionCnts,1,(0,255,255),2)
# 每排有5个选项
correct = 0
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)
        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(warp, cnts[k], -1, color, 2)
cv2.imshow("warp", warp)
# print(correct)
score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(warp, "{:.2f}%".format(score), (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Exam", warp)
cv2.waitKey(0)
cv2.waitKey()
cv2.destroyAllWindows()

步骤:

1:轮廓检测

# 读取输入
image = cv2.imread("D:\\images\\text.jpg")
contours_img = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
cv_show('blurred', blurred)
edged = cv2.Canny(blurred, 75, 200)
cv_show('edged', edged)
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, \
                        cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
cv_show('contours_img', contours_img)

先进行灰度图-->平滑操作(高斯滤波去噪音)-->边缘检测-->轮廓检测(只检测最外层轮廓)

cv2.findContours函数中mode参数选择cv2.RETR_EXTERNAL(表示只检测外轮廓)

2:透视变换

def order_points(pts):
    # 一共四个坐标点
    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)]

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


def four_point_transform(img, 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(img, M, (maxWidth, maxHeight))
    #     反回变换后的结果
    return warped

先获得图像的几个顶点坐标-->再计算图像的宽和高-->重新定义图像的坐标

np.zeros函数的作用
返回来一个给定形状和类型的用0填充的数组;
zeros(shape, dtype=float, order=‘C’)
shape:形状
dtype:数据类型,可选参数,默认numpy.float64
order:可选参数,c代表与c语言类似,行优先;F代表列优先

s = pts.sum(axis=1) 

每行像素值进行相加,如果axis=0,则表示每列像素值相加

dotCnt = None
if len(cnt) > 0:
    cnt = sorted(cnt, key=cv2.contourArea, reverse=True)
    for c in cnt:
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.02 * peri, True)
        if len(approx) == 4:
            dotCnt = approx
warp = four_point_transform(gray, dotCnt.reshape(4, 2))
cv2.arcLength:函数用于计算封闭轮廓的周长或曲线的长度
arcLength(InputArray curve, bool closed);
参数
curve,输入的二维点集(轮廓顶点),可以是 vector 或 Mat 类型。
closed,用于指示曲线是否封闭。
cv2.approxPolyDP: 使用 Douglas-Peucker 算法求得一条顶点较少的多折线/多边形,以指定的精度近似输入的曲线或多边形

参数:

curve:输入点集,二维点向量的集合
approxCurve:输出点集,表示拟合曲线或多边形,数据与输入参数 curve 一致
epsilon:指定的近似精度,原始曲线与近似曲线之间的最大距离
close: 闭合标志,True 表示闭合多边形,False 表示多边形不闭合

判断答案

# 每排有5个选项
correct = 0
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)
        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(warp, cnts[k], -1, color, 2)

选定掩膜:

 mask = np.zeros(thresh.shape, dtype='uint8')
        cv2.drawContours(mask, [c], -1, 255, -1)
        cv_show('mask', mask)
        #         通过计算非零点数量来算是否选择这个答案
        mask = cv2.bitwise_and(thresh, thresh, mask=mask)
        total = cv2.countNonZero(mask)

np.zeros(thresh.shape, dtype='uint8')把图像(像素)置为0变为全黑,

cv2.drawContours(mask, [c], -1, 255, -1)对掩膜操作得到一个个选项的位置效果为

mask = cv2.bitwise_and(thresh, thresh, mask=mask)
total = cv2.countNonZero(mask)

 cv2.bitwise_and(thresh, thresh, mask=mask)

给定指定的掩膜,得到选项

countNonZero():返回灰度值不为0的像素数,可用来判断图像是否全黑

最终效果:

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

答题卡实战 的相关文章

随机推荐

  • HTC相关开发所需SDK等工具都在这里了

    HTC相关开发所需SDK等工具都在这里了 转 OpenVR SDKhttps github com ValveSoftware openvr OpenVR SDK是由原本的SteamWorks SDK更新而来 新增对HTC VIVE开发者版
  • 时间格式转换LongToString

    import java util Calendar import java util Date import org apache commons lang3 StringUtils import org apache commons la
  • 3.Qt消息机制和事件

    9 Qt消息机制和事件 好文来自https www cnblogs com weizhixiang p 5824345 html 一 事件 鼠标 敲下键盘 或者是窗口需要重新绘制的时候 都会发出一个相应的事件 Qt 程序需要在main 函数
  • 人工智能在游戏开发中的应用:你目前所需的 6 大 AI 工具

    游戏体量越大 质量越高 所要求的标准就越严格 尤其是在 AAA 级游戏市场 任何失误都可能导致你陷入极其棘手的境地 影响玩家体验 进而招致恶评 随着对游戏的需求和预期不断攀升 游戏开发人员比以往任何时候都需要更多帮助 那么 他们如何才能紧跟
  • Consumer位移管理-Kafka从入门到精通(十一)

    上篇文章说了 sesstion time out max poll interval ms max poll records和auto offset reset等参数 KafkaConsumer Kafka从入门到精通 十 https bl
  • eclipse如何创建多层包(多级包)

    包是Java中一个非常重要的概念 实质上包就是一个文件夹 我们在每次创建工程之前 要将不同的类放在不同的包里 以方便管理和避免类名重复所带来的麻烦 以后在使用其他包的类时 只需要使用 import 关键字进行包含就可以了 那么 在eclip
  • HTTPS 证书认证具体流程

  • Appium自动化框架从0到1之 日志文件配置(log.conf)

    在config文件中 我们先把log的输出格式 输出路径等参数抽离出来作为一个配置表 这个写法 在selenium自动化框架中 是没有分离的 所以 我们有get到一个新方法 代码如下 log conf loggers keys root i
  • C#中Console.WriteLine()的用法

    C 中Console WriteLine 的用法 以前用Console WriteLine 的时候就只会用它直接输出string字符串 但后来发现它还有其它在有些场合下会十分方便的输出方法 这篇就记录一下这些方法的使用吧 代码格式我就不写了
  • 向HashSet中添加元素的过程:

    向HashSet中添加元素的过程 1 当向 HashSet 集合中存入一个元素时 HashSet 会调用该对象的 hashCode 方法来得到该对象的 hashCode 值 然后根据 hashCode 值 通过某种散列函数决定该对象在 Ha
  • 微信小程序实战八:优惠券页面的实现

    文章目录 1 效果预览 2 wxml布局 3 js逻辑 4 样式设置 1 效果预览 2 wxml布局 顶部tab切换
  • cucumber ,运行feature一直提示 Undefined step: Given login baidu

    Undefined step Given login baidu You can implement missing steps with the snippets below Given login baidu public void l
  • 软件测试员必知!压力测试总共需要几个步骤?思路总结篇

    在运维工作中 压力测试是一项很重要的工作 比如在一个网站上线之前 能承受多大访问量 在大访问量情况下性能怎样 这些数据指标好坏将会直接影响用户体验 今天我们就来深入了解下压力测试 首先 什么是压力测试 软件压力测试是一种基本的质量保证行为
  • Python 实现 RAS 加解密(ras模块,pycrypto模块)

    一 pycrypto pycryptodome模块 1 模块安装说明 crypto这个模块的安装有点小坑 需要注意 crypto pycrypto pycryptodome的功能是一样的 crypto与pycrypto已经没有维护了 所以
  • uni-app开发微信小程序,textarea组件宽度设置,解决超出父级div

    问题描述 当我们使用textarea组件时 会出现设置宽度100 但其超出了父级Div 如下图 解决方案 添加box sizing border box 属性 即可完美解决 属性定义及使用说明 box sizing 属性定义如何计算一个元素
  • 【Python】高级变量类型

    目录 列表 List 编辑列表定义与结构 编辑列表的操作 元组 Tuple 编辑元组的定义 编辑元组的操作 编辑元组与格式化字符串 编辑元组和列表的转换 字典 dictionary 编辑字典的定义 编辑字典的操作 字符串 string 编辑
  • df和du显示的磁盘空间使用情况不一致的原因及处理

    df和du显示的磁盘空间使用情况不一致的原因及处理 在Linux下查看磁盘空间使用情况 最常使用的就是du和df了 然而两者还是有很大区别的 有时候其输出结果甚至非常悬殊 1 如何记忆这两个命令 du Disk Usage df Disk
  • 【深入理解C++】可调用对象、std::function、std::bind()

    文章目录 1 可调用对象 1 1 函数指针 1 2 函数对象 1 3 可被转换为函数指针的类对象 1 4 类成员函数指针 2 std function 2 1 包装普通函数 2 2 包装函数指针 2 3 包装函数对象 2 4 包装可被转换为
  • jdbc连接数据库的基本步骤

    1 在项目中导入java sql包 2 加载数据库驱动程序 Class forName com mysql cj jdbc Driver 3 定义数据库的链接地址 String url jdbc mysql localhost studen
  • 答题卡实战

    import cv2 import matplotlib pyplot as plt import numpy as np import myutils import argparse import imutils contours 正确答