添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

最近在进一步学习Python,在网上发现有使用opencv进行机读卡识别的,
就根据大神的文章,跟着学习,自己写了一个机读卡识别,
文章一: opencv+python机读卡识别整合版
文章二: python CV 趣味项目 答题卡识别
为了减轻前期的难度,这里本篇文章中我们使用扫描的图片进行识别,
下一篇文章(进阶版) 中,会介绍进阶处理,图片拉伸裁切,边缘处理,四点变化等,
在本篇中我们只介绍图片的预处理,填图位置的识别,图片选择题答案判断输出。
本地使用环境(win10 64位,python3.7)

一、加载图片,将它转换为灰阶,轻度模糊,然后二值化处理。

# 加载图片,将它转换为灰阶,轻度模糊,然后二值化处理。
image = cv2.imread("./image/demo1.jpg")
#转换为灰度图像
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
#高斯滤波
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
#自适应二值化方法
blurred = cv2.adaptiveThreshold(blurred,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,51,2)
adaptiveThreshold函数:第一个参数src指原图像,原图像应该是灰度图。
    第二个参数x指当像素值高于(有时是小于)阈值时应该被赋予的新的像素值
    第三个参数adaptive_method 指: CV_ADAPTIVE_THRESH_MEAN_C 或 CV_ADAPTIVE_THRESH_GAUSSIAN_C
    第四个参数threshold_type  指取阈值类型:必须是下者之一  
                                 •  CV_THRESH_BINARY,
                        • CV_THRESH_BINARY_INV
    第五个参数 block_size 指用来计算阈值的象素邻域大小: 3, 5, 7, ...
    第六个参数param1    指与方法有关的参数。对方法CV_ADAPTIVE_THRESH_MEAN_C 和 CV_ADAPTIVE_THRESH_GAUSSIAN_C, 它是一个从均值或加权均值提取的常数, 尽管它可以是负数。

二、对选择题图像部分预处理

重新定义图片的大小:5000*7000,便于确定选择题区域与答案。
选择题部分最大的特点是需要将黑块突出,以及过滤掉没填涂的选项,以便确认。
预处理方法选择均值滤波及二进制二值化的方法。

#重塑可能用到的图像
thresh = cv2.resize(blurred, (5000, 7000), cv2.INTER_LANCZOS4)
fImage = cv2.resize(image, (5000, 7000), cv2.INTER_LANCZOS4)
#均值滤波
ChQImg = cv2.blur(thresh, (50, 50))
#二进制二值化
ChQImg = cv2.threshold(ChQImg, 20, 225, cv2.THRESH_BINARY)[1]
    threshold参数说明
    第一个参数 src    指原图像,原图像应该是灰度图。
    第二个参数 x      指用来对像素值进行分类的阈值。
    第三个参数 y      指当像素值高于(有时是小于)阈值时应该被赋予的新的像素值
    第四个参数 Methods  指,不同的不同的阈值方法,这些方法包括:
                •cv2.THRESH_BINARY        
                •cv2.THRESH_BINARY_INV    
                •cv2.THRESH_TRUNC        
                •cv2.THRESH_TOZERO        
                •cv2.THRESH_TOZERO_INV    

三、寻找结果中黑块坐标

这里寻找坐标的目的是为了确定黑块所代表的题号及选项,用轮廓中心来进行描述

#在二值图像中查找轮廓
cnts = cv2.findContours(ChQImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#用边缘检测绘制
cnts = cnts[1] if imutils.is_cv3() else cnts[0]
for c in cnts:
    # 计算轮廓的边界框,然后利用边界框数据计算宽高比
    (x, y, w, h) = cv2.boundingRect(c)
    if (w > 60 and h > 20) and x>370 and x<4700 and y>3390 and y<4100:
        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)
        #保存题目坐标信息
        Answer.append((cX, cY))

四、循环坐标计算选择题题号及答案

相关处理函数

#题号换行
def judgey(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(x, y=1):
    letter = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    return letter[x%(5*y)-1]
#获取题号
def judge0(x, y, m, n):
    if x/5<1 :
        num = judgey(y)
    elif x/5<2 and x/5>=1:
        num = judgey(y)+5
    elif x/5<3 and x/5>=2:
        num = judgey(y)+10
    elif x/5<4 and x/5>=3:
        num = judgey(y)+15
    else:
        num = judgey(y)+20
    option = judgex(x)
    return [num, option]

循环判断坐标点:

IDAnswer = []
# 循环判断坐标点
for i in Answer:
    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 = judge0(j, k, i[0], i[1])
                    IDAnswer.append(option)
IDAnswer.sort()
print(IDAnswer);

最后输出获取所有选项及答案:

完整测试代码:

# -*- coding: utf-8 -*-
# @Author: [FENG] <[email protected]>
# @Date:   2020-04-27 11:25:05
# @Last Modified by:   [FENG] <[email protected]>
# @Last Modified time: 2020-05-17 17:39:30
# -*- coding: utf-8 -*-
import imutils
import cv2
import csv
# 加载图片,将它转换为灰阶,轻度模糊,然后二值化处理。
image = cv2.imread("./image/demo1.jpg")
#转换为灰度图像
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
#高斯滤波
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
#自适应二值化方法
blurred = cv2.adaptiveThreshold(blurred,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,51,2)
#重塑可能用到的图像
thresh = cv2.resize(blurred, (5000, 7000), cv2.INTER_LANCZOS4)
fImage = cv2.resize(image, (5000, 7000), cv2.INTER_LANCZOS4)
#均值滤波
ChQImg = cv2.blur(thresh, (40, 40))
#二进制二值化
ChQImg = cv2.threshold(ChQImg, 30, 225, cv2.THRESH_BINARY)[1]
questionCnts = []
Answer = []
#在二值图像中查找轮廓
cnts = cv2.findContours(ChQImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#用边缘检测绘制
cnts = cnts[1] if imutils.is_cv3() else cnts[0]
for c in cnts:
    # 计算轮廓的边界框,然后利用边界框数据计算宽高比
    (x, y, w, h) = cv2.boundingRect(c)
    if (w > 60 and h > 20) and x>370 and x<4700 and y>3390 and y<4100:
        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)
        #保存题目坐标信息
        Answer.append((cX, cY))
xt1=[0,580,740,885,1036,1190,1460,1610,1765,1915,2060,2345,2500,2640,2800,2965,3110,3260,3410,3560,3720,3890,4045,4190,4345,4510,5000]
yt1=[3390,3590,3710,3825,3935,4100]
#题号换行
def judgey(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(x, y=1):
    letter = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    return letter[x%(5*y)-1]
#获取题号
def judge0(x, y, m, n):
    if x/5<1 :
        num = judgey(y)
    elif x/5<2 and x/5>=1:
        num = judgey(y)+5
    elif x/5<3 and x/5>=2:
        num = judgey(y)+10
    elif x/5<4 and x/5>=3:
        num = judgey(y)+15
    else:
        num = judgey(y)+20
    option = judgex(x)
    return [num, option]
IDAnswer = []
# 循环判断坐标点
for i in Answer:
    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 = judge0(j, k, i[0], i[1])
                    IDAnswer.append(option)
IDAnswer.sort()
print(IDAnswer);

原始扫描图片: