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]<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)

    • 多目标检测
冯奎博客
请先登录后发表评论
  • latest comments
  • 总共1条评论
冯奎博客

多目标检测 :思路清晰,简洁明了

2021-08-18 09:42:50 回复