文档章节

opencv+python机读卡识别整合版

Digimon
 Digimon
发布于 2017/07/12 16:09
字数 2581
阅读 3781
收藏 174

稍微整理了一下这个系列的一二三四章,可能看着更舒服吧……这个系列的解决方案不止一种,调参的方法也是各种各样,反正能够满足需求就极好了

1.预处理

这次的机读卡识别项目来源暑期培训,主要包括内容一张手机拍摄的机读卡位置定位,识别其中选择题模块及少量数字识别,给出样例图片: 机读卡样例

预处理目的:

对于这个识别问题而言,把图像变成二值图应该是最简单粗暴的方法了。为了找准边界,才能良好切割。而对于边缘检测的函数也只能传入灰度图……

1.1.环境配置: 环境是python3.5的,大体部分需要配置的是numpy+mlk版本,scipy,来支持opencv,另外辅助以imutils,这个包里面含有4点变换函数以及matplotlib来辅助绘图

import cv2
import matplotlib.pyplot as plt
import imutils
from imutils.perspective import four_point_transform

1.2.图片预处理 为了方便找出图片的4个顶点,所以需要一次自适应二值化,为了使图片效果更好,所以在二值化之前还加了一层高斯滤波

预处理效果图

#读入图片
image = cv2.imread("test10.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, 它是一个从均值或加权均值提取的常数, 尽管它可以是负数。
'''
#这一步可有可无,主要是增加一圈白框,以免刚好卷子边框压线后期边缘检测无果。好的样本图就不用考虑这种问题
blurred=cv2.copyMakeBorder(blurred,5,5,5,5,cv2.BORDER_CONSTANT,value=(255,255,255))

2.图像切割

图像切割的目的是将图像定个便于识别的样子。比如这里四角变换结束以后会吧图像变为2400*2800的大小,无论是什么样的案例图片,都是这个格式,这样最后在局部分割,如选择题答案的识别和数字区域的确定这套程序才能有较好的通用性

2.1.边缘检测 预处理得到二值图像就很容易做边缘检测了,找出4个点,方便之后的4点变换

#canny边缘检测
edged = cv2.Canny(blurred, 10, 100)
# 从边缘图中寻找轮廓,然后初始化答题卡对应的轮廓
'''
findContours
image -- 要查找轮廓的原图像
mode -- 轮廓的检索模式,它有四种模式:
     cv2.RETR_EXTERNAL  表示只检测外轮廓                                  
     cv2.RETR_LIST 检测的轮廓不建立等级关系
     cv2.RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,
              这个物体的边界也在顶层。
     cv2.RETR_TREE 建立一个等级树结构的轮廓。
method --  轮廓的近似办法:
     cv2.CHAIN_APPROX_NONE 存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max (abs (x1 - x2), abs(y2 - y1) == 1
     cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需
                       4个点来保存轮廓信息
      cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
'''
cnts = cv2.findContours(edged, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
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

顶点坐标的存放形式为3维数组,所以若想演示最大的4个顶点应做如下操作:

newimage=image.copy()
for i in docCnt:
    #circle函数为在图像上作图,新建了一个图像用来演示四角选取
    cv2.circle(newimage, (i[0][0],i[0][1]), 50, (255, 0, 0), -1)

四角选择图像

2.2.四点变换 四点变换直接调用大佬写好放在imutils中的函数就好了。这里存了两个,一个原图一个灰度图,原图用来配合展示,灰度图用来支配

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

四角变换效果

3.对选择题识别

3.1.对选择题图像部分预处理 经过四点变换后的图像需要经过重新转换标准长宽,以便对选择题部分标定题号及答案。这里的图像定义为2400*2800 。选择题部分最大的特点是需要将黑块突出,以及过滤掉没填涂的选项,以便确认。预处理方法选择均值滤波及二进制二值化的方法。

# 对灰度图应用二值化算法
thresh=cv2.adaptiveThreshold(warped,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,53,2)
#重塑可能用到的图像
thresh = cv2.resize(thresh, (width1, height1), cv2.INTER_LANCZOS4)
paper = cv2.resize(paper, (width1, height1), cv2.INTER_LANCZOS4)
warped = cv2.resize(warped, (width1, height1), cv2.INTER_LANCZOS4)
#均值滤波
ChQImg = cv2.blur(thresh, (23, 23))
#二进制二值化
ChQImg = cv2.threshold(ChQImg, 100, 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    
    '''

选择题部分预处理

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

# 在二值图像中查找轮廓
cnts = cv2.findContours(ChQImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
for c in cnts:
     # 计算轮廓的边界框,然后利用边界框数据计算宽高比
      (x, y, w, h) = cv2.boundingRect(c)
      if (w > 60 & h > 20)and y>900 and y<2000:
            M = cv2.moments(c)
            cX = int(M["m10"] / M["m00"])
            cY = int(M["m01"] / M["m00"])
            #绘制中心及其轮廓
            cv2.drawContours(paper, c, -1, (0, 0, 255), 5, lineType=0)
            cv2.circle(paper, (cX, cY), 7, (255, 255, 255), -1)
            #保存题目坐标信息
            Answer.append((cX, cY))

选择题轮廓勾勒

3.3.计算选择题题号及答案 比较绕,主要还是根据取余和倍数关系

def judgey0(y):
    if (y / 5 < 1):
        return  y + 1
    elif y / 5 < 2 and y/5>=1:
        return y % 5 + 20 + 1
    else:
        return y % 5 + 40 + 1
def judgex0(x):
    if(x%5==1):
        return 'A'
    elif(x%5==2):
        return 'B'
    elif(x%5==3):
        return 'C'
    elif(x%5==4):
        return 'D'
def judge0(x,y):
    if x/5<1 :
        #print(judgey0(y))
        return (judgey0(y),judgex0(x))
    elif x/5<2 and x/5>=1:
        #print(judgey0(y)+5)
        return (judgey0(y)+5,judgex0(x))
    elif x/5<3 and x/5>=2:
       # print(judgey0(y)+10)
        return (judgey0(y)+10,judgex0(x))
    else:
        #print(judgey0(y)+15)
        return (judgey0(y)+15,judgex0(x))

输出运算结果:

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]:
                    judge0(j,k)
                    IDAnswer.append(judge0(j,k))
#对答案部分重新排序,以最好的方式输出
IDAnswer.sort()
print(IDAnswer)
print(len(IDAnswer))

至此完成选择题部分 选择题演示

4.数字识别,调用百度api

数字识别经过测试总的感觉还是可以。需要注意的地方是要对数字板块需要切割出来这样给机器会好认点,但也不能单个字拿出来。最好能有一串,同时也需要注意图片尺寸

4.1.对数字图像部分进行处理 预处理部分同样需要,步骤与选择题模块相似,但目的不同,文字部分主要将数字变粗,便于识别。其实也就是和选择题模块相比变了几个参数

NumImg=cv2.blur(thresh,(15,15))
NumImg=cv2.threshold(NumImg, 170, 255, cv2.THRESH_BINARY)[1]

数字部分预处理

4.2.调用百度ocr api 试过多种检测方式,还是用别人家现成的好http://apistore.baidu.com/ 百度api使用方法: 首先需要注册一个百度云账号,这样在个人中心里就会看到apikey。这个就是和百度进行交流的钥匙。然后找到百度ocr的入口找到接口地址。虽然这里给出了但还是可以看下文档,里面有些细节,比如图片想免费就要300k以内。上面给的python示例代码是py2的,这里给出py3的方式(http://apis.baidu.com/idl_baidu/baiduocrpay/idlocrpaid)

import sys, urllib, json
import urllib.request
import urllib.parse
import base64
url = 'http://apis.baidu.com/idl_baidu/ocridcard/ocridcard'

data = {}
data['fromdevice'] = "pc"
data['clientip'] = "10.10.10.0"
data['detecttype'] = "LocateRecognize"
data['languagetype'] = "ENG"#英文模式
data['imagetype'] = "1"
#图片在本地

file_object = open('T.png','rb')
try:
     img = file_object.read( )
finally:
     file_object.close( )
data['image'] =base64.b64encode(img)


decoded_data = urllib.parse.urlencode(data)
decoded_data = decoded_data.encode('utf-8')

req = urllib.request.Request(url,decoded_data)

req.add_header("Content-Type", "application/x-www-form-urlencoded")
req.add_header("apikey", "这里填入个人中心的apikey") 

resp = urllib.request.urlopen(req)
content = resp.read()
if(content):
    content = json.loads(content.decode())
    print(content)

4.3.切割图片 根据具体情况需要切割图片才能让百度api识别,具体限制因素还是图片大小,切割方式,这里只给出示例

#切割具体位置[起始y:终止y,起始x:终止y]
tempimg1=img[240:461,213:939]
#图片切割,width,height分别填入目标宽高
tempimg1 = cv2.resize(tempimg1, (width, height), cv2.INTER_LANCZOS4)
#图片保存,png,jpg格式均可
 cv2.imwrite("T.png", tempimg1)

之后调用,若识别为英文需要转化,比如可能将0识别为D,这时转换即可,如:

def temp(char):
    if(char=='D'):
        return '0'

效果如图展示 数字识别样例 当然若是能想办法去掉答题卡外围边框效果应该会更好……

© 著作权归作者所有

共有 人打赏支持
Digimon
粉丝 40
博文 18
码字总数 14810
作品 0
成都
程序员
加载中

评论(11)

dereklin
dereklin
xt1,yt1的值是怎么来的?
kkHAIKE
kkHAIKE
把框印成虚的,会淡一些
Digimon
Digimon

引用来自“笨二十一”的评论

最后 考号识别好像有问题,1识别成0了?

引用来自“Digimon”的评论

是的,因为他有一个框,如果能想办法把框去掉应该还能再准确些

引用来自“笨二十一”的评论

可以尝试红色边框或者打分用红色笔,然后滤色,我也在做这块的产品
嗯嗯,有道理,想的是印刷出来的黑白是最经济的:joy:
笨二十一
笨二十一

引用来自“笨二十一”的评论

最后 考号识别好像有问题,1识别成0了?

引用来自“Digimon”的评论

是的,因为他有一个框,如果能想办法把框去掉应该还能再准确些
可以尝试红色边框或者打分用红色笔,然后滤色,我也在做这块的产品
Digimon
Digimon

引用来自“liujiest”的评论

666
:exclamation:
yyll11
yyll11
哇,
Digimon
Digimon

引用来自“笨二十一”的评论

最后 考号识别好像有问题,1识别成0了?
是的,因为他有一个框,如果能想办法把框去掉应该还能再准确些
liujiest
liujiest
666
bobo2cj
bobo2cj
不错
不点
mark 一下
北京中安未来电子护照阅读器(最新版本)

一、产品描述: 北京中安未来电子护照阅读器是一款外形轻巧美观的证件识读设备,它配备高清500万像素成像系统,采用TH-OCR技术可识别多种身份证件。可识读符合国际民航组织ICAO DOC 9303标准...

wenzuoyong123
04/23
0
0
关于机器识别或图像识别的问题求助?

把机读卡扫描成图片,通过对图片的处理,得到学生的得分。尽管有现成的机读卡,但是这个卡是定制的,所以只有通过自己做识别了,但是还没有思路,求大家集思广益。

小昭归来
2017/02/17
167
2
浅谈RFID技术在电子巡更中的作用

“电子巡更机”,很多人都不太认识这个是什么? 那么对于电子技术专业的人来说,如果,说起“RFID” 可能就会认识。 今天我们来聊一聊,基于RFID技术的智能巡更系统。 我们都知道读卡技术中,...

英泽FCARD
06/20
0
0
“智享未来 知行合一”,开为科技助力企业开启人工智能新时代

开为科技在春节后还将举行一场纵向产品发布会,以“新零售、新未来”为主题,届时会公布他们在新零售领域实现的两款杀手级产品。 2月6日,开为科技在中新南京生态科技岛召开了2018年首场产品...

行者武松
04/11
0
0
opencv中复制视频不成功,请大牛请教

大牛们,本人在opencv+python环境下,用下列代码创建新视频,文件创建成功,但是该视频文件没有任何内容写入,是什么原因啊 import cv2 videoCapture = cv2.VideoCapture('f://video.avi') ...

哈泥湖
2013/07/19
766
1

没有更多内容

加载失败,请刷新页面

加载更多

用Golang做了一个命令行贪吃蛇游戏

用Golang做了一个命令行贪吃蛇游戏 项目介绍 项目链接:https://gitee.com/lwow2025/snake-go 最近看了一本做几个小项目的书,突然就想用Golang做一个命令行贪吃蛇,也没啥特殊原因。 软件架...

Mediv
15分钟前
0
0
storm的利用并行度提高处理速度的经验

在storm的流计算框架中,在数据量非常大或者计算逻辑比较复杂的情况下,可能会造成处理速度变慢的情况,最后反而不满足了系统的处理要求,因此这里讨论一下。本文的内容是我在storm的使用过程...

飓风2000
25分钟前
0
0
课程推荐|深入浅出区块链博主:全栈区块链开发者的4堂必修课(线上优惠)

Tiny熊从2017年开始更新“深入浅出区块链”博客,在第一篇文章中,关于如何系统学习区块链技术,他这样描述:“从事区块链开发也有很多方向,如:区块链应用开发人员、区块链架构师、底层核心...

HiBlock
36分钟前
0
0
激活win10 亲测有效

1.首先,我们先查看一下Win10正式专业版系统的激活状态: 点击桌面左下角的“Windows”按钮,从打开的扩展面板中依次点击“设置”-“更新和安全”,并切换到“激活”选项卡,在此就可以查看到...

可达鸭眉头一皱
38分钟前
0
0
SpringWind180926

SpringWind SpringWind项目代码学习笔记 /SpringWind/src/main/webapp/WEB-INF/views/login.html 第15行action="#springUrl('/account/login.html')"【为什么是#springUrl】 第4行<a class=......

颖伙虫
51分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部