12306验证码识别初尝试(1)

首先,贴代码(尚未完成,仅供参考):

# -*- coding: utf-8 -*-
"""
@author: Steve
"""
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
# 导入Keys 模块
from selenium.webdriver.common.keys import Keys
from splinter.browser import Browser
from time import sleep
from PIL import Image
from PIL import ImageFilter
#import urllib #这是python2的用法
#import urllib2 #这是python2的用法
import urllib.request
import re
import json
import ssl
import traceback
import time, sys
import winsound

if hasattr(ssl, '_create_unverified_context'):
    ssl._create_default_https_context = ssl._create_unverified_context

UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36"

#pic_url = "https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=login&rand=sjrand&0.21191171556711197"


def get_img(pic_url):
    resp = urllib.request.urlopen(pic_url)
    raw = resp.read()
    with open("./tmp.jpg", 'wb') as fp:
        fp.write(raw)

    return Image.open("./tmp.jpg")


def get_sub_img(im, x, y):
    assert 0 <= x <= 3
    assert 0 <= y <= 2
    WITH = HEIGHT = 68
    left = 5 + (67 + 5) * x
    top = 41 + (67 + 5) * y
    right = left + 67
    bottom = top + 67

    return im.crop((left, top, right, bottom))


def baidu_stu_lookup(im):
    url = "http://stu.baidu.com/n/image?fr=html5&needRawImageUrl=true&id=WU_FILE_0&name=233.png&type=image%2Fpng&lastModifiedDate=Mon+Mar+16+2015+20%3A49%3A11+GMT%2B0800+(CST)&size="
    im.save("./query_temp_img.png")
    raw = open("./query_temp_img.png", 'rb').read()
    url = url + str(len(raw))
    req = urllib.request.Request(url, raw, {'Content-Type': 'image/png', 'User-Agent': UA})
    resp = urllib.request.urlopen(req)

    resp_url = resp.read()  # return a pure url

    url = "http://stu.baidu.com/n/searchpc?queryImageUrl=" + urllib.request.quote(resp_url)

    req = urllib.request.Request(url, headers={'User-Agent': UA})
    resp = urllib.request.urlopen(req)

    html = resp.read()

    return baidu_stu_html_extract(html)


def baidu_stu_html_extract(html):
    # pattern = re.compile(r'<script type="text/javascript">(.*?)</script>', re.DOTALL | re.MULTILINE)
    pattern = re.compile(r"keywords:'(.*?)'")
    #pattern = pattern.decode('utf-8')   # https://blog.csdn.net/zoulonglong/article/details/78547191
    html = html.decode('utf-8')
    matches = pattern.findall(html)
    if not matches:
        return '[UNKNOWN]'
    json_str = matches[0]

    json_str = json_str.replace('\\x22', '"').replace('\\\\', '\\')

    # print json_str

    result = [item['keyword'] for item in json.loads(json_str)]

    return '|'.join(result) if result else '[UNKNOWN]'


def ocr_question_extract(im):
    # git@github.com:madmaze/pytesseract.git
    global pytesseract
    try:
        import pytesseract
    except:
        print("[ERROR] pytesseract not installed")
        return
    im = im.crop((127, 3, 260, 22))
    im = pre_ocr_processing(im)
    im.show()
    return pytesseract.image_to_string(im, lang='chi_sim').strip()


def pre_ocr_processing(im):
    im = im.convert("RGB")
    width, height = im.size

    white = im.filter(ImageFilter.BLUR).filter(ImageFilter.MaxFilter(23))
    grey = im.convert('L')
    impix = im.load()
    whitepix = white.load()
    greypix = grey.load()

    for y in range(height):
        for x in range(width):
            greypix[x, y] = min(255, max(255 + impix[x, y][0] - whitepix[x, y][0],
                                         255 + impix[x, y][1] - whitepix[x, y][1],
                                         255 + impix[x, y][2] - whitepix[x, y][2]))

    new_im = grey.copy()
    binarize(new_im, 150)
    return new_im


def binarize(im, thresh=120):
    assert 0 < thresh < 255
    assert im.mode == 'L'
    w, h = im.size
    for y in range(0, h):  # for y in xrange(0, h): # xrange只用于python 2
        for x in range(0, w):  # for x in xrange(0, w): # xrange只用于python 2
            if im.getpixel((x, y)) < thresh:
                im.putpixel((x, y), 0)
            else:
                im.putpixel((x, y), 255)


class huoche(object):
    """docstring for huoche"""
    #dr = webdriver.Chrome()    #此命令可以成功打开chrome
    driver_name = ''
    executable_path = ''
    # 用户名,密码
    username = u"xxxx@163.com"
    passwd = u"xxxx"
    # cookies值,需自行寻找
    #starts = u"%u6B66%u6C49%2CWHN" # 武汉
    #ends = u"%u9EBB%u57CE%2CMCN"   # 麻城
    starts = u"%u5317%u4EAC%2CBJP"
    ends = u"%u9A7B%u9A6C%u5E97%u897F%2CZLN"
    # 时间格式2018-01-19
    dtime = u"2019-01-16"
    # 车次,选择第几趟,0则从上之下依次点击
    order = 0
    ###乘客名
    users = [u"王笨","李莎莎"]
    ##席位
    xb = u"二等座"
    pz = u"成人票"

    """网址"""
    ticket_url = "https://kyfw.12306.cn/otn/leftTicket/init"
    login_url = "https://kyfw.12306.cn/otn/login/init"
    initmy_url = "https://kyfw.12306.cn/otn/view/index.html"
    buy = "https://kyfw.12306.cn/otn/confirmPassenger/initDc"
    login_url = 'https://kyfw.12306.cn/otn/login/init'

    def __init__(self):
        self.driver_name = 'chrome'
        self.executable_path = 'C:\Python27\chromedriver.exe'

    def login(self):
        self.driver.visit(self.login_url)
        #sleep(9)
        # 填充密码
        self.driver.fill("loginUserDTO.user_name", self.username)
        # sleep(1)
        self.driver.fill("userDTO.password", self.passwd)
        print(u"等待验证码,自行输入...")
        #验证码部分
        # 定位到要右击的元素
        #qqq = self.driver.driver.find_element_by_xpath("//*[@id='loginForm']/div/ul[2]/li[4]/div/div/div[3]").text
        pic = self.driver.driver.find_element_by_xpath("//*[@id='loginForm']/div/ul[2]/li[4]/div/div/div[3]/img")
        #sleep(3)
        pic_src = pic.get_attribute('src')
        # image = pic.find_element_by_tag_name("img")
        #pic_src = pic.value_of_css_property('background-color')
        #pic_src = pic.getText()
        print('url is:  %s' % (pic_src))
        #print(pic.get_attribute("alt"))
        # 对定位到的元素执行鼠标右键操作
        #qqq2 = ActionChains(self.driver).context_click(qqq).perform()
        #ActionChains(self.driver).contextClick(qqq).sendKeys(Keys.ARROW_DOWN).build().perform()
        #actionChains = ActionChains(self.driver)
        #actionChains.context_click(qqq).send_keys(Keys.ARROW_DOWN).send_keys(Keys.ENTER).perform()
        #action = ActionChains(self.driver)
        #action.move_to_element(qqq)  # 移动到该元素
        #action.context_click(qqq)  # 右键点击该元素
        #action.send_keys(Keys.ARROW_DOWN)  # 点击键盘向下箭头
        #action.send_keys('v')  # 键盘输入V保存图
        #action.perform()  # 执行保存

        im = get_img(pic_src)
        print('OCR Question:    ',ocr_question_extract(im))
        for y in range(2):
            for x in range(4):
                im2 = get_sub_img(im, x, y)
                result = baidu_stu_lookup(im2)
                print(y, x), result

        while True:
            if self.driver.url != self.initmy_url:
                sleep(1)
            else:
                break

    def start(self):
        self.driver = Browser(driver_name=self.driver_name, executable_path=self.executable_path)
        self.driver.driver.set_window_size(1400, 1000)
        self.login()    # 此Login指上面的login函数
        # sleep(1)
        self.driver.visit(self.ticket_url)  # 打开12306界面
        try:
            print(u"购票页面开始...")
            # sleep(1)
            # 加载查询信息
            self.driver.cookies.add({"_jc_save_fromStation": self.starts})
            self.driver.cookies.add({"_jc_save_toStation": self.ends})
            self.driver.cookies.add({"_jc_save_fromDate": self.dtime})

            self.driver.reload()     # 重新加载cookies

            # 添加车次类型。放在加载之后,因为实测发现比如勾选"GC-高铁/城际"checkbox后,什么都不干页面就会自动变化
            l = ['GC-高铁/城际', 'D-动车']  # 在列表里可以去掉不需要的车次类型
            for i in l:
                btn = self.driver.find_by_text(i)
                btn.click()

            i = 0   # 上面的for循环使用了i,这里如果不清零,下面因为也用到了i,会报错。
            count = 0
            if self.order != 0:
                while self.driver.url == self.ticket_url:   # 判断是否成功跳转到目标页面
                    self.driver.find_by_text(u"查询").click()
                    count += 1
                    print(u"循环点击查询... 第 %s 次" % count)
                    # sleep(1)
                    try:
                        self.driver.find_by_text(u"预订")[self.order - 1].click()
                    except Exception as e:
                        print(e)
                        print(u"还没开始预订")
                        continue
            else:
                while self.driver.url == self.ticket_url:
                    self.driver.find_by_text(u"查询").click()
                    count += 1
                    print(u"循环点击查询... 第 %s 次" % count)
                    # sleep(0.8)
                    try:
                        for i in self.driver.find_by_text(u"预订"):
                            i.click()
                            sleep(1)
                    except Exception as e:
                        print(e)
                        print(u"还没开始预订 %s")
                        continue
            print(u"开始预订...")
            # sleep(3)
            # self.driver.reload()
            sleep(1)
            print(u'开始选择用户...')
            for user in self.users:
                self.driver.find_by_text(user).last.click()

            print(u"提交订单...")
            sleep(1)
            # self.driver.find_by_text(self.pz).click()
            # self.driver.find_by_id('').select(self.pz)
            # # sleep(1)
            # self.driver.find_by_text(self.xb).click()
            # sleep(1)
            self.driver.find_by_id('submitOrder_id').click()
            # print u"开始选座..."
            # self.driver.find_by_id('1D').last.click()
            # self.driver.find_by_id('1F').last.click()

            sleep(1.5)
            print(u"确认选座...")
            self.driver.find_by_id('qr_submit_id').click()

            # 连续发出提示音
            while True:
                winsound.Beep(300, 1000)
        except Exception as e:
            print(e)


cities = {'成都': '%u6210%u90FD%2CCDW',
          '重庆': '%u91CD%u5E86%2CCQW',
          '北京': '%u5317%u4EAC%2CBJP',
          '广州': '%u5E7F%u5DDE%2CGZQ',
          '杭州': '%u676D%u5DDE%2CHZH',
          '宜昌': '%u5B9C%u660C%2CYCN',
          '郑州': '%u90D1%u5DDE%2CZZF',
          '深圳': '%u6DF1%u5733%2CSZQ',
          '西安': '%u897F%u5B89%2CXAY',
          '大连': '%u5927%u8FDE%2CDLT',
          '武汉': '%u6B66%u6C49%2CWHN',
          '上海': '%u4E0A%u6D77%2CSHH',
          '麻城': '%u9EBB%u57CE%2CMCN',
          '临沂': '%u4E34%u6C82%2CLVK',
          '驻马店西': '%u9A7B%u9A6C%u5E97%u897F%2CZLN'}

if __name__ == '__main__':
    huoche = huoche()
    #huoche.starts = cities[sys.argv[1]]
    #huoche.ends = cities[sys.argv[2]]
    #huoche.dtime = sys.argv[3]
    huoche.start()

首先,我在寻找验证码图片的url上花费了不少时间,终于在别人的基础上写出了正确的代码:
在这里插入图片描述
注意这个img:在这里插入图片描述
结果如下:
在这里插入图片描述
上述代码的健壮性不够好,我的感觉是大概运行五次出一次问题,打印不出url,因为没有找到。
又出现了这个问题:
在这里插入图片描述
解决方法:原因是python 2的程序用在了python 3中。
python 2中的写法是:
在这里插入图片描述
在这里插入图片描述
python 3中得这样写:
在这里插入图片描述
在这里插入图片描述

然后就进入了12306验证码识别的第一个核心阶段:识别提示文字,例如:
在这里插入图片描述
在上面的程序中有这样一句报错:
在这里插入图片描述
因此在网上找到了pytesseract安装的正确方法:
在这里插入图片描述
第一步,安装pytesseract。这应该是我的火车票抢票程序安装的第一个东西:
在这里插入图片描述
第二步:安装Tesseract-OCR.(安装包下载地址与安装方法:https://jingyan.baidu.com/article/219f4bf788addfde442d38fe.html)
在这里插入图片描述
下载过程中,系统提醒我有个包没下载(好像是equ),照着下文的提示下载并放到了下文所说的地址:
https://github.com/tesseract-ocr/tessdata
放到我的这个地址:
D:\Program Files (x86)\Tesseract-OCR\tessdata
在这里插入图片描述
下载并安装之后,发现Tesseract并未自动注册路径,因此在Path中加入了Tesseract的路径,然后就可以在CMD中输入tesseract了:
在这里插入图片描述
并且注册了TESSDATA_PREFIX这个系统变量(路径是安装,值为: D:\Program Files (x86)\Tesseract-OCR\tessdata)这个必须注册,并且值是…\tessdata,不然会报错找不到chi_sim等等!:
在这里插入图片描述
我的pytesseract.py的路径为:
C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Lib\site-packages\pytesseract
打开pytesseract.py后,将tessract_cmd='tesseract’改为正确的路径(注意斜杠是右斜杠,而不是上面的左斜杠):
在这里插入图片描述
最后按照此文中的方法,截图验证了tesseract已经安装成功(https://jingyan.baidu.com/article/219f4bf788addfde442d38fe.html):
在这里插入图片描述
然后,pycharm中必须install pytesseract才能import pytesseract:
在这里插入图片描述
安装完毕,试验的时候发现print了奇怪的字符,再在cmd中试验,发现tesseract基本识别不出12306验证码中的中文字符,网上查询说需要训练。哈哈,还是躲不过人工智能这一步啊。此外,截图也有问题,明显没有截取完全:
在这里插入图片描述

从此,我的悲催生活就开始了。。。主要是查询发现,如果要训练,需要下载jtessboxeditorfx来生成tesseract-orc的字典,务必选择带FX的版本,才支持中文字符编辑。但是这个jtessboxeditorfx实在是不好用,因为我使用过250张12306的验证码截图,每张截图中的每个字都得在jtessboxeditorfx一点一点地手工修改,光修改就花了我六个小时,而这六个小时过去后,发现训练结果仍然很差。 我十分想找到更好的训练方法来训练tesseract,如果不行,就只能另寻其他方法了。
jTessBoxEditor的作用:tesseract生成的 “.box”文件中列出了每个字符在图片上的位置以及内容, jTessBoxEditor的作用就是用来调整每个字符在图片上的位置以及内容的。(参考:https://www.cr173.com/soft/432051.html)
我开始以为训练是很容易的,就只截取了五张图来训练:
在这里插入图片描述
旧版的jTessBoxEditor只能处理后缀为 “.tif” 的图片,而2.0版本就可以处理 GPEG、PNG、BItmap等几乎所有格式的图片,不再需要对样本进行 “.tif” 格式的转化了。
jTessBoxEditor或jTessBoxEditorFX无需安装,双击文件夹中的train.bat即可打开,将五张图片(jpg格式即可)–Tools–Merge TIFF成了一张:mjorcen.normal.expFinal.tif
在这里插入图片描述
在这里插入图片描述

将最终得到的tif文件转换为box文件,可见只识别出了一张:
在这里插入图片描述

此时我以为编辑box文件(把第四张图的内容(红框括起来的那部分)拷贝三次,注意最后一列的编号要变),然后在jtessboxeditor中操作后就ok了,没想到修改后还是不行(https://blog.csdn.net/dcrmg/article/details/78233459; https://wenku.baidu.com/view/6caccb76a31614791711cc7931b765ce05087aa7.html):
在这里插入图片描述
保存后,此时再用jTessEditorFX打开,竟然可以看到前三张都有框了(修改之前都没有),哈哈!:
在这里插入图片描述
此时必须修改character,X,Y,W,H。后面四项都无法直接输入,而必须要一点一点地改变,慢慢享受吧!

新建文件font_properties,并输入normal 0 0 0 0 0:
在这里插入图片描述

生成train文件,不知道这一步为什么前三页还是empty page,这里不修改了,后面用250张图片修改时加入了psm参数,就不会出现empty page了:
在这里插入图片描述

执行下列命令,生成字符集文件:在这里插入图片描述

先执行第一条命令,再执行第二条命令,生成字典数据:
在这里插入图片描述

生成之后手工修改 Clustering 过程生成的 4 个文件(inttemp、pffmtable、normproto、shapetable)的名称为 [lang].xxx。这里改为 mjorcen.inttemp、mjorcen.pffmtable、mjorcen.normproto、mjorcen.shapetable。

在终端执行此命令,合并数据文件:
在这里插入图片描述

将生成的 mjorcen.traineddata” 语言包文件拷贝到 tessdata 目录下,就可以用它来进行中文字符识别了。

现在试验一下,识别"茶几”这两个字,生成是生成了,但是字不正确:
在这里插入图片描述

再用tesseract自带的语言包试验一下,发现效果比自己训练出来的好,哈哈:
在这里插入图片描述

因此接下来多找点图片来训练,就刷新12306,得到了251张图片。

照着以下步骤做(主要是添加了psm参数,下面可以看到psm参数的巨大作用):
1.jTessEditorFX–Tools–Merge Tiff,将251张jpg文件合并成一个tif文件。
2.makebox。cmd中进入tif文件所在目录,并输入以下命令:
tesseract mjorcen.normal.exp0.tif mjorcen.normal.exp0 -l chi_sim --psm 7 batch.nochop makebox
3.jTessEditorFX–Box Editor–Open,打开tif文件,一张一张地把每个框都修改好。有一张图片实在修改不好,就放弃了。
4.新建文件font_properties(注意没有后缀!它没有文件类型,不是txt文件!),并输入normal 0 0 0 0 0
5.cmd中生成train文件:mjorcen.normal.exp0.tif mjorcen.normal.exp0 -l chi_sim --psm 7 nobatch box.train
6.cmd中生成字符集文件:unicharset_extractor mjorcen.normal.exp0.box
7.生成字典数据(先执行第一条命令,再执行第二条命令):
mftraining -F font_properties -U unicharset -O mjorcen.unicharset mjorcen.normal.exp0.tr
cntraining mjorcen.normal.exp0.tr
8.手工修改上面生成的 4 个文件(inttemp、pffmtable、normproto、shapetable)的名称为 [lang].xxx。这里改为 mjorcen.inttemp、mjorcen.pffmtable、mjorcen.normproto、mjorcen.shapetable.
9.cmd中合并数据文件: combine_tessdata mjorcen
10.将生成的 mjorcen.traineddata” 语言包文件拷贝到 tessdata 目录下,就可以用它来进行中文字符识别了。

试验一下:
cmd中输入 tesseract mjorcen.normal.exp0.jpg 1.txt -l mjorcen --psm 7
结果:
1.排风机档案袋 未能识别
2.档案袋 识别成 桃漏袋
3.药片 识别成 药片
4.茶几 识别成 茶几绿
5.拖把 识别成 跑把
6.狮子 识别成 独子口
7.订书机 识别成 灯书机
8.中国结 识别成 中口国结

可见识别效果非常之差,得想办法提高tesseract的中文识别效果,不行的话,只能弃用tesseract用其他的方法了。

在此说明一下psm的重要作用:
psm参数极为重要,上述第二步由于有psm参数,大获成功!makebox时成功率为100%,每一张都没有empty page了!
用jTessEditorFx打开来编辑box:
在这里插入图片描述
在没用psm参数时,第一张图片根本一个框都没有,这样的话,jTessEditorFX的"Insert”根本没法用,即一旦一个框都没有,那这张图片就废了,但是用了psm之后,所有的图片都有框!所以用jTessEditorFx打开可以来编辑所有图片的box
在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值