代码拉取完成,页面将自动刷新
# -*- coding: utf-8 -*-
# 参考 https://www.jianshu.com/p/2bbdb27ee7b3
#python3 t1.py --image images/IMG20190622074037.jpg
# 引入必要的库
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
import time
def showimg(img,name='cusom image'):
# cv2.namedWindow(name, cv2.WINDOW_NORMAL)
cv2.imshow(name, img)
cv2.waitKey(0)
# cv2.destroyAllWindows()
# 构建命令行参数解析并分析参数
# 对应使用方式 python3 t1.py --image images/IMG20190622074037.jpg
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to the input image")
args = vars(ap.parse_args())
# 构建答案字典,键为题目号,值为正确答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
# 加载图片,将它转换为灰阶,轻度模糊,然后边缘检测。
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 400)
## 展示图片
# showimg(edged)
start_time = time.time()
# 从边缘图中寻找轮廓,然后初始化答题卡对应的轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
if imutils.is_cv2() or imutils.is_cv4():
cnts = cnts[0]
else:
cnts = 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
# print(docCnt) #得到4个坐标
# 对原始图像和灰度图都进行四点透视变换
paper = four_point_transform(image, docCnt.reshape(4, 2))
warped = four_point_transform(gray, docCnt.reshape(4, 2))
# warped2 = four_point_transform(image, np.array([ [79,81],[29,1035],[742,1044],[734,99] ] ) )
# warped3 = four_point_transform(image, np.array([ [79,111],[59,1035],[742,1004],[734,99] ] ) )
# # 展示图片
# showimg(warped2)
# showimg(warped3)
############## 至此得到透视变换后的图像 ###########
############## 接下来开始做:以科级蓝表p2为例 ###########
'''
1.把透视变换后的图像处理为宽800高自动的图像,以便接下来基于间距定位进行处理。
2.裁剪图像边缘-20像素,以避免待会查找直线时查找到边缘线
3.查找直线,找出靠右的5条直线和靠下的9条直线,得到所有交点, # 二期:如果超出范围就改变参数再次查找
4.做一次透视变换得到比较方正的评分区域
5.再次裁剪边缘-10像素,以便排除边缘干扰
6.重新查找直线,获取交点坐标(这样就可以免去坐标转换的麻烦),记住交点坐标
7.去除直线(避免干扰填写判断)
8.对交点坐标划分出的单元格进行权重运算,高权重者即为被选项
【方案二】
1.把透视变换后的图像处理为宽800高自动的图像,以便接下来基于间距定位进行处理。
2.裁剪图像边缘-20像素,以避免待会查找直线时查找到边缘线
3.查找直线,得到所有交点(调大膨胀参数以便得到尽可能多的交点,之后再通过算法合并过于靠近的点)
4.对生成交点的图像进行膨胀,以便清除太相近的点,最后提取这些‘交点坐标’
5.算法计算相应单元格对应坐标(注意有偏移)
6.对交点坐标划分出的单元格进行透视变换/去除直线/裁剪等组合操作去除单元格边框干扰
7.对交点坐标划分出的单元格进行权重运算,高权重者即为被选项
'''
# 1.把透视变换后的图像处理为宽800高自动的图像,以便接下来基于间距定位进行处理。
x, y = warped.shape[0:2]
width = 800
r = width/y
dim = (width, int(x*r))
img800 = cv2.resize(warped, dim, interpolation=cv2.INTER_AREA)
#### cv2.imwrite("./images_save/img800.jpg",img800)
#showimg(img800)
# 2.裁剪图像边缘-20像素,以避免待会查找直线时查找到边缘线
x , y = img800.shape[0:2]
indent = 15 #裁剪像素
cropped = img800[indent:x-indent, indent:y-indent] #startX:endX, startY:endY
#### cv2.imwrite("./images_save/cropped.jpg",cropped)
# 3.查找直线,找出靠右的5条直线和靠下的9条直线,得到所有交点, # 二期:如果超出范围就改变参数再次查找
#二值化
gray = cropped
binary = cv2.adaptiveThreshold(~gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -10)
# cv2.imshow("cell", binary)
#### cv2.imwrite("./images_save/binary.jpg",binary)
# cv2.waitKey(0)
rows,cols=binary.shape
scale = 50 #关系到识别长短
erode = 2 #腐蚀参数
dilate = 5 #膨胀参数
#识别横线
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(cols//scale,1))
eroded = cv2.erode(binary,kernel,iterations = erode)
#cv2.imshow("Eroded Image",eroded)
dilatedcol = cv2.dilate(eroded,kernel,iterations = dilate)
#cv2.imshow("Dilated Image",dilatedcol)
#### cv2.imwrite("./images_save/dilatedcol.jpg",dilatedcol)
cv2.waitKey(0)
#识别竖线
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(1,rows//scale))
eroded = cv2.erode(binary,kernel,iterations = erode)
dilatedrow = cv2.dilate(eroded,kernel,iterations = dilate)
#cv2.imshow("Dilated Image",dilatedrow)
#### cv2.imwrite("./images_save/dilatedrow.jpg",dilatedrow)
cv2.waitKey(0)
# 标识交点
bitwiseAnd = cv2.bitwise_and(dilatedcol,dilatedrow)
# cv2.imshow("bitwiseAnd Image",bitwiseAnd)
#### cv2.imwrite("./images_save/bitwiseAnd.jpg",bitwiseAnd)
cv2.waitKey(0)
#标识表格
# merge = cv2.add(dilatedcol,dilatedrow)
# cv2.imshow("add Image",merge)
# cv2.imwrite("./images_save/merge.jpg",merge)
# cv2.waitKey(0)
# 4.对生成交点的图像进行膨胀,以便清除太相近的点,最后提取这些‘交点坐标’
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(4,4))
dilateddot = cv2.dilate(bitwiseAnd,kernel,iterations = 1)
# cv2.imshow("dilateddot Image 2",dilateddot)
cv2.waitKey(0)
#### cv2.imwrite("./images_save/dilateddot.jpg",dilateddot)
# 提取坐标
dots = []
cnts = cv2.findContours(dilateddot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if imutils.is_cv2() or imutils.is_cv4():
cnts = cnts[0]
else:
cnts = cnts[1]
for c in cnts:
(x, y), radius = cv2.minEnclosingCircle(c)# 计算各个轮廓的中心
# print(x,y)
center = (int(x), int(y))# 转为整数 cast to integers
# print(center)
dots.append(center)
radius = int(radius)
dilateddot2 = cv2.circle(dilateddot, center, radius, (255, 255, 255), 2)# 绘圆 draw the circle
cropped2 = cv2.circle(cropped, center, 2, (0, 0, 0), 2)# 绘圆 draw the circle
#### cv2.imwrite("./images_save/dilateddot2.jpg",dilateddot2)
#### cv2.imwrite("./images_save/cropped_2.jpg",cropped2)
# cv2.imshow("dilateddot2 Image 2",cropped2)
cv2.waitKey(0)
# print(dots)
######## 至此得到了各交点坐标。 但是请注意,分割的单元格四点,坐标存在偏移
# 5.算法计算相应单元格对应坐标(注意有偏移) ##########
'''
这个过程稍微复杂,而且依赖于表格特性
首先我们只需要表格右边的选项填写区域,这些区域单元格有一个最大宽度max_cell_width
以及最小表格高度min_cell_height。
还有就是横线的坐标纵向最大偏移max_hori_jump用于确定下一行的单元格坐标,
本程序是固定判断某页面所以固定为查找到40个格子就结束
'''
max_cell_width = 60
min_cell_height = 30
max_hori_jump = 15
xColCells = 4 # 选项区一行有4列(4个单元格)
xAllCells = 40 # 一共有40个单元格
# 注意这些坐标【基本上】是按纸上的位置从右下角为起点,向左再向上的顺序排列的,恰好符合我们提取格子的顺序
# 但是,调试过程才发现,纵向是规律的从下往上,但是横向的却不总是从右往左,存在跳跃。因此,要做处理
# 5-1 对交点坐标进行梳理
# 梳理的思路是首先区分出横向线集合(根据max_hori_jump区分),再对集合内进行排序
dots2 = []
horiOneSet = [] # 横向线集合
dots_len = len(dots)
i = 0
while i < dots_len: # 0-
horiOneSet.append( (dots[i][0],dots[i][1]) )
if i == dots_len-1: #到了最后一个
#排序已压入的,并清空
horiOneSet.sort()
horiOneSet.reverse()
dots2.extend(horiOneSet)
horiOneSet = []
elif i>0 and (abs( dots[i+1][1] - dots[i][1] ) > max_hori_jump) : # 如果下一个跳跃
#排序已压入的,并清空
horiOneSet.sort()
horiOneSet.reverse()
dots2.extend(horiOneSet)
horiOneSet = []
i += 1
dots = dots2
# 5-2 查找并绘制单元格矩形
xCell = [] # 查找结果 这里面要放坐标各个单元格的顶点坐标 如[[A,B,C,D]]如A是[x,y]
i = 0
dots_len = len(dots)
while i < dots_len:
oneA = (dots[i][0], dots[i][1]) # d[i].x , d[i].y
oneB = (dots[i+1][0], dots[i+1][1]) # d[i+1].x , d[i+1].y
j = i + 1
m = 0
while j < dots_len: # 查找下一行,下一行应偏移大于最小单元格高而且不在直线误差范围内
if ( abs(dots[j][1] - dots[i][1]) > min_cell_height) and (abs(dots[j][1]-dots[j-1][1])>max_hori_jump) :
m = j+len(xCell)%xColCells
break
j += 1
if m==0 :
print('ERROR m==0')
oneC = ( dots[m][0], dots[m][1] )
oneD = ( dots[m+1][0], dots[m+1][1] )
one = (oneA,oneB,oneD,oneC)
xCell.append(one)
if len(xCell)<=xAllCells:
# print(one)
pts = np.array(one, np.int32)
pts = pts.reshape((-1,1,2))
cv2.polylines(cropped2,[pts],True,(0,255,255)) # ####
if len(xCell) == xAllCells :
break
# 够4列换行
if len(xCell)%xColCells==0 : #利用最大表格宽度筛选也可以 abs(dots[i+1][0] - dots[i][0]) > max_cell_width
i = m - 3
else:
i += 1
# print(xCell)
#### cv2.imwrite("./images_save/cropped_2_polylines.jpg",cropped2)
# cv2.imshow("dilateddot2 Image 2",cropped2)
# cv2.waitKey(0)
# 6.对交点坐标划分出的单元格进行透视变换/去除直线/裁剪等组合操作去除单元格边框干扰
############################################################################
# 注意xCell[i]是ABDC的形式 A右下角B左下角D左上角C右上角
# D C
#
# B A
# xCell reverse之后就是从上到下从左到右的顺序了 )
xCell.reverse()
# print('xCell.len',len(xCell))
i = 0
points = [] # 每个单元的权重积分(这里采用的是计算白色像素个数)
checked= [] # 保存目标选中项,每行保存一个索引值。例如4x10的填写区,只会生成10个记录,每个记录保存选项对应的索引。
colorImg = cv2.cvtColor(cropped2, cv2.COLOR_GRAY2RGB)
for cell in xCell:
(A,B,D,C) = cell
# print(A,B,D,C)
# 透视变换
cell_toushi = four_point_transform( cropped2, np.array([ D,B,A,C ] ) )
# 切出单元格图片边缘
x , y = cell_toushi.shape[0:2]
indent = 4 #像素
cell_toushi_cut = cell_toushi[indent:x-indent, indent:y-indent] #startX:endX, startY:endY
# cv2.imwrite("./images_save/cell_toushi_cut_%s.jpg" % (i),cell_toushi_cut)
#比较 二值化后计算白色像素个数
gray = cell_toushi_cut
binary = cv2.adaptiveThreshold(~gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -10)
height, width = binary.shape
area = 0
for m in range(height):
for n in range(width):
if binary[m, n] == 255:
area += 1
# print (i,area)
points.append(area)
if (i+1)%4==0 :
maxi = max( [points[i-3],points[i-2],points[i-1],points[i]] )
# print('maxi',maxi)
rowChecks = [points[i-3],points[i-2],points[i-1],points[i]]
rowCheckedIdx = rowChecks.index(maxi)
# print('rowChecks',rowChecks)
# print('rowCheckedIdx',rowCheckedIdx)
checked.append( rowCheckedIdx )
# draw a green line around on xCell[i-4+rowCheckedIdx]
# print('i-4+rowCheckedIdx',i-3+rowCheckedIdx)
pts = np.array( (xCell[i-3+rowCheckedIdx]), np.int32)
pts = pts.reshape((-1,1,2))
cv2.polylines(colorImg,[pts],True,(0,255,0)) # ####
# cv2.imwrite("./images_save/_cbinary_%s(%s).jpg" % (i,area),binary)
i +=1
cv2.imwrite("./images_save/_colorImg.jpg" ,colorImg)
print('the answer is',checked)
end_time = time.time()
print("durations %s s" % (end_time-start_time) )
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。