├── .gitignore ├── LICENSE ├── README.md ├── data ├── download │ ├── all │ │ └── .gitignore │ ├── image │ │ └── .gitignore │ └── words │ │ └── .gitignore ├── image │ ├── test │ │ └── .gitignore │ └── train │ │ └── .gitignore └── words │ ├── test │ └── .gitignore │ └── train │ └── .gitignore ├── label └── synset ├── model ├── image │ └── .gitignore └── words │ └── .gitignore ├── src ├── __init__.py ├── classify_demo.py ├── config.py ├── image │ ├── image-classify.py │ ├── model │ │ ├── image_deploy.prototxt │ │ ├── image_solver.prototxt │ │ └── image_train_val.prototxt │ └── scripts │ │ ├── calc_mean.sh │ │ ├── create_data.py │ │ ├── create_lmdb.sh │ │ ├── image_finetune_train.sh │ │ └── image_train.sh ├── tools │ ├── __init__.py │ ├── cut_image.py │ ├── download_image.py │ ├── remove_noisy.py │ └── words.py ├── web │ ├── README.md │ ├── __init__.py │ ├── index.py │ ├── static │ │ ├── bootstrap │ │ │ ├── css │ │ │ │ ├── bootstrap-datetimepicker.css │ │ │ │ ├── bootstrap-datetimepicker.min.css │ │ │ │ ├── bootstrap-theme.css │ │ │ │ ├── bootstrap-theme.min.css │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── fonts │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ └── glyphicons-halflings-regular.woff │ │ │ └── js │ │ │ │ ├── bootstrap-datetimepicker.js │ │ │ │ ├── bootstrap-datetimepicker.min.js │ │ │ │ ├── bootstrap.js │ │ │ │ ├── bootstrap.min.js │ │ │ │ ├── bootstrap2-typeahead.min.js │ │ │ │ └── locales │ │ │ │ └── bootstrap-datetimepicker.zh-CN.js │ │ ├── css │ │ │ └── index.css │ │ ├── js │ │ │ ├── index.js │ │ │ └── jquery-2.1.1.min.js │ │ └── tmp │ │ │ └── .gitignore │ ├── templates │ │ └── 12306.html │ └── utils │ │ ├── Img12306.py │ │ └── __init__.py └── words │ ├── model │ ├── words_deploy.prototxt │ ├── words_solver.prototxt │ └── words_train_val.prototxt │ └── scripts │ ├── calc_mean.sh │ ├── create_data.py │ ├── create_lmdb.sh │ ├── words_finetune_train.sh │ └── words_train.sh └── web-demo.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 12306-captcha [![Author](https://img.shields.io/badge/Author-%E4%B8%AD%E9%BE%84%E7%A8%8B%E5%BA%8F%E5%91%98-blue.svg)](https://www.shanruifeng.win) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) [![Stars](https://img.shields.io/github/stars/aaronshan/12306-captcha.svg?label=Stars&style=social)](https://github.com/aaronshan/12306-captcha) 2 | 3 | 12306验证码识别 4 | 5 | ## 1. 训练 6 | 7 | ### 1.1 准备工作 8 | 9 | * 下载caffe并编译, 具体可参考官方文档, 此处不再赘述. 10 | * 修改`src/config.py`中的caffe根目录和项目根目录. 11 | * pip安装easydict, skimage等. 12 | 13 | ### 1.2 数据 14 | 15 | * 通过运行`src/tools/download_image.py`, 会将12306验证码下载至`data/download/all`目录. 16 | * 下载完成后, 通过运行`src/tools/cut_image.py`, 会将其裁剪为图片和文字两部分, 分别放在`data/download/image`目录和`data/download/words`目录. 17 | * 修改`src/image/scripts/words.py`文件main方法中cut方法的参数(其参数为`data/download/words`中子目录的`words_*`中的数字), 它的目的是处理`data/download/words`中的所有子文件, 对多个词语进行分割并调整大小为固定值. 18 | * 然后手工对其进行分类, 分别放至`data/image`和`data/words`目录. 可以将其分为两部分,分别放在对应的train和test目录.比如,一个示例目录如下: 19 | ``` 20 | -image 21 | --test 22 | ---蜡烛 23 | ----1-1.jpg 24 | ---沙漠 25 | ----2-1.jpg 26 | --train 27 | ---蜡烛 28 | ----1-2.jpg 29 | ---沙漠 30 | ----2-2.jpg 31 | ``` 32 | 33 | #### 图片部分 34 | 35 | * 运行`src/image/scripts/create_data.py`, 将会生成图片部分对应的train.txt和test.txt, 里面包含着训练和测试文件及其类别列表. 36 | * 运行`src/image/scripts/create_lmdb.sh`, 将会生成图片部分对应的lmdb文件. 37 | 38 | #### 文字部分 39 | 40 | * 运行`src/words/scripts/create_data.py`, 将会生成文字部分对应的train.txt和test.txt, 里面包含着训练和测试文件及其类别列表. 41 | * 运行`src/words/scripts/create_lmdb.sh`, 将会生成文字部分对应的lmdb文件. 42 | 43 | ### 1.3 参数 44 | 可以根据实际情况对`src/image/model/image_solver.prototxt`和`src/words/model/words_solver.prototxt`文件进行修改.具体修改方法可参考其他模型. 45 | 46 | ### 1.4 开始训练 47 | `src/image/scripts/image_train.sh`和`src/image/scripts/image_finetune_train.sh`脚本分别用来进行从头训练/微调训练, 训练方法可参考caffe模型训练方法. 48 | 49 | 同理: 50 | 51 | `src/words/scripts/words_train.sh`和`src/words/scripts/words_finetune_train.sh`脚本分别用来进行从头训练/微调训练, 训练方法可参考caffe模型训练方法. 52 | 53 | 54 | ## 测试 55 | `src/web`提供了一个web测试界面, 运行index.py即可. 运行前, 可以更改对应的模型文件名称. 一个简单示例如下: 56 | 57 | ![web-demo](https://github.com/aaronshan/12306-captcha/blob/master/web-demo.png) 58 | 59 | ## 其他 60 | 61 | 1. 在实际应用中, 会使用从百度/搜狗/谷歌等图片搜索引擎中爬取图片并做处理的方式来完成图片分类收集工作. 比如爬取关键词为档案袋的图片, 再进一步做处理. 以解决从12306下载并裁剪-手工分类效率太低及样本量不足的问题, 提升效率。 62 | 63 | 2. 此外, 项目里对文字部分的分割也不是很完美. 对图片的分类也是裁剪并逐个进行的, 这样的响应效率不会很高. 可以使用目标检测的方式, 对整个验证码图片做目标检测, 同时检测8个图片及文字部分. 以加快检测速度. 64 | -------------------------------------------------------------------------------- /data/download/all/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /data/download/image/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /data/download/words/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /data/image/test/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /data/image/train/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /data/words/test/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /data/words/train/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /label/synset: -------------------------------------------------------------------------------- 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 | 辣椒酱 72 | 摩天轮 73 | 球拍 74 | 纽扣 75 | 光盘 76 | 铃铛 77 | 海豹 78 | 中国结 79 | 话梅 80 | 安全帽 81 | 白菜 82 | 保温杯 83 | 本子 84 | 鞭炮 85 | 冰箱 86 | 创可贴 87 | 刺猬 88 | 蛋挞 89 | 灯笼 90 | 电话机 91 | 电线 92 | 电子秤 93 | 雕像 94 | 调色板 95 | 鹅蛋 96 | 饭盒 97 | 肥皂盒 98 | 蜂蜜 99 | 公路 100 | 核桃 101 | 荷叶 102 | 红豆 103 | 红酒 104 | 红枣 105 | 花生 106 | 馄饨 107 | 剪纸 108 | 奖状 109 | 胶卷 110 | 警示牌 111 | 恐龙 112 | 垃圾桶 113 | 拉链 114 | 篮球 115 | 龙舟 116 | 楼梯 117 | 轮胎 118 | 锣 119 | 蚂蚁 120 | 芒果 121 | 猫 122 | 猫头鹰 123 | 毛线 124 | 煤油灯 125 | 蒙古包 126 | 蜜蜂 127 | 奶瓶 128 | 南瓜 129 | 排风机 130 | 螃蟹 131 | 披萨 132 | 啤酒 133 | 皮球 134 | 气球 135 | 热水瓶 136 | 人参 137 | 薯条 138 | 树叶 139 | 水管 140 | 塑料杯 141 | 天鹅 142 | 兔子 143 | 围巾 144 | 西装 145 | 鞋刷 146 | 鸭蛋 147 | 牙膏 148 | 洋葱 149 | 椰子 150 | 衣架 151 | 印章 152 | 樱桃 153 | 油 154 | 鱼缸 155 | 熨斗 156 | 蒸笼 157 | 纸牌 158 | 钟表 159 | 钻石 160 | 贝壳 161 | 拖把 162 | 挂钟 163 | 锅铲 164 | 烛台 165 | 珊瑚 166 | 沙拉 167 | 口哨 168 | 金字塔 169 | 棉棒 170 | 锦旗 171 | 网球拍 172 | 日历 173 | 沙包 174 | 174 175 | 175 176 | 176 177 | 177 178 | 178 179 | 179 180 | 180 181 | 181 182 | 182 183 | 183 184 | 184 185 | 185 186 | 186 187 | 187 188 | 188 189 | 189 190 | 190 191 | 191 192 | 192 193 | 193 194 | 194 195 | 195 196 | 196 197 | 197 198 | 198 199 | 199 200 | 200 201 | 201 202 | 202 203 | 203 204 | 204 205 | 205 206 | 206 207 | 207 208 | 208 209 | 209 210 | 210 211 | 211 212 | 212 213 | 213 214 | 214 215 | 215 216 | 216 217 | 217 218 | 218 219 | 219 220 | 220 221 | 221 222 | 222 223 | 223 224 | 224 225 | 225 226 | 226 227 | 227 228 | 228 229 | 229 230 | 230 231 | 231 232 | 232 233 | 233 234 | 234 235 | 235 236 | 236 237 | 237 238 | 238 239 | 239 240 | 240 241 | 241 242 | 242 243 | 243 244 | 244 245 | 245 246 | 246 247 | 247 248 | 248 249 | 249 250 | 250 251 | 251 252 | 252 253 | 253 254 | 254 255 | 255 256 | 256 257 | 257 258 | 258 259 | 259 260 | 260 261 | 261 262 | 262 263 | 263 264 | 264 265 | 265 266 | 266 267 | 267 268 | 268 269 | 269 270 | 270 271 | 271 272 | 272 273 | 273 274 | 274 275 | 275 276 | 276 277 | 277 278 | 278 279 | 279 280 | 280 281 | 281 282 | 282 283 | 283 284 | 284 285 | 285 286 | 286 287 | 287 288 | 288 289 | 289 290 | 290 291 | 291 292 | 292 293 | 293 294 | 294 295 | 295 296 | 296 297 | 297 298 | 298 299 | 299 300 | 300 301 | 301 302 | 302 303 | 303 304 | 304 305 | 305 306 | 306 307 | 307 308 | 308 309 | 309 310 | 310 311 | 311 312 | 312 313 | 313 314 | 314 315 | 315 316 | 316 317 | 317 318 | 318 319 | 319 320 | 320 321 | 321 322 | 322 323 | 323 324 | 324 325 | 325 326 | 326 327 | 327 328 | 328 329 | 329 330 | 330 331 | 331 332 | 332 333 | 333 334 | 334 335 | 335 336 | 336 337 | 337 338 | 338 339 | 339 340 | 340 341 | 341 342 | 342 343 | 343 344 | 344 345 | 345 346 | 346 347 | 347 348 | 348 349 | 349 350 | 350 351 | 351 352 | 352 353 | 353 354 | 354 355 | 355 356 | 356 357 | 357 358 | 358 359 | 359 360 | 360 361 | 361 362 | 362 363 | 363 364 | 364 365 | 365 366 | 366 367 | 367 368 | 368 369 | 369 370 | 370 371 | 371 372 | 372 373 | 373 374 | 374 375 | 375 376 | 376 377 | 377 378 | 378 379 | 379 380 | 380 381 | 381 382 | 382 383 | 383 384 | 384 385 | 385 386 | 386 387 | 387 388 | 388 389 | 389 390 | 390 391 | 391 392 | 392 393 | 393 394 | 394 395 | 395 396 | 396 397 | 397 398 | 398 399 | 399 400 | 400 401 | 401 402 | 402 403 | 403 404 | 404 405 | 405 406 | 406 407 | 407 408 | 408 409 | 409 410 | 410 411 | 411 412 | 412 413 | 413 414 | 414 415 | 415 416 | 416 417 | 417 418 | 418 419 | 419 420 | 420 421 | 421 422 | 422 423 | 423 424 | 424 425 | 425 426 | 426 427 | 427 428 | 428 429 | 429 430 | 430 431 | 431 432 | 432 433 | 433 434 | 434 435 | 435 436 | 436 437 | 437 438 | 438 439 | 439 440 | 440 441 | 441 442 | 442 443 | 443 444 | 444 445 | 445 446 | 446 447 | 447 448 | 448 449 | 449 450 | 450 451 | 451 452 | 452 453 | 453 454 | 454 455 | 455 456 | 456 457 | 457 458 | 458 459 | 459 460 | 460 461 | 461 462 | 462 463 | 463 464 | 464 465 | 465 466 | 466 467 | 467 468 | 468 469 | 469 470 | 470 471 | 471 472 | 472 473 | 473 474 | 474 475 | 475 476 | 476 477 | 477 478 | 478 479 | 479 480 | 480 481 | 481 482 | 482 483 | 483 484 | 484 485 | 485 486 | 486 487 | 487 488 | 488 489 | 489 490 | 490 491 | 491 492 | 492 493 | 493 494 | 494 495 | 495 496 | 496 497 | 497 498 | 498 499 | 499 500 | 500 501 | 501 502 | 502 503 | 503 504 | 504 505 | 505 506 | 506 507 | 507 508 | 508 509 | 509 510 | 510 511 | 511 512 | 512 513 | 513 514 | 514 515 | 515 516 | 516 517 | 517 518 | 518 519 | 519 520 | 520 521 | 521 522 | 522 523 | 523 524 | 524 525 | 525 526 | 526 527 | 527 528 | 528 529 | 529 530 | 530 531 | 531 532 | 532 533 | 533 534 | 534 535 | 535 536 | 536 537 | 537 538 | 538 539 | 539 540 | 540 541 | 541 542 | 542 543 | 543 544 | 544 545 | 545 546 | 546 547 | 547 548 | 548 549 | 549 550 | 550 551 | 551 552 | 552 553 | 553 554 | 554 555 | 555 556 | 556 557 | 557 558 | 558 559 | 559 560 | 560 561 | 561 562 | 562 563 | 563 564 | 564 565 | 565 566 | 566 567 | 567 568 | 568 569 | 569 570 | 570 571 | 571 572 | 572 573 | 573 574 | 574 575 | 575 576 | 576 577 | 577 578 | 578 579 | 579 580 | 580 581 | 581 582 | 582 583 | 583 584 | 584 585 | 585 586 | 586 587 | 587 588 | 588 589 | 589 590 | 590 591 | 591 592 | 592 593 | 593 594 | 594 595 | 595 596 | 596 597 | 597 598 | 598 599 | 599 600 | 600 601 | 601 602 | 602 603 | 603 604 | 604 605 | 605 606 | 606 607 | 607 608 | 608 609 | 609 610 | 610 611 | 611 612 | 612 613 | 613 614 | 614 615 | 615 616 | 616 617 | 617 618 | 618 619 | 619 620 | 620 621 | 621 622 | 622 623 | 623 624 | 624 625 | 625 626 | 626 627 | 627 628 | 628 629 | 629 630 | 630 631 | 631 632 | 632 633 | 633 634 | 634 635 | 635 636 | 636 637 | 637 638 | 638 639 | 639 640 | 640 641 | 641 642 | 642 643 | 643 644 | 644 645 | 645 646 | 646 647 | 647 648 | 648 649 | 649 650 | 650 651 | 651 652 | 652 653 | 653 654 | 654 655 | 655 656 | 656 657 | 657 658 | 658 659 | 659 660 | 660 661 | 661 662 | 662 663 | 663 664 | 664 665 | 665 666 | 666 667 | 667 668 | 668 669 | 669 670 | 670 671 | 671 672 | 672 673 | 673 674 | 674 675 | 675 676 | 676 677 | 677 678 | 678 679 | 679 680 | 680 681 | 681 682 | 682 683 | 683 684 | 684 685 | 685 686 | 686 687 | 687 688 | 688 689 | 689 690 | 690 691 | 691 692 | 692 693 | 693 694 | 694 695 | 695 696 | 696 697 | 697 698 | 698 699 | 699 700 | 700 701 | 701 702 | 702 703 | 703 704 | 704 705 | 705 706 | 706 707 | 707 708 | 708 709 | 709 710 | 710 711 | 711 712 | 712 713 | 713 714 | 714 715 | 715 716 | 716 717 | 717 718 | 718 719 | 719 720 | 720 721 | 721 722 | 722 723 | 723 724 | 724 725 | 725 726 | 726 727 | 727 728 | 728 729 | 729 730 | 730 731 | 731 732 | 732 733 | 733 734 | 734 735 | 735 736 | 736 737 | 737 738 | 738 739 | 739 740 | 740 741 | 741 742 | 742 743 | 743 744 | 744 745 | 745 746 | 746 747 | 747 748 | 748 749 | 749 750 | 750 751 | 751 752 | 752 753 | 753 754 | 754 755 | 755 756 | 756 757 | 757 758 | 758 759 | 759 760 | 760 761 | 761 762 | 762 763 | 763 764 | 764 765 | 765 766 | 766 767 | 767 768 | 768 769 | 769 770 | 770 771 | 771 772 | 772 773 | 773 774 | 774 775 | 775 776 | 776 777 | 777 778 | 778 779 | 779 780 | 780 781 | 781 782 | 782 783 | 783 784 | 784 785 | 785 786 | 786 787 | 787 788 | 788 789 | 789 790 | 790 791 | 791 792 | 792 793 | 793 794 | 794 795 | 795 796 | 796 797 | 797 798 | 798 799 | 799 800 | 800 801 | 801 802 | 802 803 | 803 804 | 804 805 | 805 806 | 806 807 | 807 808 | 808 809 | 809 810 | 810 811 | 811 812 | 812 813 | 813 814 | 814 815 | 815 816 | 816 817 | 817 818 | 818 819 | 819 820 | 820 821 | 821 822 | 822 823 | 823 824 | 824 825 | 825 826 | 826 827 | 827 828 | 828 829 | 829 830 | 830 831 | 831 832 | 832 833 | 833 834 | 834 835 | 835 836 | 836 837 | 837 838 | 838 839 | 839 840 | 840 841 | 841 842 | 842 843 | 843 844 | 844 845 | 845 846 | 846 847 | 847 848 | 848 849 | 849 850 | 850 851 | 851 852 | 852 853 | 853 854 | 854 855 | 855 856 | 856 857 | 857 858 | 858 859 | 859 860 | 860 861 | 861 862 | 862 863 | 863 864 | 864 865 | 865 866 | 866 867 | 867 868 | 868 869 | 869 870 | 870 871 | 871 872 | 872 873 | 873 874 | 874 875 | 875 876 | 876 877 | 877 878 | 878 879 | 879 880 | 880 881 | 881 882 | 882 883 | 883 884 | 884 885 | 885 886 | 886 887 | 887 888 | 888 889 | 889 890 | 890 891 | 891 892 | 892 893 | 893 894 | 894 895 | 895 896 | 896 897 | 897 898 | 898 899 | 899 900 | 900 901 | 901 902 | 902 903 | 903 904 | 904 905 | 905 906 | 906 907 | 907 908 | 908 909 | 909 910 | 910 911 | 911 912 | 912 913 | 913 914 | 914 915 | 915 916 | 916 917 | 917 918 | 918 919 | 919 920 | 920 921 | 921 922 | 922 923 | 923 924 | 924 925 | 925 926 | 926 927 | 927 928 | 928 929 | 929 930 | 930 931 | 931 932 | 932 933 | 933 934 | 934 935 | 935 936 | 936 937 | 937 938 | 938 939 | 939 940 | 940 941 | 941 942 | 942 943 | 943 944 | 944 945 | 945 946 | 946 947 | 947 948 | 948 949 | 949 950 | 950 951 | 951 952 | 952 953 | 953 954 | 954 955 | 955 956 | 956 957 | 957 958 | 958 959 | 959 960 | 960 961 | 961 962 | 962 963 | 963 964 | 964 965 | 965 966 | 966 967 | 967 968 | 968 969 | 969 970 | 970 971 | 971 972 | 972 973 | 973 974 | 974 975 | 975 976 | 976 977 | 977 978 | 978 979 | 979 980 | 980 981 | 981 982 | 982 983 | 983 984 | 984 985 | 985 986 | 986 987 | 987 988 | 988 989 | 989 990 | 990 991 | 991 992 | 992 993 | 993 994 | 994 995 | 995 996 | 996 997 | 997 998 | 998 999 | 999 1000 | 1000 -------------------------------------------------------------------------------- /model/image/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /model/words/.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | * 3 | # 除了这个文件 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | #! _*_ coding:utf-8 _*_ 2 | 3 | # -------------------------------------------------------- 4 | # 12306-captcha 5 | # Copyright (c) 2017 6 | # Written by ruifengshan 7 | # -------------------------------------------------------- 8 | 9 | -------------------------------------------------------------------------------- /src/classify_demo.py: -------------------------------------------------------------------------------- 1 | # -*-coding:utf-8-*- 2 | 3 | # -------------------------------------------------------- 4 | # 12306-captcha 5 | # Copyright (c) 2017 6 | # Licensed under The Apache License [see LICENSE for details] 7 | # Written by ruifengshan 8 | # -------------------------------------------------------- 9 | 10 | """验证码识别""" 11 | 12 | import os 13 | import sys 14 | 15 | import cv2 16 | import numpy as np 17 | from tools import words 18 | from tools import remove_noisy 19 | import skimage 20 | import skimage.io 21 | import skimage.transform 22 | 23 | from tools import cut_image 24 | from src.config import cfg 25 | 26 | # =================================== common start ============================================ 27 | caffe_root = cfg.CAFFE_ROOT 28 | sys.path.insert(0, caffe_root + 'python') 29 | 30 | import caffe 31 | 32 | imagenet_labels_filename = cfg.ROOT + 'label/synset' 33 | labels = np.loadtxt(imagenet_labels_filename, str, delimiter='\t') 34 | # =================================== common end ============================================= 35 | 36 | # ====================================image start================================================= 37 | image_net_file = cfg.ROOT + '/src/image/model/image_deploy.prototxt' 38 | image_caffe_model = cfg.ROOT + 'model/image/f18_snapshot_iter_84600.caffemodel' 39 | image_net = caffe.Net(image_net_file, image_caffe_model, caffe.TEST) 40 | image_transformer = caffe.io.Transformer({'data': image_net.blobs['data'].data.shape}) 41 | "读取的图片文件格式为H×W×K,需转化为K×H×W" 42 | image_transformer.set_transpose('data', (2, 0, 1)) 43 | # transformer.set_mean('data', mean=np.float32([103.939, 116.779, 123.68])) 44 | "将图片存储为[0, 1],而caffe中将图片存储为[0, 255" 45 | image_transformer.set_raw_scale('data', 255) 46 | "caffe中图片是BGR格式,而原始格式是RGB,所以要转化" 47 | image_transformer.set_channel_swap('data', (2, 1, 0)) 48 | # ====================================image end================================================= 49 | 50 | 51 | # ====================================words start================================================= 52 | words_net_file = cfg.ROOT + '/src/words/model/words_deploy.prototxt' 53 | words_caffe_model = cfg.ROOT + 'model/words/words_4_snapshot_iter_160000.caffemodel' 54 | words_net = caffe.Net(words_net_file, words_caffe_model, caffe.TEST) 55 | words_transformer = caffe.io.Transformer({'data': words_net.blobs['data'].data.shape}) 56 | "读取的图片文件格式为H×W×K,需转化为K×H×W" 57 | words_transformer.set_transpose('data', (2, 0, 1)) 58 | # transformer.set_mean('data', mean=np.float32([103.939, 116.779, 123.68])) 59 | "将图片存储为[0, 1],而caffe中将图片存储为[0, 255" 60 | words_transformer.set_raw_scale('data', 255) 61 | "caffe中图片是BGR格式,而原始格式是RGB,所以要转化" 62 | words_transformer.set_channel_swap('data', (2, 1, 0)) 63 | 64 | 65 | # ====================================words end================================================= 66 | def judge_words(img): 67 | """返回文件坐标""" 68 | list_img = words.get_test(img) 69 | _text = [] 70 | if list_img is None: 71 | return None 72 | for im in list_img: 73 | # 灰度图转化为多通道RGB 74 | im = cv2.merge([im, im, im]) # 前面分离出来的三个通道 75 | words_net.blobs['data'].data[...] = words_transformer.preprocess('data', im.astype(np.int)) 76 | out = words_net.forward() 77 | prob = out['softmax'][0] 78 | top_k = words_net.blobs['softmax'].data[0].flatten().argsort()[-1:-6:-1] 79 | _text.append(labels[top_k[0]]) 80 | return _text 81 | 82 | 83 | def judge_image(list_img, list_label): 84 | """判断是不是文件夹""" 85 | if list_img is None: 86 | return None 87 | _index = 1 88 | for im in list_img: 89 | 90 | img = remove_noisy.rm_blackNoisy(im) 91 | img = cut_image.load_image(img) 92 | 93 | image_net.blobs['data'].data[...] = image_transformer.preprocess('data', img) 94 | out = image_net.forward() 95 | prob = out['softmax'][0] 96 | top_k = image_net.blobs['softmax'].data[0].flatten().argsort()[-1:-6:-1] 97 | print "第", _index, " 图预测是:", labels[top_k[0]] 98 | for label in list_label: 99 | if labels[top_k[0]] == label: 100 | print "++++++++++++++++++图片位置:", _index, label, "+++++++++++++++++" 101 | _index += 1 102 | 103 | 104 | def train_main(): 105 | path_dir = '/home/ruifengshan/github/12306-captcha/data/download/' 106 | img_names = filter(lambda s: not s.startswith("."), os.listdir(path_dir + '/all')) 107 | 108 | for img_name in img_names: 109 | im = cut_image.read_image(os.path.join(path_dir + '/all', img_name)) 110 | if im is None: 111 | print "该图片{ %s }处理异常: " % img_name 112 | continue 113 | # 转为灰度图 114 | list_text = judge_words(cut_image.get_text(cv2.cvtColor(im, cv2.COLOR_BGR2GRAY))) 115 | print "文字部分的内容:" 116 | for text in list_text: 117 | print text 118 | judge_image(cut_image.get_image(im), list_text) 119 | skimage.io.imshow(im) 120 | 121 | 122 | if __name__ == '__main__': 123 | train_main() 124 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | #! _*_ coding:utf-8 _*_ 2 | 3 | # -------------------------------------------------------- 4 | # 12306-captcha 5 | # Copyright (c) 2017 6 | # Written by ruifengshan 7 | # -------------------------------------------------------- 8 | 9 | # `pip install easydict` if you don't have it 10 | from easydict import EasyDict as edict 11 | 12 | __C = edict() 13 | # Consumers can get config by: 14 | # from src.config import cfg 15 | cfg = __C 16 | 17 | # caffe根目录 18 | __C.CAFFE_ROOT = '/home/ruifengshan/caffe-ssd/' 19 | 20 | # 项目根目录 21 | __C.ROOT = '/home/ruifengshan/github/12306-captcha/' -------------------------------------------------------------------------------- /src/image/image-classify.py: -------------------------------------------------------------------------------- 1 | # -*-coding:utf-8-*- 2 | 3 | # -------------------------------------------------------- 4 | # 12306-captcha 5 | # Copyright (c) 2017 6 | # Licensed under The Apache License [see LICENSE for details] 7 | # Written by ruifengshan 8 | # -------------------------------------------------------- 9 | 10 | import os 11 | import sys 12 | 13 | import numpy as np 14 | from src.tools import remove_noisy 15 | from src.tools import cut_image as cimg 16 | import logging 17 | from src.config import cfg 18 | 19 | caffe_root = cfg.CAFFE_ROOT 20 | sys.path.insert(0, caffe_root + 'python') 21 | 22 | import caffe 23 | 24 | net_file = cfg.ROOT + '/src/image/model/image_deploy.prototxt' 25 | caffe_model = cfg.ROOT + 'model/image/f16_snapshot_iter_208000.caffemodel' 26 | synset_file = cfg.ROOT + 'label/synset' 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | 31 | def judge(path, file_path=synset_file): 32 | """判断是不是文件夹""" 33 | img_names = os.listdir(path) 34 | for img_name in img_names: 35 | logger.info("文件名: %s", img_name) 36 | tmp_img_name = os.path.join(path, img_name) 37 | if os.path.isdir(tmp_img_name): 38 | judge(tmp_img_name) 39 | os.removedirs(tmp_img_name) 40 | print '$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$文件夹已经被删除', tmp_img_name 41 | elif tmp_img_name.split('.')[1] != "DS_Store": 42 | im1 = cimg.read_image(tmp_img_name) 43 | img = remove_noisy.rm_blackNoisy(im1) 44 | im = cimg.load_image(img) 45 | net.blobs['data'].data[...] = transformer.preprocess('data', im) 46 | out = net.forward() 47 | 48 | imagenet_labels_filename = synset_file 49 | labels = np.loadtxt(imagenet_labels_filename, str, delimiter='\t') 50 | prob = out['softmax'][0] 51 | top_k = net.blobs['softmax'].data[0].flatten().argsort()[-1:-6:-1] 52 | for i in np.arange(top_k.size): 53 | print '照片名: %s , 对应的类:%s , 概率 %%%3f' % (img_name, labels[top_k[i]], prob[top_k[i]] * 100) 54 | print top_k[i], labels[top_k[i]] 55 | new_path = os.path.join("/home/ruifengshan/github/12306-captcha/judge-image-result/", labels[top_k[0]]) 56 | cimg.makeDir(new_path) 57 | cimg.write_image(im1, os.path.join(new_path, img_name)) 58 | # '删除' 59 | os.remove(tmp_img_name) 60 | print '++++++++++++++++++++++++++++++++++++文件已经被删除', tmp_img_name 61 | # time.sleep(1) 62 | 63 | 64 | if __name__ == '__main__': 65 | net = caffe.Net(net_file, caffe_model, caffe.TEST) 66 | transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape}) 67 | "读取的图片文件格式为H×W×K,需转化为K×H×W" 68 | transformer.set_transpose('data', (2, 0, 1)) 69 | # transformer.set_mean('data', mean=np.float32([103.939, 116.779, 123.68])) 70 | "将图片存储为[0, 1],而caffe中将图片存储为[0, 255" 71 | transformer.set_raw_scale('data', 255) 72 | "caffe中图片是BGR格式,而原始格式是RGB,所以要转化" 73 | transformer.set_channel_swap('data', (2, 1, 0)) 74 | image_dir='/home/ruifengshan/github/12306-captcha/judge-image' 75 | judge(image_dir) 76 | -------------------------------------------------------------------------------- /src/image/model/image_deploy.prototxt: -------------------------------------------------------------------------------- 1 | input: "data" 2 | input_shape { 3 | dim: 1 4 | dim: 3 5 | dim: 68 6 | dim: 68 7 | } 8 | layer { 9 | name: "conv1_1" 10 | type: "Convolution" 11 | bottom: "data" 12 | top: "conv1_1" 13 | convolution_param { 14 | num_output: 64 15 | pad: 1 16 | kernel_size: 3 17 | weight_filler { 18 | type: "xavier" 19 | } 20 | bias_filler { 21 | type: "constant" 22 | value: 0.0 23 | } 24 | } 25 | } 26 | layer { 27 | name: "relu1_1" 28 | type: "ReLU" 29 | bottom: "conv1_1" 30 | top: "conv1_1" 31 | } 32 | layer { 33 | name: "conv1_2" 34 | type: "Convolution" 35 | bottom: "conv1_1" 36 | top: "conv1_2" 37 | convolution_param { 38 | num_output: 64 39 | pad: 1 40 | kernel_size: 3 41 | weight_filler { 42 | type: "xavier" 43 | } 44 | bias_filler { 45 | type: "constant" 46 | value: 0.0 47 | } 48 | } 49 | } 50 | layer { 51 | name: "relu1_2" 52 | type: "ReLU" 53 | bottom: "conv1_2" 54 | top: "conv1_2" 55 | } 56 | layer { 57 | name: "pool1" 58 | type: "Pooling" 59 | bottom: "conv1_2" 60 | top: "pool1" 61 | pooling_param { 62 | pool: MAX 63 | kernel_size: 2 64 | stride: 2 65 | } 66 | } 67 | layer { 68 | name: "conv2_1" 69 | type: "Convolution" 70 | bottom: "pool1" 71 | top: "conv2_1" 72 | convolution_param { 73 | num_output: 128 74 | pad: 1 75 | kernel_size: 3 76 | weight_filler { 77 | type: "xavier" 78 | } 79 | bias_filler { 80 | type: "constant" 81 | value: 0.0 82 | } 83 | } 84 | } 85 | layer { 86 | name: "relu2_1" 87 | type: "ReLU" 88 | bottom: "conv2_1" 89 | top: "conv2_1" 90 | } 91 | layer { 92 | name: "conv2_2" 93 | type: "Convolution" 94 | bottom: "conv2_1" 95 | top: "conv2_2" 96 | convolution_param { 97 | num_output: 128 98 | pad: 1 99 | kernel_size: 3 100 | weight_filler { 101 | type: "xavier" 102 | } 103 | bias_filler { 104 | type: "constant" 105 | value: 0.0 106 | } 107 | } 108 | } 109 | layer { 110 | name: "relu2_2" 111 | type: "ReLU" 112 | bottom: "conv2_2" 113 | top: "conv2_2" 114 | } 115 | layer { 116 | name: "pool2" 117 | type: "Pooling" 118 | bottom: "conv2_2" 119 | top: "pool2" 120 | pooling_param { 121 | pool: MAX 122 | kernel_size: 2 123 | stride: 2 124 | } 125 | } 126 | layer { 127 | name: "conv3_1" 128 | type: "Convolution" 129 | bottom: "pool2" 130 | top: "conv3_1" 131 | convolution_param { 132 | num_output: 256 133 | pad: 1 134 | kernel_size: 3 135 | weight_filler { 136 | type: "xavier" 137 | } 138 | bias_filler { 139 | type: "constant" 140 | value: 0.0 141 | } 142 | } 143 | } 144 | layer { 145 | name: "relu3_1" 146 | type: "ReLU" 147 | bottom: "conv3_1" 148 | top: "conv3_1" 149 | } 150 | layer { 151 | name: "conv3_2" 152 | type: "Convolution" 153 | bottom: "conv3_1" 154 | top: "conv3_2" 155 | convolution_param { 156 | num_output: 256 157 | pad: 1 158 | kernel_size: 3 159 | weight_filler { 160 | type: "xavier" 161 | } 162 | bias_filler { 163 | type: "constant" 164 | value: 0.0 165 | } 166 | } 167 | } 168 | layer { 169 | name: "relu3_2" 170 | type: "ReLU" 171 | bottom: "conv3_2" 172 | top: "conv3_2" 173 | } 174 | layer { 175 | name: "conv3_3" 176 | type: "Convolution" 177 | bottom: "conv3_2" 178 | top: "conv3_3" 179 | convolution_param { 180 | num_output: 256 181 | pad: 1 182 | kernel_size: 3 183 | weight_filler { 184 | type: "xavier" 185 | } 186 | bias_filler { 187 | type: "constant" 188 | value: 0.0 189 | } 190 | } 191 | } 192 | layer { 193 | name: "relu3_3" 194 | type: "ReLU" 195 | bottom: "conv3_3" 196 | top: "conv3_3" 197 | } 198 | layer { 199 | name: "pool3" 200 | type: "Pooling" 201 | bottom: "conv3_3" 202 | top: "pool3" 203 | pooling_param { 204 | pool: MAX 205 | kernel_size: 2 206 | stride: 2 207 | } 208 | } 209 | layer { 210 | name: "conv4_1" 211 | type: "Convolution" 212 | bottom: "pool3" 213 | top: "conv4_1" 214 | convolution_param { 215 | num_output: 512 216 | pad: 1 217 | kernel_size: 3 218 | weight_filler { 219 | type: "xavier" 220 | } 221 | bias_filler { 222 | type: "constant" 223 | value: 0.0 224 | } 225 | } 226 | } 227 | layer { 228 | name: "relu4_1" 229 | type: "ReLU" 230 | bottom: "conv4_1" 231 | top: "conv4_1" 232 | } 233 | layer { 234 | name: "conv4_2" 235 | type: "Convolution" 236 | bottom: "conv4_1" 237 | top: "conv4_2" 238 | convolution_param { 239 | num_output: 512 240 | pad: 1 241 | kernel_size: 3 242 | weight_filler { 243 | type: "xavier" 244 | } 245 | bias_filler { 246 | type: "constant" 247 | value: 0.0 248 | } 249 | } 250 | } 251 | layer { 252 | name: "relu4_2" 253 | type: "ReLU" 254 | bottom: "conv4_2" 255 | top: "conv4_2" 256 | } 257 | layer { 258 | name: "conv4_3" 259 | type: "Convolution" 260 | bottom: "conv4_2" 261 | top: "conv4_3" 262 | convolution_param { 263 | num_output: 512 264 | pad: 1 265 | kernel_size: 3 266 | weight_filler { 267 | type: "xavier" 268 | } 269 | bias_filler { 270 | type: "constant" 271 | value: 0.0 272 | } 273 | } 274 | } 275 | layer { 276 | name: "relu4_3" 277 | type: "ReLU" 278 | bottom: "conv4_3" 279 | top: "conv4_3" 280 | } 281 | layer { 282 | name: "pool4" 283 | type: "Pooling" 284 | bottom: "conv4_3" 285 | top: "pool4" 286 | pooling_param { 287 | pool: MAX 288 | kernel_size: 2 289 | stride: 2 290 | } 291 | } 292 | layer { 293 | name: "conv5_1" 294 | type: "Convolution" 295 | bottom: "pool4" 296 | top: "conv5_1" 297 | convolution_param { 298 | num_output: 512 299 | pad: 1 300 | kernel_size: 3 301 | weight_filler { 302 | type: "xavier" 303 | } 304 | bias_filler { 305 | type: "constant" 306 | value: 0.0 307 | } 308 | } 309 | } 310 | layer { 311 | name: "relu5_1" 312 | type: "ReLU" 313 | bottom: "conv5_1" 314 | top: "conv5_1" 315 | } 316 | layer { 317 | name: "conv5_2" 318 | type: "Convolution" 319 | bottom: "conv5_1" 320 | top: "conv5_2" 321 | convolution_param { 322 | num_output: 512 323 | pad: 1 324 | kernel_size: 3 325 | weight_filler { 326 | type: "xavier" 327 | } 328 | bias_filler { 329 | type: "constant" 330 | value: 0.0 331 | } 332 | } 333 | } 334 | layer { 335 | name: "relu5_2" 336 | type: "ReLU" 337 | bottom: "conv5_2" 338 | top: "conv5_2" 339 | } 340 | layer { 341 | name: "conv5_3" 342 | type: "Convolution" 343 | bottom: "conv5_2" 344 | top: "conv5_3" 345 | convolution_param { 346 | num_output: 512 347 | pad: 1 348 | kernel_size: 3 349 | weight_filler { 350 | type: "xavier" 351 | } 352 | bias_filler { 353 | type: "constant" 354 | value: 0.0 355 | } 356 | } 357 | } 358 | layer { 359 | name: "relu5_3" 360 | type: "ReLU" 361 | bottom: "conv5_3" 362 | top: "conv5_3" 363 | } 364 | layer { 365 | name: "pool5" 366 | type: "Pooling" 367 | bottom: "conv5_3" 368 | top: "pool5" 369 | pooling_param { 370 | pool: MAX 371 | kernel_size: 2 372 | stride: 2 373 | } 374 | } 375 | layer { 376 | name: "fc6" 377 | type: "InnerProduct" 378 | bottom: "pool5" 379 | top: "fc6" 380 | inner_product_param { 381 | num_output: 4096 382 | weight_filler { 383 | type: "xavier" 384 | } 385 | bias_filler { 386 | type: "constant" 387 | value: 0.1 388 | } 389 | } 390 | } 391 | layer { 392 | name: "relu6" 393 | type: "ReLU" 394 | bottom: "fc6" 395 | top: "fc6" 396 | } 397 | layer { 398 | name: "drop6" 399 | type: "Dropout" 400 | bottom: "fc6" 401 | top: "fc6" 402 | dropout_param { 403 | dropout_ratio: 0.5 404 | } 405 | } 406 | layer { 407 | name: "fc7" 408 | type: "InnerProduct" 409 | bottom: "fc6" 410 | top: "fc7" 411 | inner_product_param { 412 | num_output: 4096 413 | weight_filler { 414 | type: "xavier" 415 | } 416 | bias_filler { 417 | type: "constant" 418 | value: 0.1 419 | } 420 | } 421 | } 422 | layer { 423 | name: "relu7" 424 | type: "ReLU" 425 | bottom: "fc7" 426 | top: "fc7" 427 | } 428 | layer { 429 | name: "drop7" 430 | type: "Dropout" 431 | bottom: "fc7" 432 | top: "fc7" 433 | dropout_param { 434 | dropout_ratio: 0.5 435 | } 436 | } 437 | layer { 438 | name: "fc8" 439 | type: "InnerProduct" 440 | bottom: "fc7" 441 | top: "fc8" 442 | inner_product_param { 443 | num_output: 1000 444 | weight_filler { 445 | type: "xavier" 446 | } 447 | bias_filler { 448 | type: "constant" 449 | value: 0.1 450 | } 451 | } 452 | } 453 | layer { 454 | name: "softmax" 455 | type: "Softmax" 456 | bottom: "fc8" 457 | top: "softmax" 458 | } 459 | -------------------------------------------------------------------------------- /src/image/model/image_solver.prototxt: -------------------------------------------------------------------------------- 1 | net: "./image_train_val.prototxt" 2 | test_iter: 271 3 | test_interval: 100 4 | base_lr: 0.01 5 | display: 20 6 | max_iter: 25000 7 | lr_policy: "step" 8 | gamma: 0.1 9 | momentum: 0.9 10 | weight_decay: 0.0005 11 | power: 0.75 12 | stepsize: 100 13 | snapshot: 5000 14 | snapshot_prefix: "snapshot" 15 | solver_type: SGD 16 | solver_mode: GPU 17 | -------------------------------------------------------------------------------- /src/image/model/image_train_val.prototxt: -------------------------------------------------------------------------------- 1 | layer { 2 | name: "vgg-train-data" 3 | type: "Data" 4 | top: "data" 5 | top: "label" 6 | include { 7 | phase: TRAIN 8 | } 9 | transform_param { 10 | scale: 0.00390625 11 | } 12 | data_param { 13 | source: "/home/ruifengshan/github/12306-captcha/data/image/image_train_lmdb" 14 | batch_size: 64 15 | backend: LMDB 16 | } 17 | } 18 | layer { 19 | name: "val-data" 20 | type: "Data" 21 | top: "data" 22 | top: "label" 23 | include { 24 | phase: TEST 25 | } 26 | transform_param { 27 | scale: 0.00390625 28 | } 29 | data_param { 30 | source: "/home/ruifengshan/github/12306-captcha/data/image/image_test_lmdb" 31 | batch_size: 64 32 | backend: LMDB 33 | } 34 | } 35 | layer { 36 | name: "conv1_1" 37 | type: "Convolution" 38 | bottom: "data" 39 | top: "conv1_1" 40 | convolution_param { 41 | num_output: 64 42 | pad: 1 43 | kernel_size: 3 44 | weight_filler { 45 | type: "xavier" 46 | } 47 | bias_filler { 48 | type: "constant" 49 | value: 0.0 50 | } 51 | } 52 | } 53 | layer { 54 | name: "relu1_1" 55 | type: "ReLU" 56 | bottom: "conv1_1" 57 | top: "conv1_1" 58 | } 59 | layer { 60 | name: "conv1_2" 61 | type: "Convolution" 62 | bottom: "conv1_1" 63 | top: "conv1_2" 64 | convolution_param { 65 | num_output: 64 66 | pad: 1 67 | kernel_size: 3 68 | weight_filler { 69 | type: "xavier" 70 | } 71 | bias_filler { 72 | type: "constant" 73 | value: 0.0 74 | } 75 | } 76 | } 77 | layer { 78 | name: "relu1_2" 79 | type: "ReLU" 80 | bottom: "conv1_2" 81 | top: "conv1_2" 82 | } 83 | layer { 84 | name: "pool1" 85 | type: "Pooling" 86 | bottom: "conv1_2" 87 | top: "pool1" 88 | pooling_param { 89 | pool: MAX 90 | kernel_size: 2 91 | stride: 2 92 | } 93 | } 94 | layer { 95 | name: "conv2_1" 96 | type: "Convolution" 97 | bottom: "pool1" 98 | top: "conv2_1" 99 | convolution_param { 100 | num_output: 128 101 | pad: 1 102 | kernel_size: 3 103 | weight_filler { 104 | type: "xavier" 105 | } 106 | bias_filler { 107 | type: "constant" 108 | value: 0.0 109 | } 110 | } 111 | } 112 | layer { 113 | name: "relu2_1" 114 | type: "ReLU" 115 | bottom: "conv2_1" 116 | top: "conv2_1" 117 | } 118 | layer { 119 | name: "conv2_2" 120 | type: "Convolution" 121 | bottom: "conv2_1" 122 | top: "conv2_2" 123 | convolution_param { 124 | num_output: 128 125 | pad: 1 126 | kernel_size: 3 127 | weight_filler { 128 | type: "xavier" 129 | } 130 | bias_filler { 131 | type: "constant" 132 | value: 0.0 133 | } 134 | } 135 | } 136 | layer { 137 | name: "relu2_2" 138 | type: "ReLU" 139 | bottom: "conv2_2" 140 | top: "conv2_2" 141 | } 142 | layer { 143 | name: "pool2" 144 | type: "Pooling" 145 | bottom: "conv2_2" 146 | top: "pool2" 147 | pooling_param { 148 | pool: MAX 149 | kernel_size: 2 150 | stride: 2 151 | } 152 | } 153 | layer { 154 | name: "conv3_1" 155 | type: "Convolution" 156 | bottom: "pool2" 157 | top: "conv3_1" 158 | convolution_param { 159 | num_output: 256 160 | pad: 1 161 | kernel_size: 3 162 | weight_filler { 163 | type: "xavier" 164 | } 165 | bias_filler { 166 | type: "constant" 167 | value: 0.0 168 | } 169 | } 170 | } 171 | layer { 172 | name: "relu3_1" 173 | type: "ReLU" 174 | bottom: "conv3_1" 175 | top: "conv3_1" 176 | } 177 | layer { 178 | name: "conv3_2" 179 | type: "Convolution" 180 | bottom: "conv3_1" 181 | top: "conv3_2" 182 | convolution_param { 183 | num_output: 256 184 | pad: 1 185 | kernel_size: 3 186 | weight_filler { 187 | type: "xavier" 188 | } 189 | bias_filler { 190 | type: "constant" 191 | value: 0.0 192 | } 193 | } 194 | } 195 | layer { 196 | name: "relu3_2" 197 | type: "ReLU" 198 | bottom: "conv3_2" 199 | top: "conv3_2" 200 | } 201 | layer { 202 | name: "conv3_3" 203 | type: "Convolution" 204 | bottom: "conv3_2" 205 | top: "conv3_3" 206 | convolution_param { 207 | num_output: 256 208 | pad: 1 209 | kernel_size: 3 210 | weight_filler { 211 | type: "xavier" 212 | } 213 | bias_filler { 214 | type: "constant" 215 | value: 0.0 216 | } 217 | } 218 | } 219 | layer { 220 | name: "relu3_3" 221 | type: "ReLU" 222 | bottom: "conv3_3" 223 | top: "conv3_3" 224 | } 225 | layer { 226 | name: "pool3" 227 | type: "Pooling" 228 | bottom: "conv3_3" 229 | top: "pool3" 230 | pooling_param { 231 | pool: MAX 232 | kernel_size: 2 233 | stride: 2 234 | } 235 | } 236 | layer { 237 | name: "conv4_1" 238 | type: "Convolution" 239 | bottom: "pool3" 240 | top: "conv4_1" 241 | convolution_param { 242 | num_output: 512 243 | pad: 1 244 | kernel_size: 3 245 | weight_filler { 246 | type: "xavier" 247 | } 248 | bias_filler { 249 | type: "constant" 250 | value: 0.0 251 | } 252 | } 253 | } 254 | layer { 255 | name: "relu4_1" 256 | type: "ReLU" 257 | bottom: "conv4_1" 258 | top: "conv4_1" 259 | } 260 | layer { 261 | name: "conv4_2" 262 | type: "Convolution" 263 | bottom: "conv4_1" 264 | top: "conv4_2" 265 | convolution_param { 266 | num_output: 512 267 | pad: 1 268 | kernel_size: 3 269 | weight_filler { 270 | type: "xavier" 271 | } 272 | bias_filler { 273 | type: "constant" 274 | value: 0.0 275 | } 276 | } 277 | } 278 | layer { 279 | name: "relu4_2" 280 | type: "ReLU" 281 | bottom: "conv4_2" 282 | top: "conv4_2" 283 | } 284 | layer { 285 | name: "conv4_3" 286 | type: "Convolution" 287 | bottom: "conv4_2" 288 | top: "conv4_3" 289 | convolution_param { 290 | num_output: 512 291 | pad: 1 292 | kernel_size: 3 293 | weight_filler { 294 | type: "xavier" 295 | } 296 | bias_filler { 297 | type: "constant" 298 | value: 0.0 299 | } 300 | } 301 | } 302 | layer { 303 | name: "relu4_3" 304 | type: "ReLU" 305 | bottom: "conv4_3" 306 | top: "conv4_3" 307 | } 308 | layer { 309 | name: "pool4" 310 | type: "Pooling" 311 | bottom: "conv4_3" 312 | top: "pool4" 313 | pooling_param { 314 | pool: MAX 315 | kernel_size: 2 316 | stride: 2 317 | } 318 | } 319 | layer { 320 | name: "conv5_1" 321 | type: "Convolution" 322 | bottom: "pool4" 323 | top: "conv5_1" 324 | convolution_param { 325 | num_output: 512 326 | pad: 1 327 | kernel_size: 3 328 | weight_filler { 329 | type: "xavier" 330 | } 331 | bias_filler { 332 | type: "constant" 333 | value: 0.0 334 | } 335 | } 336 | } 337 | layer { 338 | name: "relu5_1" 339 | type: "ReLU" 340 | bottom: "conv5_1" 341 | top: "conv5_1" 342 | } 343 | layer { 344 | name: "conv5_2" 345 | type: "Convolution" 346 | bottom: "conv5_1" 347 | top: "conv5_2" 348 | convolution_param { 349 | num_output: 512 350 | pad: 1 351 | kernel_size: 3 352 | weight_filler { 353 | type: "xavier" 354 | } 355 | bias_filler { 356 | type: "constant" 357 | value: 0.0 358 | } 359 | } 360 | } 361 | layer { 362 | name: "relu5_2" 363 | type: "ReLU" 364 | bottom: "conv5_2" 365 | top: "conv5_2" 366 | } 367 | layer { 368 | name: "conv5_3" 369 | type: "Convolution" 370 | bottom: "conv5_2" 371 | top: "conv5_3" 372 | convolution_param { 373 | num_output: 512 374 | pad: 1 375 | kernel_size: 3 376 | weight_filler { 377 | type: "xavier" 378 | } 379 | bias_filler { 380 | type: "constant" 381 | value: 0.0 382 | } 383 | } 384 | } 385 | layer { 386 | name: "relu5_3" 387 | type: "ReLU" 388 | bottom: "conv5_3" 389 | top: "conv5_3" 390 | } 391 | layer { 392 | name: "pool5" 393 | type: "Pooling" 394 | bottom: "conv5_3" 395 | top: "pool5" 396 | pooling_param { 397 | pool: MAX 398 | kernel_size: 2 399 | stride: 2 400 | } 401 | } 402 | layer { 403 | name: "fc6" 404 | type: "InnerProduct" 405 | bottom: "pool5" 406 | top: "fc6" 407 | inner_product_param { 408 | num_output: 4096 409 | weight_filler { 410 | type: "xavier" 411 | } 412 | bias_filler { 413 | type: "constant" 414 | value: 0.1 415 | } 416 | } 417 | } 418 | layer { 419 | name: "relu6" 420 | type: "ReLU" 421 | bottom: "fc6" 422 | top: "fc6" 423 | } 424 | layer { 425 | name: "drop6" 426 | type: "Dropout" 427 | bottom: "fc6" 428 | top: "fc6" 429 | dropout_param { 430 | dropout_ratio: 0.5 431 | } 432 | } 433 | layer { 434 | name: "fc7" 435 | type: "InnerProduct" 436 | bottom: "fc6" 437 | top: "fc7" 438 | inner_product_param { 439 | num_output: 4096 440 | weight_filler { 441 | type: "xavier" 442 | } 443 | bias_filler { 444 | type: "constant" 445 | value: 0.1 446 | } 447 | } 448 | } 449 | layer { 450 | name: "relu7" 451 | type: "ReLU" 452 | bottom: "fc7" 453 | top: "fc7" 454 | } 455 | layer { 456 | name: "drop7" 457 | type: "Dropout" 458 | bottom: "fc7" 459 | top: "fc7" 460 | dropout_param { 461 | dropout_ratio: 0.5 462 | } 463 | } 464 | layer { 465 | name: "fc8" 466 | type: "InnerProduct" 467 | bottom: "fc7" 468 | top: "fc8" 469 | inner_product_param { 470 | num_output: 1000 471 | weight_filler { 472 | type: "xavier" 473 | } 474 | bias_filler { 475 | type: "constant" 476 | value: 0.1 477 | } 478 | } 479 | } 480 | layer { 481 | name: "accuracy" 482 | type: "Accuracy" 483 | bottom: "fc8" 484 | bottom: "label" 485 | top: "accuracy" 486 | include { 487 | phase: TEST 488 | }K 489 | } 490 | layer { 491 | name: "loss" 492 | type: "SoftmaxWithLoss" 493 | bottom: "fc8" 494 | bottom: "label" 495 | top: "loss" 496 | } 497 | -------------------------------------------------------------------------------- /src/image/scripts/calc_mean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TOOLS_DIR=/home/ruifengshan/caffe/build/tools 4 | DATA=/home/ruifengshan/github/12306-captcha/data/image 5 | 6 | $TOOLS_DIR/compute_image_mean $DATA/image_train_lmdb $DATA/mean.binaryproto 7 | 8 | -------------------------------------------------------------------------------- /src/image/scripts/create_data.py: -------------------------------------------------------------------------------- 1 | # _*_ coding:utf-8 _*_ 2 | 3 | import os 4 | import scipy.misc 5 | from src.config import cfg 6 | 7 | 8 | synset_file = cfg.ROOT + 'label/synset' 9 | 10 | 11 | # 将synset转换为train.txt文件 12 | def load_file(path=synset_file): 13 | synset = [line.strip() for line in open(path).readlines()] 14 | print synset 15 | return synset 16 | 17 | 18 | def create_file(data_path, file_list, file_name): 19 | """ 20 | 创建训练文件train.txt/test.txt 21 | 22 | :param data_path: 图片文件的上级路径 23 | :param file_list: synset生成的list 24 | :param train_file_name: 标签表 25 | :return: 26 | """ 27 | 28 | try: 29 | cnt = 0 30 | if not os.path.isfile(file_name): 31 | os.mknod(file_name) 32 | file_txt = open(file_name, mode='wr') 33 | for file_name in file_list: 34 | print file_name 35 | file_path = os.path.join(data_path, file_name) 36 | print file_path 37 | if not os.path.isdir(file_path): 38 | print file_path, '目录不存在' 39 | continue 40 | for path in os.listdir(file_path): 41 | file_txt.write(os.path.join(file_name, path) + " " + str(cnt) + "\n") 42 | cnt += 1 43 | except Exception, e: 44 | print "执行失败", e 45 | 46 | 47 | def create_train_file(data_path, file_list, train_file_name='train.txt'): 48 | create_file(data_path, file_list, train_file_name) 49 | 50 | 51 | def create_test_file(data_path, file_list, test_file_name='test.txt'): 52 | create_file(data_path, file_list, test_file_name) 53 | 54 | 55 | if __name__ == '__main__': 56 | create_train_file(cfg.ROOT + "/data/image/train/", load_file()) 57 | create_test_file(cfg.ROOT + "/data/image/test/", load_file()) -------------------------------------------------------------------------------- /src/image/scripts/create_lmdb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | TOOLS_DIR=/home/ruifengshan/caffe/build/tools 4 | 5 | dir=`dirname "$0"` 6 | root_path=`cd "$dir/../../..">/dev/null; pwd` 7 | echo "root_path:"$root_path 8 | 9 | DATA=$root_path/data/image 10 | 11 | 12 | rm -rf $DATA/image_train_lmdb 13 | $TOOLS_DIR/convert_imageset \ 14 | --shuffle \ 15 | --resize_height=68 \ 16 | --resize_width=68 \ 17 | $DATA/train $DATA/train.txt $DATA/image_train_lmdb 18 | 19 | 20 | rm -rf $DATA/image_test_lmdb 21 | $TOOLS_DIR/convert_imageset \ 22 | --shuffle \ 23 | --resize_height=68 \ 24 | --resize_width=68 \ 25 | $DATA/test $DATA/test.txt $DATA/image_test_lmdb 26 | -------------------------------------------------------------------------------- /src/image/scripts/image_finetune_train.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | #该指令是用来在已有的模型上,进行微调,需要注意的是模型的图片大小以及layer需要保持一致. 3 | # 如果要开启GPU模式,只需要在该指令的末尾加上 -gpu 0 即可. ps 默认情况下run as cpu-only 4 | 5 | dir=`dirname "$0"` 6 | root_path=`cd "$dir/../../..">/dev/null; pwd` 7 | echo "root_path:"$root_path 8 | 9 | TOOLS_DIR=/home/ruifengshan/caffe/build/tools 10 | 11 | MODEL_CONFIG_DIR=$root_path/src/image/model 12 | MODEL_DIR=$root_path/model/image 13 | 14 | $TOOLS_DIR/caffe train \ 15 | -solver $MODEL_CONFIG_DIR/image_solver.prototxt \ 16 | -gpu 0 \ 17 | -weights $MODEL_DIR/f14_snapshot_iter_99000.caffemodel 1> $MODEL_DIR/image_train.log 2>&1 & 18 | -------------------------------------------------------------------------------- /src/image/scripts/image_train.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | dir=`dirname "$0"` 4 | root_path=`cd "$dir/../../..">/dev/null; pwd` 5 | echo "root_path:"$root_path 6 | 7 | TOOLS_DIR=/home/ruifengshan/caffe/build/tools 8 | 9 | MODEL_CONFIG_DIR=$root_path/src/image/model 10 | MODEL_DIR=$root_path/model/image 11 | 12 | $TOOLS_DIR/caffe train \ 13 | -solver $MODEL_CONFIG_DIR/image_solver.prototxt $@ 1>$MODEL_DIR/image_train.log 2>&1 & 14 | -------------------------------------------------------------------------------- /src/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronshan/12306-captcha/af14073d15f8beb3f8c9fe30802ee52875b78bec/src/tools/__init__.py -------------------------------------------------------------------------------- /src/tools/cut_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8-*- 2 | 3 | # -------------------------------------------------------- 4 | # 12306-captcha 5 | # Copyright (c) 2017 6 | # Written by ruifengshan 7 | # -------------------------------------------------------- 8 | 9 | # 功能: 分割组合图片 10 | 11 | import cv2 12 | import os 13 | import time 14 | import skimage.io 15 | import numpy as np 16 | from src.config import cfg 17 | 18 | IMG_V_POS = [4, 76, 148, 220] 19 | IMG_H_POS = [40, 108] 20 | IMG_WIDTH = 68 # 每个小图片的宽度 21 | IMG_HEIGHT = 68 # 每个小图片的高度 22 | 23 | 24 | def read_image(fn): 25 | """ 26 | 得到验证码完整图像 27 | :param fn:图像文件路径 28 | :return:图像对象 29 | """ 30 | im = None 31 | try: 32 | im = skimage.io.imread(fn, as_grey=False) 33 | except Exception: 34 | pass 35 | return im 36 | 37 | 38 | def load_image(im, color=True): 39 | img = skimage.img_as_float(im).astype(np.float32) 40 | if img.ndim == 2: 41 | img = img[:, :, np.newaxis] 42 | if color: 43 | img = np.tile(img, (1, 1, 3)) 44 | 45 | elif img.shape[2] == 4: 46 | img = img[:, :, :3] 47 | return img 48 | 49 | 50 | def write_image(im, fn): 51 | skimage.io.imsave(fn, im) 52 | 53 | 54 | def get_text(im): 55 | """ 56 | 得到图像中的文本部分 57 | """ 58 | return im[3:24, 116:288] 59 | 60 | 61 | # 分割图片 62 | def get_image(im): 63 | img = [] 64 | for v in range(2): # 图片行 65 | for h in range(4): # 图片列 66 | img.append(im[(40 + (v * 72)):(108 + (v * 72)), (4 + (h * 72)):((h + 1) * 72)]) 67 | return img 68 | 69 | 70 | # 二值化图像 71 | def binarize(im): 72 | gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY) 73 | (retval, dst) = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) 74 | return dst 75 | 76 | 77 | # 获取图像值 78 | 79 | def show_image(im): 80 | print im.ndim, im.dtype 81 | cv2.imshow("image", im) 82 | cv2.waitKey(0) 83 | 84 | 85 | def make_dir(path): 86 | try: 87 | if not os.path.exists(path): 88 | if not os.path.isfile(path): 89 | os.mkdir(path) 90 | return True 91 | else: 92 | return False 93 | except Exception, e: 94 | print str(e) 95 | 96 | 97 | # 将文件写入到 98 | def cut_image(image_cnt): 99 | path_dir = cfg.ROOT + '/data/download/' 100 | img_names = filter(lambda s: not s.startswith("."), os.listdir(path_dir + '/all')) 101 | 102 | while True: 103 | ocr_path = os.path.join(path_dir, 'words/words_' + str(image_cnt)) 104 | if make_dir(ocr_path): 105 | break 106 | image_cnt += 1 107 | while True: 108 | img_path = os.path.join(path_dir, 'image/image_' + str(image_cnt)) 109 | if make_dir(img_path): 110 | break 111 | image_cnt += 1 112 | 113 | for img_name in img_names: 114 | f_name = os.path.join(path_dir + '/all', img_name) 115 | im = read_image(f_name) 116 | if im is None: 117 | print "该图片{ %s }处理异常: " % img_name 118 | continue 119 | print os.path.join(ocr_path, img_name) 120 | write_image(get_text(im), os.path.join(ocr_path, img_name)) 121 | num = 1 122 | sub_name = img_name.split('.') 123 | for sub_im in get_image(im): 124 | sub_img_name = sub_name[0] + '_' + str(num) + '.' + sub_name[1] 125 | num += 1 126 | write_image(sub_im, os.path.join(img_path, sub_img_name)) 127 | # '删除' 128 | # os.remove(os.path.join(path_dir + '/all', img_name)) 129 | return image_cnt 130 | 131 | 132 | def thread_main(): 133 | cnt = 0 134 | while True: 135 | cnt = cut_image(cnt) 136 | time.sleep(60) 137 | print '第{ %s }次开始切割图片' % cnt 138 | cnt += 1 139 | 140 | 141 | if __name__ == '__main__': 142 | cut_image(0) 143 | -------------------------------------------------------------------------------- /src/tools/download_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # -------------------------------------------------------- 4 | # 12306-captcha 5 | # Copyright (c) 2017 6 | # Written by ruifengshan 7 | # -------------------------------------------------------- 8 | 9 | # 功能: 用于下载12306的验证码图片 10 | 11 | import cookielib 12 | import hashlib 13 | import random 14 | import threading 15 | import time 16 | import urllib2 17 | 18 | from src.config import cfg 19 | from cut_image import thread_main 20 | 21 | "下载指定地方的验证码图片" 22 | 23 | # 'http格式封装' 24 | agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36' 25 | login = 'https://kyfw.12306.cn/otn/login/init' 26 | domain = 'kyfw.12306.cn' 27 | 28 | headers = { 29 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 30 | 'Accept-Encoding': 'gzip, deflate, sdch, br', 31 | 'Accept-Language': 'zh - CN, zh;q = 0.8', 32 | 'Cache-Control': 'no - cache', 33 | 'Connection': 'keep-alive', 34 | 'Host': domain, 35 | 'User-aget': agent, 36 | 'Referer': login 37 | } 38 | 39 | 40 | def download_image(download_dir=cfg.ROOT + '/data/download/all'): 41 | def get_url(): 42 | const_url = 'https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=login&rand=sjrand&' 43 | random_token = random.uniform(0, 1) 44 | img_url = const_url + urllib2.quote(str(random_token)) 45 | print img_url 46 | return img_url 47 | 48 | try: 49 | cookie_j = cookielib.CookieJar() 50 | op = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie_j)) 51 | op.add_handler = [('User-agent', agent)] 52 | op.add_handler = [('Host', domain)] 53 | op.open(login) 54 | # '获取cookie' 55 | cookies = '' 56 | for Cookie in enumerate(cookie_j): 57 | ck = Cookie[1] 58 | cookies += ck.name + '=' + ck.value + ";" 59 | headers['Cookie'] = cookies 60 | 61 | # 下载30次 62 | for _ in range(random.randint(50, 200)): 63 | request = urllib2.Request(get_url(), cookies, headers=headers) 64 | raw = urllib2.urlopen(request).read() 65 | "采用十进制的md5命名文件名称" 66 | fn = hashlib.md5(raw).hexdigest() 67 | with open(download_dir + "/%s.jpg" % fn, 'wb') as fp: 68 | fp.write(raw) 69 | 70 | time.sleep(1) 71 | except Exception, e: 72 | print str(e) 73 | 74 | 75 | if __name__ == '__main__': 76 | while True: 77 | try: 78 | download_image() 79 | except Exception, e: 80 | print 'error', str(e) 81 | -------------------------------------------------------------------------------- /src/tools/remove_noisy.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | # -------------------------------------------------------- 4 | # 12306-captcha 5 | # Copyright (c) 2017 6 | # Licensed under The Apache License [see LICENSE for details] 7 | # Written by ruifengshan 8 | # -------------------------------------------------------- 9 | 10 | """设置去除图像中的黑色斑点""" 11 | import cv2 12 | import numpy as np 13 | 14 | 15 | def rm_blackNoisy(im, threshold=20): 16 | """ 17 | 该函数用来去除图像的中黑色斑点,同时采用插值法进行该点像素的补充 18 | ,param threshold, 设定黑色斑点的最大值 19 | ,param img, RGB图像的数据 20 | ,return, 返回RGB图像数据 21 | """ 22 | 23 | def min(c_val, t_val): 24 | return c_val if c_val < t_val else t_val 25 | 26 | def sum(_val, _sum, _num): 27 | return _val + _sum, _num + 1 28 | 29 | def rm_blackNoisy_component(img, threshold): 30 | y, x = img.shape[:2] # x,y表示坐标 31 | for _x in range(0, x, 1): 32 | for _y in range(0, y, 1): 33 | cnt = 0 34 | _sum = num = 0 35 | if _y > 0: 36 | _sum, num = sum(img[_y - 1, _x], _sum, num) 37 | if _y + 1 < y: 38 | _sum, num = sum(img[_y + 1, _x], _sum, num) 39 | if _x > 0: 40 | _sum, num = sum(img[_y, _x - 1], _sum, num) 41 | if _x + 1 < x: 42 | _sum, num = sum(img[_y, _x + 1], _sum, num) 43 | if _y > 0 and _x > 0: 44 | _sum, num = sum(img[_y - 1, _x - 1], _sum, num) 45 | if _y > 0 and _x + 1 < x: 46 | _sum, num = sum(img[_y - 1, _x + 1], _sum, num) 47 | if _y + 1 < y and _x > 0: 48 | _sum, num = sum(img[_y + 1, _x - 1], _sum, num) 49 | 50 | if _y + 1 < y and _x + 1 < x: 51 | _sum, num = sum(img[_y + 1, _x + 1], _sum, num) 52 | if cnt > 0 or num < 1: # 说明不是单个点 53 | continue 54 | # 如果是孤点,则可以依据权重进行插值 55 | arvage = _sum / num 56 | if img[_y, _x] + 100 <= arvage: 57 | img[_y, _x] = arvage 58 | 59 | return img 60 | 61 | if im.ndim == 3: 62 | b, g, r = cv2.split(im) 63 | _b = rm_blackNoisy_component(b, threshold) 64 | _g = rm_blackNoisy_component(g, threshold) 65 | _r = rm_blackNoisy_component(r, threshold) 66 | img = cv2.merge([_b, _g, _r]) # 前面分离出来的三个通道 67 | 68 | else: 69 | return rm_blackNoisy_component(im, threshold) 70 | 71 | return img 72 | -------------------------------------------------------------------------------- /src/tools/words.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | # -------------------------------------------------------- 4 | # 12306-captcha 5 | # Copyright (c) 2017 6 | # Licensed under The Apache License [see LICENSE for details] 7 | # Written by ruifengshan 8 | # -------------------------------------------------------- 9 | 10 | import cv2 11 | import numpy as np 12 | import cv2 13 | import os 14 | import math as mt 15 | import skimage.transform 16 | import matplotlib.pyplot as plt 17 | from scipy import ndimage as ndi 18 | from skimage import morphology, color, data 19 | import matplotlib.pyplot as plt 20 | from PIL import Image 21 | from src.config import cfg 22 | 23 | 24 | def make_dir(path): 25 | try: 26 | if not os.path.exists(path): 27 | if not os.path.isfile(path): 28 | os.makedirs(path) 29 | return True 30 | except Exception, e: 31 | print str(e) 32 | return False 33 | 34 | 35 | def int_max(a, b): 36 | """求解最大值""" 37 | return a if a > b else b 38 | 39 | 40 | def int_min(a, b): 41 | """求解最小值""" 42 | return a if a < b else b 43 | 44 | 45 | def get_mean_value(img): 46 | """ 47 | 求图像分均值 48 | :param img: 49 | :return: 平均值 50 | """ 51 | y, x = img.shape[:2] 52 | sum_value = 0 53 | for _x in range(0, x, 1): 54 | # temp_value = 0 55 | for _y in range(0, y, 1): 56 | sum_value += img[_y][_x] 57 | 58 | return sum_value / (x * y) 59 | 60 | 61 | def pretreatment_image(img, times=3): 62 | """ 63 | 预处理图片 64 | :param img: 待处理的灰度图图片 65 | :return: 处理之后的图片 66 | """ 67 | 68 | img = 255 - img 69 | y, x = img.shape[:2] 70 | for _ in xrange(times): 71 | mean_value = get_mean_value(img) 72 | for _x in range(0, x, 1): 73 | # temp_value = 0 74 | for _y in range(0, y, 1): 75 | if img[_y][_x] >= mean_value: 76 | img[_y][_x] -= mean_value 77 | else: 78 | img[_y][_x] = 0 79 | while (1): 80 | max_value = -1 81 | for _x in range(0, x, 1): 82 | # temp_value = 0 83 | for _y in range(0, y, 1): 84 | if img[_y][_x] + img[_y][_x] * 0.2 >= 255: 85 | img[_y][_x] = 255 86 | else: 87 | img[_y][_x] += img[_y][_x] * 0.2 88 | if max_value < img[_y][_x] * 0.2: 89 | max_value = img[_y][_x] * 0.2 90 | if max_value >= mean_value: 91 | break 92 | mean_value -= max_value 93 | 94 | return img 95 | 96 | 97 | def binary_text(im): 98 | """ 99 | 二值化 100 | :param im: 待处理的原始图片 101 | :return: 二值化后的图片 102 | """ 103 | dst = cv2.adaptiveThreshold(im, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 7, 0) 104 | return dst 105 | 106 | 107 | def rm_noise(img): 108 | """ 109 | 用来去除噪点 110 | :param img: 原始数据图片 111 | :return: 去噪点之后的图片 112 | """ 113 | y, x = img.shape[:2] 114 | for _x in range(0, x, 1): 115 | for _y in range(0, y, 1): 116 | cnt = 0 117 | if img[_y][_x] > 0: 118 | if _y > 0 and img[_y - 1][_x] > 0: 119 | cnt += 1 120 | if _y + 1 < y and img[_y + 1][_x] > 0: 121 | cnt += 1 122 | if _x > 0 and img[_y][_x - 1] > 0: 123 | cnt += 1 124 | if _x + 1 < x and img[_y][_x + 1] > 0: 125 | cnt += 1 126 | if _y > 0 and _x > 0 and img[_y - 1][_x - 1] > 0: 127 | cnt += 1 128 | if _y > 0 and _x + 1 < x and img[_y - 1][_x + 1] > 0: 129 | cnt += 1 130 | if _y + 1 < y and _x > 0 and img[_y + 1][_x - 1] > 0: 131 | continue 132 | if _y + 1 < y and _x + 1 < x and img[_y + 1][_x + 1] > 0: 133 | cnt += 1 134 | if cnt > 1: 135 | continue 136 | img[_y][_x] = 0 137 | return img 138 | 139 | 140 | def analyse_version(img, threshold=180): 141 | """ 142 | 分析图像类别 143 | :param threshold: 阈值 默认为180 144 | :param img: 145 | :return: 图像类别 1. 表示无背景 2.表示有背景 146 | """ 147 | "我们认为图像区域的值范围在0~130之间" 148 | if get_mean_value(img) <= threshold: 149 | return 2 150 | return 1 151 | 152 | 153 | def cut_version2(img, threshold=182): 154 | """ 155 | 对版本二进行图像切割 156 | :param img: 157 | :param threshold: 阈值 158 | :return: 返回图像开始位置链表和结束位置链表 159 | """ 160 | y, x = img.shape[:2] 161 | "避免统计到开头的部分" 162 | img_st_pos = 0 163 | """图像开始位置""" 164 | img_rect_st = [] 165 | """图像结束位置""" 166 | img_rect_en = [] 167 | mean_arr = np.zeros((x, 1)) 168 | for _x in xrange(x): 169 | tmp_sum = 0 170 | for _y in xrange(y): 171 | tmp_sum += img[_y][_x] 172 | mean_arr[_x] = tmp_sum / y 173 | 174 | for _value in xrange(x): 175 | if _value > 0 and mean_arr[_value] >= mean_arr[_value - 1] + 30 and mean_arr[_value] >= threshold: 176 | if _value > img_st_pos + 14: 177 | img_rect_st.append(img_st_pos) 178 | img_rect_en.append(_value) 179 | img_st_pos = _value 180 | break 181 | img_st_pos = _value 182 | if x > img_st_pos + 14: 183 | img_rect_st.append(img_st_pos) 184 | img_rect_en.append(x) 185 | return img_rect_st, img_rect_en 186 | 187 | 188 | def mesr_text(img, del_text_path, image_cnt, words_name, y_value=10, x_value=30): 189 | """ 190 | 分水岭算法 191 | :param deep: 192 | :param x_value: 193 | :param img: 194 | :param del_text_path: 195 | :param image_cnt: 196 | :param words_name: 197 | :param a_value: 198 | :return: 199 | """ 200 | # B, G, R = cv2.split(img) 201 | mser = cv2.MSER() 202 | regions = mser.detect(img) 203 | hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions] 204 | """寻找最大区域""" 205 | for index, hull in enumerate(hulls): 206 | x_min = 256 207 | x_max = -1 208 | y_min = 256 209 | y_max = -1 210 | for point in hull: 211 | x_min = int_min(x_min, point[0][0]) 212 | x_max = int_max(x_max, point[0][0]) 213 | y_min = int_min(y_min, point[0][1]) 214 | y_max = int_max(y_max, point[0][1]) 215 | tmp = img[y_min:y_max, x_min:x_max] 216 | sub_path = os.path.join(del_text_path, 'words_' + str(image_cnt)) 217 | if make_dir(sub_path): 218 | final_path = os.path.join(sub_path, words_name.split('.')[0]) 219 | if make_dir(final_path): 220 | cv2.imwrite( 221 | os.path.join(final_path, 222 | words_name.split('.')[0] + '_' + str(index) + '.' + 223 | words_name.split('.')[1]), 224 | tmp) 225 | 226 | 227 | def get_real_image(img, threshold=130): 228 | """ 229 | 截取整体文字区域 230 | :param img: 灰度图片 231 | :param threshold: 阈值 232 | :return: 文字区域图片 233 | """ 234 | y, x = img.shape[:2] 235 | real_x_start = 0 236 | real_x_end = 0 237 | for _x in xrange(x): 238 | for _y in xrange(y): 239 | if img[_y][_x] <= threshold: 240 | real_x_start = _x 241 | break 242 | if real_x_start != 0: 243 | break 244 | for _x in range(x - 1, 0, -1): 245 | for _y in xrange(y): 246 | if img[_y][_x] <= threshold: 247 | real_x_end = _x + 1 248 | break 249 | if real_x_end != 0: 250 | break 251 | return img[0:y, real_x_start:real_x_end] 252 | 253 | 254 | def get_binary_real_image(img): 255 | """ 256 | 截取整体文字区域 257 | :param img: 灰度图片 258 | :return: 文字区域图片 259 | """ 260 | y, x = img.shape[:2] 261 | real_x_st = 0 262 | real_x_en = 0 263 | for _x in xrange(x): 264 | for _y in xrange(y): 265 | if img[_y][_x] != 0: 266 | real_x_st = _x 267 | break 268 | if real_x_st != 0: 269 | break 270 | for _x in range(x - 1, 0, -1): 271 | for _y in xrange(y): 272 | if img[_y][_x] != 0: 273 | real_x_en = _x + 1 274 | break 275 | if real_x_en != 0: 276 | break 277 | return img[0:y, real_x_st:real_x_en] 278 | 279 | 280 | def get_real_image_rect(img): 281 | """ 282 | 截取整体文字区域 283 | :param img: 灰度图片 284 | :return: 文字区域图片 285 | """ 286 | y, x = img.shape[:2] 287 | real_x_st = 0 288 | real_x_en = 0 289 | """对于水平方向""" 290 | for _x in xrange(x): 291 | for _y in xrange(y): 292 | if img[_y][_x] != 0: 293 | real_x_st = _x 294 | break 295 | if real_x_st != 0: 296 | break 297 | for _x in range(x - 1, 0, -1): 298 | for _y in xrange(y): 299 | if img[_y][_x] != 0: 300 | real_x_en = _x 301 | break 302 | if real_x_en != 0: 303 | break 304 | """对于垂直方向""" 305 | real_y_st = 0 306 | real_y_en = 0 307 | for _x in range(real_x_st, real_x_en + 1, 1): 308 | for _y in range(0, y, 1): 309 | if img[_y][_x] != 0: 310 | real_y_st = _y 311 | break 312 | if real_x_st != 0: 313 | break 314 | for _x in range(real_x_st, real_x_en + 1, 1): 315 | for _y in xrange(y - 1, 0, -1): 316 | if img[_y][_x] != 0: 317 | real_y_en = _y 318 | break 319 | if real_x_en != 0: 320 | break 321 | 322 | return real_y_en - real_y_st, real_x_en - real_x_st 323 | 324 | 325 | def judge_the_image_size(img, height=24, width=60): 326 | """ 327 | 设置成固定大小 328 | :param img: 329 | :param height: 默认为24 330 | :param width: 默认为60 331 | :return: 332 | """ 333 | y_len, x_len = img.shape[:2] 334 | new_img = np.zeros((height, width)) 335 | for y_px in xrange(height): 336 | for i in xrange(width): 337 | if y_len > y_px and x_len > i: 338 | new_img[y_px][i] = img[y_px][i] 339 | else: 340 | new_img[y_px][i] = 255 341 | 342 | return new_img 343 | 344 | 345 | # 保存图片 346 | def save_img(img, n_img, del_text_path, image_cnt, words_name, y_value=10, x_value=30): 347 | """统计能量值""" 348 | y, x = n_img.shape[:2] 349 | pol = [] 350 | 351 | for _x in range(0, x, 1): 352 | temp_value = 0 353 | for _y in range(0, y, 1): 354 | temp_value += n_img[_y][_x] 355 | pol.append(temp_value) 356 | min_value_pre = 0 357 | min_value_nex = 0 358 | _len = len(pol) 359 | equal_size = 0 360 | line_pre = [] 361 | line_nex = [] 362 | img_cnt = 0 363 | for index in range(0, _len, 1): 364 | min_value_nex = index + 1 365 | if index + 1 < _len and pol[index] > pol[index + 1]: 366 | equal_size = 1 367 | elif index + 1 < _len and equal_size > 0 and pol[index] == 0 and pol[index] == pol[index + 1]: 368 | equal_size += 1 369 | 370 | elif index + 1 < _len and equal_size > 1 and pol[index] < pol[index + 1]: 371 | while min_value_pre < min_value_nex and pol[min_value_pre] == 0: 372 | min_value_pre += 1 373 | while min_value_pre < min_value_nex and pol[min_value_nex] == 0: 374 | min_value_nex -= 1 375 | if equal_size > 5: 376 | line_pre.append(0 if min_value_pre == 0 else min_value_pre - 1) 377 | line_nex.append(min_value_nex + 1) 378 | img_cnt += 1 379 | min_value_pre = min_value_nex 380 | equal_size = 0 381 | while min_value_pre < min_value_nex and pol[min_value_pre] == 0: 382 | min_value_pre += 1 383 | while min_value_pre < min_value_nex and pol[min_value_nex - 1] == 0: 384 | min_value_nex -= 1 385 | if min_value_nex > min_value_pre + 10: 386 | line_pre.append(0 if min_value_pre == 0 else min_value_pre - 1) 387 | line_nex.append(min_value_nex + 1) 388 | write_image(img, del_text_path, image_cnt, words_name, line_pre, line_nex) 389 | """对于image进行分析""" 390 | 391 | 392 | def save_img_mat(img, n_img, y_value=10, x_value=30): 393 | """ 394 | 保存图片 395 | :param n_img: 396 | :param index: 397 | :param deep: 398 | :param x_value: 399 | :param img: 400 | :param del_text_path: 401 | :param image_cnt: 402 | :param words_name: 403 | :param a_value: 404 | 405 | :return: 406 | """ 407 | """统计能量值""" 408 | y, x = n_img.shape[:2] 409 | pol = [] 410 | 411 | for _x in range(0, x, 1): 412 | temp_value = 0 413 | for _y in range(0, y, 1): 414 | temp_value += n_img[_y][_x] 415 | pol.append(temp_value) 416 | min_value_pre = 0 417 | min_value_nex = 0 418 | _len = len(pol) 419 | equal_size = 0 420 | line_pre = [] 421 | line_nex = [] 422 | img_cnt = 0 423 | for index in range(0, _len, 1): 424 | min_value_nex = index + 1 425 | if index + 1 < _len and pol[index] > pol[index + 1]: 426 | equal_size = 1 427 | elif index + 1 < _len and equal_size > 0 and pol[index] == 0 and pol[index] == pol[index + 1]: 428 | equal_size += 1 429 | 430 | elif index + 1 < _len and equal_size > 1 and pol[index] < pol[index + 1]: 431 | while min_value_pre < min_value_nex and pol[min_value_pre] == 0: 432 | min_value_pre += 1 433 | while min_value_pre < min_value_nex and pol[min_value_nex] == 0: 434 | min_value_nex -= 1 435 | if equal_size > 5: 436 | line_pre.append(0 if min_value_pre == 0 else min_value_pre - 1) 437 | line_nex.append(min_value_nex + 1) 438 | img_cnt += 1 439 | min_value_pre = min_value_nex 440 | equal_size = 0 441 | # plt.plot(pol[int(min_value_pre):int(_len)]) 442 | # plt.show() 443 | while min_value_pre < min_value_nex and pol[min_value_pre] == 0: 444 | min_value_pre += 1 445 | while min_value_pre < min_value_nex and pol[min_value_nex - 1] == 0: 446 | min_value_nex -= 1 447 | if min_value_nex > min_value_pre + 10: 448 | line_pre.append(0 if min_value_pre == 0 else min_value_pre - 1) 449 | line_nex.append(min_value_nex + 1) 450 | return get_image(img, line_pre, line_nex) 451 | """对于image进行分析""" 452 | 453 | 454 | def write_image(img, del_text_path, image_cnt, words_name, line_pre, line_nex, binary_flag=False): 455 | """ 456 | 将图片保存到磁盘中 457 | :param img: 图片数据 458 | :param del_text_path: 路径 459 | :param image_cnt: words对应编号 460 | :param words_name: 图片名称 461 | :param line_pre: 图片开始位置数组 462 | :param line_nex: 图片结束位置数组 463 | :param binary_flag: 表示是否对图片进行二值化,默认是不二值化 464 | :return: 465 | """ 466 | y, x = img.shape[:2] 467 | for _indx in xrange(len(line_pre)): 468 | 469 | t_img = img[0:y, int(line_pre[_indx]):int(line_nex[_indx] + 1)] 470 | if binary_flag: 471 | t_img = pretreatment_image(t_img, 7) 472 | t_img = binary_text(t_img) 473 | t_img = get_binary_real_image(t_img) 474 | t_img = judge_the_image_size(t_img) 475 | sub_path = os.path.join(del_text_path, 'words_' + str(image_cnt)) 476 | if make_dir(sub_path): 477 | cv2.imwrite( 478 | os.path.join(sub_path, 479 | words_name.split('.')[0] + '_' + str(_indx) + '.' + 480 | words_name.split('.')[1]), 481 | t_img) 482 | 483 | 484 | def get_image(img, line_pre, line_nex, binary_flag=False): 485 | """ 486 | 将图片保存到磁盘中 487 | :param img: 图片数据 488 | :param line_pre: 图片开始位置数组 489 | :param line_nex: 图片结束位置数组 490 | :param binary_flag: 表示是否对图片进行二值化,默认是不二值化 491 | :return: 492 | """ 493 | y, x = img.shape[:2] 494 | list_img = [] 495 | for _indx in xrange(len(line_pre)): 496 | 497 | t_img = img[0:y, int(line_pre[_indx]):int(line_nex[_indx] + 1)] 498 | if binary_flag: 499 | t_img = pretreatment_image(t_img, 7) 500 | t_img = binary_text(t_img) 501 | t_img = get_binary_real_image(t_img) 502 | t_img = judge_the_image_size(t_img) 503 | list_img.append(t_img) 504 | return list_img 505 | 506 | 507 | def cut(image_cnt, path_dir= cfg.ROOT + '/data/download'): 508 | """ 509 | :param image_cnt: words对应编号 510 | :param path_dir: 511 | :return: 512 | """ 513 | del_text_path = os.path.join(path_dir, 'words_cut_result') 514 | make_dir(del_text_path) 515 | 516 | words_path = os.path.join(path_dir, 'words/words_' + str(image_cnt)) 517 | if not os.path.exists(words_path): 518 | return 519 | for words_name in os.listdir(words_path): 520 | try: 521 | print words_name 522 | img = cv2.imread(os.path.join(words_path, words_name), 0) 523 | img = get_real_image(img, 160) 524 | if 2 == analyse_version(img): 525 | "说明有背景" 526 | img_rect_st, img_rect_en = cut_version2(img, 182) 527 | write_image(img, del_text_path, image_cnt, words_name, img_rect_st, img_rect_en, False) 528 | else: 529 | # img = rm_noise(img) 530 | n_img = pretreatment_image(img, 3) 531 | n_img = binary_text(n_img) 532 | n_img = get_binary_real_image(n_img) 533 | save_img(img, n_img, del_text_path, image_cnt, words_name, y_value=10, x_value=28) 534 | # mesr_text(img, del_text_path, image_cnt, words_name, y_value=10, x_value=28) 535 | except Exception as e: 536 | pass 537 | 538 | 539 | def get_test(img): 540 | img = get_real_image(img, 160) 541 | if 2 == analyse_version(img): 542 | "说明有背景" 543 | img_rect_st, img_rect_en = cut_version2(img, 182) 544 | return get_image(img, img_rect_st, img_rect_en, False) 545 | else: 546 | # img = rm_noise(img) 547 | n_img = pretreatment_image(img, 3) 548 | n_img = binary_text(n_img) 549 | n_img = get_binary_real_image(n_img) 550 | return save_img_mat(img, n_img, y_value=10, x_value=28) 551 | # mesr_text(img, del_text_path, image_cnt, words_name, y_value=10, x_value=28) 552 | 553 | 554 | # 调整目录中图像大小 555 | def classify_image(path_dir=cfg.ROOT + '/data/download/words', 556 | dest_path=cfg.ROOT + '/data/download/words-classify'): 557 | """ 558 | :param path_dir: 559 | :param dest_path: 560 | :return: 561 | """ 562 | temp = filter(lambda s: not s.startswith("."), os.listdir(path_dir)) 563 | for words_name in temp: 564 | print words_name 565 | if not os.path.exists(dest_path): 566 | os.makedirs(dest_path) 567 | sub_root_path = os.path.join(path_dir, words_name) 568 | if os.path.isdir(sub_root_path): 569 | classify_image(sub_root_path, os.path.join(dest_path, words_name)) 570 | continue 571 | img = cv2.imread(sub_root_path, 0) 572 | new_img = judge_the_image_size(img) 573 | cv2.imwrite(os.path.join(dest_path, words_name), new_img) 574 | return 0 575 | 576 | if __name__ == '__main__': 577 | cut(0) 578 | -------------------------------------------------------------------------------- /src/web/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronshan/12306-captcha/af14073d15f8beb3f8c9fe30802ee52875b78bec/src/web/README.md -------------------------------------------------------------------------------- /src/web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronshan/12306-captcha/af14073d15f8beb3f8c9fe30802ee52875b78bec/src/web/__init__.py -------------------------------------------------------------------------------- /src/web/index.py: -------------------------------------------------------------------------------- 1 | # _*_ coding: utf-8 _*_ 2 | 3 | from flask import Flask 4 | from flask import render_template 5 | from flask import redirect, url_for 6 | from flask import request 7 | import urllib2 8 | import ssl 9 | import numpy as np 10 | import random 11 | import hashlib 12 | import datetime 13 | import cookielib 14 | import sys 15 | 16 | import utils.Img12306 as Img12306 17 | from src.config import cfg 18 | 19 | caffe_root = cfg.CAFFE_ROOT 20 | sys.path.insert(0, caffe_root + 'python') 21 | 22 | import caffe 23 | 24 | ### config_file_path 25 | pj_dir= cfg.ROOT + "/src/web" 26 | 27 | 28 | app = Flask(__name__) 29 | 30 | ### caffe config 31 | net_file = cfg.ROOT + '/src/image/model/image_deploy.prototxt' 32 | caffe_model = cfg.ROOT + 'model/image/f14_snapshot_iter_99000.caffemodel' 33 | labels_filename = cfg.ROOT + 'label/synset' 34 | 35 | net = caffe.Net(net_file, caffe_model, caffe.TEST) 36 | transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape}) 37 | "读取的图片文件格式为H×W×K,需转化为K×H×W" 38 | transformer.set_transpose('data', (2, 0, 1)) 39 | # transformer.set_mean('data', mean=np.float32([103.939, 116.779, 123.68])) 40 | "将图片存储为[0, 1],而caffe中将图片存储为[0, 255" 41 | transformer.set_raw_scale('data', 255) 42 | "caffe中图片是BGR格式,而原始格式是RGB,所以要转化" 43 | transformer.set_channel_swap('data', (2, 1, 0)) 44 | 45 | ### agent config 46 | ssl._create_default_https_context = ssl._create_unverified_context 47 | agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36' 48 | login = 'https://kyfw.12306.cn/otn/login/init' 49 | domain = 'kyfw.12306.cn' 50 | cookieJ = cookielib.CookieJar() 51 | op = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookieJ)) 52 | op.add_handler = [('User-agent', agent)] 53 | op.add_handler = [('Host', domain)] 54 | op.open(login) 55 | # '获取cookie' 56 | cookies = '' 57 | for Cookie in enumerate(cookieJ): 58 | ck = Cookie[1] 59 | cookies += ck.name + '=' + ck.value + ";" 60 | headers = { 61 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 62 | 'Accept-Encoding': 'gzip, deflate, sdch, br', 63 | 'Accept-Language': 'zh - CN, zh;q = 0.8', 64 | 'Cache-Control': 'no - cache', 65 | 'Connection': 'keep-alive', 66 | 'Referer': login, 67 | 'Cookie': cookies, 68 | 'Host': domain, 69 | 'User-aget': agent 70 | } 71 | 72 | 73 | @app.route('/') 74 | def hello_world(): 75 | return 'Hello World!' 76 | 77 | 78 | @app.route('/12306submit', methods=['POST']) 79 | def submit12306(): 80 | img_url = request.form['img_url'] 81 | error_idx = request.form['error_idx'] 82 | if error_idx != "": 83 | error_idx_arr = request.form['error_idx'].split("-") 84 | error_idx_arr.remove("") 85 | results=[request.form['result0'], request.form['result1'], request.form['result2'], request.form['result3'], request.form['result4'], request.form['result5'], request.form['result6'], request.form['result7']] 86 | file_path = "%s%s" % (pj_dir, img_url) 87 | img_file_name = file_path.split("/")[-1]; 88 | img = Img12306.get_img_file(file_path) 89 | img_split = Img12306.get_img(img) 90 | for idx in error_idx_arr: 91 | i = int(idx) 92 | if results[i] == "": 93 | continue 94 | out_path = "%s/error/%s/%s_%s" % (pj_dir, results[i], i, img_file_name) 95 | Img12306.save_as_img_file(img_split[i], out_path) 96 | 97 | return redirect(url_for('img')) 98 | 99 | @app.route('/12306') 100 | def img(): 101 | total = np.random.randint(1, 2) 102 | const_url = 'https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=login&rand=sjrand&' 103 | random_token = random.uniform(0, 1) 104 | img_url = const_url + urllib2.quote(str(random_token)) 105 | while total > 0: 106 | resq = urllib2.Request(img_url) 107 | response = urllib2.urlopen(resq) 108 | total = total - 1 109 | 110 | raw = response.read() 111 | fn = hashlib.md5(raw).hexdigest() 112 | dt=datetime.datetime.now() 113 | dt=dt.strftime('%Y%m%d') 114 | output_file_name = "%s/static/tmp/%s_%s.jpg" % (pj_dir, dt, fn); 115 | with open(output_file_name, 'wb') as fp: 116 | fp.write(raw) 117 | 118 | no_noise_file_name = "%s/static/tmp/%s_%s_no_noise.jpg" % (pj_dir, dt, fn); 119 | Img12306.remove_file_noise(output_file_name, no_noise_file_name) 120 | 121 | # arr = np.asarray(bytearray(raw), dtype=np.uint8) 122 | # img = cv2.imdecode(arr,cv2.IMREAD_UNCHANGED) 123 | # img = img / 255. 124 | # img = img[:,:,(2,1,0)] 125 | img = caffe.io.load_image(no_noise_file_name) 126 | 127 | img_split = Img12306.get_img(img) 128 | results = [] 129 | for i in range(img_split.__len__()): 130 | im = img_split[i] 131 | results.append(judge(im).decode("utf8")) 132 | 133 | img_src = "/static/tmp/%s_%s.jpg" % (dt, fn); 134 | return render_template('12306.html', img_url=img_src, results=results) 135 | 136 | def judge(im): 137 | net.blobs['data'].data[...] = transformer.preprocess('data', im) 138 | out = net.forward() 139 | 140 | labels = np.loadtxt(labels_filename, str, delimiter='\t') 141 | # prob = out['softmax'][0] 142 | top_k = net.blobs['softmax'].data[0].flatten().argsort()[-1:-6:-1] 143 | 144 | return labels[top_k[0]] 145 | 146 | 147 | if __name__ == '__main__': 148 | app.run(host='0.0.0.0', port=5111, debug=False) 149 | -------------------------------------------------------------------------------- /src/web/static/bootstrap/css/bootstrap-datetimepicker.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Datetimepicker for Bootstrap 3 | * 4 | * Copyright 2012 Stefan Petre 5 | * Improvements by Andrew Rowls 6 | * Licensed under the Apache License v2.0 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | */ 10 | .datetimepicker { 11 | padding: 4px; 12 | margin-top: 1px; 13 | -webkit-border-radius: 4px; 14 | -moz-border-radius: 4px; 15 | border-radius: 4px; 16 | direction: ltr; 17 | } 18 | 19 | .datetimepicker-inline { 20 | width: 220px; 21 | } 22 | 23 | .datetimepicker.datetimepicker-rtl { 24 | direction: rtl; 25 | } 26 | 27 | .datetimepicker.datetimepicker-rtl table tr td span { 28 | float: right; 29 | } 30 | 31 | .datetimepicker-dropdown, .datetimepicker-dropdown-left { 32 | top: 0; 33 | left: 0; 34 | } 35 | 36 | [class*=" datetimepicker-dropdown"]:before { 37 | content: ''; 38 | display: inline-block; 39 | border-left: 7px solid transparent; 40 | border-right: 7px solid transparent; 41 | border-bottom: 7px solid #cccccc; 42 | border-bottom-color: rgba(0, 0, 0, 0.2); 43 | position: absolute; 44 | } 45 | 46 | [class*=" datetimepicker-dropdown"]:after { 47 | content: ''; 48 | display: inline-block; 49 | border-left: 6px solid transparent; 50 | border-right: 6px solid transparent; 51 | border-bottom: 6px solid #ffffff; 52 | position: absolute; 53 | } 54 | 55 | [class*=" datetimepicker-dropdown-top"]:before { 56 | content: ''; 57 | display: inline-block; 58 | border-left: 7px solid transparent; 59 | border-right: 7px solid transparent; 60 | border-top: 7px solid #cccccc; 61 | border-top-color: rgba(0, 0, 0, 0.2); 62 | border-bottom: 0; 63 | } 64 | 65 | [class*=" datetimepicker-dropdown-top"]:after { 66 | content: ''; 67 | display: inline-block; 68 | border-left: 6px solid transparent; 69 | border-right: 6px solid transparent; 70 | border-top: 6px solid #ffffff; 71 | border-bottom: 0; 72 | } 73 | 74 | .datetimepicker-dropdown-bottom-left:before { 75 | top: -7px; 76 | right: 6px; 77 | } 78 | 79 | .datetimepicker-dropdown-bottom-left:after { 80 | top: -6px; 81 | right: 7px; 82 | } 83 | 84 | .datetimepicker-dropdown-bottom-right:before { 85 | top: -7px; 86 | left: 6px; 87 | } 88 | 89 | .datetimepicker-dropdown-bottom-right:after { 90 | top: -6px; 91 | left: 7px; 92 | } 93 | 94 | .datetimepicker-dropdown-top-left:before { 95 | bottom: -7px; 96 | right: 6px; 97 | } 98 | 99 | .datetimepicker-dropdown-top-left:after { 100 | bottom: -6px; 101 | right: 7px; 102 | } 103 | 104 | .datetimepicker-dropdown-top-right:before { 105 | bottom: -7px; 106 | left: 6px; 107 | } 108 | 109 | .datetimepicker-dropdown-top-right:after { 110 | bottom: -6px; 111 | left: 7px; 112 | } 113 | 114 | .datetimepicker > div { 115 | display: none; 116 | } 117 | 118 | .datetimepicker.minutes div.datetimepicker-minutes { 119 | display: block; 120 | } 121 | 122 | .datetimepicker.hours div.datetimepicker-hours { 123 | display: block; 124 | } 125 | 126 | .datetimepicker.days div.datetimepicker-days { 127 | display: block; 128 | } 129 | 130 | .datetimepicker.months div.datetimepicker-months { 131 | display: block; 132 | } 133 | 134 | .datetimepicker.years div.datetimepicker-years { 135 | display: block; 136 | } 137 | 138 | .datetimepicker table { 139 | margin: 0; 140 | } 141 | 142 | .datetimepicker td, 143 | .datetimepicker th { 144 | text-align: center; 145 | width: 20px; 146 | height: 20px; 147 | -webkit-border-radius: 4px; 148 | -moz-border-radius: 4px; 149 | border-radius: 4px; 150 | border: none; 151 | } 152 | 153 | .table-striped .datetimepicker table tr td, 154 | .table-striped .datetimepicker table tr th { 155 | background-color: transparent; 156 | } 157 | 158 | .datetimepicker table tr td.minute:hover { 159 | background: #eeeeee; 160 | cursor: pointer; 161 | } 162 | 163 | .datetimepicker table tr td.hour:hover { 164 | background: #eeeeee; 165 | cursor: pointer; 166 | } 167 | 168 | .datetimepicker table tr td.day:hover { 169 | background: #eeeeee; 170 | cursor: pointer; 171 | } 172 | 173 | .datetimepicker table tr td.old, 174 | .datetimepicker table tr td.new { 175 | color: #999999; 176 | } 177 | 178 | .datetimepicker table tr td.disabled, 179 | .datetimepicker table tr td.disabled:hover { 180 | background: none; 181 | color: #999999; 182 | cursor: default; 183 | } 184 | 185 | .datetimepicker table tr td.today, 186 | .datetimepicker table tr td.today:hover, 187 | .datetimepicker table tr td.today.disabled, 188 | .datetimepicker table tr td.today.disabled:hover { 189 | background-color: #fde19a; 190 | background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a); 191 | background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a); 192 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); 193 | background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a); 194 | background-image: -o-linear-gradient(top, #fdd49a, #fdf59a); 195 | background-image: linear-gradient(top, #fdd49a, #fdf59a); 196 | background-repeat: repeat-x; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); 198 | border-color: #fdf59a #fdf59a #fbed50; 199 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 200 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 201 | } 202 | 203 | .datetimepicker table tr td.today:hover, 204 | .datetimepicker table tr td.today:hover:hover, 205 | .datetimepicker table tr td.today.disabled:hover, 206 | .datetimepicker table tr td.today.disabled:hover:hover, 207 | .datetimepicker table tr td.today:active, 208 | .datetimepicker table tr td.today:hover:active, 209 | .datetimepicker table tr td.today.disabled:active, 210 | .datetimepicker table tr td.today.disabled:hover:active, 211 | .datetimepicker table tr td.today.active, 212 | .datetimepicker table tr td.today:hover.active, 213 | .datetimepicker table tr td.today.disabled.active, 214 | .datetimepicker table tr td.today.disabled:hover.active, 215 | .datetimepicker table tr td.today.disabled, 216 | .datetimepicker table tr td.today:hover.disabled, 217 | .datetimepicker table tr td.today.disabled.disabled, 218 | .datetimepicker table tr td.today.disabled:hover.disabled, 219 | .datetimepicker table tr td.today[disabled], 220 | .datetimepicker table tr td.today:hover[disabled], 221 | .datetimepicker table tr td.today.disabled[disabled], 222 | .datetimepicker table tr td.today.disabled:hover[disabled] { 223 | background-color: #fdf59a; 224 | } 225 | 226 | .datetimepicker table tr td.today:active, 227 | .datetimepicker table tr td.today:hover:active, 228 | .datetimepicker table tr td.today.disabled:active, 229 | .datetimepicker table tr td.today.disabled:hover:active, 230 | .datetimepicker table tr td.today.active, 231 | .datetimepicker table tr td.today:hover.active, 232 | .datetimepicker table tr td.today.disabled.active, 233 | .datetimepicker table tr td.today.disabled:hover.active { 234 | background-color: #fbf069; 235 | } 236 | 237 | .datetimepicker table tr td.active, 238 | .datetimepicker table tr td.active:hover, 239 | .datetimepicker table tr td.active.disabled, 240 | .datetimepicker table tr td.active.disabled:hover { 241 | background-color: #006dcc; 242 | background-image: -moz-linear-gradient(top, #0088cc, #0044cc); 243 | background-image: -ms-linear-gradient(top, #0088cc, #0044cc); 244 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); 245 | background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); 246 | background-image: -o-linear-gradient(top, #0088cc, #0044cc); 247 | background-image: linear-gradient(top, #0088cc, #0044cc); 248 | background-repeat: repeat-x; 249 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); 250 | border-color: #0044cc #0044cc #002a80; 251 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 252 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 253 | color: #ffffff; 254 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 255 | } 256 | 257 | .datetimepicker table tr td.active:hover, 258 | .datetimepicker table tr td.active:hover:hover, 259 | .datetimepicker table tr td.active.disabled:hover, 260 | .datetimepicker table tr td.active.disabled:hover:hover, 261 | .datetimepicker table tr td.active:active, 262 | .datetimepicker table tr td.active:hover:active, 263 | .datetimepicker table tr td.active.disabled:active, 264 | .datetimepicker table tr td.active.disabled:hover:active, 265 | .datetimepicker table tr td.active.active, 266 | .datetimepicker table tr td.active:hover.active, 267 | .datetimepicker table tr td.active.disabled.active, 268 | .datetimepicker table tr td.active.disabled:hover.active, 269 | .datetimepicker table tr td.active.disabled, 270 | .datetimepicker table tr td.active:hover.disabled, 271 | .datetimepicker table tr td.active.disabled.disabled, 272 | .datetimepicker table tr td.active.disabled:hover.disabled, 273 | .datetimepicker table tr td.active[disabled], 274 | .datetimepicker table tr td.active:hover[disabled], 275 | .datetimepicker table tr td.active.disabled[disabled], 276 | .datetimepicker table tr td.active.disabled:hover[disabled] { 277 | background-color: #0044cc; 278 | } 279 | 280 | .datetimepicker table tr td.active:active, 281 | .datetimepicker table tr td.active:hover:active, 282 | .datetimepicker table tr td.active.disabled:active, 283 | .datetimepicker table tr td.active.disabled:hover:active, 284 | .datetimepicker table tr td.active.active, 285 | .datetimepicker table tr td.active:hover.active, 286 | .datetimepicker table tr td.active.disabled.active, 287 | .datetimepicker table tr td.active.disabled:hover.active { 288 | background-color: #003399; 289 | } 290 | 291 | .datetimepicker table tr td span { 292 | display: block; 293 | width: 23%; 294 | height: 54px; 295 | line-height: 54px; 296 | float: left; 297 | margin: 1%; 298 | cursor: pointer; 299 | -webkit-border-radius: 4px; 300 | -moz-border-radius: 4px; 301 | border-radius: 4px; 302 | } 303 | 304 | .datetimepicker .datetimepicker-hours span { 305 | height: 26px; 306 | line-height: 26px; 307 | } 308 | 309 | .datetimepicker .datetimepicker-hours table tr td span.hour_am, 310 | .datetimepicker .datetimepicker-hours table tr td span.hour_pm { 311 | width: 14.6%; 312 | } 313 | 314 | .datetimepicker .datetimepicker-hours fieldset legend, 315 | .datetimepicker .datetimepicker-minutes fieldset legend { 316 | margin-bottom: inherit; 317 | line-height: 30px; 318 | } 319 | 320 | .datetimepicker .datetimepicker-minutes span { 321 | height: 26px; 322 | line-height: 26px; 323 | } 324 | 325 | .datetimepicker table tr td span:hover { 326 | background: #eeeeee; 327 | } 328 | 329 | .datetimepicker table tr td span.disabled, 330 | .datetimepicker table tr td span.disabled:hover { 331 | background: none; 332 | color: #999999; 333 | cursor: default; 334 | } 335 | 336 | .datetimepicker table tr td span.active, 337 | .datetimepicker table tr td span.active:hover, 338 | .datetimepicker table tr td span.active.disabled, 339 | .datetimepicker table tr td span.active.disabled:hover { 340 | background-color: #006dcc; 341 | background-image: -moz-linear-gradient(top, #0088cc, #0044cc); 342 | background-image: -ms-linear-gradient(top, #0088cc, #0044cc); 343 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); 344 | background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); 345 | background-image: -o-linear-gradient(top, #0088cc, #0044cc); 346 | background-image: linear-gradient(top, #0088cc, #0044cc); 347 | background-repeat: repeat-x; 348 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); 349 | border-color: #0044cc #0044cc #002a80; 350 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 351 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 352 | color: #ffffff; 353 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 354 | } 355 | 356 | .datetimepicker table tr td span.active:hover, 357 | .datetimepicker table tr td span.active:hover:hover, 358 | .datetimepicker table tr td span.active.disabled:hover, 359 | .datetimepicker table tr td span.active.disabled:hover:hover, 360 | .datetimepicker table tr td span.active:active, 361 | .datetimepicker table tr td span.active:hover:active, 362 | .datetimepicker table tr td span.active.disabled:active, 363 | .datetimepicker table tr td span.active.disabled:hover:active, 364 | .datetimepicker table tr td span.active.active, 365 | .datetimepicker table tr td span.active:hover.active, 366 | .datetimepicker table tr td span.active.disabled.active, 367 | .datetimepicker table tr td span.active.disabled:hover.active, 368 | .datetimepicker table tr td span.active.disabled, 369 | .datetimepicker table tr td span.active:hover.disabled, 370 | .datetimepicker table tr td span.active.disabled.disabled, 371 | .datetimepicker table tr td span.active.disabled:hover.disabled, 372 | .datetimepicker table tr td span.active[disabled], 373 | .datetimepicker table tr td span.active:hover[disabled], 374 | .datetimepicker table tr td span.active.disabled[disabled], 375 | .datetimepicker table tr td span.active.disabled:hover[disabled] { 376 | background-color: #0044cc; 377 | } 378 | 379 | .datetimepicker table tr td span.active:active, 380 | .datetimepicker table tr td span.active:hover:active, 381 | .datetimepicker table tr td span.active.disabled:active, 382 | .datetimepicker table tr td span.active.disabled:hover:active, 383 | .datetimepicker table tr td span.active.active, 384 | .datetimepicker table tr td span.active:hover.active, 385 | .datetimepicker table tr td span.active.disabled.active, 386 | .datetimepicker table tr td span.active.disabled:hover.active { 387 | background-color: #003399; 388 | } 389 | 390 | .datetimepicker table tr td span.old { 391 | color: #999999; 392 | } 393 | 394 | .datetimepicker th.switch { 395 | width: 145px; 396 | } 397 | 398 | .datetimepicker th span.glyphicon { 399 | pointer-events: none; 400 | } 401 | 402 | .datetimepicker thead tr:first-child th, 403 | .datetimepicker tfoot tr:first-child th { 404 | cursor: pointer; 405 | } 406 | 407 | .datetimepicker thead tr:first-child th:hover, 408 | .datetimepicker tfoot tr:first-child th:hover { 409 | background: #eeeeee; 410 | } 411 | 412 | .input-append.date .add-on i, 413 | .input-prepend.date .add-on i, 414 | .input-group.date .input-group-addon span { 415 | cursor: pointer; 416 | width: 14px; 417 | height: 14px; 418 | } 419 | -------------------------------------------------------------------------------- /src/web/static/bootstrap/css/bootstrap-datetimepicker.min.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronshan/12306-captcha/af14073d15f8beb3f8c9fe30802ee52875b78bec/src/web/static/bootstrap/css/bootstrap-datetimepicker.min.css -------------------------------------------------------------------------------- /src/web/static/bootstrap/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | .btn-default, 2 | .btn-primary, 3 | .btn-success, 4 | .btn-info, 5 | .btn-warning, 6 | .btn-danger { 7 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 8 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 9 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 10 | } 11 | 12 | .btn-default:active, 13 | .btn-primary:active, 14 | .btn-success:active, 15 | .btn-info:active, 16 | .btn-warning:active, 17 | .btn-danger:active, 18 | .btn-default.active, 19 | .btn-primary.active, 20 | .btn-success.active, 21 | .btn-info.active, 22 | .btn-warning.active, 23 | .btn-danger.active { 24 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 25 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 26 | } 27 | 28 | .btn:active, 29 | .btn.active { 30 | background-image: none; 31 | } 32 | 33 | .btn-default { 34 | text-shadow: 0 1px 0 #fff; 35 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6)); 36 | background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%); 37 | background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%); 38 | background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%); 39 | background-repeat: repeat-x; 40 | border-color: #e0e0e0; 41 | border-color: #ccc; 42 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); 43 | } 44 | 45 | .btn-default:active, 46 | .btn-default.active { 47 | background-color: #e6e6e6; 48 | border-color: #e0e0e0; 49 | } 50 | 51 | .btn-primary { 52 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 53 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 54 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 55 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 56 | background-repeat: repeat-x; 57 | border-color: #2d6ca2; 58 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 59 | } 60 | 61 | .btn-primary:active, 62 | .btn-primary.active { 63 | background-color: #3071a9; 64 | border-color: #2d6ca2; 65 | } 66 | 67 | .btn-success { 68 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 69 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 70 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 71 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 72 | background-repeat: repeat-x; 73 | border-color: #419641; 74 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 75 | } 76 | 77 | .btn-success:active, 78 | .btn-success.active { 79 | background-color: #449d44; 80 | border-color: #419641; 81 | } 82 | 83 | .btn-warning { 84 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 85 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 86 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 87 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 88 | background-repeat: repeat-x; 89 | border-color: #eb9316; 90 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 91 | } 92 | 93 | .btn-warning:active, 94 | .btn-warning.active { 95 | background-color: #ec971f; 96 | border-color: #eb9316; 97 | } 98 | 99 | .btn-danger { 100 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 101 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 102 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 103 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 104 | background-repeat: repeat-x; 105 | border-color: #c12e2a; 106 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 107 | } 108 | 109 | .btn-danger:active, 110 | .btn-danger.active { 111 | background-color: #c9302c; 112 | border-color: #c12e2a; 113 | } 114 | 115 | .btn-info { 116 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 117 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 118 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 119 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 120 | background-repeat: repeat-x; 121 | border-color: #2aabd2; 122 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 123 | } 124 | 125 | .btn-info:active, 126 | .btn-info.active { 127 | background-color: #31b0d5; 128 | border-color: #2aabd2; 129 | } 130 | 131 | .thumbnail, 132 | .img-thumbnail { 133 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 134 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 135 | } 136 | 137 | .dropdown-menu > li > a:hover, 138 | .dropdown-menu > li > a:focus, 139 | .dropdown-menu > .active > a, 140 | .dropdown-menu > .active > a:hover, 141 | .dropdown-menu > .active > a:focus { 142 | background-color: #357ebd; 143 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 144 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 145 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 146 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 147 | background-repeat: repeat-x; 148 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 149 | } 150 | 151 | .navbar { 152 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); 153 | background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%); 154 | background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 155 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 156 | background-repeat: repeat-x; 157 | border-radius: 4px; 158 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 159 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 160 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 161 | } 162 | 163 | .navbar .navbar-nav > .active > a { 164 | background-color: #f8f8f8; 165 | } 166 | 167 | .navbar-brand, 168 | .navbar-nav > li > a { 169 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 170 | } 171 | 172 | .navbar-inverse { 173 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); 174 | background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%); 175 | background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); 176 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 177 | background-repeat: repeat-x; 178 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 179 | } 180 | 181 | .navbar-inverse .navbar-nav > .active > a { 182 | background-color: #222222; 183 | } 184 | 185 | .navbar-inverse .navbar-brand, 186 | .navbar-inverse .navbar-nav > li > a { 187 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 188 | } 189 | 190 | .navbar-static-top, 191 | .navbar-fixed-top, 192 | .navbar-fixed-bottom { 193 | border-radius: 0; 194 | } 195 | 196 | .alert { 197 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 198 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 199 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 200 | } 201 | 202 | .alert-success { 203 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); 204 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%); 205 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 206 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 207 | background-repeat: repeat-x; 208 | border-color: #b2dba1; 209 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 210 | } 211 | 212 | .alert-info { 213 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); 214 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%); 215 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 216 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 217 | background-repeat: repeat-x; 218 | border-color: #9acfea; 219 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 220 | } 221 | 222 | .alert-warning { 223 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); 224 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%); 225 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 226 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 227 | background-repeat: repeat-x; 228 | border-color: #f5e79e; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 230 | } 231 | 232 | .alert-danger { 233 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); 234 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%); 235 | background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 236 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 237 | background-repeat: repeat-x; 238 | border-color: #dca7a7; 239 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 240 | } 241 | 242 | .progress { 243 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); 244 | background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%); 245 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 246 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 247 | background-repeat: repeat-x; 248 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 249 | } 250 | 251 | .progress-bar { 252 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 253 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 254 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 255 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 256 | background-repeat: repeat-x; 257 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 258 | } 259 | 260 | .progress-bar-success { 261 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 262 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 263 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 264 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 265 | background-repeat: repeat-x; 266 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 267 | } 268 | 269 | .progress-bar-info { 270 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 271 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 272 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 273 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 274 | background-repeat: repeat-x; 275 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 276 | } 277 | 278 | .progress-bar-warning { 279 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 280 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 281 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 282 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 283 | background-repeat: repeat-x; 284 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 285 | } 286 | 287 | .progress-bar-danger { 288 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 289 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 290 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 291 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 292 | background-repeat: repeat-x; 293 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 294 | } 295 | 296 | .list-group { 297 | border-radius: 4px; 298 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 299 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 300 | } 301 | 302 | .list-group-item.active, 303 | .list-group-item.active:hover, 304 | .list-group-item.active:focus { 305 | text-shadow: 0 -1px 0 #3071a9; 306 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); 307 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%); 308 | background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); 309 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 310 | background-repeat: repeat-x; 311 | border-color: #3278b3; 312 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 313 | } 314 | 315 | .panel { 316 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 317 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 318 | } 319 | 320 | .panel-default > .panel-heading { 321 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 322 | background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%); 323 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 324 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 325 | background-repeat: repeat-x; 326 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 327 | } 328 | 329 | .panel-primary > .panel-heading { 330 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 331 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 332 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 333 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 334 | background-repeat: repeat-x; 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 336 | } 337 | 338 | .panel-success > .panel-heading { 339 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); 340 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%); 341 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 342 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 343 | background-repeat: repeat-x; 344 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 345 | } 346 | 347 | .panel-info > .panel-heading { 348 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); 349 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%); 350 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 351 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 352 | background-repeat: repeat-x; 353 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 354 | } 355 | 356 | .panel-warning > .panel-heading { 357 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); 358 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%); 359 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 360 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 361 | background-repeat: repeat-x; 362 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 363 | } 364 | 365 | .panel-danger > .panel-heading { 366 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); 367 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%); 368 | background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 369 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 370 | background-repeat: repeat-x; 371 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 372 | } 373 | 374 | .well { 375 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); 376 | background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%); 377 | background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 378 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 379 | background-repeat: repeat-x; 380 | border-color: #dcdcdc; 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 382 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 383 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 384 | } -------------------------------------------------------------------------------- /src/web/static/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | .btn-default, .btn-primary, .btn-success, .btn-info, .btn-warning, .btn-danger { 2 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 3 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 4 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075) 5 | } 6 | 7 | .btn-default:active, .btn-primary:active, .btn-success:active, .btn-info:active, .btn-warning:active, .btn-danger:active, .btn-default.active, .btn-primary.active, .btn-success.active, .btn-info.active, .btn-warning.active, .btn-danger.active { 8 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 9 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125) 10 | } 11 | 12 | .btn:active, .btn.active { 13 | background-image: none 14 | } 15 | 16 | .btn-default { 17 | text-shadow: 0 1px 0 #fff; 18 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#fff), to(#e6e6e6)); 19 | background-image: -webkit-linear-gradient(top, #fff, 0%, #e6e6e6, 100%); 20 | background-image: -moz-linear-gradient(top, #fff 0, #e6e6e6 100%); 21 | background-image: linear-gradient(to bottom, #fff 0, #e6e6e6 100%); 22 | background-repeat: repeat-x; 23 | border-color: #e0e0e0; 24 | border-color: #ccc; 25 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0) 26 | } 27 | 28 | .btn-default:active, .btn-default.active { 29 | background-color: #e6e6e6; 30 | border-color: #e0e0e0 31 | } 32 | 33 | .btn-primary { 34 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#428bca), to(#3071a9)); 35 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 36 | background-image: -moz-linear-gradient(top, #428bca 0, #3071a9 100%); 37 | background-image: linear-gradient(to bottom, #428bca 0, #3071a9 100%); 38 | background-repeat: repeat-x; 39 | border-color: #2d6ca2; 40 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0) 41 | } 42 | 43 | .btn-primary:active, .btn-primary.active { 44 | background-color: #3071a9; 45 | border-color: #2d6ca2 46 | } 47 | 48 | .btn-success { 49 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#5cb85c), to(#449d44)); 50 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 51 | background-image: -moz-linear-gradient(top, #5cb85c 0, #449d44 100%); 52 | background-image: linear-gradient(to bottom, #5cb85c 0, #449d44 100%); 53 | background-repeat: repeat-x; 54 | border-color: #419641; 55 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0) 56 | } 57 | 58 | .btn-success:active, .btn-success.active { 59 | background-color: #449d44; 60 | border-color: #419641 61 | } 62 | 63 | .btn-warning { 64 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#f0ad4e), to(#ec971f)); 65 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 66 | background-image: -moz-linear-gradient(top, #f0ad4e 0, #ec971f 100%); 67 | background-image: linear-gradient(to bottom, #f0ad4e 0, #ec971f 100%); 68 | background-repeat: repeat-x; 69 | border-color: #eb9316; 70 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0) 71 | } 72 | 73 | .btn-warning:active, .btn-warning.active { 74 | background-color: #ec971f; 75 | border-color: #eb9316 76 | } 77 | 78 | .btn-danger { 79 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#d9534f), to(#c9302c)); 80 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 81 | background-image: -moz-linear-gradient(top, #d9534f 0, #c9302c 100%); 82 | background-image: linear-gradient(to bottom, #d9534f 0, #c9302c 100%); 83 | background-repeat: repeat-x; 84 | border-color: #c12e2a; 85 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0) 86 | } 87 | 88 | .btn-danger:active, .btn-danger.active { 89 | background-color: #c9302c; 90 | border-color: #c12e2a 91 | } 92 | 93 | .btn-info { 94 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#5bc0de), to(#31b0d5)); 95 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 96 | background-image: -moz-linear-gradient(top, #5bc0de 0, #31b0d5 100%); 97 | background-image: linear-gradient(to bottom, #5bc0de 0, #31b0d5 100%); 98 | background-repeat: repeat-x; 99 | border-color: #2aabd2; 100 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0) 101 | } 102 | 103 | .btn-info:active, .btn-info.active { 104 | background-color: #31b0d5; 105 | border-color: #2aabd2 106 | } 107 | 108 | .thumbnail, .img-thumbnail { 109 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 110 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) 111 | } 112 | 113 | .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus, .dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus { 114 | background-color: #357ebd; 115 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#428bca), to(#357ebd)); 116 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 117 | background-image: -moz-linear-gradient(top, #428bca 0, #357ebd 100%); 118 | background-image: linear-gradient(to bottom, #428bca 0, #357ebd 100%); 119 | background-repeat: repeat-x; 120 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0) 121 | } 122 | 123 | .navbar { 124 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#fff), to(#f8f8f8)); 125 | background-image: -webkit-linear-gradient(top, #fff, 0%, #f8f8f8, 100%); 126 | background-image: -moz-linear-gradient(top, #fff 0, #f8f8f8 100%); 127 | background-image: linear-gradient(to bottom, #fff 0, #f8f8f8 100%); 128 | background-repeat: repeat-x; 129 | border-radius: 4px; 130 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 131 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 132 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075) 133 | } 134 | 135 | .navbar .navbar-nav > .active > a { 136 | background-color: #f8f8f8 137 | } 138 | 139 | .navbar-brand, .navbar-nav > li > a { 140 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25) 141 | } 142 | 143 | .navbar-inverse { 144 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#3c3c3c), to(#222)); 145 | background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222, 100%); 146 | background-image: -moz-linear-gradient(top, #3c3c3c 0, #222 100%); 147 | background-image: linear-gradient(to bottom, #3c3c3c 0, #222 100%); 148 | background-repeat: repeat-x; 149 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0) 150 | } 151 | 152 | .navbar-inverse .navbar-nav > .active > a { 153 | background-color: #222 154 | } 155 | 156 | .navbar-inverse .navbar-brand, .navbar-inverse .navbar-nav > li > a { 157 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25) 158 | } 159 | 160 | .navbar-static-top, .navbar-fixed-top, .navbar-fixed-bottom { 161 | border-radius: 0 162 | } 163 | 164 | .alert { 165 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 166 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 167 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05) 168 | } 169 | 170 | .alert-success { 171 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#dff0d8), to(#c8e5bc)); 172 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%); 173 | background-image: -moz-linear-gradient(top, #dff0d8 0, #c8e5bc 100%); 174 | background-image: linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%); 175 | background-repeat: repeat-x; 176 | border-color: #b2dba1; 177 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0) 178 | } 179 | 180 | .alert-info { 181 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#d9edf7), to(#b9def0)); 182 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%); 183 | background-image: -moz-linear-gradient(top, #d9edf7 0, #b9def0 100%); 184 | background-image: linear-gradient(to bottom, #d9edf7 0, #b9def0 100%); 185 | background-repeat: repeat-x; 186 | border-color: #9acfea; 187 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0) 188 | } 189 | 190 | .alert-warning { 191 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#fcf8e3), to(#f8efc0)); 192 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%); 193 | background-image: -moz-linear-gradient(top, #fcf8e3 0, #f8efc0 100%); 194 | background-image: linear-gradient(to bottom, #fcf8e3 0, #f8efc0 100%); 195 | background-repeat: repeat-x; 196 | border-color: #f5e79e; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0) 198 | } 199 | 200 | .alert-danger { 201 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#f2dede), to(#e7c3c3)); 202 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%); 203 | background-image: -moz-linear-gradient(top, #f2dede 0, #e7c3c3 100%); 204 | background-image: linear-gradient(to bottom, #f2dede 0, #e7c3c3 100%); 205 | background-repeat: repeat-x; 206 | border-color: #dca7a7; 207 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0) 208 | } 209 | 210 | .progress { 211 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#ebebeb), to(#f5f5f5)); 212 | background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%); 213 | background-image: -moz-linear-gradient(top, #ebebeb 0, #f5f5f5 100%); 214 | background-image: linear-gradient(to bottom, #ebebeb 0, #f5f5f5 100%); 215 | background-repeat: repeat-x; 216 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0) 217 | } 218 | 219 | .progress-bar { 220 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#428bca), to(#3071a9)); 221 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 222 | background-image: -moz-linear-gradient(top, #428bca 0, #3071a9 100%); 223 | background-image: linear-gradient(to bottom, #428bca 0, #3071a9 100%); 224 | background-repeat: repeat-x; 225 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0) 226 | } 227 | 228 | .progress-bar-success { 229 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#5cb85c), to(#449d44)); 230 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 231 | background-image: -moz-linear-gradient(top, #5cb85c 0, #449d44 100%); 232 | background-image: linear-gradient(to bottom, #5cb85c 0, #449d44 100%); 233 | background-repeat: repeat-x; 234 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0) 235 | } 236 | 237 | .progress-bar-info { 238 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#5bc0de), to(#31b0d5)); 239 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 240 | background-image: -moz-linear-gradient(top, #5bc0de 0, #31b0d5 100%); 241 | background-image: linear-gradient(to bottom, #5bc0de 0, #31b0d5 100%); 242 | background-repeat: repeat-x; 243 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0) 244 | } 245 | 246 | .progress-bar-warning { 247 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#f0ad4e), to(#ec971f)); 248 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 249 | background-image: -moz-linear-gradient(top, #f0ad4e 0, #ec971f 100%); 250 | background-image: linear-gradient(to bottom, #f0ad4e 0, #ec971f 100%); 251 | background-repeat: repeat-x; 252 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0) 253 | } 254 | 255 | .progress-bar-danger { 256 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#d9534f), to(#c9302c)); 257 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 258 | background-image: -moz-linear-gradient(top, #d9534f 0, #c9302c 100%); 259 | background-image: linear-gradient(to bottom, #d9534f 0, #c9302c 100%); 260 | background-repeat: repeat-x; 261 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0) 262 | } 263 | 264 | .list-group { 265 | border-radius: 4px; 266 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 267 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) 268 | } 269 | 270 | .list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { 271 | text-shadow: 0 -1px 0 #3071a9; 272 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#428bca), to(#3278b3)); 273 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%); 274 | background-image: -moz-linear-gradient(top, #428bca 0, #3278b3 100%); 275 | background-image: linear-gradient(to bottom, #428bca 0, #3278b3 100%); 276 | background-repeat: repeat-x; 277 | border-color: #3278b3; 278 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0) 279 | } 280 | 281 | .panel { 282 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 283 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) 284 | } 285 | 286 | .panel-default > .panel-heading { 287 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#f5f5f5), to(#e8e8e8)); 288 | background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%); 289 | background-image: -moz-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%); 290 | background-image: linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%); 291 | background-repeat: repeat-x; 292 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0) 293 | } 294 | 295 | .panel-primary > .panel-heading { 296 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#428bca), to(#357ebd)); 297 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 298 | background-image: -moz-linear-gradient(top, #428bca 0, #357ebd 100%); 299 | background-image: linear-gradient(to bottom, #428bca 0, #357ebd 100%); 300 | background-repeat: repeat-x; 301 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0) 302 | } 303 | 304 | .panel-success > .panel-heading { 305 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#dff0d8), to(#d0e9c6)); 306 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%); 307 | background-image: -moz-linear-gradient(top, #dff0d8 0, #d0e9c6 100%); 308 | background-image: linear-gradient(to bottom, #dff0d8 0, #d0e9c6 100%); 309 | background-repeat: repeat-x; 310 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0) 311 | } 312 | 313 | .panel-info > .panel-heading { 314 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#d9edf7), to(#c4e3f3)); 315 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%); 316 | background-image: -moz-linear-gradient(top, #d9edf7 0, #c4e3f3 100%); 317 | background-image: linear-gradient(to bottom, #d9edf7 0, #c4e3f3 100%); 318 | background-repeat: repeat-x; 319 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0) 320 | } 321 | 322 | .panel-warning > .panel-heading { 323 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#fcf8e3), to(#faf2cc)); 324 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%); 325 | background-image: -moz-linear-gradient(top, #fcf8e3 0, #faf2cc 100%); 326 | background-image: linear-gradient(to bottom, #fcf8e3 0, #faf2cc 100%); 327 | background-repeat: repeat-x; 328 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0) 329 | } 330 | 331 | .panel-danger > .panel-heading { 332 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#f2dede), to(#ebcccc)); 333 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%); 334 | background-image: -moz-linear-gradient(top, #f2dede 0, #ebcccc 100%); 335 | background-image: linear-gradient(to bottom, #f2dede 0, #ebcccc 100%); 336 | background-repeat: repeat-x; 337 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0) 338 | } 339 | 340 | .well { 341 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#e8e8e8), to(#f5f5f5)); 342 | background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%); 343 | background-image: -moz-linear-gradient(top, #e8e8e8 0, #f5f5f5 100%); 344 | background-image: linear-gradient(to bottom, #e8e8e8 0, #f5f5f5 100%); 345 | background-repeat: repeat-x; 346 | border-color: #dcdcdc; 347 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 348 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 349 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) 350 | } -------------------------------------------------------------------------------- /src/web/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronshan/12306-captcha/af14073d15f8beb3f8c9fe30802ee52875b78bec/src/web/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/web/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronshan/12306-captcha/af14073d15f8beb3f8c9fe30802ee52875b78bec/src/web/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/web/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronshan/12306-captcha/af14073d15f8beb3f8c9fe30802ee52875b78bec/src/web/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/web/static/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if ("undefined" == typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery"); 7 | +function (a) { 8 | "use strict"; 9 | function b() { 10 | var a = document.createElement("bootstrap"), b = { 11 | WebkitTransition: "webkitTransitionEnd", 12 | MozTransition: "transitionend", 13 | OTransition: "oTransitionEnd otransitionend", 14 | transition: "transitionend" 15 | }; 16 | for (var c in b)if (void 0 !== a.style[c])return {end: b[c]}; 17 | return !1 18 | } 19 | 20 | a.fn.emulateTransitionEnd = function (b) { 21 | var c = !1, d = this; 22 | a(this).one(a.support.transition.end, function () { 23 | c = !0 24 | }); 25 | var e = function () { 26 | c || a(d).trigger(a.support.transition.end) 27 | }; 28 | return setTimeout(e, b), this 29 | }, a(function () { 30 | a.support.transition = b() 31 | }) 32 | }(jQuery), +function (a) { 33 | "use strict"; 34 | var b = '[data-dismiss="alert"]', c = function (c) { 35 | a(c).on("click", b, this.close) 36 | }; 37 | c.prototype.close = function (b) { 38 | function c() { 39 | f.trigger("closed.bs.alert").remove() 40 | } 41 | 42 | var d = a(this), e = d.attr("data-target"); 43 | e || (e = d.attr("href"), e = e && e.replace(/.*(?=#[^\s]*$)/, "")); 44 | var f = a(e); 45 | b && b.preventDefault(), f.length || (f = d.hasClass("alert") ? d : d.parent()), f.trigger(b = a.Event("close.bs.alert")), b.isDefaultPrevented() || (f.removeClass("in"), a.support.transition && f.hasClass("fade") ? f.one(a.support.transition.end, c).emulateTransitionEnd(150) : c()) 46 | }; 47 | var d = a.fn.alert; 48 | a.fn.alert = function (b) { 49 | return this.each(function () { 50 | var d = a(this), e = d.data("bs.alert"); 51 | e || d.data("bs.alert", e = new c(this)), "string" == typeof b && e[b].call(d) 52 | }) 53 | }, a.fn.alert.Constructor = c, a.fn.alert.noConflict = function () { 54 | return a.fn.alert = d, this 55 | }, a(document).on("click.bs.alert.data-api", b, c.prototype.close) 56 | }(jQuery), +function (a) { 57 | "use strict"; 58 | var b = function (c, d) { 59 | this.$element = a(c), this.options = a.extend({}, b.DEFAULTS, d), this.isLoading = !1 60 | }; 61 | b.DEFAULTS = {loadingText: "loading..."}, b.prototype.setState = function (b) { 62 | var c = "disabled", d = this.$element, e = d.is("input") ? "val" : "html", f = d.data(); 63 | b += "Text", f.resetText || d.data("resetText", d[e]()), d[e](f[b] || this.options[b]), setTimeout(a.proxy(function () { 64 | "loadingText" == b ? (this.isLoading = !0, d.addClass(c).attr(c, c)) : this.isLoading && (this.isLoading = !1, d.removeClass(c).removeAttr(c)) 65 | }, this), 0) 66 | }, b.prototype.toggle = function () { 67 | var a = !0, b = this.$element.closest('[data-toggle="buttons"]'); 68 | if (b.length) { 69 | var c = this.$element.find("input"); 70 | "radio" == c.prop("type") && (c.prop("checked") && this.$element.hasClass("active") ? a = !1 : b.find(".active").removeClass("active")), a && c.prop("checked", !this.$element.hasClass("active")).trigger("change") 71 | } 72 | a && this.$element.toggleClass("active") 73 | }; 74 | var c = a.fn.button; 75 | a.fn.button = function (c) { 76 | return this.each(function () { 77 | var d = a(this), e = d.data("bs.button"), f = "object" == typeof c && c; 78 | e || d.data("bs.button", e = new b(this, f)), "toggle" == c ? e.toggle() : c && e.setState(c) 79 | }) 80 | }, a.fn.button.Constructor = b, a.fn.button.noConflict = function () { 81 | return a.fn.button = c, this 82 | }, a(document).on("click.bs.button.data-api", "[data-toggle^=button]", function (b) { 83 | var c = a(b.target); 84 | c.hasClass("btn") || (c = c.closest(".btn")), c.button("toggle"), b.preventDefault() 85 | }) 86 | }(jQuery), +function (a) { 87 | "use strict"; 88 | var b = function (b, c) { 89 | this.$element = a(b), this.$indicators = this.$element.find(".carousel-indicators"), this.options = c, this.paused = this.sliding = this.interval = this.$active = this.$items = null, "hover" == this.options.pause && this.$element.on("mouseenter", a.proxy(this.pause, this)).on("mouseleave", a.proxy(this.cycle, this)) 90 | }; 91 | b.DEFAULTS = {interval: 5e3, pause: "hover", wrap: !0}, b.prototype.cycle = function (b) { 92 | return b || (this.paused = !1), this.interval && clearInterval(this.interval), this.options.interval && !this.paused && (this.interval = setInterval(a.proxy(this.next, this), this.options.interval)), this 93 | }, b.prototype.getActiveIndex = function () { 94 | return this.$active = this.$element.find(".item.active"), this.$items = this.$active.parent().children(), this.$items.index(this.$active) 95 | }, b.prototype.to = function (b) { 96 | var c = this, d = this.getActiveIndex(); 97 | return b > this.$items.length - 1 || 0 > b ? void 0 : this.sliding ? this.$element.one("slid.bs.carousel", function () { 98 | c.to(b) 99 | }) : d == b ? this.pause().cycle() : this.slide(b > d ? "next" : "prev", a(this.$items[b])) 100 | }, b.prototype.pause = function (b) { 101 | return b || (this.paused = !0), this.$element.find(".next, .prev").length && a.support.transition && (this.$element.trigger(a.support.transition.end), this.cycle(!0)), this.interval = clearInterval(this.interval), this 102 | }, b.prototype.next = function () { 103 | return this.sliding ? void 0 : this.slide("next") 104 | }, b.prototype.prev = function () { 105 | return this.sliding ? void 0 : this.slide("prev") 106 | }, b.prototype.slide = function (b, c) { 107 | var d = this.$element.find(".item.active"), e = c || d[b](), f = this.interval, g = "next" == b ? "left" : "right", h = "next" == b ? "first" : "last", i = this; 108 | if (!e.length) { 109 | if (!this.options.wrap)return; 110 | e = this.$element.find(".item")[h]() 111 | } 112 | if (e.hasClass("active"))return this.sliding = !1; 113 | var j = a.Event("slide.bs.carousel", {relatedTarget: e[0], direction: g}); 114 | return this.$element.trigger(j), j.isDefaultPrevented() ? void 0 : (this.sliding = !0, f && this.pause(), this.$indicators.length && (this.$indicators.find(".active").removeClass("active"), this.$element.one("slid.bs.carousel", function () { 115 | var b = a(i.$indicators.children()[i.getActiveIndex()]); 116 | b && b.addClass("active") 117 | })), a.support.transition && this.$element.hasClass("slide") ? (e.addClass(b), e[0].offsetWidth, d.addClass(g), e.addClass(g), d.one(a.support.transition.end, function () { 118 | e.removeClass([b, g].join(" ")).addClass("active"), d.removeClass(["active", g].join(" ")), i.sliding = !1, setTimeout(function () { 119 | i.$element.trigger("slid.bs.carousel") 120 | }, 0) 121 | }).emulateTransitionEnd(1e3 * d.css("transition-duration").slice(0, -1))) : (d.removeClass("active"), e.addClass("active"), this.sliding = !1, this.$element.trigger("slid.bs.carousel")), f && this.cycle(), this) 122 | }; 123 | var c = a.fn.carousel; 124 | a.fn.carousel = function (c) { 125 | return this.each(function () { 126 | var d = a(this), e = d.data("bs.carousel"), f = a.extend({}, b.DEFAULTS, d.data(), "object" == typeof c && c), g = "string" == typeof c ? c : f.slide; 127 | e || d.data("bs.carousel", e = new b(this, f)), "number" == typeof c ? e.to(c) : g ? e[g]() : f.interval && e.pause().cycle() 128 | }) 129 | }, a.fn.carousel.Constructor = b, a.fn.carousel.noConflict = function () { 130 | return a.fn.carousel = c, this 131 | }, a(document).on("click.bs.carousel.data-api", "[data-slide], [data-slide-to]", function (b) { 132 | var c, d = a(this), e = a(d.attr("data-target") || (c = d.attr("href")) && c.replace(/.*(?=#[^\s]+$)/, "")), f = a.extend({}, e.data(), d.data()), g = d.attr("data-slide-to"); 133 | g && (f.interval = !1), e.carousel(f), (g = d.attr("data-slide-to")) && e.data("bs.carousel").to(g), b.preventDefault() 134 | }), a(window).on("load", function () { 135 | a('[data-ride="carousel"]').each(function () { 136 | var b = a(this); 137 | b.carousel(b.data()) 138 | }) 139 | }) 140 | }(jQuery), +function (a) { 141 | "use strict"; 142 | var b = function (c, d) { 143 | this.$element = a(c), this.options = a.extend({}, b.DEFAULTS, d), this.transitioning = null, this.options.parent && (this.$parent = a(this.options.parent)), this.options.toggle && this.toggle() 144 | }; 145 | b.DEFAULTS = {toggle: !0}, b.prototype.dimension = function () { 146 | var a = this.$element.hasClass("width"); 147 | return a ? "width" : "height" 148 | }, b.prototype.show = function () { 149 | if (!this.transitioning && !this.$element.hasClass("in")) { 150 | var b = a.Event("show.bs.collapse"); 151 | if (this.$element.trigger(b), !b.isDefaultPrevented()) { 152 | var c = this.$parent && this.$parent.find("> .panel > .in"); 153 | if (c && c.length) { 154 | var d = c.data("bs.collapse"); 155 | if (d && d.transitioning)return; 156 | c.collapse("hide"), d || c.data("bs.collapse", null) 157 | } 158 | var e = this.dimension(); 159 | this.$element.removeClass("collapse").addClass("collapsing")[e](0), this.transitioning = 1; 160 | var f = function () { 161 | this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"), this.transitioning = 0, this.$element.trigger("shown.bs.collapse") 162 | }; 163 | if (!a.support.transition)return f.call(this); 164 | var g = a.camelCase(["scroll", e].join("-")); 165 | this.$element.one(a.support.transition.end, a.proxy(f, this)).emulateTransitionEnd(350)[e](this.$element[0][g]) 166 | } 167 | } 168 | }, b.prototype.hide = function () { 169 | if (!this.transitioning && this.$element.hasClass("in")) { 170 | var b = a.Event("hide.bs.collapse"); 171 | if (this.$element.trigger(b), !b.isDefaultPrevented()) { 172 | var c = this.dimension(); 173 | this.$element[c](this.$element[c]())[0].offsetHeight, this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"), this.transitioning = 1; 174 | var d = function () { 175 | this.transitioning = 0, this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse") 176 | }; 177 | return a.support.transition ? void this.$element[c](0).one(a.support.transition.end, a.proxy(d, this)).emulateTransitionEnd(350) : d.call(this) 178 | } 179 | } 180 | }, b.prototype.toggle = function () { 181 | this[this.$element.hasClass("in") ? "hide" : "show"]() 182 | }; 183 | var c = a.fn.collapse; 184 | a.fn.collapse = function (c) { 185 | return this.each(function () { 186 | var d = a(this), e = d.data("bs.collapse"), f = a.extend({}, b.DEFAULTS, d.data(), "object" == typeof c && c); 187 | !e && f.toggle && "show" == c && (c = !c), e || d.data("bs.collapse", e = new b(this, f)), "string" == typeof c && e[c]() 188 | }) 189 | }, a.fn.collapse.Constructor = b, a.fn.collapse.noConflict = function () { 190 | return a.fn.collapse = c, this 191 | }, a(document).on("click.bs.collapse.data-api", "[data-toggle=collapse]", function (b) { 192 | var c, d = a(this), e = d.attr("data-target") || b.preventDefault() || (c = d.attr("href")) && c.replace(/.*(?=#[^\s]+$)/, ""), f = a(e), g = f.data("bs.collapse"), h = g ? "toggle" : d.data(), i = d.attr("data-parent"), j = i && a(i); 193 | g && g.transitioning || (j && j.find('[data-toggle=collapse][data-parent="' + i + '"]').not(d).addClass("collapsed"), d[f.hasClass("in") ? "addClass" : "removeClass"]("collapsed")), f.collapse(h) 194 | }) 195 | }(jQuery), +function (a) { 196 | "use strict"; 197 | function b(b) { 198 | a(d).remove(), a(e).each(function () { 199 | var d = c(a(this)), e = {relatedTarget: this}; 200 | d.hasClass("open") && (d.trigger(b = a.Event("hide.bs.dropdown", e)), b.isDefaultPrevented() || d.removeClass("open").trigger("hidden.bs.dropdown", e)) 201 | }) 202 | } 203 | 204 | function c(b) { 205 | var c = b.attr("data-target"); 206 | c || (c = b.attr("href"), c = c && /#[A-Za-z]/.test(c) && c.replace(/.*(?=#[^\s]*$)/, "")); 207 | var d = c && a(c); 208 | return d && d.length ? d : b.parent() 209 | } 210 | 211 | var d = ".dropdown-backdrop", e = "[data-toggle=dropdown]", f = function (b) { 212 | a(b).on("click.bs.dropdown", this.toggle) 213 | }; 214 | f.prototype.toggle = function (d) { 215 | var e = a(this); 216 | if (!e.is(".disabled, :disabled")) { 217 | var f = c(e), g = f.hasClass("open"); 218 | if (b(), !g) { 219 | "ontouchstart" in document.documentElement && !f.closest(".navbar-nav").length && a('