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 = "

耗时:"+timeTake+"秒 ,识别结果为:

"; var tableString =p+ "" for(var i=0;i"; + imgBoxes.push(jsObject[i]["box"]); } tableString+="
序号

"+jsObject[i]["text"]+"

"; //jQuery("#mytable").append(p); jQuery("#mytable").append(tableString); - + drawRbox(imgBoxes,'myCanvas'); + } + +function drawRbox(boxes,canvasId){ + /*canvas上绘制倾斜矩形 + */ + var canvas = document.getElementById(canvasId); + + if(canvas.getContext){ + + var ctx = canvas.getContext("2d"); + ctx.strokeStyle = 'rgba(255,0,0,0.5)'; + ctx.lineWidth = 5; + ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.beginPath(); + for(var i=0;i0.5){ + var canvas = document.getElementById('myCanvas'); + if(canvas.getContext){ + var ctx = canvas.getContext("2d"); + ctx.clearRect(0,0,canvas.width,canvas.height); + } + } + else{ + if(imgJson['ocrFlag']==false){ + drawRbox(imgBoxes,'myCanvas'); + }} + +} diff --git a/templates/ocr.html b/templates/ocr.html index b6db485..f9d5c32 100644 --- a/templates/ocr.html +++ b/templates/ocr.html @@ -2,7 +2,7 @@ - +