Python+Opencv答题卡识别用例详解
作者:我是马克思小清新 发布时间:2021-09-08 22:16:13
使用Python3和Opencv识别一张标准的答题卡。大致的过程如下:
1.读取图片
2.利用霍夫圆检测,检测出四个角的黑圆位置,从确定四个角的位置
3.利用透视变换和四个角的位置,矫正图片(直接用的网上的图片,没有拍照,所以这一步没有实现)
4.裁剪四个边框,获取边框上小黑格的位置
5.根据小黑格的位置确定每个涂卡区域的位置
6.将答题卡腐蚀和膨胀,遍历所有的格子的区域,计算每个区域内像素值为0的个数,若数量达到某个值,那么就确认这个格子是被黑笔涂过,并记录该位置的题目选项。
具体的实现
一、读取图片,用是imread函数。
二、利用霍夫圆检测位置,这里注意的是HoughCircles的param参数,调的不准,所以检测出来还有其他的干扰圆,最后可以通过设置半径阈值,将四个角的圆筛选出来。最后这个函数返回四个圆的位置和半径。
#检测图中的圆,并返回每个圆的位置和半径
def detect_circles_demo(image):
temp = image.copy()
rows,cols,channels = temp.shape
print(rows,cols)
location_vcol = []
dst = cv.pyrMeanShiftFiltering(image,10,100)
cimage = cv.cvtColor(dst,cv.COLOR_BGR2GRAY)
#不同的图片,Parmal的值是不一样的
circles = cv.HoughCircles(cimage,cv.HOUGH_GRADIENT,1,20,param1= 27,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
if i[2] < 20 and i[2] > 10:
cv.circle(image,(i[0],i[1]),i[2],(0,0,255),2)
location_vcol.append((i[0],i[1],i[2]))
# 画出图中圆的位置
cv.imshow("image",image)
return location_vcol
三、先将图片整体二直化,再在二直化的图片将四个边框裁剪下来,直接根据四个边角的位置向外或者向里增加或者较少一个半径,确定边框矩形的对角位置。
#获取四个边角的位置
location_RT = location_vcol[0]
location_RB = location_vcol[1]
location_LT = location_vcol[2]
location_LB = location_vcol[3]
# 先将原图二值化,注意阈值的取范围,再将二值化图片转换成BGR,方便后面标出小方格的位置
gray = cv.cvtColor(temp, cv.COLOR_BGR2GRAY)
ret, binary = cv.threshold(gray, 100, 255, cv.THRESH_BINARY_INV)
Matimage = cv.cvtColor(binary, cv.COLOR_GRAY2BGR)
cv.imshow("Matimage",Matimage)
#将图片的四条边裁剪出来
roilLeft = Matimage[location_LT[1] - location_LT[2]:location_LB[1] + location_LB[2],location_LT[0] -location_LT[2]:location_LB[0] + location_LB[2]]
roilRight = Matimage[location_RT[1] - location_RT[2]:location_RB[1] + location_RB[2],location_RT[0] - location_RT[2]:location_RB[0]+location_RB[2]]
roilTop = Matimage[location_LT[0] - location_LT[2]:location_RT[1]+location_RT[2],location_LT[0] - location_LT[2]:location_RT[0] +location_RT[2]]
roilBottom = Matimage[location_RB[1]-location_RB[2]:location_RB[1] + location_RB[2],location_LB[0] -location_LB[2]:location_RB[0]+location_RB[2]]
# 展示四条边的图片
cv.imshow("left",roilLeft)
cv.imshow("right", roilRight)
cv.imshow("top", roilTop)
cv.imshow("bottom", roilBottom)
四、根据裁剪的边框确定边框上小白格的位置。每个方位的计算方式是一样的。每个函数返回的是小格子在源图中的位置。
先来说底部的。这个过程是把截图看成一个坐标系,以行长为横坐标,列高为中坐标,计算统计每个横坐标下,有多少个点是白色的,并将该数值存放于列表vcol中。根据这种算法,能够得出这个列表中应该是rows个元素,而且每个元素的值不超过列高,不低于0。
遍历vcol列表,如果某一个位置为0,后一个位置不为0,那么这个位置就是小白格的起始位置。并记录起始位置在原图中的位置,方便后期遍历整个涂卡区域。
其他的三个方向是同样的道理,
#计算底部小黑格的位置
def sure_bottom(roilBottom,src,a):
rows, cols, channels = roilBottom.shape
src_rows,src_cols,src_channels = src.shape
itemp = 0
vcol = []
bottom_vcol = []
for i in range(0, cols):
for j in range(0, rows):
if roilBottom[j, i][0] == 255 and roilBottom[j, i][1] == 255 and roilBottom[j, i][2] == 255:
itemp = itemp + 1
vcol.append(itemp)
itemp = 0
for i in range(vcol.__len__()):
if 0 == vcol[i] and vcol[i + 1] > 0:
cv.line(roilBottom, (i, 0), (i, rows), (0, 0, 255), 2)
cv.line(src, (i + a, src_rows), (i + a, src_rows - rows), (0, 255, 0), 2)
bottom_vcol.append(i + a)
cv.imshow("src-1", roilBottom)
return bottom_vcol
#计算右部小黑格的位置
def sure_Right(roilRight,src,a):
rows, cols, channels = roilRight.shape
src_rows, src_cols, src_channels = src.shape
itemp = 0
vcol = []
right_vcol = []
for i in range(0, rows):
for j in range(0, cols):
if roilRight[i, j][0] == 255 and roilRight[i, j][1] == 255 and roilRight[i, j][2] == 255:
itemp = itemp + 1
vcol.append(itemp)
itemp = 0
print(vcol)
for i in range(vcol.__len__()):
if vcol[i] == 0 and vcol[i + 1] > 0:
cv.line(roilRight, (0, i), (cols, i), (0, 0, 255), 2)
cv.line(src, (src_cols - cols, i + a ), (src_cols, i + a), (0, 0, 255), 2)
right_vcol.append(i + a)
cv.imshow("src", src)
return right_vcol
#计算左边小黑格的位置
def sure_Left(roilLeft,src,a):
rows, cols, channels = roilLeft.shape
print(rows, cols)
itemp = 0
vcol = []
left_vcol = []
for i in range(0, rows):
for j in range(0, cols):
if roilLeft[i, j][0] == 255 and roilLeft[i, j][1] == 255 and roilLeft[i, j][2] == 255:
itemp = itemp + 1
vcol.append(itemp)
itemp = 0
for i in range(vcol.__len__()):
if vcol[i] == 0 and vcol[i + 1] > 0:
cv.line(roilLeft, (0, i), (cols, i), (0, 0, 255), 2)
cv.line(src, (16, i + a), (16 + cols, i + a), (0, 0, 255), 2)
left_vcol.append(i + a)
cv.imshow("src", src)
return left_vcol
# 确定顶部小黑格的位置
def sure_Top(roilTop,src,a):
rows, cols, channels = roilTop.shape
src_rows, src_cols, src_channels = src.shape
itemp = 0
vcol = []
top_vcol = []
for i in range(0, cols):
for j in range(0, rows):
if roilTop[j, i][0] == 255 and roilTop[j, i][1] == 255 and roilTop[j, i][2] == 255:
itemp = itemp + 1
vcol.append(itemp)
itemp = 0
for i in range(vcol.__len__()):
if vcol[i] == 0 and vcol[i + 1] > 0:
cv.line(roilTop, (i, 0), (i, rows), (0, 0, 255), 2)
cv.line(src, (i + a, 12), (a + i,48), (0, 0, 255), 2)
top_vcol.append(i + a)
cv.imshow("src", src)
return top_vcol
#获取原图位置
bottom_vcol = sure_bottom(roilBottom,temp,location_LB[0] -location_LB[2])
right_vcol = sure_Right(roilRight,temp,location_RT[1] - location_RT[2])
left_vcol = sure_Left(roilLeft,temp,location_LT[1] - location_LT[2])
top_vcol = sure_Top(roilTop,temp,location_LT[0] - location_LT[2])
五、在原图中全出每个小格子。在此之前要将图片做个预处理,腐蚀和膨胀。这个程序先圈出来的是答题区域的小格子,所以i和j 的范围分别是底部格子数和右边格子数。
先将每个格子的区域找出来,然后遍历这个格子的全部像素,判断是否为0 ,如果为0,那么就isum+1,最后遍历结束,看看isum的个数有没有达到总像素个数的10%,如果达到,那么就可以判定这个区域是被涂过的,记录这个区域对应的题号和选项,以备后面计分。
dst = sure_if_fill(temp)
for i in range(0,20):
for j in range(10,26):
rect = dst[bottom_vcol[i]:bottom_vcol[i] + 9,right_vcol[i]:right_vcol[j] + 3]
rect_up = (bottom_vcol[i],right_vcol[j])
rect_down = (bottom_vcol[i] + 9,right_vcol[j] + 3)
# 判断ROI区域是否被填充
isum = 0
for ii in range(rect.shape[0]):
for jj in range(rect.shape[1]):
if dst[ii,jj] == 0:
isum = isum + 1
# if isum > 0.1 * rect.shape[0] * rect.shape[1]:
cv.rectangle(temp,rect_down,rect_up,(0,255,0),2)
isum = 0
cv.imshow("dst",temp)
#检查空格是否被填充
def sure_if_fill(image):
temp = image.copy()
gray = cv.cvtColor(temp,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,100,255,cv.THRESH_BINARY)
kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))
dst = cv.erode(binary,kernel= kernel)
cv.imshow("dst",dst)
return dst
这是腐蚀后的图片
这是圈出所有答题区域的图片
来源:https://blog.csdn.net/qq_33531400/article/details/85690148
猜你喜欢
- 代码如下:'===================================== '转换内容,防止意外 '==
- 代码如下:<% set rs=server.createobject("adodb.recordset&
- 无论使用int还是varchar,对于Status的多选查询都是不易应对的。举例,常规思维下对CustomerStatus的Enum设置如下
- PHP children() 函数实例查找 note 节点的子节点:<?php $note=<<<XML<no
- MySQL是一个小型关系型数据库管理系统,开发者为瑞典MySQLAB公司,在2008年1月16号被Sun公司收购。MySQL被广泛地应用在I
- string iconv ( string $in_charset , string $out_charset , string $str
- 相比SQL Server 2000提供的FOR XML查询,SQL Server 2005版本对现有功能增强的基础上增加了不少新功能,最为吸
- 本文实例为大家分享了python可视化动态CPU性能监控的具体代码,供大家参考,具体内容如下打算开发web性能监控,以后会去学js,现在用m
- MongoDB是一个文档型数据库,是NOSQL家族中最重要的成员之一,以下代码封装了MongoDB的基本操作。MongoDBConfig.j
- 所以对应的asp处理代码如下代码如下:dedearr=split(xiangguanid2,chr(13)) '分割成数组
- 一、设计说明设计这个自动化的目的是想要交替、重复地使用固定的几个分区(分区编号01~05)来保存数据,当最后一个分区就是快满的时候,我们会把
- 在IE进行文档链接时,如果遇到OLE支持的文档,IE会自动调用相应程序打开它,有时候这种功能并不是我们所需的,虽然我们可以提醒用户用鼠标右键
- asp获取application对象代码如下: <%application("new&qu
- 本文实例讲述了Python实现获取nginx服务器ip及流量统计信息功能。分享给大家供大家参考,具体如下:#!/usr/bin/python
- 假设你有一套登录注册业务。一开始很简单,老板说只需要常规的注册登录就行。但是到了后面,接口被刷,老板然你在注册登录前加个验证码然后没过多久,
- Symfony是一个强大的基于PHP的Web开发框架,在这里我们用十分钟的时间来做一个简单的增删改查的程序, 任何不熟悉Symfony的人都
- # -*- coding:utf-8 -*-# python3.3.3import sys,time,re,urllib.par
- 1、XML 是什么?XML仅仅是一种数据存放格式,这种格式是一种文本(虽然XML规范中也提供了存放二进制数据的解决方案)。事实上有很多文本格
- Sybase于2008年11月4日在大中华区用户大会上宣布,联手神州数码金程(北京)科技有限公司对旗下领先的SQL Anywhere数据库进
- 本文实例讲述了php基于curl主动推送最新内容给百度收录的方法。分享给大家供大家参考,具体如下:php curl的好处可以以最快的方式并且