2021-08-15

自动判卷 、答题卡识别、六级答题卡客观题自动判卷系统1.0

一、前言

二、代码

三、处理原图片、以及效果图片

四、总结

  • 一.引言:
    1.本程序设计想法来源于一次四六级考试,因为六级没有好好准备,裸考上阵,结果肯定是看不懂哈哈哈,边做还边走神,想着既然学了opencv,要不自己搞个六级的判卷系统玩玩。于是考试完就网上找了一张六级答题卡彩印后随便填涂一些信息。总共跨越一两个月才完成吧(一直完成其它事情拖着)。但是写程序时间就三天左右。一方面是因为答题卡的质量,是A4纸,以及放书包有些变形,导致处理起来有些麻烦。
    2.目前的自动判卷以及答题卡识别是很多人都做过的,我也只是练习一下,其实还有很多需要改进的。
    3.本程序大家可以代码直接复制过去,然后根据我提供的图片,将图片路径改一下,运行就可以用了哦。如果没有对应的一些包,可以在终端输入一下代码就行啦。(package:需要下载的库)
pip install --index-url https://pypi.douban.com/simple   package
  • 二.程序总代码:

"""
程序思路:
1.首先参考网上试卷检测的相关例程,模仿着来,网上大部分检测部分基本用的Canny算法,
但是我用了试过调Canny函数的参数、以及通过图像增强进行边缘检测,但是效果不佳,出现断断续续的效果,不能用于检测图片。

2.出现这种情况,我对各种边缘检测算法进行了尝试,其中sobel的边缘检测算法效果比较好,laplacian算法较为一般。

3.当轮廓提取出来后,下一步就是对轮廓进行了框选,我一开始的想法只是外接矩形,或者一个正接的矩形,先是保留自己的看法,
参考网上,基本都是Canny边缘检测算法后进行霍夫直线的检测。我也就跟着进行霍夫直线试了很多次,但是效果一直不佳,
一方面是我答题卷四条边直线度的问题,由于不是纯的直线,所以进行直线检测的时候就会出现很多杂乱的曲线。网上的例子能检测出直线,
然后进行求四个交点,方便后面进行透视变换。由于这个方法行不通,所以准备换另一种方法进行检测框。

4.这个时候我就参数了二值化的两种方法,其中就是全局二值化,另一个是局部阈值化,全局二值化只是用一个阈值进行分割,局部阈值化能进行手动设置图片的阈值个数
二值化之后进行进行框选了,这个时候我就开始按照一开始的想法进行狂框选了,用的是最小外接矩形,与正接矩形,但是因为框选出来的误差太大,除了试卷还有很多背景
后面进行发现最大轮廓————>填充颜色————>sobel边缘检测(看看会不胡边缘会端正了很多,但是结果跟没天赐差不多)
————>发现可以用拟合矩形进行检测。于是我就尝试了利用sobel+拟合矩形的方式进行检测,结果效果不错,但是存在另一个问题,一方面答题卷没有完全拍摄到的时候就会出现框选不出来。、

5.改进,为什么不直接用二值化直接检测答题卷呢,最后直接二值化+矩形拟合,检测多张图片后效果良好。

"""

#遇到问题:1-3图片角度跟7——16的图片角度不一样,导致识别方向出现了问题,故可以利用获取坐标点进行排序。
# 功能:利用二值化+矩形拟合
# 链接:https://blog.csdn.net/xue_csdn/article/details/97616177
# 矩形拟合:https://segmentfault.com/a/1190000015663722
# 透视变换坐标对应:https://blog.csdn.net/xxxy502/article/details/89668722#:~:text=python%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86%EF%BC%9A%E9%80%8F%E8%A7%86%E5%8F%98%E6%8D%A2%E5%8E%9F%E7%90%86%E6%95%88%E6%9E%9C%E4%BB%A3%E7%A0%81%20%E5%8E%9F%E7%90%86%20%E9%80%8F%E8%A7%86%E5%8F%98%E6%8D%A2%28Perspective%20Transformation%29%E6%98%AF%E5%B0%86%E5%9B%BE%E7%89%87%E6%8A%95%E5%BD%B1%E5%88%B0%E4%B8%80%E4%B8%AA%E6%96%B0%E7%9A%84%E8%A7%86%E5%B9%B3%E9%9D%A2%28Viewing,Plane%29%EF%BC%8C%E4%B9%9F%E7%A7%B0%E4%BD%9C%E6%8A%95%E5%BD%B1%E6%98%A0%E5%B0%84%28Projective%20Mapping%29%E3%80%82%20%E9%80%9A%E7%94%A8%E7%9A%84%E5%8F%98%E6%8D%A2%E5%85%AC%E5%BC%8F%E4%B8%BA%EF%BC%9A%20%20u%2Cv%E6%98%AF%E5%8E%9F%E5%A7%8B%E5%9B%BE%E7%89%87%E5%9D%90%E6%A0%87%EF%BC%8C%E5%AF%B9%E5%BA%94%E5%BE%97%E5%88%B0%E5%8F%98%E6%8D%A2%E5%90%8E%E7%9A%84%E5%9B%BE%E7%89%87%E5%9D%90%E6%A0%87x%2Cy%2C%E5%85%B6%E4%B8%AD%E3%80%82
# coding=utf-8



# 导入相关的库
import cv2
import cv2 as cv
import numpy as np
from functools import reduce
import operator
import math



def ad_number(pos):
    """准考证号识别"""
    list_y = [190,245,300,355,400,460,520,570,630,680]
    list_num = [0,1,2,3,4,5,6,7,8,9]
    for i,y  in enumerate(list_y):
        if pos >y-25 and pos x-20 and pos x-20 and pos  5000:
            """过滤一些不必要的轮廓"""
            x, y, w, h = cv.boundingRect(c)  # 获取矩形框的四个参数
            mm = cv.moments(c)  # 几何重心的获取
            cx = mm['m10'] / mm['m00']  # 获取几何重心的x
            cy = mm['m01'] / mm['m00']  # 获取几何重心的y
            if w / h > 2:
                """对长条形红色线框进行追加"""
                # i+=1
                # cv2.circle(result, (np.int(cx), np.int(cy)), 15, (0, 255, 0), -1)  # 画出中心
                # cv.rectangle(result, (x, y), (x + w, y + h), (255, 0, 0), 10)  # 画外接矩形
                # cv.putText(result, str(i), (np.int(cx), np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 4,(255,255,0), 10)
                line.append(c)
            else:
                """对内容框进行追加"""
                # j += 1
                # cv.rectangle(result, (x, y), (x + w, y + h), (255, 255, 0), 10)  # 画外接矩形
                # cv.putText(result, str(j), (np.int(cx), np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 4,(255,255,0), 30)

                content.append(c)

    # ------------------------------获取学号以及选项区域------------------------------------#
    x1, y1, w1, h1 = cv.boundingRect(content[2])  # 获取学号位置线框轮廓正接矩形
    x2, y2, w2, h2 = cv.boundingRect(line[1])  # 获取第二条红色框轮廓正接矩形
    x3, y3, w3, h3 = cv.boundingRect(line[0])  # 获取第条红色框轮廓正接矩形
    cv.rectangle(result, (x1, y1), (x1 + w1, y1 + h1), (255, 0, 0), 20)  # 画外接矩形
    cv.rectangle(result, (x2, y2 + h2), (x2 + w2, y3), (255, 0, 0), 20)  # 画外接矩形

    # --------------------------------选取roi-----------------------------------#

    roi_1_b_h = b_h[y1:y1 + h1, x1:x1 + w1]
    roi_2_b_h = b_h[y2 + h2:y3, x2:x2 + w2]
    roi_1 = result[y1:y1 + h1, x1:x1 + w1]
    roi_2 = result[y2 + h2:y3, x2:x2 + w2]



    return  roi_1_b_h,roi_2_b_h,roi_1,roi_2,b_h

def sort_stu_num_oul(roi_1_b_h):
    # -----------------------------学号轮廓排序------------------------------------#
    contours_1 = cv2.findContours(roi_1_b_h, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[
        1]  # 获取图片的轮廓    对HSV的图像进行二值化需要用到这个

    list_x1 = []  # 定义一个列表、用于储存每个轮廓对应的X坐标
    sort_contours_1 = []  # 用于储存排序好的轮廓
    for n, c_1 in enumerate(contours_1):
        """遍历轮廓对x坐标及逆行排序"""
        x, y, w, h = cv.boundingRect(c_1)
        list_x1.append(x)

    list_x2 = list_x1.copy()  # copy出来的列表用来排序
    list_x2.sort(reverse=False)

    for i in range(len(list_x1)):
        """对应的轮廓进行按找list_x2的顺序追加,行程按照x顺序排列的轮廓,sort_contours_1 """
        sort_contours_1.append(contours_1[list_x1.index(list_x2[i])])

    l = 0
    test_number = []

    for c_1 in sort_contours_1:
        """在轮廓中画出序号、"""
        l += 1
        x, y, w, h = cv.boundingRect(c_1)
        # 获取矩形框的四个参数
        mm = cv.moments(c_1)  # 几何重心的获取
        cx = mm['m10'] / mm['m00']  # 获取几何中心的x
        cy = mm['m01'] / mm['m00']  # 获取几何重心的y
        cv.rectangle(roi_1, (x, y), (x + w, y + h), (0, 255, 0), 5)  # 画外接矩形
        # cv.putText(roi_1, str(l), (np.int(cx), np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 2,(255,0,0), 3)
        num = ad_number(cy)
        test_number.append(num)  # 准考证号
        cv.putText(roi_1, str(num), (x, 80), cv.FONT_HERSHEY_SIMPLEX, 2.4, (0, 0, 255), 5)

    print("2.考生准考证号码为:\t", end="")
    for i in test_number:
        print(i, end="")
    print()


def opt(roi_2_b_h):
    # -----------------------------学号选择发现轮廓------------------------------------#

    contours_2 = cv2.findContours(roi_2_b_h, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[1]  # 获取图片的轮廓    对HSV的图像进行二值化需要用到这个
    # -----------------------------选择轮廓排序------------------------------------#

    yl_contours_2 = []  # 储存左边轮廓
    yr_contours_2 = []  # 储存右边轮廓
    sort_contours_l2 = []  # 用于储存排序好的轮廓
    sort_contours_r2 = []  # 用于储存排序好的轮廓

    list_yl1 = []  # 定义一个列表、用于储存每个轮廓对应的X坐标
    list_yr1 = []  # 定义一个列表、用于储存每个轮廓对应的X坐标

    list_x3 = []
    for i, c_2 in enumerate(contours_2):
        x, y, w, h = cv.boundingRect(c_2)
        # 获取矩形框的四个参数
        mm = cv.moments(c_2)  # 几何重心的获取
        cx = mm['m10'] / mm['m00']  # 获取几何中心的x
        cy = mm['m01'] / mm['m00']  # 获取几何重心的y
        # print( str(i)+"\t"+str(y) +"\t"+ str(x))
        if y < 1650 or x < 1000:
            list_yl1.append(y)
            yl_contours_2.append(c_2)
        else:
            list_yr1.append(y)
            list_x3.append(x)
            yr_contours_2.append(c_2)

    # --------------对左边轮廓进行排序---------------------------
    list_yl2 = list_yl1.copy()
    list_yl2.sort(reverse=False)
    for i in range(len(list_yl1)):
        sort_contours_l2.append(yl_contours_2[list_yl1.index(list_yl2[i])])

    # ----------------------画出左边轮廓----------------------------------
    m = 35
    answer_l_list = []
    rn = 0
    for c_2 in sort_contours_l2:
        m += 1
        x, y, w, h = cv.boundingRect(c_2)
        # 获取矩形框的四个参数
        mm = cv.moments(c_2)  # 几何重心的获取
        cx = mm['m10'] / mm['m00']  # 获取几何中心的x
        cy = mm['m01'] / mm['m00']  # 获取几何重心的y
        cv.putText(roi_2, str(m), (200, np.int(cy) + 20), cv.FONT_HERSHEY_SIMPLEX, 1.7, (255, 0, 0), 4)
        answer_l, color, rn = l_optioon(cx, (m - 36), rn)
        answer_l_list.append(answer_l)
        cv.rectangle(roi_2, (x, y), (x + w, y + h), color, 5)  # 画外接矩形
        cv.putText(roi_2, answer_l, (np.int(cx) + 100, np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 2, color, 4)

    # ----------------------画出右边轮廓-----------------------------

    list_yr2 = list_yr1.copy()
    list_yr2.sort(reverse=False)
    for i in range(len(list_yr1)):
        sort_contours_r2.append(yr_contours_2[list_yr1.index(list_yr2[i])])

    n = 60
    answer_r_list = []
    for c_2 in sort_contours_r2:
        n += 1
        x, y, w, h = cv.boundingRect(c_2)
        # 获取矩形框的四个参数
        mm = cv.moments(c_2)  # 几何重心的获取
        cx = mm['m10'] / mm['m00']  # 获取几何中心的x
        cy = mm['m01'] / mm['m00']  # 获取几何重心的y
        cv.putText(roi_2, str(n), (920, np.int(cy) + 20), cv.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 4)
        # cv.putText(roi_2, str(m), (130, np.int(cy)-20), cv.FONT_HERSHEY_SIMPLEX, 1.7,(255,0,0), 4)
        answer_r, color, rn = r_optioon(cx, n - 61, rn)
        answer_r_list.append(answer_r)
        cv.putText(roi_2, answer_r, (np.int(cx) + 100, np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 2, color, 4)
        cv.rectangle(roi_2, (x, y), (x + w, y + h), color, 5)  # 画外接矩形

    # print(answer_r_list)
    print("-" * 40)
    your_ansewer = answer_l_list + answer_r_list
    print("3.学生填选答案为:\t")
    for i, answer in enumerate(your_ansewer):
        print(str(i + 36) + ":" + answer, end="\t")
        if (i + 36) % 5 == 0:
            print()



    print("-"*40)
    print("4.分数为:\t",end='')
    print(rn*2)

    print("-"*50+"试卷批改完毕"+"-"*50)



#------------------图像预处理--------------------------------
img = cv2.imread("F:\\img\\test_paper\\15.jpg") # 读取图片、为BGR格式

begin()
result = Perspective_transformation(img)
roi_1_b_h,roi_2_b_h,roi_1,roi_2,b_h = get_roi(result)
sort_stu_num_oul(roi_1_b_h)
opt(roi_2_b_h)


# result[y1:y1+h1,x1:x1+w1]=roi_1
# result[y2+h2:y3,x2:x2+w2]=roi_2

cv.imwrite(".\\img\\result_11.jpg",result)
cv2.namedWindow("result",0)
cv2.imshow("result",result)
cv2.namedWindow("roi_2_b_h",0)
cv2.imshow("roi_2_b_h",roi_2_b_h)
cv2.namedWindow("roi_1_b_h",0)
cv2.imshow("roi_1_b_h",roi_1_b_h)
cv2.namedWindow("img",0)
cv2.imshow("img",img)


cv2.waitKey(0)
cv2.destroyAllWindows()
  • 三、处理原图片、以及效果图片
    1.原图片(是不是很清晰以及有些小变形,用边缘检测我提取不出来)
    2021-08-15_第1张图片
    2.效果图片
    2021-08-15_第2张图片
    2021-08-15_第3张图片
    2021-08-15_第4张图片

2021-08-15_第5张图片

  • 四、总结

1.本次程序存在缺陷,由于纸张的问题,以及背景的问题,手机拍摄的问题,还有我技术的问题,目前只能处理我上次的那张照片。由于时间的关系,后面我会利用某宝购买答题纸,利用USB工业相机进行拍摄,对处理算法进行优化。到时候再跟大家讲解一些实现思路。
2.本次代码没有用太多时间去整理,以及备注更多的内容,就是直接写完copy上来而已。所以有些乱。
3.对本文需要改进的地方大家可以多多留言、
4.觉得不错的小伙伴帮我帮我点个赞或者关注我支持一下哦!

你可能感兴趣的:(六级答题卡判卷系统,opencv,答题卡识别,python,opencv,图像识别,评分算法,算法)