程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

全国增值税发票查验平台验证码识别

发布于2019-08-20 11:10     阅读(2777)     评论(0)     点赞(11)     收藏(1)


拿到一张发票,如何把上面的内容转化为计算机中结构化的数据呢?

直接拿到图片OCR,虽然目前的技术可以识别出内容,但很关键的问题就是,不知道哪条数据是什么。譬如“100”是数量还是单价。例如下面是一张增值税发票。
在这里插入图片描述
OCR识别结果如下:
在这里插入图片描述

这样的数据没啥意义。

发票左上角有一个二维码,通过扫描可以得到,发票的四要素(发票代码,发票号码,开票日期,校验码)。然后可以去全国增值税发票查验平台,输入四要素,查看发票详细信息。
在这里插入图片描述
二维码识别代码,放这里。要安装zxing包。参考:Python生成+识别二维码

import os
from PIL import Image
import zxing  # 导入解析包
import random


# 在当前目录生成临时文件,规避java的路径问题
def ocr_qrcode_zxing(filename):
    img = Image.open(filename)
    ran = int(random.random() * 100000)  # 设置随机数据的大小
    img.save('%s%s.png' % (os.path.basename(filename).split('.')[0], ran))
    zx = zxing.BarCodeReader()  # 调用zxing二维码读取包
    data = ''
    zxdata = zx.decode('%s%s.png' % (os.path.basename(filename).split('.')[0], ran))  # 图片解码
    print(zxdata)
    # 删除临时文件
    os.remove('%s%s.png' % (os.path.basename(filename).split('.')[0], ran))

    return zxdata  # 返回记录的内容


if __name__ == '__main__':
    filename = '3.png'  # 二维码图片
    # zxing二维码识别
    ltext = ocr_qrcode_zxing(filename)  # 将图片文件里的信息转码放到ltext里面
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
官网查验需要验证码,回到本文主题,验证码识别。

1 数据集

全国增值税发票查验平台的验证码主要构成是:数字、字母、汉字的6位组合,以及红、黄、蓝、黑4种颜色,每次会随机要求输入其中某种颜色。没有颜色要求即是黑色。

基于selenium包进行验证码收集和标注。
参考:
selenium 安装与 chromedriver安装
自动化测试 selenium 模块 webdriver使用

通过对网站的分析发现,必须输入发票的四要素才能获得验证码的图片。

get_captcha.py

from selenium import webdriver #安装:pip install selenium
import time
import base64  #安装:pip install base64


def labelme(prex,filename):
    src = browser.find_element_by_id('yzm_img').get_property('src')
    filename = prex + str(filename)
    label = "#"
    while label.endswith("#"):  #修改:要么从末尾backspace,要么输入"#"回车。注意从中间修改无效。看不清直接回车
        label = input(filename+":")#输入:内容(字母一律小写)/颜色 颜色编码:除黑色为e,其余均为英文单词首字母
    label = label.split('/')
    if label == ['']:
        return False
    if len(label)!=2 or len(label[0])!=6 or len(label[1])!=6:
        print("输入长度有误,内容颜色均6位,用/隔开")
        return False
    with open('labels.txt', 'a', encoding='utf-8') as f:  # 标签保存在同目录下的labels.txt
        f.write(filename + ":" + str(label) + "\n")

    img = base64.b64decode(src.split(',')[-1])
    with open('pic/'+filename + '.png', 'wb') as f:
        f.write(img)
    return True


if __name__ == "__main__":

    prex = input('输入唯一标示(姓名首字母,例如:fsf):')
    cnt = int(input('输入中断文件号,第一次为1:'))

    browser = webdriver.Chrome()

    browser.get("http://inv-veri.chinatax.gov.cn")

    browser.find_element_by_css_selector('#fpdm').send_keys("发票代码")
    browser.find_element_by_css_selector('#fphm').send_keys("发票号码")
    browser.find_element_by_css_selector('#kprq').send_keys("开票日期")
    browser.find_element_by_css_selector('#kjje').send_keys("校验码")
    time.sleep(1)
    print('修改:要么从末尾backspace,要么输入"#"回车。注意从中间修改无效。看不清直接回车')
    print('输入:内容(字母一律小写)/颜色(颜色编码:除黑色为e,其余均为英文单词首字母)')
    '''
    获取需要输入的颜色,已注释
    try:
        color = browser.find_element_by_css_selector('#yzminfo font').text
    except:
        color = None
    print(color)
    '''

    while True:

        if labelme(prex,cnt):
            cnt += 1
        browser.find_element_by_css_selector('#imgarea a').click()
        time.sleep(0.5)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

2 模型

EndToEnd文本识别网络-CRNN(CNN+GRU/LSTM+CTC)
参考:语音识别:深入理解CTC Loss原理
模型结构图
输入是一张(35,90,3)的图片,分别是高、宽、通道数。(tensorflow里面高是在第一位)。通过一个CNN,这是一个类似VGG的三层卷积池化层。得到了(4,11,128)的特征向量。之后需要把高宽转置一下,让宽在第一位,reshape成(11,4*128),这个特征的含义,相当于把图片按宽度从左到右分成了11条,每一条是一个512维向量,代表这一条的特征。

在这里插入图片描述
之后这个(11,512)的特征向量作为第一个双向GRU的输入,一路从左到右输入到 GRU,一路从右到左输入到 GRU,然后将他们输出的结果加起来(sum)。然后输入第二个双向GRU,还是一路正方向,一路反方向,只不过这次直接将它们的输出连起来(contact)。这样得到了一个(11,128)维的向量。这个时候再分两路,一路全连接层输出11个(标签是6个,这时候就得靠ctc来对齐了)字符的概率,另一个全连接层输出11个颜色的概率。最后就是最小化ctc损失了。这里模型整体有两个ctc损失,一个来自颜色,一个来自字符。我这里按照颜色,字符3:7的权重计算最后总的loss(个人觉得颜色比较简单),这里大家可以自行调整。或者可以颜色训练一轮,字符训练两轮。好了,放代码吧。
先放个代码目录:
在这里插入图片描述
简单解释一下:
1.datasets,文件夹就是数据集咯,包括训练集train和测试集test。每个文件夹下又分别有输入inputs.npy、
字符标签labels_str.npy和颜色标签labels_color.npy。
2.get_captcha,数据集的获取和标注。由于本文只使用了少量数据集,想要提升识别效果,可以使用这个代码增加数据集。
其中pic存放验证码图片,labels存放文件名对应的标签。
3.model,一个是模型的代码,一个是模型的训练权重(只用了少量数据集7000,数字字母表现效果良好)。
4.captcha_predict.py, 验证码预测,sigin_in的中间文件。
5.color.txt, 所有颜色。
6.string.txt,所有字符(不全,只包含7000数据集中出现的字符)
7.config.py,配置文件。
8.pretreat.py, 图片预处理文件。
9.evaluate.py, 模型评估文件。
10.train.py, 模型训练文件。
11.sign_in.py,运行该文件,自动登录发票平台,查询发票信息。

ctc_model.py

from keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense, GRU,\
    Lambda,Permute,TimeDistributed,Bidirectional
from keras.models import Input,Model
from keras import backend as K


Height = 35
Width = 90
rnn_size = 64  # GRU的隐层大小
n_str = 1288  # 字符类别 + 1
n_color = 5  #
n_len = 6
conv_shape = (None, 11, 512)


def ctc_lambda_func(args):
    y_pred, labels, input_length, label_length = args
    return K.ctc_batch_cost(labels, y_pred, input_length, label_length)


def nn_base(input_tensor):

    conv1_1 = Conv2D(32, 3, activation='relu', padding='same', kernel_initializer='he_normal')(input_tensor)
    conv1_2 = Conv2D(32, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv1_1)
    pool1 = MaxPooling2D((2, 2))(conv1_2)
    conv2_1 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool1)
    conv2_2 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv2_1)
    pool2 = MaxPooling2D((2, 2))(conv2_2)
    conv3_1 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool2)
    conv3_2 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv3_1)
    pool3 = MaxPooling2D((2, 2))(conv3_2)

    m = Permute((2, 1, 3), name='permute')(pool3)   # 高宽转置

    flt = TimeDistributed(Flatten(), name='timedistrib')(m)  #相当于flatten最后两个维度

    des = Dense(32)(flt)

    gru_1 = Bidirectional(GRU(rnn_size, return_sequences=True, kernel_initializer='he_normal'), merge_mode='sum')(des)
    gru_2 = Bidirectional(GRU(rnn_size, return_sequences=True, kernel_initializer='he_normal'), merge_mode='concat')(
        gru_1)

    x = Dropout(0.25)(gru_2)
    x1 = Dense(n_str, kernel_initializer='he_normal', activation='softmax',name='str_output')(x)
    x2 = Dense(n_color, kernel_initializer='he_normal', activation='softmax', name='color_output')(x)

    return x1,x2


def ctc_model(input_tensor, return_layer):

    labels1 = Input(name='the_labels1', shape=[n_len], dtype='float32')
    input_length = Input(name='input_length1', shape=[1], dtype='int64')
    label_length = Input(name='label_length1', shape=[1], dtype='int64')
    loss_out1 = Lambda(ctc_lambda_func, output_shape=(1,),
                       name='ctc1')([return_layer, labels1, input_length, label_length])

    model = Model(inputs=[input_tensor, labels1, input_length, label_length], outputs=loss_out1)
    model.compile(loss={'ctc1': lambda y_true, y_pred: y_pred}, optimizer='adadelta')
    model.summary()
    return model


input_tensor = Input(shape=(Height, Width, 3))
return_layers = nn_base(input_tensor)

model_str = ctc_model(input_tensor, return_layers[0])
model_color = ctc_model(input_tensor, return_layers[1])
model_all = Model(input_tensor, [return_layers[0], return_layers[1]])
model_all.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

train.py

from model.ctc_model import *
import os
import numpy as np
from keras.utils import generic_utils
from config import Config


def train():
    train_x = np.load(os.path.join(Config.train_path,"inputs.npy"))
    train_y_str = np.load(os.path.join(Config.train_path,"labels_str.npy"))
    train_y_color = np.load(os.path.join(Config.train_path,"labels_color.npy"))

    best_loss = np.inf
    n_epoch = Config.n_epoch
    batch_size = Config.batch_size
    data_length = len(train_x)
    steps = data_length // batch_size + 1
    losses = np.zeros(shape=(steps, 2))  # 记录每个step的两个loss,一个epoch后又覆盖
    for epoch in range(n_epoch):
        bar = generic_utils.Progbar(steps)   # keras进度条
        print('Epoch {}/{}'.format(epoch + 1, n_epoch))
        for step in range(steps):
            start = batch_size * step
            end = min(batch_size * (step + 1), data_length)   # 获取批次首尾
            X = train_x[start:end]
            Y = train_y_str[start:end]
            Y1 = train_y_color[start:end]
            loss_str = model_str.train_on_batch([X, Y, np.array(np.ones(len(X)) * int(conv_shape[1])),
                                                 np.array(np.ones(len(X), ) * n_len)], Y)
            loss_color = model_color.train_on_batch([X, Y1, np.array(np.ones(len(X)) * int(conv_shape[1])),
                                                     np.array(np.ones(len(X), ) * n_len)], Y1)
            losses[step, 0] = loss_str
            losses[step, 1] = loss_color
            bar.update(step, [('str', np.mean(losses[:step + 1, 0])), ('color', np.mean(losses[:step + 1, 1]))])
        mean_loss_str = np.mean(losses[:, 0])
        mean_loss_color = np.mean(losses[:, 1])
        mean_all_loss = 0.7 * mean_loss_str + 0.3 * mean_loss_color  # 3:7 分配loss权重
        print()
        print("loss_str {} loss_color {} all_loss {}".format(mean_loss_str, mean_loss_color, mean_all_loss))
        if mean_all_loss < best_loss:  # 保存最佳val_loss的模型
            best_loss = mean_all_loss
            print("save weights")
            model_all.save_weights(Config.model_path)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

运行train.py就可以开始训练啦。

完了之后,模型用起来。

captcha_predict.py

from model.ctc_model import model_all,conv_shape
from pretreat import img_pretreat
from PIL import Image
import numpy as np
from keras import backend as K
from config import Config


def predict(filename):
    '''
    根据识别结果返回颜色和字符
    :param filename: 验证码图片路径
    :return:
    '''
    img = Image.open(filename)
    arr = np.expand_dims(img_pretreat(img),0)
    model_all.load_weights(Config.model_path)
    result = model_all.predict(arr)
    pred_str = K.get_value(
        K.ctc_decode(result[0], input_length=np.ones(1, dtype=int) * int(conv_shape[1]), )[0][0])[:, :6]  # ctc解码,[:,:6]只取前6位
    pred_color = K.get_value(
        K.ctc_decode(result[1], input_length=np.ones(1, dtype=int) * int(conv_shape[1]), )[0][0])[:, :6]
    with open(Config.str_table_path,"r",encoding="utf-8") as f:
        t_string = f.read()
    with open(Config.color_table_path, "r", encoding="utf-8") as f:
        t_color = f.read()
    # print(pred_str,pred_color)
    strings = [search_table(t_string,item)for item in pred_str[0]]
    colors = [search_table(t_color,item)for item in pred_color[0]]
    return strings,colors


def search_table(table,idx):
    '''
    排除掉小于0或者大于分类数目的索引
    :param table: srting or color
    :param idx: 索引
    :return:
    '''
    if idx < 0:
        return "0"
    elif idx >= len(table):
        return "1"
    else:
        return table[idx]

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

最后selenium脚本一键登录

sign_in.py

from selenium import webdriver
import time
import base64
from captcha_predict import predict


def captcha_img_ocr(input_color=None):
    '''
    :param input_color: 要求输入的颜色
    :return:待输入字符串
    '''
    filename = time.strftime("%Y%m%d-%H%M%S")+'.png'
    src = browser.find_element_by_id('yzm_img').get_property('src')
    img = base64.b64decode(src.split(',')[-1])
    with open(filename, 'wb') as f:
        f.write(img)
    strings, colors = predict(filename)

    if input_color:
        res = ""
        for char,_color in zip(strings,colors):
            if _color == input_color:
                res += char
    else:
        res = "".join(strings)

    return res


def set_text():
    try:
        color = browser.find_element_by_css_selector('#yzminfo font').text[0]
    except:
        color = None
    print(color)
    text = captcha_img_ocr(color)
    print(text)
    browser.find_element_by_css_selector('#yzm').clear()
    browser.find_element_by_css_selector('#yzm').send_keys(text)


if __name__ == "__main__":

    browser = webdriver.Chrome()
    browser.get("http://inv-veri.chinatax.gov.cn")

    browser.find_element_by_css_selector('#fpdm').send_keys("发票代码")
    browser.find_element_by_css_selector('#fphm').send_keys("发票号码")
    browser.find_element_by_css_selector('#kprq').send_keys("开票日期")
    browser.find_element_by_css_selector('#kjje').send_keys("校验码")
    time.sleep(1)

    set_text()
    check = browser.find_element_by_css_selector('#checkfp')
    check.click()
    time.sleep(1)
    try:
        popup = browser.find_element_by_css_selector('#popup_ok')
    except:
        popup = None
    while popup:  # 输错后弹窗,点击换验证码,反复输入
        popup.click()
        browser.find_element_by_css_selector('#imgarea a').click()
        time.sleep(1)
        set_text()
        check.click()
        time.sleep(1)
        try:
            popup = browser.find_element_by_css_selector('#popup_ok')
        except:
            popup = None
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

源码:https://download.csdn.net/download/okfu_dl/11193913



所属网站分类: 技术文章 > 博客

作者:83whjh

链接:https://www.pythonheidong.com/blog/article/49074/76b89877c4d595439a4c/

来源:python黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

11 0
收藏该文
已收藏

评论内容:(最多支持255个字符)