代码拉取完成,页面将自动刷新
同步操作将从 Luderson/opencv-table-reader 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
# -*- coding: utf-8 -*-
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
import time
import json
import threading
from websocket_server import WebsocketServer
# Called for every client connecting (after handshake)
def new_client(client, server):
print("New client connected and was given id %d" % client['id'])
# server.send_message_to_all("Hey all, a new client has joined us")
# Called for every client disconnecting
def client_left(client, server):
print("Client(%d) disconnected" % client['id'])
# Called when a client sends a message
def message_received(client, server, message):
if len(message) > 200:
message = message[:200]+'..'
print("Client(%d) said: %s" % (client['id'], message))
PORT=9001
server = WebsocketServer(PORT)
server.set_fn_new_client(new_client)
server.set_fn_client_left(client_left)
server.set_fn_message_received(message_received)
# 在新进程中打开
socket_thread = threading.Thread(target=server.run_forever)
socket_thread.setDaemon(True) #把主线程设置为守护线程,主程序结束这个也要结束,如果不做这个设置,ctrl+c主进程结束后子进程仍然无法退出
socket_thread.start()
####
cap=cv2.VideoCapture(2) # 参数代表摄像头次序
i=0
cv2.namedWindow('camera',0)
while(1):
# nowTime = time.asctime( time.localtime(time.time()) )
# print(nowTime)
# server.send_message_to_all( 'while 1 '+nowTime )
try:
ret ,frame = cap.read()
# 灰度
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 翻转
# (h, w) = gray.shape[:2]
# center = (w // 2, h // 2)
# M = cv2.getRotationMatrix2D(center, -90, 1.0) #定义反转矩阵
# gray = cv2.warpAffine(gray, M, (w, h))
rotate = np.rot90(gray,3)
x, y = rotate.shape[0:2]
width = 800
r = width/y
dim = (width, int(x*r))
img800 = cv2.resize(rotate, dim, interpolation=cv2.INTER_AREA)
#cv2.imwrite("./images_save/img800.jpg",img800)
#cv2.imshow("camera", img800)
#cv2.resizeWindow("camera", w, h)
k=cv2.waitKey(2)
if k==27: # esc
break
elif k==ord('s'):
cv2.imwrite('./images_save/'+str(i)+'.jpg',img800)
i+=1
blurred = cv2.GaussianBlur(img800, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 400)
# 从边缘图中寻找轮廓,然后初始化张张对应的轮廓
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个坐标
# 对原始图像和灰度图都进行四点透视变换
try:
warped = four_point_transform(img800, docCnt.reshape(4, 2))
except Exception as err:
# print('ERR when four_point_transform : %s' % (err) )
continue
# 2.裁剪图像边缘一定的像素(看情况),以避免待会查找直线时查找到边缘线
x , y = warped.shape[0:2]
indent = 15 #裁剪像素
cropped = warped[indent:x-indent, indent:y-indent] #startX:endX, startY:endY
# 3.查找直线,找出靠右的5条直线和靠下的9条直线,得到所有交点, # 二期:如果超出范围就改变参数再次查找
#二值化
gray = cropped
binary = cv2.adaptiveThreshold(~gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 55, -4)
# 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)
### 这里显示一下
# (h, w) = bitwiseAnd.shape[:2]
# cv2.imshow("camera", bitwiseAnd)
# cv2.resizeWindow("camera", w, h)
# 4.对生成交点的图像进行膨胀,以便清除太相近的点,最后提取这些‘交点坐标’
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(4,4))
dilateddot = cv2.dilate(bitwiseAnd,kernel,iterations = 1)
# cv2.imshow("dilateddot Image 2",dilateddot)
#### 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
# 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)
continueWhile = False
for cell in xCell:
(A,B,D,C) = cell
# 检查有偏移过大的点的话本轮检索取消( 取消整个while(1) ) 顶点偏移超过max_cell_width就认为过大
if abs(D[0]-A[0]) > max_cell_width or abs( B[0]-C[0] ) > max_cell_width :
continueWhile = True
# print('continueWhile',cell)
break
# 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
if continueWhile or len(checked)!=10:
(h, w) = colorImg.shape[:2]
cv2.resizeWindow("camera", w-8, h)
continue
else:
(h, w) = colorImg.shape[:2]
cv2.imshow("camera", colorImg)
cv2.resizeWindow("camera", w, h)
# print('checked:',checked) #显示答案
if 1 : #显示答案2
trans = ['A','B','C','D']
checked2 = []
for checkedOne in checked:
checked2.append(trans[checkedOne])
nowTime = time.asctime( time.localtime(time.time()) )
# print( nowTime +' 答案:' + ','.join(checked2)) #显示答案2
# server.send_message_to_all( nowTime +' 答案 ' + ','.join(checked2) )
ret = json.dumps(checked2)
print(ret)
server.send_message_to_all(ret)
except Exception as err:
# print('ERR when while(1) : %s' % (err) )
continue
print("here it is run over")
cap.release()
cv2.destroyAllWindows()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。