最近在进一步学习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);
原始扫描图片: