基于opencv的答题卡识别

文章目录

一、背景需求

传统的手动评分方法耗时且容易出错,自动化评分可以可以显著提高评分过程的速度和准确性、减少人工成本。

答题卡图片处理效果如下:

二、处理步骤

图片预处理

python 复制代码
# 读图片
img = cv2.imread('./images/test_01.png')
cv_show('img', img)
# 变成黑白图片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 去掉一些噪点
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
cv_show('blurred', blurred)

# 边缘检测
edged = cv2.Canny(blurred, 75, 200)
cv_show('edged', edged)

检测到答题卡轮廓

python 复制代码
# 检测轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
# 画轮廓会修改被画轮廓的图. 
contours_img = img.copy()
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
cv_show('contous_img', contours_img)
# 确保我们拿到的轮廓是答题卡的轮廓.
if len(cnts) > 0:
    # 根据轮廓面积对轮廓进行排序.
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
    
    # 遍历每一个轮廓
    for c in cnts:
        # 计算周长
        perimeter = cv2.arcLength(c, True)
        # 得到近似的轮廓
        approx = cv2.approxPolyDP(c, 0.02 * perimeter, True)
        # 近似完了之后, 应该只剩下4个角的坐标.
#         print(c)
#         print(approx)
        if len(approx) == 4:
            # 保存approx
            docCnt = approx
            # 找到答题卡近似轮廓, 直接推荐.
            break

透视变换

python 复制代码
# 把透视变换功能封装成一个函数
def four_point_transform(image, pts):
    # 对输入的4个坐标排序
    rect = order_points(pts)
    (tl, tr, br, bl) = rect
    
    # 空间中两点的距离
    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)
    max_width = 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)
    max_height = max(int(heightA), int(heightB))
    
    # 构造变换之后的对应坐标位置.
    dst = np.array([
        [0, 0],
        [max_width - 1, 0],
        [max_width - 1, max_height - 1],
        [0, max_height - 1]], dtype='float32')
    
    # 计算变换矩阵
    M = cv2.getPerspectiveTransform(rect, dst)
    # 透视变换
    warped = cv2.warpPerspective(image, M, (max_width, max_height))
    return warped

透视变化后二值化:

python 复制代码
thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh', thresh)

此时图片预处理好后如果需要 OCR 文本识别可借助 tesseract 工具识别文字

python 复制代码
import pytesseract
from PIL import Image
# pytesseract要求的image不是opencv读进来的image, 而是pillow这个包, 即PIL,按照 pip install pillow
text = pytesseract.image_to_string(Image.open('./scan.jpg'))
print(text)

找每个圆圈的轮廓

python 复制代码
# 找到每一个圆圈的轮廓
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
thresh_contours = thresh.copy()
cv2.drawContours(thresh_contours, cnts, -1, 255, 3)
cv_show('thresh_contours', thresh_contours)
plt.imshow(thresh_contours, cmap='gray')

# 遍历所有的轮廓, 找到特定宽高和特定比例的轮廓, 即圆圈的轮廓.
question_cnts = []
for c in cnts:
    # 找到轮廓的外接矩形
    (x, y, w, h) = cv2.boundingRect(c)
    # 计算宽高比
    ar = w / float(h)
    
    # 根据实际情况制定标准.
    if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1:
        question_cnts.append(c)

轮廓排序

对轮廓按照y轴排序

python 复制代码
# 轮廓排序功能封装成函数
def sort_contours(cnts, method='left-to-right'):
    reverse = False
    # 排序的时候, 取x轴数据, i=0, 取y轴数据i =1
    i = 0
    if method == 'right-to-left' or method == 'bottom-to-top':
        reverse = True
    # 按y轴坐标排序
    if method == 'top-to-bottom' or method == 'bottom-to-top':
        i = 1
    # 计算每个轮廓的外接矩形
    bounding_boxes = [cv2.boundingRect(c) for c in cnts]
    (cnts, bounding_boxes) = zip(*sorted(zip(cnts, bounding_boxes), key=lambda b: b[1][i], reverse=reverse))
    return cnts, bounding_boxes
python 复制代码
# 按照从上到下的顺序对question_cnts排序.
question_cnts = sort_contours(question_cnts, method='top-to-bottom')[0]

判断是否答题正确

进一步按照x轴排序,构造圆圈轮廓的掩膜得出答案,最后和正确答案比较评判答题卡分数。

python 复制代码
# 正确答案
ANSWER_KEY = {0:1, 1:4, 2:0, 3:3, 4:1}
correct = 0
for (q, i) in enumerate(np.arange(0, 25, 5)):
#     print(q, i)
    # 每次取出5个轮廓, 再按照x轴坐标从小到大排序
    cnts = sort_contours(question_cnts[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)
#         cv_show('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]:
        correct += 1
        color = (0, 255, 0)
    # 绘图
    cv2.drawContours(warped, [cnts[k]], -1, color, 3)
        
# 计算分数
print(correct)
score = (correct / 5.0) * 100
print(f'score: {score:.2f}%')
cv2.putText(warped, str(score) + '%', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv_show('result', warped)
相关推荐
奔跑中的小象3 小时前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南3 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang4 小时前
LangGraph 状态机实战
人工智能
刹那芳华19924 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作4 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门4 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜4 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟4 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦4 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能
阿里云云原生5 小时前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能