diff --git a/README.md b/README.md
index ba40834..43f3e15 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
# 实现功能
- [x] 文字方向检测 0、90、180、270度检测(支持dnn/tensorflow)
-- [x] 支持(darknet/opencv dnn /keras)文字检测,暂时公布(keras版本训练)
-- [x] 不定长OCR训练(英文、中英文) crnn\dense ocr
+- [x] 支持(darknet/opencv dnn /keras)文字检测,支持darknet/keras训练
+- [x] 不定长OCR训练(英文、中英文) crnn\dense ocr 识别及训练 ,新增pytorch转keras模型代码(tools/pytorch_to_keras.py)
- [x] 新增对身份证/火车票结构化数据识别
## 环境部署
@@ -45,9 +45,10 @@ ipython app.py 8080 ##8080端口号,可以设置任意端口
## 识别结果展示
-
-
-
+
+
+
+
## Play with Docker Container(镜像有些滞后)
``` Bash
@@ -58,7 +59,7 @@ docker run -d -p 8080:8080 zergmk2/chineseocr
## 访问服务
http://127.0.0.1:8080/ocr
-
+
## 参考
diff --git a/app.py b/app.py
index 5b53af9..f4cc398 100644
--- a/app.py
+++ b/app.py
@@ -3,17 +3,18 @@
@author: lywen
"""
import os
-from PIL import Image
+import cv2
import json
import time
import uuid
import base64
import web
+from PIL import Image
web.config.debug = True
import model
render = web.template.render('templates', base='base')
from config import DETECTANGLE
-from apphelper.image import union_rbox
+from apphelper.image import union_rbox,adjust_box_to_origin
from application import trainTicket,idcard
@@ -37,49 +38,65 @@ def POST(self):
data = web.data()
data = json.loads(data)
billModel = data.get('billModel','')
+ textAngle = data.get('textAngle',False)##文字检测
+ textLine = data.get('textLine',False)##只进行单行识别
+
imgString = data['imgString'].encode().split(b';base64,')[-1]
imgString = base64.b64decode(imgString)
jobid = uuid.uuid1().__str__()
- path = '/tmp/{}.jpg'.format(jobid)
+ path = 'test/{}.jpg'.format(jobid)
with open(path,'wb') as f:
f.write(imgString)
- img = Image.open(path).convert("RGB")
- W,H = img.size
+ img = cv2.imread(path)##GBR
+ H,W = img.shape[:2]
timeTake = time.time()
- _,result,angle= model.model(img,
- detectAngle=DETECTANGLE,##是否进行文字方向检测
- config=dict(MAX_HORIZONTAL_GAP=100,##字符之间的最大间隔,用于文本行的合并
- MIN_V_OVERLAPS=0.7,
- MIN_SIZE_SIM=0.7,
- TEXT_PROPOSALS_MIN_SCORE=0.1,
- TEXT_PROPOSALS_NMS_THRESH=0.3,
- TEXT_LINE_NMS_THRESH = 0.99,##文本行之间测iou值
- MIN_RATIO=1.0,
- LINE_MIN_SCORE=0.2,
- TEXT_PROPOSALS_WIDTH=0,
- MIN_NUM_PROPOSALS=0,
- ),
- leftAdjust=True,##对检测的文本行进行向左延伸
- rightAdjust=True,##对检测的文本行进行向右延伸
- alph=0.2,##对检测的文本行进行向右、左延伸的倍数
- ifadjustDegree=False##是否先小角度调整文字倾斜角度
- )
-
-
-
- if billModel=='' or billModel=='通用OCR' :
- result = union_rbox(result,0.2)
- res = [{'text':x['text'],'name':str(i)} for i,x in enumerate(result)]
- elif billModel=='火车票':
- res = trainTicket.trainTicket(result)
- res = res.res
- res =[ {'text':res[key],'name':key} for key in res]
-
- elif billModel=='身份证':
-
- res = idcard.idcard(result)
- res = res.res
- res =[ {'text':res[key],'name':key} for key in res]
+ if textLine:
+ ##单行识别
+ partImg = Image.fromarray(img)
+ text = model.crnnOcr(partImg.convert('L'))
+ res =[ {'text':text,'name':'0','box':[0,0,W,0,W,H,0,H]} ]
+ else:
+ detectAngle = textAngle
+ _,result,angle= model.model(img,
+ detectAngle=detectAngle,##是否进行文字方向检测,通过web传参控制
+ config=dict(MAX_HORIZONTAL_GAP=50,##字符之间的最大间隔,用于文本行的合并
+ MIN_V_OVERLAPS=0.6,
+ MIN_SIZE_SIM=0.6,
+ TEXT_PROPOSALS_MIN_SCORE=0.1,
+ TEXT_PROPOSALS_NMS_THRESH=0.3,
+ TEXT_LINE_NMS_THRESH = 0.7,##文本行之间测iou值
+ ),
+ leftAdjust=True,##对检测的文本行进行向左延伸
+ rightAdjust=True,##对检测的文本行进行向右延伸
+ alph=0.01,##对检测的文本行进行向右、左延伸的倍数
+ )
+
+
+
+ if billModel=='' or billModel=='通用OCR' :
+ result = union_rbox(result,0.2)
+ res = [{'text':x['text'],
+ 'name':str(i),
+ 'box':{'cx':x['cx'],
+ 'cy':x['cy'],
+ 'w':x['w'],
+ 'h':x['h'],
+ 'angle':x['degree']
+
+ }
+ } for i,x in enumerate(result)]
+ res = adjust_box_to_origin(img,angle, res)##修正box
+
+ elif billModel=='火车票':
+ res = trainTicket.trainTicket(result)
+ res = res.res
+ res =[ {'text':res[key],'name':key,'box':{}} for key in res]
+
+ elif billModel=='身份证':
+
+ res = idcard.idcard(result)
+ res = res.res
+ res =[ {'text':res[key],'name':key,'box':{}} for key in res]
timeTake = time.time()-timeTake
@@ -89,8 +106,6 @@ def POST(self):
return json.dumps({'res':res,'timeTake':round(timeTake,4)},ensure_ascii=False)
-
-
urls = ('/ocr','OCR',)
if __name__ == "__main__":
diff --git a/apphelper/image.py b/apphelper/image.py
index 99d8a36..a6472d7 100644
--- a/apphelper/image.py
+++ b/apphelper/image.py
@@ -539,7 +539,7 @@ def diff(box1,box2):
h1 = box1['h']
h2 = box2['h']
- return abs(cy1-cy2)/max(0.01,min(h1/2,h1/2))
+ return abs(cy1-cy2)/max(0.01,min(h1/2,h2/2))
def sort_group_box(boxes):
"""
@@ -589,3 +589,26 @@ def sort_group_box(boxes):
return newBox
+def adjust_box_to_origin(img,angle, result):
+ """
+ 调整box到原图坐标
+ """
+ h,w = img.shape[:2]
+ if angle in [90,270]:
+ imgW,imgH = img.shape[:2]
+
+ else:
+ imgH,imgW= img.shape[:2]
+ newresult = []
+ for line in result:
+ cx =line['box']['cx']
+ cy = line['box']['cy']
+ degree =line['box']['angle']
+ w = line['box']['w']
+ h = line['box']['h']
+ x1,y1,x2,y2,x3,y3,x4,y4 = xy_rotate_box(cx, cy, w, h, degree/180*np.pi)
+ x1,y1,x2,y2,x3,y3,x4,y4 = box_rotate([x1,y1,x2,y2,x3,y3,x4,y4],angle=(360-angle)%360,imgH=imgH,imgW=imgW)
+ box = x1,y1,x2,y2,x3,y3,x4,y4
+ newresult.append({'name':line['name'],'text':line['text'],'box':box})
+
+ return newresult
\ No newline at end of file
diff --git a/config.py b/config.py
index dfc6b8c..289925e 100644
--- a/config.py
+++ b/config.py
@@ -1,11 +1,11 @@
import os
########################文字检测########################
-##文字检测引擎 keras,opencv,darknet
+##文字检测引擎
pwd = os.getcwd()
-opencvFlag = 'keras'
+opencvFlag = 'keras' ##keras,opencv,darknet,模型性能 keras>darknet>opencv
IMGSIZE = (608,608)## yolo3 输入图像尺寸
## keras 版本anchors
-keras_anchors = '8,9, 8,18, 8,31, 8,59, 8,124, 8,351, 8,509, 8,605, 8,800'
+keras_anchors = '8,11, 8,16, 8,23, 8,33, 8,48, 8,97, 8,139, 8,198, 8,283'
class_names = ['none','text',]
kerasTextModel=os.path.join(pwd,"models","text.h5")##keras版本模型权重文件
@@ -23,8 +23,9 @@
GPUID=0##调用GPU序号
## nms选择,支持cython,gpu,python
-nmsFlag='gpu'## cython/gpu/python
-
+nmsFlag='gpu'## cython/gpu/python ##容错性 优先启动GPU,其次是cpython 最后是python
+if not GPU:
+ nmsFlag='cython'
##vgg文字方向检测模型
@@ -38,9 +39,9 @@
##OCR模型是否调用LSTM层
LSTMFLAG = True
##模型选择 True:中英文模型 False:英文模型
-
+ocrFlag = 'torch'##ocr模型 支持 keras torch版本
chinsesModel = True
-
+ocrModelKeras = os.path.join(pwd,"models","ocr-dense-keras.h5")##keras版本OCR,暂时支持dense
if chinsesModel:
if LSTMFLAG:
ocrModel = os.path.join(pwd,"models","ocr-lstm.pth")
diff --git a/crnn/crnn_keras.py b/crnn/crnn_keras.py
new file mode 100644
index 0000000..c1f515b
--- /dev/null
+++ b/crnn/crnn_keras.py
@@ -0,0 +1,42 @@
+#coding:utf-8
+from crnn.utils import strLabelConverter,resizeNormalize
+
+from crnn.network_keras import keras_crnn as CRNN
+import tensorflow as tf
+graph = tf.get_default_graph()##解决web.py 相关报错问题
+
+from crnn import keys
+from config import ocrModelKeras
+import numpy as np
+def crnnSource():
+ alphabet = keys.alphabetChinese##中英文模型
+ converter = strLabelConverter(alphabet)
+ model = CRNN(32, 1, len(alphabet)+1, 256, 1,lstmFlag=False)
+ model.load_weights(ocrModelKeras)
+ return model,converter
+
+##加载模型
+model,converter = crnnSource()
+
+def crnnOcr(image):
+ """
+ crnn模型,ocr识别
+ image:PIL.Image.convert("L")
+ """
+ scale = image.size[1]*1.0 / 32
+ w = image.size[0] / scale
+ w = int(w)
+ transformer = resizeNormalize((w, 32))
+ image = transformer(image)
+ image = image.astype(np.float32)
+ image = np.array([[image]])
+ global graph
+ with graph.as_default():
+ preds = model.predict(image)
+ preds = preds[0]
+ preds = np.argmax(preds,axis=2).reshape((-1,))
+ sim_pred = converter.decode(preds)
+ return sim_pred
+
+
+
diff --git a/crnn/crnn.py b/crnn/crnn_torch.py
similarity index 73%
rename from crnn/crnn.py
rename to crnn/crnn_torch.py
index f4e9c0f..a024b14 100644
--- a/crnn/crnn.py
+++ b/crnn/crnn_torch.py
@@ -1,21 +1,23 @@
#coding:utf-8
import torch
-import torch.utils.data
+import numpy as np
from torch.autograd import Variable
-from crnn import util
-from crnn import dataset
-from crnn.network import CRNN
+from crnn.utils import strLabelConverter,resizeNormalize
+from crnn.network_torch import CRNN
from crnn import keys
from collections import OrderedDict
from config import ocrModel,LSTMFLAG,GPU
from config import chinsesModel
def crnnSource():
+ """
+ 加载模型
+ """
if chinsesModel:
alphabet = keys.alphabetChinese##中英文模型
else:
alphabet = keys.alphabetEnglish##英文模型
- converter = util.strLabelConverter(alphabet)
+ converter = strLabelConverter(alphabet)
if torch.cuda.is_available() and GPU:
model = CRNN(32, 1, len(alphabet)+1, 256, 1,lstmFlag=LSTMFLAG).cuda()##LSTMFLAG=True crnn 否则 dense ocr
else:
@@ -27,15 +29,14 @@ def crnnSource():
name = k.replace('module.','') # remove `module.`
modelWeights[name] = v
# load params
-
+
model.load_state_dict(modelWeights)
- model.eval()
return model,converter
##加载模型
model,converter = crnnSource()
-
+model.eval()
def crnnOcr(image):
"""
crnn模型,ocr识别
@@ -44,21 +45,22 @@ def crnnOcr(image):
scale = image.size[1]*1.0 / 32
w = image.size[0] / scale
w = int(w)
- transformer = dataset.resizeNormalize((w, 32))
+ transformer = resizeNormalize((w, 32))
+ image = transformer(image)
+ image = image.astype(np.float32)
+ image = torch.from_numpy(image)
+
if torch.cuda.is_available() and GPU:
- image = transformer(image).cuda()
+ image = image.cuda()
else:
- image = transformer(image).cpu()
+ image = image.cpu()
- image = image.view(1, *image.size())
+ image = image.view(1,1, *image.size())
image = Variable(image)
- model.eval()
preds = model(image)
_, preds = preds.max(2)
preds = preds.transpose(1, 0).contiguous().view(-1)
- preds_size = Variable(torch.IntTensor([preds.size(0)]))
- sim_pred = converter.decode(preds.data, preds_size.data, raw=False)
-
+ sim_pred = converter.decode(preds)
return sim_pred
diff --git a/crnn/network_keras.py b/crnn/network_keras.py
new file mode 100644
index 0000000..d6691d0
--- /dev/null
+++ b/crnn/network_keras.py
@@ -0,0 +1,74 @@
+from keras.layers import Conv2D,BatchNormalization,MaxPool2D,Input,Permute,Reshape,Dense,LeakyReLU,Activation
+from keras.models import Model
+from keras.layers import ZeroPadding2D
+from keras.activations import relu
+
+
+def keras_crnn(imgH, nc, nclass, nh, n_rnn=2, leakyRelu=False,lstmFlag=True):
+ """
+ 基于pytorch 实现 keras dense ocr
+ pytorch lstm 层暂时无法转换为 keras lstm层
+ """
+ data_format='channels_first'
+ ks = [3, 3, 3, 3, 3, 3, 2]
+ ps = [1, 1, 1, 1, 1, 1, 0]
+ ss = [1, 1, 1, 1, 1, 1, 1]
+ nm = [64, 128, 256, 256, 512, 512, 512]
+ imgInput = Input(shape=(1,imgH,None),name='imgInput')
+
+ def convRelu(i, batchNormalization=False,x=None):
+ ##padding: one of `"valid"` or `"same"` (case-insensitive).
+ ##nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
+ nIn = nc if i == 0 else nm[i - 1]
+ nOut = nm[i]
+ if leakyRelu:
+ activation = LeakyReLU(alpha=0.2)
+ else:
+ activation = Activation(relu,name='relu{0}'.format(i))
+
+ x = Conv2D(filters=nOut,
+ kernel_size=ks[i],
+ strides=(ss[i], ss[i]),
+ padding= 'valid' if ps[i]==0 else 'same',
+ dilation_rate=(1, 1),
+ activation=None, use_bias=True,data_format=data_format,
+ name='cnn.conv{0}'.format(i)
+ )(x)
+
+ if batchNormalization:
+ ## torch nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
+ x = BatchNormalization(epsilon=1e-05,axis=1, momentum=0.1,name='cnn.batchnorm{0}'.format(i))(x)
+
+
+ x = activation(x)
+ return x
+
+ x = imgInput
+ x = convRelu(0,batchNormalization=False,x=x)
+
+ #x = ZeroPadding2D(padding=(0, 0), data_format=data_format)(x)
+ x = MaxPool2D(pool_size=(2, 2),name='cnn.pooling{0}'.format(0),padding='valid',data_format=data_format)(x)
+
+
+ x = convRelu(1,batchNormalization=False,x=x)
+ #x = ZeroPadding2D(padding=(0, 0), data_format=data_format)(x)
+ x = MaxPool2D(pool_size=(2, 2),name='cnn.pooling{0}'.format(1),padding='valid',data_format=data_format)(x)
+
+ x = convRelu(2, batchNormalization=True,x=x)
+ x = convRelu(3, batchNormalization=False,x=x)
+ x = ZeroPadding2D(padding=(0, 1), data_format=data_format)(x)
+ x = MaxPool2D(pool_size=(2, 2),strides=(2,1),padding='valid',name='cnn.pooling{0}'.format(2),data_format=data_format)(x)
+
+ x = convRelu(4, batchNormalization=True,x=x)
+ x = convRelu(5, batchNormalization=False,x=x)
+ x = ZeroPadding2D(padding=(0, 1), data_format=data_format)(x)
+ x = MaxPool2D(pool_size=(2, 2),strides=(2,1),padding='valid',name='cnn.pooling{0}'.format(3),data_format=data_format)(x)
+ x = convRelu(6, batchNormalization=True,x=x)
+
+ x = Permute((3, 2, 1))(x)
+
+ x = Reshape((-1,512))(x)
+ out = Dense(nclass,name='linear')(x)
+ out = Reshape((-1, 1, nclass),name='out')(out)
+
+ return Model(imgInput,out)
\ No newline at end of file
diff --git a/crnn/network.py b/crnn/network_torch.py
similarity index 99%
rename from crnn/network.py
rename to crnn/network_torch.py
index d4a29c5..9b9a7f8 100644
--- a/crnn/network.py
+++ b/crnn/network_torch.py
@@ -3,7 +3,6 @@ class BidirectionalLSTM(nn.Module):
def __init__(self, nIn, nHidden, nOut):
super(BidirectionalLSTM, self).__init__()
-
self.rnn = nn.LSTM(nIn, nHidden, bidirectional=True)
self.embedding = nn.Linear(nHidden * 2, nOut)
@@ -11,10 +10,8 @@ def forward(self, input):
recurrent, _ = self.rnn(input)
T, b, h = recurrent.size()
t_rec = recurrent.view(T * b, h)
-
output = self.embedding(t_rec) # [T * b, nOut]
output = output.view(T, b, -1)
-
return output
diff --git a/crnn/utils.py b/crnn/utils.py
new file mode 100644
index 0000000..4f6f3bb
--- /dev/null
+++ b/crnn/utils.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+# encoding: utf-8
+from PIL import Image
+import numpy as np
+class strLabelConverter(object):
+
+ def __init__(self, alphabet):
+ self.alphabet = alphabet + 'ç' # for `-1` index
+ self.dict = {}
+ for i, char in enumerate(alphabet):
+ # NOTE: 0 is reserved for 'blank' required by wrap_ctc
+ self.dict[char] = i + 1
+
+ def decode(self,res):
+ N = len(res)
+ raw = []
+ for i in range(N):
+ if res[i] != 0 and (not (i > 0 and res[i - 1] == res[i])):
+ raw.append(self.alphabet[res[i] - 1])
+ return ''.join(raw)
+
+
+class resizeNormalize(object):
+
+ def __init__(self, size, interpolation=Image.BILINEAR):
+ self.size = size
+ self.interpolation = interpolation
+
+ def __call__(self, img):
+ size = self.size
+ imgW,imgH = size
+ scale = img.size[1]*1.0 / imgH
+ w = img.size[0] / scale
+ w = int(w)
+ img = img.resize((w,imgH),self.interpolation)
+ w,h = img.size
+ img = (np.array(img)/255.0-0.5)/0.5
+
+ return img
\ No newline at end of file
diff --git a/model.py b/model.py
index 8d48be9..c248963 100644
--- a/model.py
+++ b/model.py
@@ -1,13 +1,23 @@
# -*- coding: utf-8 -*-
+from config import opencvFlag,GPU,IMGSIZE,ocrFlag
+if not GPU:
+ import os
+ os.environ["CUDA_VISIBLE_DEVICES"]=''##不启用GPU
+
+if ocrFlag=='torch':
+ from crnn.crnn_torch import crnnOcr as crnnOcr ##torch版本ocr
+elif ocrFlag=='keras':
+ from crnn.crnn_keras import crnnOcr as crnnOcr ##keras版本OCR
+
import time
import cv2
import numpy as np
from PIL import Image
from glob import glob
-from crnn.crnn import crnnOcr as crnnOcr
+
from text.detector.detectors import TextDetector
from apphelper.image import get_boxes,letterbox_image
-from config import opencvFlag,GPU,IMGSIZE
+
from text.opencv_dnn_detect import angle_detect##文字方向检测,支持dnn/tensorflow
from apphelper.image import estimate_skew_angle ,rotate_cut_img,xy_rotate_box,sort_box,box_rotate,solve
@@ -28,27 +38,19 @@ def text_detect(img,
TEXT_PROPOSALS_MIN_SCORE=0.7,
TEXT_PROPOSALS_NMS_THRESH=0.3,
TEXT_LINE_NMS_THRESH = 0.3,
- MIN_RATIO=1.0,
- LINE_MIN_SCORE=0.8,
- TEXT_PROPOSALS_WIDTH=5,
- MIN_NUM_PROPOSALS=1,
-
):
boxes, scores = detect.text_detect(np.array(img))
boxes = np.array(boxes,dtype=np.float32)
scores = np.array(scores,dtype=np.float32)
textdetector = TextDetector(MAX_HORIZONTAL_GAP,MIN_V_OVERLAPS,MIN_SIZE_SIM)
- shape = img.size[::-1]
+ shape = img.shape[:2]
boxes = textdetector.detect(boxes,
scores[:, np.newaxis],
shape,
TEXT_PROPOSALS_MIN_SCORE,
TEXT_PROPOSALS_NMS_THRESH,
TEXT_LINE_NMS_THRESH,
- MIN_RATIO,
- LINE_MIN_SCORE,
- TEXT_PROPOSALS_WIDTH,
- MIN_NUM_PROPOSALS)
+ )
text_recs = get_boxes(boxes)
newBox = []
@@ -67,71 +69,59 @@ def text_detect(img,
def crnnRec(im,boxes,leftAdjust=False,rightAdjust=False,alph=0.2,f=1.0):
"""
crnn模型,ocr识别
- @@model,
- @@converter,
- @@im:Array
- @@text_recs:text box
- @@ifIm:是否输出box对应的img
-
+ leftAdjust,rightAdjust 是否左右调整box 边界误差,解决文字漏检
"""
results = []
im = Image.fromarray(im)
for index,box in enumerate(boxes):
-
degree,w,h,cx,cy = solve(box)
partImg,newW,newH = rotate_cut_img(im,degree,box,w,h,leftAdjust,rightAdjust,alph)
- newBox = xy_rotate_box(cx,cy,newW,newH,degree)
- partImg_ = partImg.convert('L')
- simPred = crnnOcr(partImg_)##识别的文本
- if simPred.strip()!=u'':
- results.append({'cx':cx*f,'cy':cy*f,'text':simPred,'w':newW*f,'h':newH*f,'degree':degree*180.0/np.pi})
+ text = crnnOcr(partImg.convert('L'))
+ if text.strip()!=u'':
+ results.append({'cx':cx*f,'cy':cy*f,'text':text,'w':newW*f,'h':newH*f,'degree':degree*180.0/np.pi})
return results
-def eval_angle(im,detectAngle=False,ifadjustDegree=True):
+def eval_angle(im,detectAngle=False):
"""
估计图片偏移角度
@@param:im
- @@param:ifadjustDegree 调整文字识别结果
@@param:detectAngle 是否检测文字朝向
"""
angle = 0
- degree=0.0
img = np.array(im)
if detectAngle:
angle = angle_detect(img=np.copy(img))##文字朝向检测
if angle==90:
- im = im.transpose(Image.ROTATE_90)
+ im = Image.fromarray(im).transpose(Image.ROTATE_90)
elif angle==180:
- im = im.transpose(Image.ROTATE_180)
+ im = Image.fromarray(im).transpose(Image.ROTATE_180)
elif angle==270:
- im = im.transpose(Image.ROTATE_270)
+ im = Image.fromarray(im).transpose(Image.ROTATE_270)
img = np.array(im)
- if ifadjustDegree:
- degree = estimate_skew_angle(np.array(im.convert('L')))
- return angle,degree,im.rotate(degree)
+ return angle,img
-def model(img,detectAngle=False,config={},leftAdjust=False,rightAdjust=False,alph=0.2,ifadjustDegree=False):
+def model(img,detectAngle=False,config={},leftAdjust=False,rightAdjust=False,alph=0.2):
"""
@@param:img,
@@param:ifadjustDegree 调整文字识别倾斜角度
@@param:detectAngle,是否检测文字朝向
"""
- angle,degree,img = eval_angle(img,detectAngle=detectAngle,ifadjustDegree=ifadjustDegree)
+ angle,img = eval_angle(img,detectAngle=detectAngle)##文字方向检测
if opencvFlag!='keras':
- img,f =letterbox_image(img, IMGSIZE)
+ img,f =letterbox_image(Image.fromarray(img), IMGSIZE)## pad
+ img = np.array(img)
else:
f=1.0##解决box在原图坐标不一致问题
config['img'] = img
- text_recs = text_detect(**config)
-
- newBox = sort_box(text_recs)
+ text_recs = text_detect(**config)##文字检测
+ newBox = sort_box(text_recs)##行文本识别
result = crnnRec(np.array(img),newBox,leftAdjust,rightAdjust,alph,1.0/f)
return img,result,angle
diff --git a/models/text.cfg b/models/text.cfg
index eeaf099..16a111e 100644
--- a/models/text.cfg
+++ b/models/text.cfg
@@ -607,7 +607,7 @@ activation=linear
[yolo]
mask = 6,7,8
-anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
+anchors = 8,11, 8,16, 8,23, 8,33, 8,48, 8,97, 8,139, 8,198, 8,283
classes=2
num=9
jitter=.3
@@ -691,7 +691,7 @@ activation=linear
[yolo]
mask = 3,4,5
-anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
+anchors = 8,11, 8,16, 8,23, 8,33, 8,48, 8,97, 8,139, 8,198, 8,283
classes=2
num=9
jitter=.3
@@ -775,7 +775,7 @@ activation=linear
[yolo]
mask = 0,1,2
-anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
+anchors = 8,11, 8,16, 8,23, 8,33, 8,48, 8,97, 8,139, 8,198, 8,283
classes=2
num=9
jitter=.3
diff --git a/models/text.names b/models/text.names
index 9de5aa3..05aa78b 100644
--- a/models/text.names
+++ b/models/text.names
@@ -1,2 +1,2 @@
-text
-none
\ No newline at end of file
+none
+text
\ No newline at end of file
diff --git a/static/css/checkbox.css b/static/css/checkbox.css
new file mode 100644
index 0000000..b46e7ff
--- /dev/null
+++ b/static/css/checkbox.css
@@ -0,0 +1,304 @@
+.el-radio,
+.el-checkbox {
+ position: relative;
+ font-size: 100%;
+}
+label.el-radio,
+label.el-checkbox {
+ display: block;
+ cursor: pointer;
+}
+.el-radio > input[type="radio"],
+.el-checkbox > input[type="checkbox"] {
+ display: none;
+}
+.el-radio > input[type="radio"][disabled],
+.el-checkbox > input[type="checkbox"][disabled] {
+ cursor: not-allowed;
+}
+.el-radio > input[type="radio"] + .el-radio-style,
+.el-checkbox > input[type="checkbox"] + .el-checkbox-style {
+ position: relative;
+ display: inline-block;
+ width: 1.4em;
+ height: 1.4em;
+ vertical-align: middle;
+ cursor: pointer;
+}
+.el-radio > input[type="radio"] + .el-radio-style:hover:before,
+.el-checkbox > input[type="checkbox"] + .el-checkbox-style:hover:before {
+ border-color: #20a0ff;
+}
+.el-radio > input[type="radio"] + .el-radio-style:before,
+.el-checkbox > input[type="checkbox"] + .el-checkbox-style:before {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ width: 1.4em;
+ height: 1.4em;
+ content: '';
+ border: 1px solid #C0CCDA;
+}
+.el-radio > input[type="radio"] + .el-radio-style:after,
+.el-checkbox > input[type="checkbox"] + .el-checkbox-style:after {
+ position: absolute;
+ display: none;
+ content: '';
+}
+.el-radio > input[type="radio"][disabled] + .el-radio-style,
+.el-checkbox > input[type="checkbox"][disabled] + .el-checkbox-style {
+ cursor: not-allowed;
+ color: #D3DCE6;
+}
+.el-radio > input[type="radio"][disabled] + .el-radio-style:hover,
+.el-radio > input[type="radio"][disabled] + .el-radio-style:before,
+.el-radio > input[type="radio"][disabled] + .el-radio-style:after,
+.el-checkbox > input[type="checkbox"][disabled] + .el-checkbox-style:hover,
+.el-checkbox > input[type="checkbox"][disabled] + .el-checkbox-style:before,
+.el-checkbox > input[type="checkbox"][disabled] + .el-checkbox-style:after {
+ cursor: not-allowed;
+}
+.el-radio > input[type="radio"][disabled] + .el-radio-style:hover:before,
+.el-checkbox > input[type="checkbox"][disabled] + .el-checkbox-style:hover:before {
+ border: 1px solid #D3DCE6;
+ animation-name: none;
+}
+.el-radio > input[type="radio"][disabled] + .el-radio-style:before,
+.el-checkbox > input[type="checkbox"][disabled] + .el-checkbox-style:before {
+ border-color: #D3DCE6;
+}
+.el-radio > input[type="radio"]:checked + .el-radio-style:before,
+.el-checkbox > input[type="checkbox"]:checked + .el-checkbox-style:before {
+ animation-name: none;
+}
+.el-radio > input[type="radio"]:checked + .el-radio-style:after,
+.el-checkbox > input[type="checkbox"]:checked + .el-checkbox-style:after {
+ display: block;
+}
+.el-radio > input[type="radio"] + .el-radio-style:before {
+ border-radius: 50%;
+}
+.el-radio > input[type="radio"] + .el-radio-style:after {
+ top: 0.4em;
+ left: 0.4em;
+ width: 0.6em;
+ height: 0.6em;
+ border-radius: 50%;
+ background: #20a0ff;
+}
+.el-radio > input[type="radio"]:checked + .el-radio-style:before {
+ border: 1px solid #20a0ff;
+}
+.el-radio > input[type="radio"]:checked[disabled] + .el-radio-style:before {
+ border: 1px solid #b0d7f5;
+}
+.el-radio > input[type="radio"]:checked[disabled] + .el-radio-style:after {
+ background: #b0d7f5;
+}
+.el-checkbox > input[type="checkbox"] + .el-checkbox-style:before {
+ border-radius: 3px;
+}
+.el-checkbox > input[type="checkbox"] + .el-checkbox-style:after {
+ top: 0.15em;
+ left: 0.5em;
+ box-sizing: border-box;
+ width: 0.4em;
+ height: 0.85em;
+ transform: rotate(45deg);
+ border-right: 2px solid #fff;
+ border-bottom: 2px solid #fff;
+}
+.el-checkbox > input[type="checkbox"]:checked + .el-checkbox-style:before {
+ border: #20a0ff;
+ background: #20a0ff;
+}
+.el-checkbox > input[type="checkbox"]:checked[disabled] + .el-checkbox-style:before {
+ border: #b0d7f5;
+ background: #b0d7f5;
+}
+/*blue theme*/
+.el-radio.el-radio-blue > input[type="radio"] + label:hover:before,
+.el-checkbox.el-checkbox-blue > input[type="checkbox"] + label:hover:before,
+.el-radio.el-radio-blue > input[type="radio"]:checked + label:before,
+.el-checkbox.el-checkbox-blue > input[type="checkbox"]:checked + label:before {
+ border-color: #20a0ff;
+}
+.el-checkbox.el-checkbox-blue > input[type="checkbox"]:checked + label:before,
+.el-radio.el-radio-blue > input[type="radio"] + label:after {
+ background: #20a0ff;
+}
+.el-radio.el-radio-blue > input[type="radio"][disabled] + label:hover:before,
+.el-checkbox.el-checkbox-blue > input[type="checkbox"][disabled] + label:hover:before {
+ border-color: #D3DCE6;
+}
+.el-checkbox.el-checkbox-blue > input[type="checkbox"]:checked[disabled] + label:before,
+.el-radio.el-radio-blue > input[type="radio"]:checked[disabled] + label:before {
+ border-color: #b0d7f5;
+}
+.el-checkbox.el-checkbox-blue > input[type="checkbox"][disabled]:checked + label:before,
+.el-radio.el-radio-blue > input[type="radio"]:checked[disabled] + label:after {
+ background: #b0d7f5;
+}
+/*green theme*/
+.el-radio.el-radio-green > input[type="radio"] + label:hover:before,
+.el-checkbox.el-checkbox-green > input[type="checkbox"] + label:hover:before,
+.el-radio.el-radio-green > input[type="radio"]:checked + label:before,
+.el-checkbox.el-checkbox-green > input[type="checkbox"]:checked + label:before {
+ border-color: #13ce66;
+}
+.el-checkbox.el-checkbox-green > input[type="checkbox"]:checked + label:before,
+.el-radio.el-radio-green > input[type="radio"] + label:after {
+ background: #13ce66;
+}
+.el-radio.el-radio-green > input[type="radio"][disabled] + label:hover:before,
+.el-checkbox.el-checkbox-green > input[type="checkbox"][disabled] + label:hover:before {
+ border-color: #D3DCE6;
+}
+.el-checkbox.el-checkbox-green > input[type="checkbox"]:checked[disabled] + label:before,
+.el-radio.el-radio-green > input[type="radio"]:checked[disabled] + label:before {
+ border-color: #a1efc4;
+}
+.el-checkbox.el-checkbox-green > input[type="checkbox"][disabled]:checked + label:before,
+.el-radio.el-radio-green > input[type="radio"]:checked[disabled] + label:after {
+ background: #a1efc4;
+}
+/*red theme*/
+.el-radio.el-radio-red > input[type="radio"] + label:hover:before,
+.el-checkbox.el-checkbox-red > input[type="checkbox"] + label:hover:before,
+.el-radio.el-radio-red > input[type="radio"]:checked + label:before,
+.el-checkbox.el-checkbox-red > input[type="checkbox"]:checked + label:before {
+ border-color: #ff4949;
+}
+.el-checkbox.el-checkbox-red > input[type="checkbox"]:checked + label:before,
+.el-radio.el-radio-red > input[type="radio"] + label:after {
+ background: #ff4949;
+}
+.el-radio.el-radio-red > input[type="radio"][disabled] + label:hover:before,
+.el-checkbox.el-checkbox-red > input[type="checkbox"][disabled] + label:hover:before {
+ border-color: #D3DCE6;
+}
+.el-checkbox.el-checkbox-red > input[type="checkbox"]:checked[disabled] + label:before,
+.el-radio.el-radio-red > input[type="radio"]:checked[disabled] + label:before {
+ border-color: #f9b3b3;
+}
+.el-checkbox.el-checkbox-red > input[type="checkbox"][disabled]:checked + label:before,
+.el-radio.el-radio-red > input[type="radio"]:checked[disabled] + label:after {
+ background: #f9b3b3;
+}
+/*yellow theme*/
+.el-radio.el-radio-yellow > input[type="radio"] + label:hover:before,
+.el-checkbox.el-checkbox-yellow > input[type="checkbox"] + label:hover:before,
+.el-radio.el-radio-yellow > input[type="radio"]:checked + label:before,
+.el-checkbox.el-checkbox-yellow > input[type="checkbox"]:checked + label:before {
+ border-color: #f7ba2a;
+}
+.el-checkbox.el-checkbox-yellow > input[type="checkbox"]:checked + label:before,
+.el-radio.el-radio-yellow > input[type="radio"] + label:after {
+ background: #f7ba2a;
+}
+.el-radio.el-radio-yellow > input[type="radio"][disabled] + label:hover:before,
+.el-checkbox.el-checkbox-yellow > input[type="checkbox"][disabled] + label:hover:before {
+ border-color: #D3DCE6;
+}
+.el-checkbox.el-checkbox-yellow > input[type="checkbox"]:checked[disabled] + label:before,
+.el-radio.el-radio-yellow > input[type="radio"]:checked[disabled] + label:before {
+ border-color: #fbeac1;
+}
+.el-checkbox.el-checkbox-yellow > input[type="checkbox"][disabled]:checked + label:before,
+.el-radio.el-radio-yellow > input[type="radio"]:checked[disabled] + label:after {
+ background: #fbeac1;
+}
+/*switch component*/
+.el-switch,
+.el-switch-style,
+.el-switch-style:before {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.el-switch {
+ display: inline-block;
+ font-size: 100%;
+ height: 1.6em;
+ position: relative;
+}
+.el-switch .el-switch-style {
+ height: 1.6em;
+ left: 0;
+ background: #C0CCDA;
+ -webkit-border-radius: 0.8em;
+ border-radius: 0.8em;
+ display: inline-block;
+ position: relative;
+ top: 0;
+ -webkit-transition: all 0.3s ease-in-out;
+ transition: all 0.3s ease-in-out;
+ width: 3em;
+ cursor: pointer;
+}
+.el-switch .el-switch-style:before {
+ display: block;
+ content: '';
+ height: 1.4em;
+ position: absolute;
+ width: 1.4em;
+ background-color: #fff;
+ -webkit-border-radius: 50%;
+ border-radius: 50%;
+ left: 0.1em;
+ top: 0.1em;
+ -webkit-transition: all 0.3s ease-in-out;
+ transition: all 0.3s ease-in-out;
+}
+.el-switch > input[type="checkbox"] {
+ display: none;
+}
+.el-switch > input[type="checkbox"][disabled] + .el-switch-style {
+ cursor: not-allowed;
+ background-color: #D3DCE6;
+}
+.el-switch > input[type="checkbox"]:checked + .el-switch-style {
+ background-color: #20a0ff;
+}
+.el-switch > input[type="checkbox"]:checked + .el-switch-style:before {
+ left: 50%;
+}
+.el-switch > input[type="checkbox"]:checked[disabled] + .el-switch-style {
+ background-color: #b0d7f5;
+}
+.el-switch.el-switch-blue > input[type="checkbox"]:checked + .el-switch-style {
+ background-color: #20a0ff;
+}
+.el-switch.el-switch-blue > input[type="checkbox"]:checked[disabled] + .el-switch-style {
+ background-color: #b0d7f5;
+}
+.el-switch.el-switch-green > input[type="checkbox"]:checked + .el-switch-style {
+ background-color: #13ce66;
+}
+.el-switch.el-switch-green > input[type="checkbox"]:checked[disabled] + .el-switch-style {
+ background-color: #a1efc4;
+}
+.el-switch.el-switch-red > input[type="checkbox"]:checked + .el-switch-style {
+ background-color: #ff4949;
+}
+.el-switch.el-switch-red > input[type="checkbox"]:checked[disabled] + .el-switch-style {
+ background-color: #f9b3b3;
+}
+.el-switch.el-switch-yellow > input[type="checkbox"]:checked + .el-switch-style {
+ background-color: #f7ba2a;
+}
+.el-switch.el-switch-yellow > input[type="checkbox"]:checked[disabled] + .el-switch-style {
+ background-color: #fbeac1;
+}
+/*define size*/
+.el-radio.el-radio-sm,
+.el-checkbox.el-checkbox-sm,
+.el-switch.el-switch-sm {
+ font-size: 85%;
+}
+.el-radio.el-radio-lg,
+.el-checkbox.el-checkbox-lg,
+.el-switch.el-switch-lg {
+ font-size: 125%;
+}
diff --git a/static/js/helps.js b/static/js/helps.js
index 1bf1b3d..0669646 100644
--- a/static/js/helps.js
+++ b/static/js/helps.js
@@ -1,15 +1,21 @@
function postImg(){
//执行post请求,识别图片
jQuery("#billmodeltable").remove();//清空界面识别结果
-
+ imgJson["textAngle"] = document.getElementById("textAngle").checked;//是否自动进行文字方向检测
+ imgJson["textLine"] = document.getElementById("textLine").checked;//是否只检测但行文字
if(imgJson['num']==0)
{ loadingGif('loadingGif');
imgJson['num']=1;//防止重复提交
//alert(imgJson["billModel"]);
+ imgJson['ocrFlag']=true;
jQuery.ajax({
type: "post",
url: 'ocr',
- data:JSON.stringify({"imgString":imgJson["imgString"],"billModel":imgJson["billModel"]}),
+ data:JSON.stringify({"imgString":imgJson["imgString"],
+ "billModel":imgJson["billModel"],
+ "textAngle":imgJson["textAngle"],
+ "textLine":imgJson["textLine"]
+ }),
success:function(d){
loadingGif('loadingGif');
imgJson['num']=0;//防止重复提交
@@ -19,6 +25,7 @@ function postImg(){
getChildDetail();
W = imgJson["width"];
H = imgJson["height"];
+ imgJson['ocrFlag']=false;
}
});}
@@ -37,12 +44,12 @@ function loadingGif(loadingGif){
function resize_im(w,h, scale, max_scale){
f=parseFloat(scale)/Math.min(h, w);
if(f*Math.max(h, w)>max_scale){
- f=parseFloat.float(max_scale)/max(h, w);
+ f=parseFloat(max_scale)/Math.max(h, w);
}
newW = parseInt(w*f);
- newH =parseInt(h*f);
+ newH = parseInt(h*f);
- return [newW,newH]
+ return [newW,newH,f]
}
@@ -57,14 +64,14 @@ function FunimgPreview(avatarSlect,avatarPreview,myCanvas) {
fr.onload=function () {
jQuery("#"+avatarPreview).attr('src',this.result);
imgJson.imgString = this.result;
-
var image = new Image();
image.onload=function(){
var width = image.width;
var height = image.height;
- newWH =resize_im(width,height, 800, 1600);
+ newWH =resize_im(width,height, 800, 1200);
newW = newWH[0];
- newH = newWH[1];
+ newH = newWH[1];
+ imgRate = newWH[2];
imgJson.width = width;
imgJson.height = height;
jQuery("#"+avatarPreview).attr('width',newW);
@@ -112,17 +119,76 @@ function createTable(result,timeTake){
//根据获取的数据,创建table
jQuery("#mytable").empty();
var jsObject = result;
+ imgBoxes=[];
//var jsObject = [{"name":10,"value":20},{"name":10,"value":20}];
var p = "
序号 | 值 | "+jsObject[i]["name"]+" | "+jsObject[i]["text"]+" | ";
+ imgBoxes.push(jsObject[i]["box"]);
}
tableString+="
---|