python 答题卡识别_opencv+python机读卡识别(最终版)

本文是对之前编写的机读卡进行完善,

只记录相关代码,不介绍具体编写流程,

具体流程:opencv+python机读卡识别(进阶版)

完善相关机读卡的适配,记录相关调试函数以及使用方法。

# -*- coding: utf-8 -*-

# @Author: [FENG] <1161634940@qq.com>

# @Date: 2020-05-12 12:35:58

# @Last Modified by: [FENG] <1161634940@qq.com>

# @Last Modified time: 2020-07-14 19:09:14

from imutils.perspective import four_point_transform

import imutils

import numpy

import cv2

import csv

import os

import re

import time

import matplotlib.pyplot as plt

from PIL import Image

# import tkinter as tk

from tkinter import filedialog

import win32api,win32con

class cardReading(object):

# 机读卡信息读取

def reading(self, url, count):

# 加载图片,将它转换为灰阶,轻度模糊,然后边缘检测。

image = cv2.imread(url)

#转换为灰度图像

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

#高斯滤波

blurred = cv2.GaussianBlur(gray, (7, 7), 0)

#自适应二值化方法

blurred=cv2.adaptiveThreshold(blurred,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,51,2)

blurred=cv2.copyMakeBorder(blurred,5,5,5,5,cv2.BORDER_CONSTANT,value=(255,255,255))

edged = cv2.Canny(blurred, 75, 200)

cnts = cv2.findContours(edged, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

cnts = cnts[1] if imutils.is_cv3() else cnts[0]

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

# (右下角特殊)对右下角坐标点进行处理

bottom_left = bottom_right = None

for x in range(0, len(docCnt)):

doc = list(docCnt[x][0]);

doc.append(x)

if doc[0] < 1000 and doc[1] > 1500:

bottom_left = doc

if doc[0] > 1000 and doc[1] > 1500:

bottom_right = doc

if bottom_left is not None and bottom_right is not None:

if abs(bottom_right[1] - bottom_left[1]) > 70:

docCnt[bottom_right[2]][0][1] = bottom_right[1] + 70

else:

docCnt[bottom_right[2]][0][0] = bottom_right[0] + 70

# 使用封装方法,获取相关坐标点

(fImage, cnts) = self.coordinate_point(docCnt, image, 101, (5000,7000), (60,60), 8)

newImage = fImage

Answer = [] # 题号相关坐标

quyu = [] # 进一步获取答题区域

for c in cnts:

# 计算轮廓的边界框,然后利用边界框数据计算宽高比

(x, y, w, h) = cv2.boundingRect(c)

# if ((y>3320 and y<5400) or (y>2090 and y<3100)) and x > 400 and x < 4730 and w > 60 and h > 20:

if w > 80 and h > 40:

M = cv2.moments(c)

cX = int(M["m10"] / M["m00"])

cY = int(M["m01"] / M["m00"])

#绘制中心及其轮廓

cv2.drawContours(fImage, c, -1, (0, 0, 255), 5, lineType=0)

cv2.circle(fImage, (cX, cY), 7, (255, 255, 255), -1)

#保存题目坐标信息

quyu.append([cX, cY])

# 处理四点位置

quyu = self.bubble_sort(quyu)

if len(quyu) == 6: # 特殊点处理

quyu.pop(5)

quyu.pop(2)

quyu =[[v] for v in quyu]

quyu = numpy.array(list(zip(quyu)))

# 使用封装方法,获取相关坐标点

(fImage, cnts) = self.coordinate_point(quyu, newImage, 201, (2000, 2800), (20,20), 80)

for c in cnts:

# 计算轮廓的边界框,然后利用边界框数据计算宽高比

(x, y, w, h) = cv2.boundingRect(c)

if ((y>774 and y<1200) or (y>1360 and y<2226)) and w > 40 and h > 15:

# if ((y>3320 and y<5400) or (y>2090 and y<3100)) and x > 400 and x < 4730 and w > 60 and h > 20:

M = cv2.moments(c)

cX = int(M["m10"] / M["m00"])

cY = int(M["m01"] / M["m00"])

#绘制中心及其轮廓

cv2.drawContours(fImage, c, -1, (0, 0, 255), 5, lineType=0)

cv2.circle(fImage, (cX, cY), 10, (255, 255, 255), -1)

#保存题目坐标信息

Answer.append((cX, cY))

# self.see_img(fImage)

xt0=[0,190,390,590,790,990,1190,1390,1590,1790,1990,2000]

yt0=[774,824,865,907,949,988,1031,1073,1115,1156,1200]

xt1=[0,120,194,260,330,400,520,590,660,728,790,925,991,1058,1130,1200,1270,1339,1408,1476,1550,1630,1698,1768,1837,1907,2000]

yt1=[1360,1421,1472,1525,1574,1640,1712,1763,1816,1866,1940,2006,2060,2110,2160,2226]

student = []

IDAnswer = []

# 获取对应题号与选项

for i in Answer:

if i[1] > yt0[0] and i[1] < yt0[-1]:

student.append(i)

else :

for j in range(0,len(xt1)-1):

if i[0]>xt1[j] and i[0]

for k in range(0,len(yt1)-1):

if i[1]>yt1[k] and i[1]

option = self.judge0(j, k, i[0], i[1])

IDAnswer.append(option)

xuehao = '';

for i in self.bubble_sort(student):

for k in range(0,len(yt0)-1):

if i[1]>yt0[k] and i[1]

xuehao += str(k)

result = {'学号':str(xuehao), '备注':''}

IDAnswer.sort()

newIDAnswer = {x+1:'' for x in range(60)}

for answer in IDAnswer:

if answer[0] <= 60 and newIDAnswer[answer[0]] != answer[1]:

newIDAnswer[answer[0]] += answer[1]

# 循环判断相关选项

for k,v in newIDAnswer.items():

# print(k,v);

if k >= 0 and k <= 20:

result['备注'] += self.option_judgment(k, v, 'ABC')

elif k >= 36 and k <= 40:

result['备注'] += self.option_judgment(k, v, 'ABCDEFG', 0)

else:

result['备注'] += self.option_judgment(k, v)

result.update(newIDAnswer)

# print(result)

# self.see_img(fImage)

return result

# 画直线

def link_image(self, image, list, type='x', start=0, end=100, color=(0, 255, 0), thickness=2):

for x in list:

if type == 'x':

cv2.line(image, (x, start), (x, end), color, thickness)

else:

cv2.line(image, (start, x), (end, x), color, thickness)

return image

# 预览图片

def see_img(self, image, type=0):

cv2.namedWindow("image",0);

cv2.resizeWindow("image", 480, 640);

cv2.imshow("image", image)

cv2.waitKey(0)

if type == 0:

exit()

# 相关选项判断

def option_judgment(self, num, option, list='ABCD', length=1):

list = [x for x in list]

explain = ''

if option == '':

explain = '未选择'

elif length > 0 and len(option) > length:

explain = '选项长度错误'

else:

for x in option:

if x not in list:

explain = "不存在({0})选项".format(x)

if explain != '':

explain = '第{0}题{1}'.format(num, explain)

return explain

# 图片裁切拉伸

def coordinate_point(self, docCnt, image, *tup):

# print(docCnt)

if len(docCnt) == 4:

paper = four_point_transform(image, docCnt.reshape(4, 2))

warped = cv2.cvtColor(paper, cv2.COLOR_BGR2GRAY)

# warped = four_point_transform(gray, docCnt.reshape(4, 2))

# 对灰度图应用二值化算法

thresh = cv2.adaptiveThreshold(warped, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, tup[0], 2)

# 拉伸

thresh = cv2.resize(thresh, tup[1], cv2.INTER_LANCZOS4)

fImage = cv2.resize(paper, tup[1], cv2.INTER_LANCZOS4)

ChQImg = cv2.blur(thresh, tup[2])

ChQImg = cv2.threshold(ChQImg, tup[3], 225, cv2.THRESH_BINARY)[1]

# 在二值图像中查找轮廓

cnts = cv2.findContours(ChQImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

cnts = cnts[1] if imutils.is_cv3() else cnts[0]

return [fImage, cnts]

# 冒泡排序

def bubble_sort(self, list):

count = len(list)

for i in range(count):

for j in range(i + 1, count):

if list[i] > list[j]:

list[i], list[j] = list[j], list[i]

return list

# 卷子model0判题

def judgey0(self, y):

if (y / 5 < 1):

return y + 1

elif y / 5 < 2 and y/5>=1:

return y % 5 + 25 + 1

else:

return y % 5 + 45 + 1

# 获取选项

def judgex(self, x, y=1):

letter = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

return letter[x%(5*y)-1]

# 获取目录下的文件

def print_all_file_path(self, init_file_path, keyword='jpg'):

url = []

for cur_dir, sub_dir, included_file in os.walk(init_file_path):

if included_file:

for file in included_file:

if re.search(keyword, file):

url.append(cur_dir + "/" + file)

return url

# 题号与对应选项进行处理

def judge0(self, x, y, m, n):

# score = 20

if n > 1640 and n < 1936:

if x/5<1 :

num = self.judgey0(y)

elif x/5<2 and x/5>=1:

num = self.judgey0(y)+5

elif x/5<4 and x/5>=2:

num = self.judgey0(y)+10

else:

num = self.judgey0(y)+15

else:

num = self.judgey0(y)+5*int(x/5)

if m > 925 and m < 1475 and n > 1640 and n < 1936:

option = self.judgex(x, 2)

else:

option = self.judgex(x, 1)

return [num, option]

if __name__=="__main__":

cardReading = cardReading()

url = cardReading.print_all_file_path("./card");

if len(url) == 0:

Folderpath = filedialog.askdirectory() # 获得选择好的文件夹

url = cardReading.print_all_file_path(Folderpath);

# print(url)

if len(url) > 0:

# for x in range(0,len(url)):

# # print(url[x])

# aaaa = cardReading.reading(url[x], x)

# print(aaaa)

# # exit()

with open('names.csv', 'w') as csvfile:

fieldnames = list(range(1, 61))

fieldnames.insert(0,'学号')

fieldnames.insert(1,'备注')

writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

writer.writeheader() # 注意有写header操作

for x in range(0,len(url)):

# print(url[x])

aaaa = cardReading.reading(url[x], x)

writer.writerow(aaaa)

print(aaaa)

if x == len(url)-1:

print('已全部读取');

for i in range(0,3):

time.sleep(1)

print('即将关闭...%2d.....' % (3-i));

else:

win32api.MessageBox(0, "未选择识别目录", "提醒", win32con.MB_OK)

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值