本文是对之前编写的机读卡进行完善,
只记录相关代码,不介绍具体编写流程,
具体流程: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]<xt1[j+1]:
for k in range(0,len(yt1)-1):
if i[1]>yt1[k] and i[1]<yt1[k+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]<yt0[k+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', newline='') 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)
本文为冯奎原创文章,转载无需和我联系,但请注明来自冯奎博客fengkui.net
最新评论