├── .gitignore ├── README.md ├── code ├── README.md ├── ch04_单元测试与代码覆盖率 │ ├── scale.py │ └── test.py ├── ch05_列表生成式 │ └── ch05.ipynb ├── ch06_Collections 库 │ └── ch06.ipynb ├── ch07_迭代器 │ └── ch07.ipynb ├── ch08_Python 多线程与多进程浅析 │ └── ch08.ipynb ├── ch09_Python 程序性能分析初步 │ ├── ch09.ipynb │ ├── test_CPU_01.py │ ├── test_CPU_02.py │ └── test_CPU_03.py ├── ch12_K近邻算法 │ └── ch12.ipynb ├── ch13_主成分分析 │ └── ch13.ipynb ├── ch14_逻辑回归 │ └── ch14.ipynb ├── ch15_朴素贝叶斯 │ └── ch15.ipynb ├── ch16_决策树 │ └── ch16.ipynb ├── ch17_支持向量机 │ ├── ch17.ipynb │ └── imgs │ │ └── digits 数据集.png ├── ch18_K-Means聚类 │ └── ch18.ipynb ├── ch19_人工神经网络 │ ├── ch19.ipynb │ ├── model_structure.json │ └── model_weight.h5 ├── ch21_Python 机器学习工具 │ └── ch21.ipynb ├── ch22_基于RFM的P2P用户聚类模型 │ ├── ch22.ipynb │ ├── clean_data.csv │ └── data.csv ├── ch23_文本的主题分类 │ ├── sample.txt │ └── text_calssify.py ├── ch24_利用机器翻译实现自然语言查询 │ ├── seq2seq_model.py │ └── train.py └── ch25_身份证汉字和数字识别 │ ├── Dependency.txt │ ├── back_all │ ├── __init__.py │ ├── back_rotate │ │ ├── .DS_Store │ │ ├── Back_rotate.py │ │ ├── Preprocess.py │ │ ├── TextLine_Index.py │ │ └── __init__.py │ ├── east_part │ │ ├── __init__.py │ │ ├── cut_back_v3.py │ │ ├── data_util.py │ │ ├── east_segment_line.py │ │ ├── icdar.py │ │ ├── lanms │ │ │ ├── .gitignore │ │ │ ├── .ycm_extra_conf.py │ │ │ ├── Makefile │ │ │ ├── __init__.py │ │ │ ├── __main__.py │ │ │ ├── adaptor.cpp │ │ │ ├── include │ │ │ │ ├── clipper │ │ │ │ │ ├── clipper.cpp │ │ │ │ │ └── clipper.hpp │ │ │ │ └── pybind11 │ │ │ │ │ ├── attr.h │ │ │ │ │ ├── buffer_info.h │ │ │ │ │ ├── cast.h │ │ │ │ │ ├── chrono.h │ │ │ │ │ ├── class_support.h │ │ │ │ │ ├── common.h │ │ │ │ │ ├── complex.h │ │ │ │ │ ├── descr.h │ │ │ │ │ ├── eigen.h │ │ │ │ │ ├── embed.h │ │ │ │ │ ├── eval.h │ │ │ │ │ ├── functional.h │ │ │ │ │ ├── numpy.h │ │ │ │ │ ├── operators.h │ │ │ │ │ ├── options.h │ │ │ │ │ ├── pybind11.h │ │ │ │ │ ├── pytypes.h │ │ │ │ │ ├── stl.h │ │ │ │ │ ├── stl_bind.h │ │ │ │ │ └── typeid.h │ │ │ └── lanms.h │ │ ├── locality_aware_nms.py │ │ ├── model.py │ │ └── nets │ │ │ ├── __init__.py │ │ │ ├── resnet_utils.py │ │ │ └── resnet_v1.py │ ├── main.py │ ├── model_loader.py │ ├── other_recognize │ │ ├── __init__.py │ │ ├── characterMap.txt │ │ ├── combined_model_structure.json │ │ ├── combined_model_weight.h5 │ │ ├── cut_character_v1.py │ │ ├── do_recognition_for_sorted_id_card.py │ │ ├── filter_using_cnn_prob_v1.py │ │ ├── main_cut_v2_without_cut_line_sorted_part.py │ │ ├── other_main.py │ │ └── prepare_cut_character_v2.py │ ├── test.png │ └── valid_recognition │ │ ├── CNN_test_date_modify.py │ │ ├── Cut_Year_Date.py │ │ ├── Preprocess_Image.py │ │ ├── Split_Image.py │ │ ├── WholeProcess_valid.py │ │ ├── __init__.py │ │ ├── model_structure_date.json │ │ ├── model_weight_date.h5 │ │ ├── prepare_cut_character_v2.py │ │ └── valid_main.py │ └── front_all │ ├── __init__.py │ ├── east_part │ ├── __init__.py │ ├── data_util.py │ ├── east_model │ ├── east_segment_line.py │ ├── icdar.py │ ├── lanms │ │ ├── .gitignore │ │ ├── .ycm_extra_conf.py │ │ ├── Makefile │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── adaptor.cpp │ │ ├── include │ │ │ ├── clipper │ │ │ │ ├── clipper.cpp │ │ │ │ └── clipper.hpp │ │ │ └── pybind11 │ │ │ │ ├── attr.h │ │ │ │ ├── buffer_info.h │ │ │ │ ├── cast.h │ │ │ │ ├── chrono.h │ │ │ │ ├── class_support.h │ │ │ │ ├── common.h │ │ │ │ ├── complex.h │ │ │ │ ├── descr.h │ │ │ │ ├── eigen.h │ │ │ │ ├── embed.h │ │ │ │ ├── eval.h │ │ │ │ ├── functional.h │ │ │ │ ├── numpy.h │ │ │ │ ├── operators.h │ │ │ │ ├── options.h │ │ │ │ ├── pybind11.h │ │ │ │ ├── pytypes.h │ │ │ │ ├── stl.h │ │ │ │ ├── stl_bind.h │ │ │ │ └── typeid.h │ │ └── lanms.h │ ├── locality_aware_nms.py │ ├── model.py │ ├── nets │ │ ├── __init__.py │ │ ├── resnet_utils.py │ │ └── resnet_v1.py │ └── sort_cut_line_v2.py │ ├── main.py │ ├── model_loader.py │ ├── number_recognition │ ├── CNN_test_number_v3_modify.py │ ├── Overall_Process_input_line.py │ ├── Preprocess.py │ ├── TextLine_Index.py │ ├── __init__.py │ ├── model_structure_number.json │ ├── model_weight_number.h5 │ └── number_main.py │ ├── other_recognize │ ├── __init__.py │ ├── characterMap.txt │ ├── combined_model_structure.json │ ├── combined_model_weight.h5 │ ├── cut_character_v1.py │ ├── do_recognition_for_sorted_id_card.py │ ├── filter_using_cnn_prob_v1.py │ ├── main_cut_v2_without_cut_line_sorted_part.py │ ├── other_main.py │ └── prepare_cut_character_v2.py │ ├── rotate_cert │ ├── FaceDetection_usedto_adjust_rotate_via_MTCNN.py │ ├── __init__.py │ ├── det1.npy │ ├── det2.npy │ ├── det3.npy │ ├── detect_face.py │ └── haarcascade_frontalface_default.xml │ └── test.png ├── datasets └── README.md ├── docs └── README.md ├── imgs ├── README.md └── book-cover.jpg └── 勘误.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | code/ch14_逻辑回归/.ipynb_checkpoints/ch14-checkpoint.ipynb 2 | code/ch18_K-Means聚类/.ipynb_checkpoints/ch18-checkpoint.ipynb 3 | code/ch05_列表生成式/.ipynb_checkpoints/ch05-checkpoint.ipynb 4 | code/ch09_Python 程序性能分析初步/.ipynb_checkpoints/ch09-checkpoint.ipynb 5 | code/ch04_单元测试与代码覆盖率/__pycache__/scale.cpython-35.pyc 6 | code/ch07_迭代器/.ipynb_checkpoints/ch07-checkpoint.ipynb 7 | code/ch08_Python 多线程与多进程浅析/.ipynb_checkpoints/ch08-checkpoint.ipynb 8 | code/ch06_Collections 库/.ipynb_checkpoints/ch06-checkpoint.ipynb 9 | code/ch12_K近邻算法/.ipynb_checkpoints/ch12-checkpoint.ipynb 10 | code/ch13_主成分分析/.ipynb_checkpoints/ch13-checkpoint.ipynb 11 | code/ch15_朴素贝叶斯/.ipynb_checkpoints/ch15-checkpoint.ipynb 12 | code/ch16_决策树/.ipynb_checkpoints/ch16-checkpoint.ipynb 13 | code/ch17_支持向量机/.ipynb_checkpoints/ch17-checkpoint.ipynb 14 | code/ch19_人工神经网络/.ipynb_checkpoints/ch19-checkpoint.ipynb 15 | code/ch21_Python 机器学习工具/.ipynb_checkpoints/ch21-checkpoint.ipynb 16 | code/ch22_基于RFM的P2P用户聚类模型/.ipynb_checkpoints/ch22-checkpoint.ipynb 17 | code/ch25_身份证汉子和数字识别/back_all/.DS_Store 18 | code/ch25_身份证汉子和数字识别/front_all/.DS_Store 19 | code/ch25_身份证汉字和数字识别/back_all/.DS_Store 20 | code/ch25_身份证汉字和数字识别/front_all/.DS_Store 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## **《Python 机器学习实战》 代码仓库** 2 | 3 | ------ 4 | 5 | **Python 机器学习实战** 6 | 7 | - 出版社: 科学技术文献出版社 8 | - ISBN:9787518938087 9 | - 版次:1 10 | - 商品编码:12289823 11 | - 包装:平装 12 | - 开本:16开 13 | - 出版时间:2018-02-01![![img](file:///C:/Users/yimeng.zhang/Documents/git/How-to-Python-and-Machine-Learning-Book/imgs/book-cover.jpg?lastModify=1518101896)![img](file:///C:/Users/yimeng.zhang/Documents/git/How-to-Python-and-Machine-Learning-Book/imgs/book-cover.jpg?lastModify=1518101896)book-cover](imgs/book-cover.jpg) 14 | 15 | ​ 16 | 17 | 18 | 19 | 20 | 21 | ### **购买链接** 22 | 23 | [京东商城](https://item.jd.com/12289823.html) 24 | 25 | -------------------------------------------------------------------------------- /code/README.md: -------------------------------------------------------------------------------- 1 | 这是 code 文件夹的帮助文档。 2 | 3 | 本文件夹会记录书籍相关的代码,按章节归类。 4 | 5 | -------------------------------------------------------------------------------- /code/ch04_单元测试与代码覆盖率/scale.py: -------------------------------------------------------------------------------- 1 | # 这段代码中,我们实现了一个 n 进制转换的小工具,可以在任意进制之间进行转化。 2 | 3 | class Scale(object): 4 | 5 | #这里用常用的字母代表数字的字典。 6 | 7 | dic = {'10': 'A', '11': 'B', '12': 'C ', 8 | '13': 'D', '14': 'E', '15': 'F'} 9 | 10 | # 定义一个函数将 weight 进制的某一位的值对应的十进制的值算出来。 11 | 12 | @staticmethod 13 | def place_value(n_value, scale, digits): 14 | # 某一位的权值,初始为 1 15 | weight = 1 16 | for i in range(1, digits + 1): 17 | weight = scale * weight 18 | return n_value * weight 19 | 20 | # 定义一个函数将 scale 进制的值 value 转为对应十进制的值。 21 | 22 | @staticmethod 23 | def n_2_decimal(value_, scale): 24 | sum_ = 0 25 | n = len(str(value_)) 26 | for i in range(1, n + 1): 27 | sum_ = sum_ + Scale.place_value(int(str(value_) 28 | [i-1]), scale, n-i) 29 | return sum_ 30 | 31 | # 这个函数将十进制的值 value 转为对应 scale 进制的值。 32 | 33 | @staticmethod 34 | def decimal_2_n(value_, scale): 35 | arr = [] 36 | i = 0 37 | while value_ is not 0: 38 | rem = value_ % scale 39 | if rem >= 16: 40 | rem = "*" + str(rem) + "*" 41 | elif 10 <= rem <= 15: 42 | rem = Scale.dic[str(rem)] 43 | value_ = value_ // scale 44 | arr.append(rem) 45 | i += 1 46 | return arr 47 | 48 | # 最后,这个函数可以进行不同进制间的转化。 49 | 50 | @staticmethod 51 | def any_scale(scale1_, value_, scale2_): 52 | mid_value = Scale.n_2_decimal(value_, scale1_) 53 | fin_value = Scale.decimal_2_n(mid_value, scale2_) 54 | fin_value.reverse() 55 | fin_value = ''.join([str(x) for x in fin_value]) 56 | return fin_value 57 | -------------------------------------------------------------------------------- /code/ch04_单元测试与代码覆盖率/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from scale import Scale 3 | 4 | # 创建单元测试类,并在 setUp 和 tearDown 中对 scale 属性进行初始化和还原。 5 | 6 | class AnyScaleTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.scale = Scale() 10 | 11 | def tearDown(self): 12 | self.scale = None 13 | 14 | # 测试用例 1,测试十进制下的 16 转化为十六进制。 15 | 16 | def test1_10_16_16(self): 17 | self.assertEquals('10', self.scale.any_scale(10, 16, 16)) 18 | 19 | # 测试用例 2,测试十进制下的 17 转化为八进制。 20 | 21 | def test2_10_17_8(self): 22 | self.assertEquals('19', self.scale.any_scale(10, 17, 8)) 23 | 24 | # 测试用例 3,测试八进制下的 19 转化为十进制。 25 | 26 | def test3_8_19_10(self): 27 | self.assertEquals('17', self.scale.any_scale(8, 19, 10)) 28 | 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /code/ch09_Python 程序性能分析初步/test_CPU_01.py: -------------------------------------------------------------------------------- 1 | @profile 2 | def my_func(): 3 | a = [1] * (10 ** 6) 4 | b = [2] * (2 * 10 ** 7) 5 | del b 6 | return a 7 | 8 | if __name__ == '__main__': 9 | my_func() 10 | -------------------------------------------------------------------------------- /code/ch09_Python 程序性能分析初步/test_CPU_02.py: -------------------------------------------------------------------------------- 1 | from memory_profiler import profile 2 | 3 | @profile 4 | def my_func(): 5 | a = [1] * (10 ** 6) 6 | b = [2] * (2 * 10 ** 7) 7 | del b 8 | return a 9 | 10 | if __name__ == '__main__': 11 | my_func() 12 | -------------------------------------------------------------------------------- /code/ch09_Python 程序性能分析初步/test_CPU_03.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from concurrent.futures import * 3 | from memory_profiler import profile 4 | 5 | 6 | def my_cal(a): 7 | j = 0 8 | for i in range(a): 9 | j = j + i 10 | print(j) 11 | return j 12 | 13 | 14 | @profile 15 | def run(): 16 | list_01 = [1000000, 2000000, 1500000, 2500000, 3000000] 17 | start = time() 18 | pool = ProcessPoolExecutor(max_workers=10) 19 | list_02 = list(pool.map(my_cal, list_01)) 20 | print(list_02) 21 | end = time() 22 | print('cost time {:f} s'.format(end - start)) 23 | 24 | if __name__ == '__main__': 25 | run() 26 | -------------------------------------------------------------------------------- /code/ch12_K近邻算法/ch12.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Python 机器学习实战 ——代码样例\n", 8 | "\n", 9 | "# 第十二章 K 近邻算法" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": { 15 | "collapsed": true 16 | }, 17 | "source": [ 18 | "## 欧氏距离计算\n", 19 | "\n", 20 | "首先,构造两个样本点:" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 1, 26 | "metadata": { 27 | "collapsed": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "import numpy as np\n", 32 | "x=np.array([1,1])\n", 33 | "y=np.array([4,5]) \n" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": { 39 | "collapsed": true 40 | }, 41 | "source": [ 42 | "计算上述两个样本点之间的欧式距离:" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "metadata": { 49 | "collapsed": false 50 | }, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | "5.0\n" 57 | ] 58 | } 59 | ], 60 | "source": [ 61 | "from math import *\n", 62 | "def e_distance(x,y):\n", 63 | " return sqrt(sum(pow(a-b,2) for a, b in zip(x, y)))\n", 64 | " \n", 65 | "print(e_distance(x, y))\n" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "## 曼哈顿距离计算" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "仍然使用上述的两个样本点,计算它们之间的曼哈顿距离:" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 3, 85 | "metadata": { 86 | "collapsed": false 87 | }, 88 | "outputs": [ 89 | { 90 | "name": "stdout", 91 | "output_type": "stream", 92 | "text": [ 93 | "7\n" 94 | ] 95 | } 96 | ], 97 | "source": [ 98 | "from math import *\n", 99 | "def m_distance(x,y):\n", 100 | " return sum(abs(x-y))\n", 101 | " \n", 102 | "print(m_distance(x, y))\n" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "## 切比雪夫距离计算" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "计算两点之间的切比雪夫距离:" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 4, 122 | "metadata": { 123 | "collapsed": false 124 | }, 125 | "outputs": [ 126 | { 127 | "name": "stdout", 128 | "output_type": "stream", 129 | "text": [ 130 | "4\n" 131 | ] 132 | } 133 | ], 134 | "source": [ 135 | "from math import *\n", 136 | "def q_distance(x,y):\n", 137 | " return abs(x-y).max()\n", 138 | " \n", 139 | "print(q_distance(x, y))\n" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "## 夹角余弦距离计算" 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "metadata": {}, 152 | "source": [ 153 | "计算两点之间的夹角余弦距离:" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 5, 159 | "metadata": { 160 | "collapsed": false 161 | }, 162 | "outputs": [ 163 | { 164 | "name": "stdout", 165 | "output_type": "stream", 166 | "text": [ 167 | "0.993883734674\n" 168 | ] 169 | } 170 | ], 171 | "source": [ 172 | "from math import *\n", 173 | "def cos_distance(x,y):\n", 174 | " return np.dot(x,y)/(np.linalg.norm(x)*np.linalg.norm(y))\n", 175 | " \n", 176 | "print(cos_distance(x, y))\n" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "## 使用K近邻算法对 Iris 数据集进行分类" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "本节我们将通过一个例子讲解 K 近邻对 Iris 数据集进行分类。Iris 数据集是一个常用的分类用数据集,以鸢尾花的特征作为数据来源,数据集包含 150 个样本,分为 3 类花种,每类 50 个样本,每个样本包含 4 个独立属性 ( 萼片长度、萼片宽度、花瓣长度、花瓣宽度 )。" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 13, 196 | "metadata": { 197 | "collapsed": false 198 | }, 199 | "outputs": [], 200 | "source": [ 201 | "# 首先,我们导入将用到的库。\n", 202 | "\n", 203 | "from sklearn import datasets\n", 204 | "from sklearn.neighbors import KNeighborsClassifier\n", 205 | "from sklearn import cross_validation\n", 206 | "from sklearn.metrics import classification_report\n", 207 | "from sklearn.metrics import confusion_matrix\n", 208 | "from sklearn.metrics import accuracy_score\n", 209 | "\n", 210 | "\n", 211 | "# 准备数据集,并分离训练集和验证集。\n", 212 | "\n", 213 | "iris = datasets.load_iris() \n", 214 | "X = iris.data \n", 215 | "Y = iris.target \n", 216 | "validation_size = 0.20\n", 217 | "seed = 1 \n", 218 | "X_train, X_validation, Y_train, Y_validation = cross_validation.train_test_split(X, Y, test_size=validation_size, random_state=seed) \n" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": 16, 224 | "metadata": { 225 | "collapsed": false 226 | }, 227 | "outputs": [ 228 | { 229 | "name": "stdout", 230 | "output_type": "stream", 231 | "text": [ 232 | "accuracy_score: 1.0\n", 233 | "混淆矩阵: \n", 234 | " [[11 0 0]\n", 235 | " [ 0 13 0]\n", 236 | " [ 0 0 6]]\n", 237 | "分类报告: \n", 238 | " precision recall f1-score support\n", 239 | "\n", 240 | " 0 1.00 1.00 1.00 11\n", 241 | " 1 1.00 1.00 1.00 13\n", 242 | " 2 1.00 1.00 1.00 6\n", 243 | "\n", 244 | "avg / total 1.00 1.00 1.00 30\n", 245 | "\n" 246 | ] 247 | } 248 | ], 249 | "source": [ 250 | "# 创建 KNN 分类器,并拟合数据集。\n", 251 | "\n", 252 | "knn = KNeighborsClassifier()\n", 253 | "knn.fit(X_train, Y_train)\n", 254 | "\n", 255 | "# 在验证集上进行预测,并输出 accuracy score,混淆矩阵和分类报告。\n", 256 | "\n", 257 | "predictions = knn.predict(X_validation)\n", 258 | "print('accuracy_score:',accuracy_score(Y_validation, predictions))\n", 259 | "print('混淆矩阵: \\n',confusion_matrix(Y_validation, predictions))\n", 260 | "print('分类报告: \\n',classification_report(Y_validation, predictions))\n" 261 | ] 262 | } 263 | ], 264 | "metadata": { 265 | "kernelspec": { 266 | "display_name": "Python 3", 267 | "language": "python", 268 | "name": "python3" 269 | }, 270 | "language_info": { 271 | "codemirror_mode": { 272 | "name": "ipython", 273 | "version": 3 274 | }, 275 | "file_extension": ".py", 276 | "mimetype": "text/x-python", 277 | "name": "python", 278 | "nbconvert_exporter": "python", 279 | "pygments_lexer": "ipython3", 280 | "version": "3.5.1" 281 | } 282 | }, 283 | "nbformat": 4, 284 | "nbformat_minor": 0 285 | } 286 | -------------------------------------------------------------------------------- /code/ch14_逻辑回归/ch14.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Python 机器学习实战 ——代码样例\n", 8 | "\n", 9 | "# 第十四章 逻辑回归" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## 使用逻辑回归进行二分类\n", 17 | "\n", 18 | "数据集介绍:乳腺癌数据集是一个经典并且简单的二分类数据集。一共有 569 个样本,其中 212 个样本为恶性 ( malignant, 0 ),357 个样本为良性 ( benign, 1 )。每个样本有 30 个特征,均为非负实数。30 个特征分为三类,前 10 个是相关指标的平均值,中间 10 个是指标的偏差,最后 10 个是指标的最差极值。" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 4, 24 | "metadata": { 25 | "collapsed": false 26 | }, 27 | "outputs": [ 28 | { 29 | "name": "stdout", 30 | "output_type": "stream", 31 | "text": [ 32 | "混淆矩阵: \n", 33 | " [[50 4]\n", 34 | " [ 2 87]]\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "# 导入需要的库。\n", 40 | "\n", 41 | "import numpy as np\n", 42 | "from sklearn import datasets\n", 43 | "from sklearn.linear_model import LogisticRegression\n", 44 | "from sklearn.metrics import confusion_matrix\n", 45 | "from sklearn.model_selection import train_test_split\n", 46 | "\n", 47 | "# 导入数据并分为训练集和测试集。\n", 48 | "\n", 49 | "breast_cancer = datasets.load_breast_cancer()\n", 50 | "x = breast_cancer['data']\n", 51 | "y = breast_cancer['target']\n", 52 | "X_train, X_test, y_train, y_test = train_test_split(x, y, random_state=42) \n", 53 | "\n", 54 | "# 逻辑回归拟合。\n", 55 | "\n", 56 | "log_reg = LogisticRegression()\n", 57 | "log_reg.fit(X_train, y_train)\n", 58 | "\n", 59 | "# 测试集效果检验,输出混淆矩阵。\n", 60 | "\n", 61 | "y_predict = log_reg.predict(X_test)\n", 62 | "print('混淆矩阵: \\n',confusion_matrix(y_test, y_predict))\n" 63 | ] 64 | } 65 | ], 66 | "metadata": { 67 | "kernelspec": { 68 | "display_name": "Python 3", 69 | "language": "python", 70 | "name": "python3" 71 | }, 72 | "language_info": { 73 | "codemirror_mode": { 74 | "name": "ipython", 75 | "version": 3 76 | }, 77 | "file_extension": ".py", 78 | "mimetype": "text/x-python", 79 | "name": "python", 80 | "nbconvert_exporter": "python", 81 | "pygments_lexer": "ipython3", 82 | "version": "3.5.1" 83 | } 84 | }, 85 | "nbformat": 4, 86 | "nbformat_minor": 0 87 | } 88 | -------------------------------------------------------------------------------- /code/ch15_朴素贝叶斯/ch15.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Python 机器学习实战 ——代码样例\n", 8 | "\n", 9 | "# 第十五章 朴素贝叶斯分类器" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## 使用朴素贝叶斯进行二分类\n", 17 | "\n", 18 | "本节我们将通过一个例子演示使用朴素贝叶斯分类器进行简单的分类。数据集依然使用上一节的逻辑回归中的乳腺癌数据集,一个经典的二分类数据集。\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 5, 24 | "metadata": { 25 | "collapsed": false 26 | }, 27 | "outputs": [ 28 | { 29 | "name": "stdout", 30 | "output_type": "stream", 31 | "text": [ 32 | "accuracy_score: 0.941489361702\n", 33 | "混淆矩阵:\n", 34 | " [[ 61 6]\n", 35 | " [ 5 116]]\n", 36 | "分类报告:\n", 37 | " precision recall f1-score support\n", 38 | "\n", 39 | " 0 0.92 0.91 0.92 67\n", 40 | " 1 0.95 0.96 0.95 121\n", 41 | "\n", 42 | "avg / total 0.94 0.94 0.94 188\n", 43 | "\n" 44 | ] 45 | } 46 | ], 47 | "source": [ 48 | "# 首先,导入需要的库。\n", 49 | "\n", 50 | "from sklearn.datasets import load_breast_cancer\n", 51 | "from sklearn.cross_validation import train_test_split \n", 52 | "from sklearn.naive_bayes import GaussianNB\n", 53 | "from sklearn.metrics import classification_report\n", 54 | "from sklearn.metrics import confusion_matrix\n", 55 | "from sklearn.metrics import accuracy_score\n", 56 | "\n", 57 | "# 准备数据集。\n", 58 | "\n", 59 | "data = load_breast_cancer()\n", 60 | "label_names = data['target_names']\n", 61 | "labels = data['target']\n", 62 | "feature_names = data['feature_names']\n", 63 | "features = data['data']\n", 64 | "\n", 65 | "# 分离训练集和验证集。\n", 66 | "\n", 67 | "train, test, train_labels, test_labels = train_test_split(features,labels,test_size=0.33,random_state=42) \n", 68 | "\n", 69 | "# 创建朴素贝叶斯分类器,并拟合数据集。\n", 70 | "\n", 71 | "gnb = GaussianNB()\n", 72 | "model = gnb.fit(train, train_labels)\n", 73 | "\n", 74 | "# 在验证集上进行预测,并输出 accuracy score,混淆矩阵和分类报告。\n", 75 | "\n", 76 | "preds = gnb.predict(test) \n", 77 | "print('accuracy_score:',accuracy_score(test_labels, preds))\n", 78 | "print('混淆矩阵:\\n',confusion_matrix(test_labels, preds))\n", 79 | "print('分类报告:\\n',classification_report(test_labels, preds))\n" 80 | ] 81 | } 82 | ], 83 | "metadata": { 84 | "kernelspec": { 85 | "display_name": "Python 3", 86 | "language": "python", 87 | "name": "python3" 88 | }, 89 | "language_info": { 90 | "codemirror_mode": { 91 | "name": "ipython", 92 | "version": 3 93 | }, 94 | "file_extension": ".py", 95 | "mimetype": "text/x-python", 96 | "name": "python", 97 | "nbconvert_exporter": "python", 98 | "pygments_lexer": "ipython3", 99 | "version": "3.5.1" 100 | } 101 | }, 102 | "nbformat": 4, 103 | "nbformat_minor": 0 104 | } 105 | -------------------------------------------------------------------------------- /code/ch17_支持向量机/imgs/digits 数据集.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch17_支持向量机/imgs/digits 数据集.png -------------------------------------------------------------------------------- /code/ch19_人工神经网络/model_structure.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "config": [{"class_name": "Convolution2D", "config": {"trainable": true, "init": "glorot_uniform", "border_mode": "valid", "name": "convolution2d_1", "b_constraint": null, "subsample": [1, 1], "W_constraint": null, "dim_ordering": "tf", "input_dtype": "float32", "b_regularizer": null, "activation": "linear", "W_regularizer": null, "nb_col": 3, "nb_row": 3, "batch_input_shape": [null, 28, 28, 1], "nb_filter": 32, "bias": true, "activity_regularizer": null}}, {"class_name": "Activation", "config": {"trainable": true, "name": "activation_1", "activation": "relu"}}, {"class_name": "Convolution2D", "config": {"trainable": true, "init": "glorot_uniform", "border_mode": "valid", "name": "convolution2d_2", "b_constraint": null, "subsample": [1, 1], "W_constraint": null, "dim_ordering": "tf", "nb_filter": 32, "b_regularizer": null, "activation": "linear", "W_regularizer": null, "nb_col": 3, "nb_row": 3, "bias": true, "activity_regularizer": null}}, {"class_name": "Activation", "config": {"trainable": true, "name": "activation_2", "activation": "relu"}}, {"class_name": "MaxPooling2D", "config": {"trainable": true, "border_mode": "valid", "strides": [2, 2], "pool_size": [2, 2], "name": "maxpooling2d_1", "dim_ordering": "tf"}}, {"class_name": "Flatten", "config": {"trainable": true, "name": "flatten_1"}}, {"class_name": "Dense", "config": {"trainable": true, "init": "glorot_uniform", "b_constraint": null, "bias": true, "W_constraint": null, "output_dim": 128, "input_dim": 4608, "b_regularizer": null, "activation": "linear", "W_regularizer": null, "name": "dense_1", "activity_regularizer": null}}, {"class_name": "Activation", "config": {"trainable": true, "name": "activation_3", "activation": "relu"}}, {"class_name": "Dense", "config": {"trainable": true, "init": "glorot_uniform", "b_constraint": null, "bias": true, "W_constraint": null, "output_dim": 10, "input_dim": 128, "b_regularizer": null, "activation": "linear", "W_regularizer": null, "name": "dense_2", "activity_regularizer": null}}, {"class_name": "Activation", "config": {"trainable": true, "name": "activation_4", "activation": "softmax"}}], "keras_version": "1.2.0"} -------------------------------------------------------------------------------- /code/ch19_人工神经网络/model_weight.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch19_人工神经网络/model_weight.h5 -------------------------------------------------------------------------------- /code/ch23_文本的主题分类/text_calssify.py: -------------------------------------------------------------------------------- 1 | # 本例实现一个对文本内容二分类的例子 2 | 3 | import numpy as np 4 | import pandas as pd 5 | from sklearn.feature_extraction.text import TfidfVectorizer 6 | from sklearn.metrics import classification_report, confusion_matrix 7 | from sklearn.feature_selection import SelectKBest 8 | from sklearn.feature_selection import chi2 9 | from sklearn.naive_bayes import MultinomialNB 10 | from sklearn.svm import SVC 11 | import re 12 | import jieba 13 | from sklearn.metrics import f1_score 14 | 15 | # 设置变量 16 | 17 | # 读取文件路径 18 | file_path = r'sample.txt' 19 | 20 | # 是否分词/不分词 (True=分词) 21 | cut_word = True 22 | 23 | # 去除特殊字符类型 all=所有 24 | stop_character_type = 'all' 25 | 26 | # 停用词词典 27 | stopword = False # 是否调用停用词词典 28 | stopwords_path = 'stopword.txt' # 停用词词典路径 29 | 30 | # 分词方法选择(目前仅支持jieba) 31 | cut_word_method = 'jieba' 32 | 33 | # 是否调用行业自定义词典 34 | load_userdict = False 35 | 36 | # 行业自定义词典路径 37 | userdict_path = r'userdict.txt' 38 | 39 | # 训练集切割比例 40 | train_ratio = 0.7 # 训练集的比例,取值[0-1] 41 | 42 | # 模型参数 43 | model = 'NB' # 选择分类算法 'NB'=朴素贝叶斯算法, 'SVM'=SVM算法 44 | 45 | analyzer = 'char' # 文本特征组成方式: string, {‘word’, ‘char’} 46 | 47 | ngram_range = (1, 2) # n_gram的上限和下限: tuple (min_n, max_n) 48 | 49 | min_df = 1 # 忽略出现次数小于该值的词项: float in range [0.0, 1.0] or int, default=1 50 | 51 | k = 2000 # 保留最有效的k个特征 52 | 53 | kernel = 'linear' # SVM算法的kernal: string, optional,{‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’} 54 | 55 | svm_c = 1.0 # SVM算法的C值(误差项的惩罚系数) 56 | 57 | alpha = 0.1 # 朴素贝叶斯算法的alpha值 58 | 59 | # 1. 读取文本数据 60 | # 读取文件存入Pandas dataframe 61 | data = pd.read_table(file_path) 62 | 63 | # 2. 删除含缺失值的样本 64 | data = data.dropna() 65 | 66 | # 3. 分割训练集和测试集 67 | # 随机选取一定比例的数据作为训练样本 68 | print('总数据条数',len(data)) 69 | train = data.sample(n=int(len(data)*train_ratio)) # 生成训练集 70 | 71 | # 剩余部分作为测试样本 72 | index = data.index.tolist() 73 | train_index = train.index.tolist() # 生成训练集的index 74 | test_index = [i for i in index if i not in train_index] # 生成测试集的index 75 | test = data.loc[test_index, ] # 生成测试集 76 | 77 | # 将训练集和测试集进一步拆分成文本和标签,并转成list 78 | text = data.columns[0] # 获取第一列列名,即存放文本内容的列 79 | label = data.columns[1] # 获取第二列列名,即存放标签的列(0,1两个类别) 80 | x_train = train[text].tolist() # 训练集的文本内容 81 | y_train = train[label].tolist() # 训练集的标签 82 | x_test = test[text].tolist() # 测试集的文本内容 83 | y_test = test[label].tolist() # 测试集的标签 84 | 85 | 86 | # 3. 判断使用分词/不分词方法 87 | if cut_word is True: 88 | print('使用分词方法') 89 | 90 | # 4. 选择要去除的特殊字符 91 | if stop_character_type == 'all': # 去除所有 92 | stop_character = u'[’()〈〉:.!"#$%&\'()*+,-./:;~<=>?@,。?↓★、�…【】《》?©“”▪►‘’!•[\\]^_`{|}~]+|[a-zA-Z0-9]' 93 | elif stop_character_type == 'chararter': # 去除符号 94 | stop_character = u'[’()〈〉:.!"#$%&\'()*+,-./:;~<=>?@,。?↓★、�…【】《》?©“”▪►‘’!•[\\]^_`{|}~]+' 95 | elif stop_character_type == 'english_word': # 去除英文 96 | stop_character = u'[a-zA-Z]' 97 | elif stop_character_type == 'number': # 去除数字 98 | stop_character = u'[0-9]' 99 | elif stop_character_type == 'url': # 去除url 100 | stop_character_type = u'http://[a-zA-Z0-9.?/&=:]*' 101 | 102 | # 去除停用词 103 | if stopword is True: 104 | with open(stopwords_path, 'r') as f: 105 | stopwords_set = {line.strip() for line in f} # 从停用词字典中获取所有停用词 106 | print('成功从以下路径获取停用词词典', stopwords_path) 107 | else: 108 | stopwords_set = {} 109 | print('停用词词典为空') 110 | 111 | # 构建jieba分词方法 112 | if cut_word_method == 'jieba': 113 | print('使用jieba分词') 114 | # 是否调用行业自定义词典, True=调用 115 | if load_userdict is True: 116 | jieba.load_userdict(f=userdict_path) # 调用自定义词典,帮助实现更精确的分词(如“汇付天下”等) 117 | print('使用jieba自定义词典') 118 | else: 119 | print('不使用jieba自定义词典') 120 | 121 | def jieba_cutword(paper, stopwords_set): 122 | r = re.compile(stop_character) 123 | paper = re.sub(r, '', paper) 124 | seg_words = jieba.cut(paper, cut_all=False) 125 | words = [word for word in seg_words if word not in stopwords_set and word != ' '] 126 | return " ".join(words) 127 | 128 | def my_sentence(paper_list): 129 | words = [] 130 | for paper in paper_list: 131 | words.append(jieba_cutword(paper, stopwords_set)) 132 | return words 133 | 134 | x_train = my_sentence(x_train) # 对数据执行jieba分词 135 | x_test = my_sentence(x_test) 136 | 137 | 138 | if cut_word is False: 139 | print('使用不分词方法') 140 | 141 | stop_character = '' 142 | 143 | # 4. 选择要去除的特殊字符 144 | if stop_character_type == 'all': 145 | stop_character = u'[’()〈〉:.!"#$%&\'()*+,-./:;~<=>?@,。?↓★、�…【】《》?©“”▪►‘’!•[\\]^_`{|}~]+|[a-zA-Z0-9]' 146 | elif stop_character_type == 'chararter': 147 | stop_character = u'[’()〈〉:.!"#$%&\'()*+,-./:;~<=>?@,。?↓★、�…【】《》?©“”▪►‘’!•[\\]^_`{|}~]+' 148 | elif stop_character_type == 'english_word': 149 | stop_character = u'[a-zA-Z]' 150 | elif stop_character_type == 'number': 151 | stop_character = u'[0-9]' 152 | elif stop_character_type == 'url': 153 | stop_character = u'http://[a-zA-Z0-9.?/&=:]*' 154 | 155 | r = re.compile(stop_character) 156 | x_train = [re.sub(r, '', text) for text in x_train] 157 | x_test = [re.sub(r, '', text) for text in x_test] 158 | 159 | # 5. 文本特征提取 160 | # 对训练集的文本提取特征 161 | vectorizer = TfidfVectorizer(analyzer=analyzer, ngram_range=ngram_range, min_df=min_df) # 构建TF-IDF特征提取器 162 | fea_train = vectorizer.fit_transform(x_train) # 对训练集文本提取特征 163 | chi_vectorizer = SelectKBest(chi2, k=k) # 构建特征筛选器,只保留最有效的k个特征 164 | trainvec = chi_vectorizer.fit_transform(fea_train, y_train) # 对训练集文本提取最有效的k个特征 165 | 166 | 167 | # 6. 模型建立 168 | 169 | clf = None 170 | 171 | if model == 'SVM': # 模型一:SVM算法 172 | clf = SVC(kernel=kernel, C=svm_c) # 可调参数 173 | clf.fit(trainvec, y_train) 174 | 175 | if model == 'NB': # 模型二:朴素贝叶斯算法 176 | clf = MultinomialNB(alpha=alpha) # 可调参数 177 | clf.fit(trainvec, y_train) 178 | 179 | 180 | # 7. 利用模型进行预测,并输出准确率表现 181 | # 对预测集进行特征转化 182 | testvec = chi_vectorizer.transform(vectorizer.transform(x_test)) 183 | 184 | # 利用训练好的模型对测试样本预测 185 | y_pred = clf.predict(testvec) 186 | 187 | # 输出混淆矩阵和准确率报告 188 | print('模型预测结果:') 189 | print('混淆矩阵') 190 | print(confusion_matrix(y_test, y_pred)) 191 | print(classification_report(y_test, y_pred)) 192 | print('f1分数', f1_score(y_test, y_pred)) 193 | print('Test Accuracy:%.2f' % clf.score(testvec, y_test)) 194 | -------------------------------------------------------------------------------- /code/ch24_利用机器翻译实现自然语言查询/train.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import numpy as np 5 | import tensorflow as tf 6 | import math 7 | import os 8 | import sys 9 | import time 10 | import seq2seq_model 11 | 12 | def train(): 13 | with tf.Session() as sess: 14 | # 1.创建一个模型 15 | model = seq2seq_model.create_model(sess, False) # forward_only= False 训练时false 16 | # 2.读入测试集 17 | dev_set = seq2seq_model.read_data("test_prepared.cn","test_prepared.sq") 18 | # 3.读入训练集 19 | train_set = seq2seq_model.read_data("train_prepared.cn","train_prepared.sq") 20 | print("data prepared") 21 | train_bucket_sizes = [len(train_set[b]) for b in range(len(seq2seq_model._buckets))] 22 | train_total_size = float(sum(train_bucket_sizes)) 23 | train_buckets_scale = [sum(train_bucket_sizes[:i + 1]) / train_total_size for i in range(len(train_bucket_sizes))] 24 | print("buckets prepared") 25 | step_time = 0.0 26 | loss = 0.0 27 | current_step = 0 28 | previous_losses = [] 29 | while True: 30 | # Choose a bucket according to data distribution. We pick a random number 31 | # in [0, 1] and use the corresponding interval in train_buckets_scale. 32 | # 随机生成一个0-1数,在生成bucket_id中使用 33 | random_number_01 = np.random.random_sample() 34 | bucket_id = min([i for i in range(len(train_buckets_scale)) if train_buckets_scale[i] > random_number_01]) 35 | start_time = time.time() 36 | # Get a batch and make a step. 37 | encoder_inputs, decoder_inputs, target_weights = model.get_batch(train_set, bucket_id) # get a batch 38 | _, step_loss, _ = model.step(sess, encoder_inputs, decoder_inputs,target_weights, bucket_id, False) # make a step 39 | step_time += (time.time() - start_time) / seq2seq_model.FLAGS.steps_per_checkpoint # 平均一次的时间 40 | loss += step_loss / seq2seq_model.FLAGS.steps_per_checkpoint # 平均loss 200个batch的Loss的平均值 41 | current_step += 1 42 | if current_step % seq2seq_model.FLAGS.steps_per_checkpoint == 0: 43 | perplexity = math.exp(float(loss)) if loss < 300 else float("inf") # 总混淆度(加权平均?) 44 | print ("global step %d learning rate %.4f step-time %.2f perplexity " 45 | "%.2f" % (model.global_step.eval(), model.learning_rate.eval(), 46 | step_time, perplexity)) 47 | # 如果损失值在最近3次内没有再降低,减小学习率 48 | if len(previous_losses) > 2 and loss > max(previous_losses[-3:]): 49 | sess.run(model.learning_rate_decay_op) 50 | previous_losses.append(loss) 51 | checkpoint_path = os.path.join('dir', "translate.ckpt") 52 | model.saver.save(sess, checkpoint_path, global_step=model.global_step) 53 | step_time, loss = 0.0, 0.0 54 | for bucket_id in range(len(seq2seq_model._buckets)): 55 | if len(dev_set[bucket_id]) == 0: 56 | print(" eval: empty bucket %d" % (bucket_id)) 57 | continue 58 | encoder_inputs, decoder_inputs, target_weights = model.get_batch(dev_set, bucket_id) 59 | _, eval_loss, _ = model.step(sess, encoder_inputs, decoder_inputs,target_weights, bucket_id, True) 60 | eval_ppx = math.exp(float(eval_loss)) if eval_loss < 300 else float("inf") 61 | print(" eval: bucket %d perplexity %.2f" % (bucket_id, eval_ppx)) # 每个Bucket对应一个混淆度 62 | sys.stdout.flush() 63 | 64 | def main(_): 65 | train() 66 | 67 | if __name__ == "__main__": 68 | tf.app.run() 69 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/Dependency.txt: -------------------------------------------------------------------------------- 1 | tensorflow==1.2.0 2 | requests==2.10.0 3 | pandas==0.18.1 4 | Flask==0.11.1 5 | python_dateutil==2.6.1 6 | flask_sqlalchemy==2.2 7 | imutils==0.4.3 8 | keras==2.0.8 9 | gunicorn==19.7.1 10 | scikit_image==0.12.3 11 | h5py==2.6.0 12 | PyMySQL==0.7.11 13 | Pillow==3.2.0 14 | fishbase==1.0.12 15 | celery==4.1.1 16 | redis==2.10.6 17 | requests_toolbelt==0.8.0 18 | configparser==3.5.0 19 | opencv-python==3.4.1.15 20 | shapely==1.6.4.post1 21 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/back_rotate/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/back_all/back_rotate/.DS_Store -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/back_rotate/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/cut_back_v3.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | 4 | import numpy as np 5 | import os 6 | import cv2 7 | import shutil 8 | #from matplotlib import pyplot as plt 9 | 10 | 11 | def check_dir(path): 12 | if not (os.path.exists(path) and os.path.isdir(path)): 13 | os.mkdir(path) 14 | 15 | 16 | def rotate(image, angle, center=None, scale=1.0): 17 | # 获取图像尺寸 18 | (h, w) = image.shape[:2] 19 | 20 | # 若未指定旋转中心,则将图像中心设为旋转中心 21 | if center is None: 22 | center = (w / 2, h / 2) 23 | 24 | # 执行旋转 25 | M = cv2.getRotationMatrix2D(center, angle, scale) 26 | rotated = cv2.warpAffine(image, M, (w, h)) 27 | 28 | # 返回旋转后的图像 29 | return rotated 30 | 31 | 32 | def calc_rate(line): 33 | x1, y1, x2, y2, x3, y3, x4, y4 = line.split(',') 34 | width = abs(float(x2) - float(x1)) 35 | height = abs(float(y1) - float(y4)) 36 | return height, [int(d) for d in (x1, y1, x3, y3)] 37 | # return width*height, height, height/width 38 | 39 | # def do_cut_back_v2(src_path, f): 40 | def do_cut_back(img, line_box): 41 | line_height_list = [] 42 | line_pts_list = [] 43 | 44 | return_result = [] 45 | 46 | # with open(os.path.join(src_path, f), 'r') as reader: 47 | # lines = reader.readlines() 48 | 49 | lines = [','.join([str(e) for e in d]) for d in line_box] 50 | if len(lines) != 3: 51 | # print(f) 52 | return None 53 | 54 | for line in lines: 55 | h, pts = calc_rate(line) 56 | line_height_list.append(h) 57 | line_pts_list.append(pts) 58 | height_max_index = np.argmax(line_height_list) 59 | x1, y1, x3, y3 = line_pts_list[height_max_index] 60 | all_y1_list = [d[1] for d in line_pts_list] 61 | 62 | little_y1_index_list = [i for i, d in enumerate(all_y1_list) if d > y1] 63 | # img = cv2.imread(os.path.join(src_path, f.replace('.txt', '.png')), 1) 64 | 65 | if len(little_y1_index_list) == 2: 66 | 67 | current_y1_list = [all_y1_list[d] for d in little_y1_index_list] 68 | current_y1_list_index = np.argsort(current_y1_list) 69 | 70 | for save_index, i in enumerate([little_y1_index_list[d] for d in current_y1_list_index]): 71 | current_x1, current_y1, current_x3, current_y3 = line_pts_list[i] 72 | return_result.append(img[current_y1 - 2: current_y3, (current_x1 - 2): (current_x3 + 8)]) 73 | elif len(little_y1_index_list) == 0: 74 | useful_line_index = [d for d in range(3) if d not in [height_max_index]] 75 | 76 | current_y1_list = [all_y1_list[d] for d in useful_line_index] 77 | current_y1_list_index = np.argsort(current_y1_list).tolist()[::-1] 78 | 79 | for save_index, i in enumerate([useful_line_index[d] for d in current_y1_list_index]): 80 | current_x1, current_y1, current_x3, current_y3 = line_pts_list[i] 81 | line_img = img[current_y1: current_y3, (current_x1 - 2): (current_x3 + 8)] 82 | line_img = rotate(line_img, 180) 83 | return_result.append(line_img) 84 | return return_result 85 | 86 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/data_util.py: -------------------------------------------------------------------------------- 1 | ''' 2 | this file is modified from keras implemention of data process multi-threading, 3 | see https://github.com/fchollet/keras/blob/master/keras/utils/data_utils.py 4 | ''' 5 | import time 6 | import numpy as np 7 | import threading 8 | import multiprocessing 9 | try: 10 | import queue 11 | except ImportError: 12 | import Queue as queue 13 | 14 | 15 | class GeneratorEnqueuer(): 16 | """Builds a queue out of a data generator. 17 | 18 | Used in `fit_generator`, `evaluate_generator`, `predict_generator`. 19 | 20 | # Arguments 21 | generator: a generator function which endlessly yields data 22 | use_multiprocessing: use multiprocessing if True, otherwise threading 23 | wait_time: time to sleep in-between calls to `put()` 24 | random_seed: Initial seed for workers, 25 | will be incremented by one for each workers. 26 | """ 27 | 28 | def __init__(self, generator, 29 | use_multiprocessing=False, 30 | wait_time=0.05, 31 | random_seed=None): 32 | self.wait_time = wait_time 33 | self._generator = generator 34 | self._use_multiprocessing = use_multiprocessing 35 | self._threads = [] 36 | self._stop_event = None 37 | self.queue = None 38 | self.random_seed = random_seed 39 | 40 | def start(self, workers=1, max_queue_size=10): 41 | """Kicks off threads which add data from the generator into the queue. 42 | 43 | # Arguments 44 | workers: number of worker threads 45 | max_queue_size: queue size 46 | (when full, threads could block on `put()`) 47 | """ 48 | 49 | def data_generator_task(): 50 | while not self._stop_event.is_set(): 51 | try: 52 | if self._use_multiprocessing or self.queue.qsize() < max_queue_size: 53 | generator_output = next(self._generator) 54 | self.queue.put(generator_output) 55 | else: 56 | time.sleep(self.wait_time) 57 | except Exception: 58 | self._stop_event.set() 59 | raise 60 | 61 | try: 62 | if self._use_multiprocessing: 63 | self.queue = multiprocessing.Queue(maxsize=max_queue_size) 64 | self._stop_event = multiprocessing.Event() 65 | else: 66 | self.queue = queue.Queue() 67 | self._stop_event = threading.Event() 68 | 69 | for _ in range(workers): 70 | if self._use_multiprocessing: 71 | # Reset random seed else all children processes 72 | # share the same seed 73 | np.random.seed(self.random_seed) 74 | thread = multiprocessing.Process(target=data_generator_task) 75 | thread.daemon = True 76 | if self.random_seed is not None: 77 | self.random_seed += 1 78 | else: 79 | thread = threading.Thread(target=data_generator_task) 80 | self._threads.append(thread) 81 | thread.start() 82 | except: 83 | self.stop() 84 | raise 85 | 86 | def is_running(self): 87 | return self._stop_event is not None and not self._stop_event.is_set() 88 | 89 | def stop(self, timeout=None): 90 | """Stops running threads and wait for them to exit, if necessary. 91 | 92 | Should be called by the same thread which called `start()`. 93 | 94 | # Arguments 95 | timeout: maximum time to wait on `thread.join()`. 96 | """ 97 | if self.is_running(): 98 | self._stop_event.set() 99 | 100 | for thread in self._threads: 101 | if thread.is_alive(): 102 | if self._use_multiprocessing: 103 | thread.terminate() 104 | else: 105 | thread.join(timeout) 106 | 107 | if self._use_multiprocessing: 108 | if self.queue is not None: 109 | self.queue.close() 110 | 111 | self._threads = [] 112 | self._stop_event = None 113 | self.queue = None 114 | 115 | def get(self): 116 | """Creates a generator to extract data from the queue. 117 | 118 | Skip the data if it is `None`. 119 | 120 | # Returns 121 | A generator 122 | """ 123 | while self.is_running(): 124 | if not self.queue.empty(): 125 | inputs = self.queue.get() 126 | if inputs is not None: 127 | yield inputs 128 | else: 129 | time.sleep(self.wait_time) -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/.gitignore: -------------------------------------------------------------------------------- 1 | adaptor.so 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (C) 2014 Google Inc. 4 | # 5 | # This file is part of YouCompleteMe. 6 | # 7 | # YouCompleteMe is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # YouCompleteMe is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with YouCompleteMe. If not, see . 19 | 20 | import os 21 | import sys 22 | import glob 23 | import ycm_core 24 | 25 | # These are the compilation flags that will be used in case there's no 26 | # compilation database set (by default, one is not set). 27 | # CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. 28 | sys.path.append(os.path.dirname(__file__)) 29 | 30 | 31 | BASE_DIR = os.path.dirname(os.path.realpath(__file__)) 32 | 33 | from plumbum.cmd import python_config 34 | 35 | 36 | flags = [ 37 | '-Wall', 38 | '-Wextra', 39 | '-Wnon-virtual-dtor', 40 | '-Winvalid-pch', 41 | '-Wno-unused-local-typedefs', 42 | '-std=c++11', 43 | '-x', 'c++', 44 | '-Iinclude', 45 | ] + python_config('--cflags').split() 46 | 47 | 48 | # Set this to the absolute path to the folder (NOT the file!) containing the 49 | # compile_commands.json file to use that instead of 'flags'. See here for 50 | # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 51 | # 52 | # Most projects will NOT need to set this to anything; you can just change the 53 | # 'flags' list of compilation flags. 54 | compilation_database_folder = '' 55 | 56 | if os.path.exists( compilation_database_folder ): 57 | database = ycm_core.CompilationDatabase( compilation_database_folder ) 58 | else: 59 | database = None 60 | 61 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] 62 | 63 | def DirectoryOfThisScript(): 64 | return os.path.dirname( os.path.abspath( __file__ ) ) 65 | 66 | 67 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 68 | if not working_directory: 69 | return list( flags ) 70 | new_flags = [] 71 | make_next_absolute = False 72 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 73 | for flag in flags: 74 | new_flag = flag 75 | 76 | if make_next_absolute: 77 | make_next_absolute = False 78 | if not flag.startswith( '/' ): 79 | new_flag = os.path.join( working_directory, flag ) 80 | 81 | for path_flag in path_flags: 82 | if flag == path_flag: 83 | make_next_absolute = True 84 | break 85 | 86 | if flag.startswith( path_flag ): 87 | path = flag[ len( path_flag ): ] 88 | new_flag = path_flag + os.path.join( working_directory, path ) 89 | break 90 | 91 | if new_flag: 92 | new_flags.append( new_flag ) 93 | return new_flags 94 | 95 | 96 | def IsHeaderFile( filename ): 97 | extension = os.path.splitext( filename )[ 1 ] 98 | return extension in [ '.h', '.hxx', '.hpp', '.hh' ] 99 | 100 | 101 | def GetCompilationInfoForFile( filename ): 102 | # The compilation_commands.json file generated by CMake does not have entries 103 | # for header files. So we do our best by asking the db for flags for a 104 | # corresponding source file, if any. If one exists, the flags for that file 105 | # should be good enough. 106 | if IsHeaderFile( filename ): 107 | basename = os.path.splitext( filename )[ 0 ] 108 | for extension in SOURCE_EXTENSIONS: 109 | replacement_file = basename + extension 110 | if os.path.exists( replacement_file ): 111 | compilation_info = database.GetCompilationInfoForFile( 112 | replacement_file ) 113 | if compilation_info.compiler_flags_: 114 | return compilation_info 115 | return None 116 | return database.GetCompilationInfoForFile( filename ) 117 | 118 | 119 | # This is the entry point; this function is called by ycmd to produce flags for 120 | # a file. 121 | def FlagsForFile( filename, **kwargs ): 122 | if database: 123 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 124 | # python list, but a "list-like" StringVec object 125 | compilation_info = GetCompilationInfoForFile( filename ) 126 | if not compilation_info: 127 | return None 128 | 129 | final_flags = MakeRelativePathsInFlagsAbsolute( 130 | compilation_info.compiler_flags_, 131 | compilation_info.compiler_working_dir_ ) 132 | else: 133 | relative_to = DirectoryOfThisScript() 134 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 135 | 136 | return { 137 | 'flags': final_flags, 138 | 'do_cache': True 139 | } 140 | 141 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = -I include -std=c++11 -O3 $(shell python3-config --cflags) 2 | LDFLAGS = $(shell python3-config --ldflags) 3 | 4 | DEPS = lanms.h $(shell find include -xtype f) 5 | CXX_SOURCES = adaptor.cpp include/clipper/clipper.cpp 6 | 7 | LIB_SO = adaptor.so 8 | 9 | $(LIB_SO): $(CXX_SOURCES) $(DEPS) 10 | $(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $(CXX_SOURCES) --shared -fPIC 11 | 12 | clean: 13 | rm -rf $(LIB_SO) 14 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/__init__.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import numpy as np 4 | 5 | BASE_DIR = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | if subprocess.call(['make', '-C', BASE_DIR]) != 0: # return value 8 | raise RuntimeError('Cannot compile lanms: {}'.format(BASE_DIR)) 9 | 10 | 11 | def merge_quadrangle_n9(polys, thres=0.3, precision=10000): 12 | from .adaptor import merge_quadrangle_n9 as nms_impl 13 | if len(polys) == 0: 14 | return np.array([], dtype='float32') 15 | p = polys.copy() 16 | p[:,:8] *= precision 17 | ret = np.array(nms_impl(p, thres), dtype='float32') 18 | ret[:,:8] /= precision 19 | return ret 20 | 21 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/__main__.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | from . import merge_quadrangle_n9 5 | 6 | if __name__ == '__main__': 7 | # unit square with confidence 1 8 | q = np.array([0, 0, 0, 1, 1, 1, 1, 0, 1], dtype='float32') 9 | 10 | print(merge_quadrangle_n9(np.array([q, q + 0.1, q + 2]))) 11 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/adaptor.cpp: -------------------------------------------------------------------------------- 1 | #include "pybind11/pybind11.h" 2 | #include "pybind11/numpy.h" 3 | #include "pybind11/stl.h" 4 | #include "pybind11/stl_bind.h" 5 | 6 | #include "lanms.h" 7 | 8 | namespace py = pybind11; 9 | 10 | 11 | namespace lanms_adaptor { 12 | 13 | std::vector> polys2floats(const std::vector &polys) { 14 | std::vector> ret; 15 | for (size_t i = 0; i < polys.size(); i ++) { 16 | auto &p = polys[i]; 17 | auto &poly = p.poly; 18 | ret.emplace_back(std::vector{ 19 | float(poly[0].X), float(poly[0].Y), 20 | float(poly[1].X), float(poly[1].Y), 21 | float(poly[2].X), float(poly[2].Y), 22 | float(poly[3].X), float(poly[3].Y), 23 | float(p.score), 24 | }); 25 | } 26 | 27 | return ret; 28 | } 29 | 30 | 31 | /** 32 | * 33 | * \param quad_n9 an n-by-9 numpy array, where first 8 numbers denote the 34 | * quadrangle, and the last one is the score 35 | * \param iou_threshold two quadrangles with iou score above this threshold 36 | * will be merged 37 | * 38 | * \return an n-by-9 numpy array, the merged quadrangles 39 | */ 40 | std::vector> merge_quadrangle_n9( 41 | py::array_t quad_n9, 42 | float iou_threshold) { 43 | auto pbuf = quad_n9.request(); 44 | if (pbuf.ndim != 2 || pbuf.shape[1] != 9) 45 | throw std::runtime_error("quadrangles must have a shape of (n, 9)"); 46 | auto n = pbuf.shape[0]; 47 | auto ptr = static_cast(pbuf.ptr); 48 | return polys2floats(lanms::merge_quadrangle_n9(ptr, n, iou_threshold)); 49 | } 50 | 51 | } 52 | 53 | PYBIND11_PLUGIN(adaptor) { 54 | py::module m("adaptor", "NMS"); 55 | 56 | m.def("merge_quadrangle_n9", &lanms_adaptor::merge_quadrangle_n9, 57 | "merge quadrangels"); 58 | 59 | return m.ptr(); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/include/clipper/clipper.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/include/clipper/clipper.cpp -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/include/pybind11/buffer_info.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/buffer_info.h: Python buffer object interface 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "common.h" 13 | 14 | NAMESPACE_BEGIN(pybind11) 15 | 16 | /// Information record describing a Python buffer object 17 | struct buffer_info { 18 | void *ptr = nullptr; // Pointer to the underlying storage 19 | ssize_t itemsize = 0; // Size of individual items in bytes 20 | ssize_t size = 0; // Total number of entries 21 | std::string format; // For homogeneous buffers, this should be set to format_descriptor::format() 22 | ssize_t ndim = 0; // Number of dimensions 23 | std::vector shape; // Shape of the tensor (1 entry per dimension) 24 | std::vector strides; // Number of entries between adjacent entries (for each per dimension) 25 | 26 | buffer_info() { } 27 | 28 | buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, 29 | detail::any_container shape_in, detail::any_container strides_in) 30 | : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim), 31 | shape(std::move(shape_in)), strides(std::move(strides_in)) { 32 | if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) 33 | pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length"); 34 | for (size_t i = 0; i < (size_t) ndim; ++i) 35 | size *= shape[i]; 36 | } 37 | 38 | template 39 | buffer_info(T *ptr, detail::any_container shape_in, detail::any_container strides_in) 40 | : buffer_info(private_ctr_tag(), ptr, sizeof(T), format_descriptor::format(), static_cast(shape_in->size()), std::move(shape_in), std::move(strides_in)) { } 41 | 42 | buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t size) 43 | : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}) { } 44 | 45 | template 46 | buffer_info(T *ptr, ssize_t size) 47 | : buffer_info(ptr, sizeof(T), format_descriptor::format(), size) { } 48 | 49 | explicit buffer_info(Py_buffer *view, bool ownview = true) 50 | : buffer_info(view->buf, view->itemsize, view->format, view->ndim, 51 | {view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}) { 52 | this->view = view; 53 | this->ownview = ownview; 54 | } 55 | 56 | buffer_info(const buffer_info &) = delete; 57 | buffer_info& operator=(const buffer_info &) = delete; 58 | 59 | buffer_info(buffer_info &&other) { 60 | (*this) = std::move(other); 61 | } 62 | 63 | buffer_info& operator=(buffer_info &&rhs) { 64 | ptr = rhs.ptr; 65 | itemsize = rhs.itemsize; 66 | size = rhs.size; 67 | format = std::move(rhs.format); 68 | ndim = rhs.ndim; 69 | shape = std::move(rhs.shape); 70 | strides = std::move(rhs.strides); 71 | std::swap(view, rhs.view); 72 | std::swap(ownview, rhs.ownview); 73 | return *this; 74 | } 75 | 76 | ~buffer_info() { 77 | if (view && ownview) { PyBuffer_Release(view); delete view; } 78 | } 79 | 80 | private: 81 | struct private_ctr_tag { }; 82 | 83 | buffer_info(private_ctr_tag, void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, 84 | detail::any_container &&shape_in, detail::any_container &&strides_in) 85 | : buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in)) { } 86 | 87 | Py_buffer *view = nullptr; 88 | bool ownview = false; 89 | }; 90 | 91 | NAMESPACE_BEGIN(detail) 92 | 93 | template struct compare_buffer_info { 94 | static bool compare(const buffer_info& b) { 95 | return b.format == format_descriptor::format() && b.itemsize == (ssize_t) sizeof(T); 96 | } 97 | }; 98 | 99 | template struct compare_buffer_info::value>> { 100 | static bool compare(const buffer_info& b) { 101 | return (size_t) b.itemsize == sizeof(T) && (b.format == format_descriptor::value || 102 | ((sizeof(T) == sizeof(long)) && b.format == (std::is_unsigned::value ? "L" : "l")) || 103 | ((sizeof(T) == sizeof(size_t)) && b.format == (std::is_unsigned::value ? "N" : "n"))); 104 | } 105 | }; 106 | 107 | NAMESPACE_END(detail) 108 | NAMESPACE_END(pybind11) 109 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/include/pybind11/complex.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/complex.h: Complex number support 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "pybind11.h" 13 | #include 14 | 15 | /// glibc defines I as a macro which breaks things, e.g., boost template names 16 | #ifdef I 17 | # undef I 18 | #endif 19 | 20 | NAMESPACE_BEGIN(pybind11) 21 | 22 | template struct format_descriptor, detail::enable_if_t::value>> { 23 | static constexpr const char c = format_descriptor::c; 24 | static constexpr const char value[3] = { 'Z', c, '\0' }; 25 | static std::string format() { return std::string(value); } 26 | }; 27 | 28 | template constexpr const char format_descriptor< 29 | std::complex, detail::enable_if_t::value>>::value[3]; 30 | 31 | NAMESPACE_BEGIN(detail) 32 | 33 | template struct is_fmt_numeric, detail::enable_if_t::value>> { 34 | static constexpr bool value = true; 35 | static constexpr int index = is_fmt_numeric::index + 3; 36 | }; 37 | 38 | template class type_caster> { 39 | public: 40 | bool load(handle src, bool convert) { 41 | if (!src) 42 | return false; 43 | if (!convert && !PyComplex_Check(src.ptr())) 44 | return false; 45 | Py_complex result = PyComplex_AsCComplex(src.ptr()); 46 | if (result.real == -1.0 && PyErr_Occurred()) { 47 | PyErr_Clear(); 48 | return false; 49 | } 50 | value = std::complex((T) result.real, (T) result.imag); 51 | return true; 52 | } 53 | 54 | static handle cast(const std::complex &src, return_value_policy /* policy */, handle /* parent */) { 55 | return PyComplex_FromDoubles((double) src.real(), (double) src.imag()); 56 | } 57 | 58 | PYBIND11_TYPE_CASTER(std::complex, _("complex")); 59 | }; 60 | NAMESPACE_END(detail) 61 | NAMESPACE_END(pybind11) 62 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/include/pybind11/eval.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/exec.h: Support for evaluating Python expressions and statements 3 | from strings and files 4 | 5 | Copyright (c) 2016 Klemens Morgenstern and 6 | Wenzel Jakob 7 | 8 | All rights reserved. Use of this source code is governed by a 9 | BSD-style license that can be found in the LICENSE file. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include "pybind11.h" 15 | 16 | NAMESPACE_BEGIN(pybind11) 17 | 18 | enum eval_mode { 19 | /// Evaluate a string containing an isolated expression 20 | eval_expr, 21 | 22 | /// Evaluate a string containing a single statement. Returns \c none 23 | eval_single_statement, 24 | 25 | /// Evaluate a string containing a sequence of statement. Returns \c none 26 | eval_statements 27 | }; 28 | 29 | template 30 | object eval(str expr, object global = globals(), object local = object()) { 31 | if (!local) 32 | local = global; 33 | 34 | /* PyRun_String does not accept a PyObject / encoding specifier, 35 | this seems to be the only alternative */ 36 | std::string buffer = "# -*- coding: utf-8 -*-\n" + (std::string) expr; 37 | 38 | int start; 39 | switch (mode) { 40 | case eval_expr: start = Py_eval_input; break; 41 | case eval_single_statement: start = Py_single_input; break; 42 | case eval_statements: start = Py_file_input; break; 43 | default: pybind11_fail("invalid evaluation mode"); 44 | } 45 | 46 | PyObject *result = PyRun_String(buffer.c_str(), start, global.ptr(), local.ptr()); 47 | if (!result) 48 | throw error_already_set(); 49 | return reinterpret_steal(result); 50 | } 51 | 52 | template 53 | object eval(const char (&s)[N], object global = globals(), object local = object()) { 54 | /* Support raw string literals by removing common leading whitespace */ 55 | auto expr = (s[0] == '\n') ? str(module::import("textwrap").attr("dedent")(s)) 56 | : str(s); 57 | return eval(expr, global, local); 58 | } 59 | 60 | inline void exec(str expr, object global = globals(), object local = object()) { 61 | eval(expr, global, local); 62 | } 63 | 64 | template 65 | void exec(const char (&s)[N], object global = globals(), object local = object()) { 66 | eval(s, global, local); 67 | } 68 | 69 | template 70 | object eval_file(str fname, object global = globals(), object local = object()) { 71 | if (!local) 72 | local = global; 73 | 74 | int start; 75 | switch (mode) { 76 | case eval_expr: start = Py_eval_input; break; 77 | case eval_single_statement: start = Py_single_input; break; 78 | case eval_statements: start = Py_file_input; break; 79 | default: pybind11_fail("invalid evaluation mode"); 80 | } 81 | 82 | int closeFile = 1; 83 | std::string fname_str = (std::string) fname; 84 | #if PY_VERSION_HEX >= 0x03040000 85 | FILE *f = _Py_fopen_obj(fname.ptr(), "r"); 86 | #elif PY_VERSION_HEX >= 0x03000000 87 | FILE *f = _Py_fopen(fname.ptr(), "r"); 88 | #else 89 | /* No unicode support in open() :( */ 90 | auto fobj = reinterpret_steal(PyFile_FromString( 91 | const_cast(fname_str.c_str()), 92 | const_cast("r"))); 93 | FILE *f = nullptr; 94 | if (fobj) 95 | f = PyFile_AsFile(fobj.ptr()); 96 | closeFile = 0; 97 | #endif 98 | if (!f) { 99 | PyErr_Clear(); 100 | pybind11_fail("File \"" + fname_str + "\" could not be opened!"); 101 | } 102 | 103 | #if PY_VERSION_HEX < 0x03000000 && defined(PYPY_VERSION) 104 | PyObject *result = PyRun_File(f, fname_str.c_str(), start, global.ptr(), 105 | local.ptr()); 106 | (void) closeFile; 107 | #else 108 | PyObject *result = PyRun_FileEx(f, fname_str.c_str(), start, global.ptr(), 109 | local.ptr(), closeFile); 110 | #endif 111 | 112 | if (!result) 113 | throw error_already_set(); 114 | return reinterpret_steal(result); 115 | } 116 | 117 | NAMESPACE_END(pybind11) 118 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/include/pybind11/functional.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/functional.h: std::function<> support 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "pybind11.h" 13 | #include 14 | 15 | NAMESPACE_BEGIN(pybind11) 16 | NAMESPACE_BEGIN(detail) 17 | 18 | template 19 | struct type_caster> { 20 | using type = std::function; 21 | using retval_type = conditional_t::value, void_type, Return>; 22 | using function_type = Return (*) (Args...); 23 | 24 | public: 25 | bool load(handle src, bool convert) { 26 | if (src.is_none()) { 27 | // Defer accepting None to other overloads (if we aren't in convert mode): 28 | if (!convert) return false; 29 | return true; 30 | } 31 | 32 | if (!isinstance(src)) 33 | return false; 34 | 35 | auto func = reinterpret_borrow(src); 36 | 37 | /* 38 | When passing a C++ function as an argument to another C++ 39 | function via Python, every function call would normally involve 40 | a full C++ -> Python -> C++ roundtrip, which can be prohibitive. 41 | Here, we try to at least detect the case where the function is 42 | stateless (i.e. function pointer or lambda function without 43 | captured variables), in which case the roundtrip can be avoided. 44 | */ 45 | if (auto cfunc = func.cpp_function()) { 46 | auto c = reinterpret_borrow(PyCFunction_GET_SELF(cfunc.ptr())); 47 | auto rec = (function_record *) c; 48 | 49 | if (rec && rec->is_stateless && 50 | same_type(typeid(function_type), *reinterpret_cast(rec->data[1]))) { 51 | struct capture { function_type f; }; 52 | value = ((capture *) &rec->data)->f; 53 | return true; 54 | } 55 | } 56 | 57 | value = [func](Args... args) -> Return { 58 | gil_scoped_acquire acq; 59 | object retval(func(std::forward(args)...)); 60 | /* Visual studio 2015 parser issue: need parentheses around this expression */ 61 | return (retval.template cast()); 62 | }; 63 | return true; 64 | } 65 | 66 | template 67 | static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) { 68 | if (!f_) 69 | return none().inc_ref(); 70 | 71 | auto result = f_.template target(); 72 | if (result) 73 | return cpp_function(*result, policy).release(); 74 | else 75 | return cpp_function(std::forward(f_), policy).release(); 76 | } 77 | 78 | PYBIND11_TYPE_CASTER(type, _("Callable[[") + 79 | argument_loader::arg_names() + _("], ") + 80 | make_caster::name() + 81 | _("]")); 82 | }; 83 | 84 | NAMESPACE_END(detail) 85 | NAMESPACE_END(pybind11) 86 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/include/pybind11/options.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/options.h: global settings that are configurable at runtime. 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "common.h" 13 | 14 | NAMESPACE_BEGIN(pybind11) 15 | 16 | class options { 17 | public: 18 | 19 | // Default RAII constructor, which leaves settings as they currently are. 20 | options() : previous_state(global_state()) {} 21 | 22 | // Class is non-copyable. 23 | options(const options&) = delete; 24 | options& operator=(const options&) = delete; 25 | 26 | // Destructor, which restores settings that were in effect before. 27 | ~options() { 28 | global_state() = previous_state; 29 | } 30 | 31 | // Setter methods (affect the global state): 32 | 33 | options& disable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = false; return *this; } 34 | 35 | options& enable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = true; return *this; } 36 | 37 | options& disable_function_signatures() & { global_state().show_function_signatures = false; return *this; } 38 | 39 | options& enable_function_signatures() & { global_state().show_function_signatures = true; return *this; } 40 | 41 | // Getter methods (return the global state): 42 | 43 | static bool show_user_defined_docstrings() { return global_state().show_user_defined_docstrings; } 44 | 45 | static bool show_function_signatures() { return global_state().show_function_signatures; } 46 | 47 | // This type is not meant to be allocated on the heap. 48 | void* operator new(size_t) = delete; 49 | 50 | private: 51 | 52 | struct state { 53 | bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings. 54 | bool show_function_signatures = true; //< Include auto-generated function signatures in docstrings. 55 | }; 56 | 57 | static state &global_state() { 58 | static state instance; 59 | return instance; 60 | } 61 | 62 | state previous_state; 63 | }; 64 | 65 | NAMESPACE_END(pybind11) 66 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/lanms/include/pybind11/typeid.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/typeid.h: Compiler-independent access to type identifiers 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #if defined(__GNUG__) 16 | #include 17 | #endif 18 | 19 | NAMESPACE_BEGIN(pybind11) 20 | NAMESPACE_BEGIN(detail) 21 | /// Erase all occurrences of a substring 22 | inline void erase_all(std::string &string, const std::string &search) { 23 | for (size_t pos = 0;;) { 24 | pos = string.find(search, pos); 25 | if (pos == std::string::npos) break; 26 | string.erase(pos, search.length()); 27 | } 28 | } 29 | 30 | PYBIND11_NOINLINE inline void clean_type_id(std::string &name) { 31 | #if defined(__GNUG__) 32 | int status = 0; 33 | std::unique_ptr res { 34 | abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status), std::free }; 35 | if (status == 0) 36 | name = res.get(); 37 | #else 38 | detail::erase_all(name, "class "); 39 | detail::erase_all(name, "struct "); 40 | detail::erase_all(name, "enum "); 41 | #endif 42 | detail::erase_all(name, "pybind11::"); 43 | } 44 | NAMESPACE_END(detail) 45 | 46 | /// Return a string representation of a C++ type 47 | template static std::string type_id() { 48 | std::string name(typeid(T).name()); 49 | detail::clean_type_id(name); 50 | return name; 51 | } 52 | 53 | NAMESPACE_END(pybind11) 54 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/locality_aware_nms.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from shapely.geometry import Polygon 3 | 4 | 5 | def intersection(g, p): 6 | g = Polygon(g[:8].reshape((4, 2))) 7 | p = Polygon(p[:8].reshape((4, 2))) 8 | if not g.is_valid or not p.is_valid: 9 | return 0 10 | inter = Polygon(g).intersection(Polygon(p)).area 11 | union = g.area + p.area - inter 12 | if union == 0: 13 | return 0 14 | else: 15 | return inter/union 16 | 17 | 18 | def weighted_merge(g, p): 19 | g[:8] = (g[8] * g[:8] + p[8] * p[:8])/(g[8] + p[8]) 20 | g[8] = (g[8] + p[8]) 21 | return g 22 | 23 | 24 | def standard_nms(S, thres): 25 | order = np.argsort(S[:, 8])[::-1] 26 | keep = [] 27 | while order.size > 0: 28 | i = order[0] 29 | keep.append(i) 30 | ovr = np.array([intersection(S[i], S[t]) for t in order[1:]]) 31 | 32 | inds = np.where(ovr <= thres)[0] 33 | order = order[inds+1] 34 | 35 | return S[keep] 36 | 37 | 38 | def nms_locality(polys, thres=0.3): 39 | ''' 40 | locality aware nms of EAST 41 | :param polys: a N*9 numpy array. first 8 coordinates, then prob 42 | :return: boxes after nms 43 | ''' 44 | S = [] 45 | p = None 46 | for g in polys: 47 | if p is not None and intersection(g, p) > thres: 48 | p = weighted_merge(g, p) 49 | else: 50 | if p is not None: 51 | S.append(p) 52 | p = g 53 | if p is not None: 54 | S.append(p) 55 | 56 | if len(S) == 0: 57 | return np.array([]) 58 | return standard_nms(np.array(S), thres) 59 | 60 | 61 | if __name__ == '__main__': 62 | # 343,350,448,135,474,143,369,359 63 | print(Polygon(np.array([[343, 350], [448, 135], 64 | [474, 143], [369, 359]])).area) 65 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/model.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | 4 | import tensorflow as tf 5 | import numpy as np 6 | 7 | from tensorflow.contrib import slim 8 | 9 | tf.app.flags.DEFINE_integer('text_scale', 512, '') 10 | 11 | from east_part.nets import resnet_v1 12 | 13 | FLAGS = tf.app.flags.FLAGS 14 | 15 | 16 | def unpool(inputs): 17 | return tf.image.resize_bilinear(inputs, size=[tf.shape(inputs)[1]*2, tf.shape(inputs)[2]*2]) 18 | 19 | 20 | def mean_image_subtraction(images, means=[123.68, 116.78, 103.94]): 21 | ''' 22 | image normalization 23 | :param images: 24 | :param means: 25 | :return: 26 | ''' 27 | num_channels = images.get_shape().as_list()[-1] 28 | if len(means) != num_channels: 29 | raise ValueError('len(means) must match the number of channels') 30 | channels = tf.split(axis=3, num_or_size_splits=num_channels, value=images) 31 | for i in range(num_channels): 32 | channels[i] -= means[i] 33 | return tf.concat(axis=3, values=channels) 34 | 35 | 36 | def model(images, weight_decay=1e-5, is_training=True): 37 | ''' 38 | define the model, we use slim's implemention of resnet 39 | ''' 40 | images = mean_image_subtraction(images) 41 | 42 | with slim.arg_scope(resnet_v1.resnet_arg_scope(weight_decay=weight_decay)): 43 | logits, end_points = resnet_v1.resnet_v1_50(images, is_training=is_training, scope='resnet_v1_50') 44 | 45 | with tf.variable_scope('feature_fusion', values=[end_points.values]): 46 | batch_norm_params = { 47 | 'decay': 0.997, 48 | 'epsilon': 1e-5, 49 | 'scale': True, 50 | 'is_training': is_training 51 | } 52 | with slim.arg_scope([slim.conv2d], 53 | activation_fn=tf.nn.relu, 54 | normalizer_fn=slim.batch_norm, 55 | normalizer_params=batch_norm_params, 56 | weights_regularizer=slim.l2_regularizer(weight_decay)): 57 | f = [end_points['pool5'], end_points['pool4'], 58 | end_points['pool3'], end_points['pool2']] 59 | for i in range(4): 60 | print('Shape of f_{} {}'.format(i, f[i].shape)) 61 | g = [None, None, None, None] 62 | h = [None, None, None, None] 63 | num_outputs = [None, 128, 64, 32] 64 | for i in range(4): 65 | if i == 0: 66 | h[i] = f[i] 67 | else: 68 | c1_1 = slim.conv2d(tf.concat([g[i-1], f[i]], axis=-1), num_outputs[i], 1) 69 | h[i] = slim.conv2d(c1_1, num_outputs[i], 3) 70 | if i <= 2: 71 | g[i] = unpool(h[i]) 72 | else: 73 | g[i] = slim.conv2d(h[i], num_outputs[i], 3) 74 | print('Shape of h_{} {}, g_{} {}'.format(i, h[i].shape, i, g[i].shape)) 75 | 76 | # here we use a slightly different way for regression part, 77 | # we first use a sigmoid to limit the regression range, and also 78 | # this is do with the angle map 79 | F_score = slim.conv2d(g[3], 1, 1, activation_fn=tf.nn.sigmoid, normalizer_fn=None) 80 | # 4 channel of axis aligned bbox and 1 channel rotation angle 81 | geo_map = slim.conv2d(g[3], 4, 1, activation_fn=tf.nn.sigmoid, normalizer_fn=None) * FLAGS.text_scale 82 | angle_map = (slim.conv2d(g[3], 1, 1, activation_fn=tf.nn.sigmoid, normalizer_fn=None) - 0.5) * np.pi/2 # angle is between [-45, 45] 83 | F_geometry = tf.concat([geo_map, angle_map], axis=-1) 84 | 85 | return F_score, F_geometry 86 | 87 | 88 | def dice_coefficient(y_true_cls, y_pred_cls, 89 | training_mask): 90 | ''' 91 | dice loss 92 | :param y_true_cls: 93 | :param y_pred_cls: 94 | :param training_mask: 95 | :return: 96 | ''' 97 | eps = 1e-5 98 | intersection = tf.reduce_sum(y_true_cls * y_pred_cls * training_mask) 99 | union = tf.reduce_sum(y_true_cls * training_mask) + tf.reduce_sum(y_pred_cls * training_mask) + eps 100 | loss = 1. - (2 * intersection / union) 101 | tf.summary.scalar('classification_dice_loss', loss) 102 | return loss 103 | 104 | 105 | 106 | def loss(y_true_cls, y_pred_cls, 107 | y_true_geo, y_pred_geo, 108 | training_mask): 109 | ''' 110 | define the loss used for training, contraning two part, 111 | the first part we use dice loss instead of weighted logloss, 112 | the second part is the iou loss defined in the paper 113 | :param y_true_cls: ground truth of text 114 | :param y_pred_cls: prediction os text 115 | :param y_true_geo: ground truth of geometry 116 | :param y_pred_geo: prediction of geometry 117 | :param training_mask: mask used in training, to ignore some text annotated by ### 118 | :return: 119 | ''' 120 | classification_loss = dice_coefficient(y_true_cls, y_pred_cls, training_mask) 121 | # scale classification loss to match the iou loss part 122 | classification_loss *= 0.01 123 | 124 | # d1 -> top, d2->right, d3->bottom, d4->left 125 | d1_gt, d2_gt, d3_gt, d4_gt, theta_gt = tf.split(value=y_true_geo, num_or_size_splits=5, axis=3) 126 | d1_pred, d2_pred, d3_pred, d4_pred, theta_pred = tf.split(value=y_pred_geo, num_or_size_splits=5, axis=3) 127 | area_gt = (d1_gt + d3_gt) * (d2_gt + d4_gt) 128 | area_pred = (d1_pred + d3_pred) * (d2_pred + d4_pred) 129 | w_union = tf.minimum(d2_gt, d2_pred) + tf.minimum(d4_gt, d4_pred) 130 | h_union = tf.minimum(d1_gt, d1_pred) + tf.minimum(d3_gt, d3_pred) 131 | area_intersect = w_union * h_union 132 | area_union = area_gt + area_pred - area_intersect 133 | L_AABB = -tf.log((area_intersect + 1.0)/(area_union + 1.0)) 134 | L_theta = 1 - tf.cos(theta_pred - theta_gt) 135 | tf.summary.scalar('geometry_AABB', tf.reduce_mean(L_AABB * y_true_cls * training_mask)) 136 | tf.summary.scalar('geometry_theta', tf.reduce_mean(L_theta * y_true_cls * training_mask)) 137 | L_g = L_AABB + 20 * L_theta 138 | 139 | return tf.reduce_mean(L_g * y_true_cls * training_mask) + classification_loss 140 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/east_part/nets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/back_all/east_part/nets/__init__.py -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | 4 | ### use model loader 5 | from model_loader import id_card_model 6 | 7 | from back_rotate.Back_rotate import back_rotate 8 | # from east_part.east_segment_line import east_main 9 | from other_recognize.other_main import other_propcessing 10 | from valid_recognition.valid_main import valid_processing 11 | 12 | import cv2 13 | 14 | import time 15 | 16 | def check_dir(path): 17 | if not(os.path.exists(path) and os.path.isdir(path)): 18 | os.mkdir(path) 19 | 20 | def cert_back_recog(src_img, models, debug_flag=False): 21 | 22 | debug_path = './{}'.format(time.time()) 23 | if debug_flag: 24 | check_dir(debug_path) 25 | 26 | # 旋转校正身份证背面 27 | rotated_img = back_rotate(src_img) 28 | if rotated_img is None: 29 | print('error in back_rotate') 30 | return None 31 | if debug_flag: 32 | cv2.imwrite(os.path.join(debug_path, 'rotate_back.jpg'), rotated_img) 33 | 34 | # EAST检测结果(带框图, 坐标, 切割后每一行) 35 | ### use model loader to do east predict ### 36 | im, east_outpoints, line_cutted_imgs = models.east_predict(rotated_img) 37 | # im, east_outpoints, line_cutted_imgs = east_main(rotated_img) 38 | 39 | if line_cutted_imgs is None: 40 | # 识别行数超过3行(无法确定多哪行或少哪行,无法继续进行识别) 41 | print('error in east locate') 42 | return None 43 | 44 | if debug_flag: 45 | with open(os.path.join(debug_path, 'east_main_outpoint.txt'), 'w') as out: 46 | for line in east_outpoints: 47 | out.write('{}\n'.format(','.join([str(d) for d in line]))) 48 | cv2.imwrite(os.path.join(debug_path, 'east_output_img.jpg'), im) 49 | debug_line_img_path = os.path.join(debug_path, 'line_img') 50 | check_dir(debug_line_img_path) 51 | # save line 52 | for line_findex, line in enumerate(line_cutted_imgs): 53 | cv2.imwrite(os.path.join(debug_line_img_path, '{}.png'.format(line_findex)), line) 54 | 55 | # 分为两个部分1. 签发机关识别, 2. 有效日期识别 56 | # 签发机关部分 57 | iss_result, iss_characters = other_propcessing([line_cutted_imgs[0]], models) 58 | if debug_flag: 59 | debug_iss_path = os.path.join(debug_path, 'iss') 60 | check_dir(debug_iss_path) 61 | for iss_ch_findex, ch in enumerate(iss_characters[0]): 62 | cv2.imwrite(os.path.join(debug_iss_path, '{}.png'.format(iss_ch_findex)), ch) 63 | 64 | # 有效日期部分 65 | valid_result, valid_characters = valid_processing(line_cutted_imgs[1], models) 66 | if debug_flag: 67 | debug_val_path = os.path.join(debug_path, 'val') 68 | check_dir(debug_val_path) 69 | for val_ch_findex, ch in enumerate(valid_characters): 70 | cv2.imwrite(os.path.join(debug_val_path, '{}.png'.format(val_ch_findex)), ch) 71 | 72 | return [iss_result[0], valid_result] 73 | 74 | if __name__ == '__main__': 75 | 76 | ### first init all model ### 77 | models = id_card_model() 78 | 79 | start_time = time.time() 80 | 81 | src_img = cv2.imread('./test.png', 1) 82 | recog_result = cert_back_recog(src_img, models, debug_flag=True) 83 | with open('test_result.txt', 'w', encoding='utf-8') as out: 84 | out.write('\n'.join(recog_result)) 85 | 86 | print('consume time: {}'.format(time.time() - start_time)) -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/other_recognize/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/other_recognize/combined_model_structure.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "keras_version": "1.1.0", "config": [{"class_name": "Convolution2D", "config": {"subsample": [1, 1], "batch_input_shape": [null, 32, 32, 1], "trainable": true, "dim_ordering": "tf", "b_constraint": null, "activity_regularizer": null, "b_regularizer": null, "border_mode": "valid", "input_dtype": "float32", "nb_filter": 16, "W_constraint": null, "activation": "linear", "bias": true, "nb_col": 3, "name": "convolution2d_1", "init": "glorot_uniform", "W_regularizer": null, "nb_row": 3}}, {"class_name": "Activation", "config": {"name": "activation_1", "activation": "tanh", "trainable": true}}, {"class_name": "MaxPooling2D", "config": {"border_mode": "valid", "pool_size": [2, 2], "strides": [2, 2], "name": "maxpooling2d_1", "dim_ordering": "tf", "trainable": true}}, {"class_name": "Convolution2D", "config": {"nb_row": 3, "subsample": [1, 1], "init": "glorot_uniform", "dim_ordering": "tf", "b_constraint": null, "activity_regularizer": null, "b_regularizer": null, "border_mode": "valid", "nb_filter": 32, "activation": "linear", "bias": true, "nb_col": 3, "name": "convolution2d_2", "trainable": true, "W_regularizer": null, "W_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_2", "activation": "tanh", "trainable": true}}, {"class_name": "MaxPooling2D", "config": {"border_mode": "valid", "pool_size": [2, 2], "strides": [2, 2], "name": "maxpooling2d_2", "dim_ordering": "tf", "trainable": true}}, {"class_name": "Convolution2D", "config": {"nb_row": 3, "subsample": [1, 1], "init": "glorot_uniform", "dim_ordering": "tf", "b_constraint": null, "activity_regularizer": null, "b_regularizer": null, "border_mode": "valid", "nb_filter": 32, "activation": "linear", "bias": true, "nb_col": 3, "name": "convolution2d_3", "trainable": true, "W_regularizer": null, "W_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_3", "activation": "tanh", "trainable": true}}, {"class_name": "Flatten", "config": {"name": "flatten_1", "trainable": true}}, {"class_name": "Dense", "config": {"output_dim": 128, "trainable": true, "b_regularizer": null, "b_constraint": null, "activity_regularizer": null, "W_regularizer": null, "activation": "linear", "bias": true, "name": "dense_1", "init": "glorot_normal", "W_constraint": null, "input_dim": null}}, {"class_name": "Activation", "config": {"name": "activation_4", "activation": "tanh", "trainable": true}}, {"class_name": "Dropout", "config": {"name": "dropout_1", "trainable": true, "p": 0.5}}, {"class_name": "Dense", "config": {"output_dim": 7024, "trainable": true, "b_regularizer": null, "b_constraint": null, "activity_regularizer": null, "W_regularizer": null, "activation": "linear", "bias": true, "name": "dense_2", "init": "glorot_normal", "W_constraint": null, "input_dim": null}}, {"class_name": "Activation", "config": {"name": "activation_5", "activation": "softmax", "trainable": true}}]} -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/other_recognize/combined_model_weight.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/back_all/other_recognize/combined_model_weight.h5 -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/other_recognize/do_recognition_for_sorted_id_card.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # caculate accuracy of model by picture 4 | from __future__ import absolute_import 5 | from __future__ import print_function 6 | import os 7 | import cv2 8 | import numpy as np 9 | import shutil 10 | from keras.models import model_from_json 11 | import csv 12 | 13 | import time 14 | 15 | ## globale ## 16 | # 设置切割图片的像素阈值 17 | cutThreahold = 70 18 | # 设置分辨率的统一值 19 | size = 32 20 | # 设置导入图片的尺寸和通道 21 | img_rows, img_cols = 32, 32 22 | # 单通道(灰度图片)-1 RGB图片-3 23 | img_channels = 1 24 | # 设定每次进入计算的样本batch尺寸 25 | batch_size=50 26 | 27 | 28 | # 2. 图像预处理 29 | # 灰度化 - 图像增强(直方图均衡化)- 二值化 - 切割边缘 - 统一分辨率 30 | 31 | # 2.1 灰度化 32 | def gray_img_modified(imgscr): 33 | # 读入 34 | # imgscr = cv2.imread(filename) 35 | if imgscr is None: 36 | return 'Fault' 37 | # 灰度化 38 | newimg = cv2.cvtColor(imgscr,cv2.COLOR_RGB2GRAY) 39 | # 返回灰度化后的图片 40 | return newimg 41 | 42 | # 2.2 图像增强 - 直方图均衡化 43 | def enhance_img(img): 44 | # 增强 45 | newimg=cv2.equalizeHist(img) 46 | return newimg 47 | 48 | # 2.3 二值化 自定义阈值 49 | def binary_img(img): 50 | 51 | # cv.Threshold(src, dst, threshold, maxValue, thresholdType) 52 | # threshold_type=CV_THRESH_TRUNC: 53 | # 如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y). 54 | # 二值化 55 | ret,newimg=cv2.threshold(img,127,255,cv2.THRESH_TRUNC) # 此种方法效果最好 56 | return newimg 57 | 58 | 59 | # 2.4 判断像素值是否小于阈值,小于则返回像素点对应坐标 60 | def indices(a, func): 61 | return [i for (i, val) in enumerate(a) if func(val)] 62 | 63 | # 2.5 切割边缘 64 | # cutThreahold = 70 默认切割时使用的像素阈值,当像素值<70,认为是字符像素点,而非噪声 65 | def cut_img(img,cutThreahold): 66 | 67 | # img.shape[0] -- 图像高度 img.shape[1] ---- 图像宽度 68 | # 得到每列最小像素值 ---图像横向 69 | width_val = np.min(img,axis=0) 70 | # 得到每行最小像素值 --- 图像纵向 71 | height_val = np.min(img,axis=1) 72 | 73 | # 获得截取部分的左边界 74 | left_point = np.min(indices(width_val,lambda x:x 3: 69 | peak_left_pts.append(temp[-1] - 3) 70 | peak_right_pts.append(temp[0] - 3) 71 | temp = [pt] 72 | # add end two point 73 | peak_left_pts.append(temp[-1] - 3) 74 | peak_right_pts.append(temp[0] - 3) 75 | # remove the point when we first add (three zero) 76 | peak_left_pts.remove(peak_left_pts[-1]) 77 | peak_right_pts.remove(peak_right_pts[0]) 78 | 79 | # filter the noise peak 80 | region_width_list = [] 81 | updated_peak_left = [] 82 | updated_peak_right = [] 83 | 84 | for i in range(len(peak_left_pts)): 85 | current_width = peak_right_pts[i] - peak_left_pts[i] 86 | if current_width > peak_width_threshold: 87 | region_width_list.append(current_width) 88 | updated_peak_left.append(peak_left_pts[i]) 89 | updated_peak_right.append(peak_right_pts[i]) 90 | # if all peak is noise return None 91 | if len(region_width_list) == 0: 92 | return None 93 | # add two pixel because dilation have been extend 10 pixels 94 | left_point = updated_peak_left[0] + 2 95 | right_point = updated_peak_right[-1] -2 96 | # left and right cut down 97 | left_right_cutted_img = src_img[:, left_point:right_point] 98 | 99 | img = cv2.cvtColor(left_right_cutted_img, cv2.COLOR_BGR2GRAY) 100 | 101 | cv2.normalize(img, img, 0, 255, cv2.NORM_MINMAX) 102 | try: 103 | # do hog (because cutted image have possibly get error in HOG detect so use try and except 104 | fd, hog_img = hog(img, orientations=4, pixels_per_cell=(3, 3), visualise=True) 105 | 106 | hog_img = np.asarray(hog_img, dtype='uint8') 107 | 108 | cv2.normalize(hog_img, hog_img, 0, 255, cv2.NORM_MINMAX) 109 | res, binary_img = cv2.threshold(hog_img, 100, 255, cv2.THRESH_BINARY) 110 | 111 | y_map = np.mean(binary_img, axis=1) 112 | y_map /= np.max(y_map) 113 | 114 | # avoid error 115 | try: 116 | down_point = np.min(np.where(y_map > threshold_value)[0]) 117 | except: 118 | down_point = 0 119 | try: 120 | up_point = np.max(np.where(y_map > threshold_value)[0]) 121 | except: 122 | up_point = len(y_map) -1 123 | 124 | # adjust 1 pixel (make sure not cutted the text part) 125 | down_point = down_point - 1 if down_point > 0 else down_point 126 | up_point = up_point + 1 if up_point < src_img.shape[0] - 1 else up_point 127 | 128 | return src_img[down_point:up_point, left_point:right_point] 129 | except: 130 | return None 131 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/back_all/test.png -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/valid_recognition/CNN_test_date_modify.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Apr 27 13:55:17 2017 4 | 5 | @author: yi.xiong 6 | """ 7 | 8 | # caculate accuracy of model by picture 9 | from __future__ import absolute_import 10 | from __future__ import print_function 11 | import os 12 | import cv2 13 | import numpy as np 14 | # from keras.models import model_from_json 15 | 16 | # global # 17 | # 设置切割图片的像素阈值 18 | cutThreahold = 70 19 | # 设置分辨率的统一值 20 | size = 32 21 | # 设置导入图片的尺寸和通道 22 | img_rows, img_cols = 32, 32 23 | # 单通道(灰度图片)-1 RGB图片-3 24 | img_channels = 1 25 | # 设定每次进入计算的样本batch尺寸 26 | batch_size=50 27 | 28 | 29 | # 1. 图像预处理 30 | # 灰度化 - 图像增强(直方图均衡化)- 二值化 - 切割边缘 - 统一分辨率 31 | 32 | # 1.1 灰度化 33 | def gray_img_modified(imgscr): 34 | # 灰度化 35 | newimg = cv2.cvtColor(imgscr,cv2.COLOR_RGB2GRAY) 36 | # 返回灰度化后的图片 37 | return newimg 38 | 39 | # 1.2 图像增强 - 直方图均衡化 40 | def enhance_img(img): 41 | # 增强 42 | newimg=cv2.equalizeHist(img) 43 | return newimg 44 | 45 | # 1.3 二值化 自定义阈值 46 | def binary_img(img): 47 | 48 | # cv.Threshold(src, dst, threshold, maxValue, thresholdType) 49 | # threshold_type=CV_THRESH_TRUNC: 50 | # 如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y). 51 | # 二值化 52 | ret,newimg=cv2.threshold(img,127,255,cv2.THRESH_TRUNC) 53 | return newimg 54 | 55 | 56 | # 1.4 判断像素值是否小于阈值,小于则返回像素点对应坐标 57 | def indices(a, func): 58 | return [i for (i, val) in enumerate(a) if func(val)] 59 | 60 | # 1.5 切割边缘 61 | # cutThreahold = 70 默认切割时使用的像素阈值,当像素值<70,认为是字符像素点,而非噪声 62 | def cut_img(img,cutThreahold): 63 | 64 | # img.shape[0] -- 图像高度 img.shape[1] ---- 图像宽度 65 | # 得到每列最小像素值 ---图像横向 66 | width_val = np.min(img,axis=0) 67 | # 得到每行最小像素值 --- 图像纵向 68 | height_val = np.min(img,axis=1) 69 | 70 | # 获得截取部分的左边界 71 | left_point = np.min(indices(width_val,lambda x:xthreshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y). 30 | # 二值化 31 | ret,thresh1=cv2.threshold(img,80,255,cv2.THRESH_BINARY) 32 | 33 | return thresh1 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 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/valid_recognition/Split_Image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Apr 11 17:32:49 2017 4 | 5 | @author: yi.xiong 6 | """ 7 | 8 | import pandas as pd 9 | 10 | 11 | 12 | ######################################################### 13 | # abnormal 14 | def find_range_abnormal(img_data): 15 | columns = img_data.shape[1] # width of the image 16 | start = int(columns * 0.7) 17 | for i in range(start, 0, -1): 18 | if img_data[i].all(): 19 | return i 20 | return 'error' 21 | 22 | 23 | def cut_image_abnormal(img, edge): 24 | return [img[:,0:edge,:]] 25 | 26 | # 只返回xxxx.xx.xx 27 | def cut_abnormal(img_preprocess, imgscr): 28 | img_data = pd.DataFrame(img_preprocess) 29 | edge = find_range_abnormal(img_data) 30 | if edge != 'error': 31 | sub_img = cut_image_abnormal(imgscr, edge) 32 | return sub_img 33 | else: 34 | return 'error' 35 | 36 | 37 | ########################################################## 38 | 39 | ########################################################## 40 | # regular 41 | def find_center_regular(img): 42 | img_data = pd.DataFrame(img) 43 | 44 | columns = img_data.shape[1] # width of the image 45 | 46 | if img_data[int(columns/2)].all() == False: 47 | return img_data, int(columns/2) 48 | 49 | find_c = False 50 | find_r = False 51 | left_c = int(columns/2) 52 | right_c = int(columns/2) 53 | while not find_c and not find_r: 54 | if img_data[left_c].all(): # if all zeros, then return True 55 | left_c = left_c -1 56 | else: 57 | find_c = True 58 | if img_data[right_c].all(): 59 | right_c = right_c + 1 60 | else: 61 | find_r = True 62 | 63 | if int(columns/2) - left_c >= right_c - int(columns/2): 64 | return img_data, right_c 65 | else: 66 | return img_data, left_c 67 | 68 | 69 | def find_range_regular(img,center): 70 | rows = img.shape[0] # height of the image 71 | for i in range(rows): 72 | # find start point 73 | if img.at[i,center] == 0: 74 | # find left and right point 75 | left = find_left_regular(i, center, img) 76 | right = find_right_regular(i,center, img) 77 | if left != 'error' and right != 'error': 78 | return min(left), max(right) 79 | else: 80 | return 'error', 'error' 81 | 82 | 83 | def find_left_regular(start_row,start_column, img): 84 | rows = img.shape[0] # height of the image 85 | left = [] 86 | for i in range(start_row, rows): 87 | for j in range(start_column, 0, -1): 88 | if img.at[i,j] == 255: 89 | left.append(j+1) 90 | break 91 | if len(left) == 0: 92 | return 'error' 93 | else: 94 | return left 95 | 96 | 97 | def find_right_regular(start_row,start_column, img): 98 | rows = img.shape[0] # height of the image 99 | columns = img.shape[1] # width of the image 100 | right = [] 101 | for i in range(start_row, rows): 102 | for j in range(start_column, columns): 103 | if img.at[i,j] == 255: 104 | right.append(j-1) 105 | break 106 | if len(right) == 0: 107 | return 'error' 108 | else: 109 | return right 110 | 111 | 112 | def cut_image_regular(img, left, right): 113 | sub_imgs = [] 114 | columns = img.shape[1] # width of the image 115 | sub_imgs.append(img[:,0:left-1,:]) 116 | #sub_imgs.append(img[:,left:right,:]) 117 | sub_imgs.append(img[:,right+1:columns-1,:]) 118 | return sub_imgs 119 | 120 | # 返回 xxxx.xx.xx 和 xxxx.xx.xx 121 | def cut_regular(img_preprocess, imgscr): 122 | sub_imgs =[] 123 | img_data, center = find_center_regular(img_preprocess) 124 | left, right = find_range_regular(img_data, center) 125 | if left != 'error' and right != 'error': 126 | sub_imgs = cut_image_regular(imgscr, left, right) 127 | return sub_imgs 128 | else: 129 | return 'error' 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 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/valid_recognition/WholeProcess_valid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sat Apr 15 16:32:20 2017 4 | 5 | @author: yi.xiong 6 | """ 7 | 8 | # 1. 切割出有效期片段 9 | # 2. 以宽高比判断是正规日期格式还是带长期格式 10 | # 3. 图像灰度 - 增强 - 二值化 (去噪预处理) 11 | # 4. 按照两种格式的规则分别找出日期中间的横线,将图像分割为三部分,日期 - 日期 12 | # 5. 先切割出年和日期,然后分别切割出单个字符 13 | 14 | import sys 15 | sys.path.append('../') 16 | 17 | import os 18 | import shutil 19 | import cv2 20 | import csv 21 | 22 | import valid_recognition.Preprocess_Image as PI 23 | import valid_recognition.Split_Image as SI 24 | import valid_recognition.Cut_Year_Date as YD 25 | import valid_recognition.prepare_cut_character_v2 as prepare 26 | 27 | def resize_img(img): 28 | row,columns = img.shape[:2] 29 | ratio = 40 / row 30 | img = cv2.resize(img,(int(columns*ratio),int(row*ratio))) 31 | return img 32 | 33 | # 根据txt文件中的有效期坐标得到左右边界 34 | def read_coordinate(file): 35 | with open(file, "r", encoding= 'utf8') as f: 36 | mark_start = False 37 | mark_end = False 38 | previous_line = '' 39 | lines = f.readlines() 40 | for line in lines: 41 | if mark_end == True: 42 | points = previous_line.split(',') 43 | end= [int(points[0])+int(points[2]), int(points[1])+int(points[3])] 44 | return start, end 45 | if mark_start == True: 46 | start = [int(line.split(',')[0]), int(line.split(',')[1])] 47 | mark_start = False 48 | if line == "[expire]\n": 49 | mark_start = True 50 | if line in ['[police_characters]\n','[expire_characters]\n']: 51 | mark_end = True 52 | if mark_end == False: 53 | previous_line = line 54 | return start, end 55 | 56 | # 切割出有效期区域 57 | def cut_area(file, start, end): 58 | img = cv2.imread(file) 59 | return img[start[1]:end[1],start[0]:end[0],:] 60 | 61 | 62 | # 根据宽高比判断是正规长度格式还是带长期的格式 63 | def calculate_range(img): 64 | height = img.shape[0] 65 | width = img.shape[1] 66 | ratio = round(width * 1.0 / height,1) # 宽高比保留至1位小数 67 | if ratio>= 7: # sdk里此值为9.8,针对带背景身份证,此值改为7 68 | return 'regular' # xxxx.xx.xx - xxxx.xx.xx 69 | else: 70 | return 'abnormal' # xxxx.xx.xx - 长期 71 | 72 | # 图像灰度 - 增强 - 二值化 (去噪预处理) 73 | def preprocess_img(scr): 74 | # 灰度化 75 | img_Gray = PI.gray_img(scr) 76 | # 图像增强 - 直方图均衡化 77 | img_Enhance = PI.enhance_img(img_Gray) 78 | # 二值化 79 | img_Binary = PI.binary_img(img_Enhance) 80 | return img_Binary 81 | 82 | def do_wholeprocess_valid(line_src_img): 83 | img_area = resize_img(prepare.prepare_character(line_src_img)) 84 | # 2. 根据图像宽高比判断有效期格式类型 85 | label = calculate_range(img_area) 86 | # print (label) 87 | # 3. 将得到的有效期图像进行灰度 - 增强 - 二值化 (去噪预处理) 88 | # 用于确定切割点 89 | img_area_preprocess = preprocess_img(img_area) 90 | # 4. 分别对正规格式和带长期的图像进行分割,得到xxxx.xx.xx 91 | if label == 'regular': 92 | imgs_cut = SI.cut_regular(img_area_preprocess, img_area) # 2 93 | else: 94 | imgs_cut = SI.cut_abnormal(img_area_preprocess, img_area) # 1 95 | # 5.将xxxx.xx.xx预处理,切割出单个字符 96 | if imgs_cut != 'error': 97 | imgs_cut_preprocess = [] 98 | for img_cut in imgs_cut: 99 | imgs_cut_preprocess.append(preprocess_img(img_cut)) 100 | single_imgs = YD.cut_year_date(imgs_cut_preprocess, imgs_cut) 101 | if single_imgs != 'error': 102 | for single_img in single_imgs: 103 | x, y, z = single_img.shape 104 | if x == 0 or y == 0 or z == 0: 105 | wrong_list.append(folder) 106 | single_imgs = 'error' 107 | break 108 | if single_imgs != 'error': 109 | return single_imgs 110 | else: 111 | return None 112 | else: 113 | return None 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/valid_recognition/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/valid_recognition/model_structure_date.json: -------------------------------------------------------------------------------- 1 | {"keras_version": "1.1.0", "class_name": "Sequential", "config": [{"class_name": "Convolution2D", "config": {"nb_row": 3, "b_constraint": null, "nb_filter": 16, "batch_input_shape": [null, 32, 32, 1], "border_mode": "valid", "bias": true, "trainable": true, "activation": "linear", "b_regularizer": null, "name": "convolution2d_1", "input_dtype": "float32", "subsample": [1, 1], "W_regularizer": null, "init": "glorot_uniform", "W_constraint": null, "activity_regularizer": null, "dim_ordering": "tf", "nb_col": 3}}, {"class_name": "Activation", "config": {"trainable": true, "activation": "tanh", "name": "activation_1"}}, {"class_name": "MaxPooling2D", "config": {"strides": [2, 2], "border_mode": "valid", "trainable": true, "pool_size": [2, 2], "dim_ordering": "tf", "name": "maxpooling2d_1"}}, {"class_name": "Convolution2D", "config": {"nb_row": 3, "b_constraint": null, "nb_filter": 32, "border_mode": "valid", "bias": true, "trainable": true, "activation": "linear", "b_regularizer": null, "name": "convolution2d_2", "nb_col": 3, "subsample": [1, 1], "W_regularizer": null, "init": "glorot_uniform", "W_constraint": null, "activity_regularizer": null, "dim_ordering": "tf"}}, {"class_name": "Activation", "config": {"trainable": true, "activation": "tanh", "name": "activation_2"}}, {"class_name": "MaxPooling2D", "config": {"strides": [2, 2], "border_mode": "valid", "trainable": true, "pool_size": [2, 2], "dim_ordering": "tf", "name": "maxpooling2d_2"}}, {"class_name": "Convolution2D", "config": {"nb_row": 3, "b_constraint": null, "nb_filter": 32, "border_mode": "valid", "bias": true, "trainable": true, "activation": "linear", "b_regularizer": null, "name": "convolution2d_3", "nb_col": 3, "subsample": [1, 1], "W_regularizer": null, "init": "glorot_uniform", "W_constraint": null, "activity_regularizer": null, "dim_ordering": "tf"}}, {"class_name": "Activation", "config": {"trainable": true, "activation": "tanh", "name": "activation_3"}}, {"class_name": "Flatten", "config": {"trainable": true, "name": "flatten_1"}}, {"class_name": "Dense", "config": {"b_constraint": null, "activation": "linear", "bias": true, "trainable": true, "output_dim": 128, "b_regularizer": null, "name": "dense_1", "W_regularizer": null, "init": "he_normal", "W_constraint": null, "activity_regularizer": null, "input_dim": null}}, {"class_name": "Activation", "config": {"trainable": true, "activation": "tanh", "name": "activation_4"}}, {"class_name": "Dense", "config": {"b_constraint": null, "activation": "linear", "bias": true, "trainable": true, "output_dim": 10, "b_regularizer": null, "name": "dense_2", "W_regularizer": null, "init": "he_normal", "W_constraint": null, "activity_regularizer": null, "input_dim": null}}, {"class_name": "Activation", "config": {"trainable": true, "activation": "softmax", "name": "activation_5"}}]} -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/valid_recognition/model_weight_date.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/back_all/valid_recognition/model_weight_date.h5 -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/valid_recognition/prepare_cut_character_v2.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import os 4 | import cv2 5 | import numpy as np 6 | from matplotlib import pyplot as plt 7 | from skimage.filters import threshold_otsu 8 | from skimage.feature import hog 9 | 10 | def check_dir(path): 11 | if not (os.path.exists(path) and os.path.isdir(path)): 12 | os.mkdir(path) 13 | 14 | def prepare_character(src_img, threshold_value=0.05, peak_width_threshold=30): 15 | ''' 16 | 此脚本用于处理cut_line_scrpt_v4产生的切行样本, 去除行周围(上下左右)的空白部分,使得样本可以作为cut_character_v1的输入进行切字。 17 | 18 | 主要思想: 19 | 使用HOG特征(检测文字的纹理),先左右切割,切出文字区域(HOG特征并且水平膨胀10像素,使得进行垂直投影时文字区域是一个大峰)找出峰宽大于30的区域作为文字区域 20 | 合并获得完整的文字区域,随后使用HOG特征水平投影切除上下区域的空白 21 | 22 | :param src_img: 原始图像 (cut_line_script_v4 output) 23 | :param threshold_value: 上下切割时使用的阈值 24 | :param peak_width_threshold: 左右切割时使用的峰宽阈值. (用于去噪) 25 | :return: 切除空白后的行样本(cut_character_v1 input) 26 | ''' 27 | # 备份原始图片 28 | img = src_img.copy() 29 | # 灰度化 30 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 31 | # 标准化 32 | cv2.normalize(img, img, 0, 255, cv2.NORM_MINMAX) 33 | # hog特征 , pixels_per_cell与特征尺寸相关 34 | fd, hog_img = hog(img,orientations=4,pixels_per_cell=(3,3), visualise=True) 35 | # 转化为UINT8类型 36 | hog_img = np.asarray(hog_img, dtype='uint8') 37 | # 标准化特征 38 | cv2.normalize(hog_img, hog_img, 0, 255, cv2.NORM_MINMAX) 39 | # 二值化 40 | res, binary_img = cv2.threshold(hog_img, 100, 255, cv2.THRESH_BINARY) 41 | 42 | # 水平膨胀10像素 43 | element = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 1)) 44 | binary_img = cv2.dilate(binary_img, element, iterations = 1) 45 | 46 | # 开始分割 47 | # 垂直投影 48 | x_map = np.mean(binary_img, axis=0) 49 | x_map /= np.max(x_map) 50 | 51 | ''' 投影具有几个峰 (每个峰有可能是噪声或者是字,需要通过峰宽来判断) ''' 52 | # 找出字符的边界 53 | # 左右加三个黑色像素 54 | tmp_x_map = np.append(np.append([0, 0, 0], x_map), [0, 0, 0]) 55 | # 找出所有峰与峰之间的间隔位置 56 | x_map_candidate = np.where(tmp_x_map == 0)[0] 57 | 58 | # 找出每个峰的左右两侧的点 59 | peak_left_pts = [] 60 | peak_right_pts = [] 61 | temp = [] 62 | for pt in x_map_candidate: 63 | if len(temp) == 0: 64 | temp.append(pt) 65 | elif pt - temp[-1] <= 3: 66 | temp.append(pt) 67 | elif pt - temp[-1] > 3: 68 | peak_left_pts.append(temp[-1] - 3) 69 | peak_right_pts.append(temp[0] - 3) 70 | temp = [pt] 71 | # 增加最后剩余的两个点 72 | peak_left_pts.append(temp[-1] - 3) 73 | peak_right_pts.append(temp[0] - 3) 74 | # 去除开始和结尾部分的点 75 | peak_left_pts.remove(peak_left_pts[-1]) 76 | peak_right_pts.remove(peak_right_pts[0]) 77 | 78 | # 过滤噪声峰 79 | region_width_list = [] 80 | updated_peak_left = [] 81 | updated_peak_right = [] 82 | # 当但钱的峰宽大于设定的峰宽阈值时,记录当前峰宽、峰起始点和终止点 83 | for i in range(len(peak_left_pts)): 84 | current_width = peak_right_pts[i] - peak_left_pts[i] 85 | if current_width > peak_width_threshold: 86 | region_width_list.append(current_width) 87 | updated_peak_left.append(peak_left_pts[i]) 88 | updated_peak_right.append(peak_right_pts[i]) 89 | # 如果没有峰(即空白行),返回空 90 | if len(region_width_list) == 0: 91 | return None 92 | # 切除文字区域并减小2个像素(由于之前横向膨胀了10个像素,这边减两个像素,防止左右空白太大) 93 | left_point = updated_peak_left[0] + 2 94 | right_point = updated_peak_right[-1] -2 95 | left_right_cutted_img = src_img[:, left_point:right_point] 96 | # 灰度化 97 | img = cv2.cvtColor(left_right_cutted_img, cv2.COLOR_BGR2GRAY) 98 | # 标准化 99 | cv2.normalize(img, img, 0, 255, cv2.NORM_MINMAX) 100 | try: 101 | # 再次HOG特征 102 | fd, hog_img = hog(img, orientations=4, pixels_per_cell=(3, 3), visualise=True) 103 | # 转化为UINT8类型 104 | hog_img = np.asarray(hog_img, dtype='uint8') 105 | # 标准化特征 106 | cv2.normalize(hog_img, hog_img, 0, 255, cv2.NORM_MINMAX) 107 | # 二值化 108 | res, binary_img = cv2.threshold(hog_img, 100, 255, cv2.THRESH_BINARY) 109 | # 水平投影 110 | y_map = np.mean(binary_img, axis=1) 111 | y_map /= np.max(y_map) 112 | 113 | # 防错 114 | try: 115 | # 下边界为峰的第一个点 116 | down_point = np.min(np.where(y_map > threshold_value)[0]) 117 | except: 118 | # 如果出错则让下边界为0 119 | down_point = 0 120 | try: 121 | # 上边界为峰的最后一个点 122 | up_point = np.max(np.where(y_map > threshold_value)[0]) 123 | except: 124 | # 如果出错则让上边界为高度 125 | up_point = len(y_map) -1 126 | 127 | # 调整多一个像素(防止切掉太多) 128 | down_point = down_point - 1 if down_point > 0 else down_point 129 | up_point = up_point + 1 if up_point < src_img.shape[0] - 1 else up_point 130 | # 返回结果 131 | return src_img[down_point:up_point, left_point:right_point] 132 | except: 133 | # 防错如果产生错误则返回空 134 | return None 135 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/back_all/valid_recognition/valid_main.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | sys.path.append('../') 4 | 5 | from valid_recognition.WholeProcess_valid import do_wholeprocess_valid 6 | from valid_recognition.CNN_test_date_modify import do_CNN_test_date_modify 7 | 8 | ### send loaded model ### 9 | def valid_processing(valid_line, model): 10 | valid_character = do_wholeprocess_valid(valid_line) 11 | 12 | ### send loaded model ### 13 | return do_CNN_test_date_modify(valid_character, model), valid_character 14 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/data_util.py: -------------------------------------------------------------------------------- 1 | ''' 2 | this file is modified from keras implemention of data process multi-threading, 3 | see https://github.com/fchollet/keras/blob/master/keras/utils/data_utils.py 4 | ''' 5 | import time 6 | import numpy as np 7 | import threading 8 | import multiprocessing 9 | try: 10 | import queue 11 | except ImportError: 12 | import Queue as queue 13 | 14 | 15 | class GeneratorEnqueuer(): 16 | """Builds a queue out of a data generator. 17 | 18 | Used in `fit_generator`, `evaluate_generator`, `predict_generator`. 19 | 20 | # Arguments 21 | generator: a generator function which endlessly yields data 22 | use_multiprocessing: use multiprocessing if True, otherwise threading 23 | wait_time: time to sleep in-between calls to `put()` 24 | random_seed: Initial seed for workers, 25 | will be incremented by one for each workers. 26 | """ 27 | 28 | def __init__(self, generator, 29 | use_multiprocessing=False, 30 | wait_time=0.05, 31 | random_seed=None): 32 | self.wait_time = wait_time 33 | self._generator = generator 34 | self._use_multiprocessing = use_multiprocessing 35 | self._threads = [] 36 | self._stop_event = None 37 | self.queue = None 38 | self.random_seed = random_seed 39 | 40 | def start(self, workers=1, max_queue_size=10): 41 | """Kicks off threads which add data from the generator into the queue. 42 | 43 | # Arguments 44 | workers: number of worker threads 45 | max_queue_size: queue size 46 | (when full, threads could block on `put()`) 47 | """ 48 | 49 | def data_generator_task(): 50 | while not self._stop_event.is_set(): 51 | try: 52 | if self._use_multiprocessing or self.queue.qsize() < max_queue_size: 53 | generator_output = next(self._generator) 54 | self.queue.put(generator_output) 55 | else: 56 | time.sleep(self.wait_time) 57 | except Exception: 58 | self._stop_event.set() 59 | raise 60 | 61 | try: 62 | if self._use_multiprocessing: 63 | self.queue = multiprocessing.Queue(maxsize=max_queue_size) 64 | self._stop_event = multiprocessing.Event() 65 | else: 66 | self.queue = queue.Queue() 67 | self._stop_event = threading.Event() 68 | 69 | for _ in range(workers): 70 | if self._use_multiprocessing: 71 | # Reset random seed else all children processes 72 | # share the same seed 73 | np.random.seed(self.random_seed) 74 | thread = multiprocessing.Process(target=data_generator_task) 75 | thread.daemon = True 76 | if self.random_seed is not None: 77 | self.random_seed += 1 78 | else: 79 | thread = threading.Thread(target=data_generator_task) 80 | self._threads.append(thread) 81 | thread.start() 82 | except: 83 | self.stop() 84 | raise 85 | 86 | def is_running(self): 87 | return self._stop_event is not None and not self._stop_event.is_set() 88 | 89 | def stop(self, timeout=None): 90 | """Stops running threads and wait for them to exit, if necessary. 91 | 92 | Should be called by the same thread which called `start()`. 93 | 94 | # Arguments 95 | timeout: maximum time to wait on `thread.join()`. 96 | """ 97 | if self.is_running(): 98 | self._stop_event.set() 99 | 100 | for thread in self._threads: 101 | if thread.is_alive(): 102 | if self._use_multiprocessing: 103 | thread.terminate() 104 | else: 105 | thread.join(timeout) 106 | 107 | if self._use_multiprocessing: 108 | if self.queue is not None: 109 | self.queue.close() 110 | 111 | self._threads = [] 112 | self._stop_event = None 113 | self.queue = None 114 | 115 | def get(self): 116 | """Creates a generator to extract data from the queue. 117 | 118 | Skip the data if it is `None`. 119 | 120 | # Returns 121 | A generator 122 | """ 123 | while self.is_running(): 124 | if not self.queue.empty(): 125 | inputs = self.queue.get() 126 | if inputs is not None: 127 | yield inputs 128 | else: 129 | time.sleep(self.wait_time) -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/east_model: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/.gitignore: -------------------------------------------------------------------------------- 1 | adaptor.so 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (C) 2014 Google Inc. 4 | # 5 | # This file is part of YouCompleteMe. 6 | # 7 | # YouCompleteMe is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # YouCompleteMe is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with YouCompleteMe. If not, see . 19 | 20 | import os 21 | import sys 22 | import glob 23 | import ycm_core 24 | 25 | # These are the compilation flags that will be used in case there's no 26 | # compilation database set (by default, one is not set). 27 | # CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. 28 | sys.path.append(os.path.dirname(__file__)) 29 | 30 | 31 | BASE_DIR = os.path.dirname(os.path.realpath(__file__)) 32 | 33 | from plumbum.cmd import python_config 34 | 35 | 36 | flags = [ 37 | '-Wall', 38 | '-Wextra', 39 | '-Wnon-virtual-dtor', 40 | '-Winvalid-pch', 41 | '-Wno-unused-local-typedefs', 42 | '-std=c++11', 43 | '-x', 'c++', 44 | '-Iinclude', 45 | ] + python_config('--cflags').split() 46 | 47 | 48 | # Set this to the absolute path to the folder (NOT the file!) containing the 49 | # compile_commands.json file to use that instead of 'flags'. See here for 50 | # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 51 | # 52 | # Most projects will NOT need to set this to anything; you can just change the 53 | # 'flags' list of compilation flags. 54 | compilation_database_folder = '' 55 | 56 | if os.path.exists( compilation_database_folder ): 57 | database = ycm_core.CompilationDatabase( compilation_database_folder ) 58 | else: 59 | database = None 60 | 61 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] 62 | 63 | def DirectoryOfThisScript(): 64 | return os.path.dirname( os.path.abspath( __file__ ) ) 65 | 66 | 67 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 68 | if not working_directory: 69 | return list( flags ) 70 | new_flags = [] 71 | make_next_absolute = False 72 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 73 | for flag in flags: 74 | new_flag = flag 75 | 76 | if make_next_absolute: 77 | make_next_absolute = False 78 | if not flag.startswith( '/' ): 79 | new_flag = os.path.join( working_directory, flag ) 80 | 81 | for path_flag in path_flags: 82 | if flag == path_flag: 83 | make_next_absolute = True 84 | break 85 | 86 | if flag.startswith( path_flag ): 87 | path = flag[ len( path_flag ): ] 88 | new_flag = path_flag + os.path.join( working_directory, path ) 89 | break 90 | 91 | if new_flag: 92 | new_flags.append( new_flag ) 93 | return new_flags 94 | 95 | 96 | def IsHeaderFile( filename ): 97 | extension = os.path.splitext( filename )[ 1 ] 98 | return extension in [ '.h', '.hxx', '.hpp', '.hh' ] 99 | 100 | 101 | def GetCompilationInfoForFile( filename ): 102 | # The compilation_commands.json file generated by CMake does not have entries 103 | # for header files. So we do our best by asking the db for flags for a 104 | # corresponding source file, if any. If one exists, the flags for that file 105 | # should be good enough. 106 | if IsHeaderFile( filename ): 107 | basename = os.path.splitext( filename )[ 0 ] 108 | for extension in SOURCE_EXTENSIONS: 109 | replacement_file = basename + extension 110 | if os.path.exists( replacement_file ): 111 | compilation_info = database.GetCompilationInfoForFile( 112 | replacement_file ) 113 | if compilation_info.compiler_flags_: 114 | return compilation_info 115 | return None 116 | return database.GetCompilationInfoForFile( filename ) 117 | 118 | 119 | # This is the entry point; this function is called by ycmd to produce flags for 120 | # a file. 121 | def FlagsForFile( filename, **kwargs ): 122 | if database: 123 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 124 | # python list, but a "list-like" StringVec object 125 | compilation_info = GetCompilationInfoForFile( filename ) 126 | if not compilation_info: 127 | return None 128 | 129 | final_flags = MakeRelativePathsInFlagsAbsolute( 130 | compilation_info.compiler_flags_, 131 | compilation_info.compiler_working_dir_ ) 132 | else: 133 | relative_to = DirectoryOfThisScript() 134 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 135 | 136 | return { 137 | 'flags': final_flags, 138 | 'do_cache': True 139 | } 140 | 141 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = -I include -std=c++11 -O3 $(shell python3-config --cflags) 2 | LDFLAGS = $(shell python3-config --ldflags) 3 | 4 | DEPS = lanms.h $(shell find include -xtype f) 5 | CXX_SOURCES = adaptor.cpp include/clipper/clipper.cpp 6 | 7 | LIB_SO = adaptor.so 8 | 9 | $(LIB_SO): $(CXX_SOURCES) $(DEPS) 10 | $(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $(CXX_SOURCES) --shared -fPIC 11 | 12 | clean: 13 | rm -rf $(LIB_SO) 14 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/__init__.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import numpy as np 4 | 5 | BASE_DIR = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | if subprocess.call(['make', '-C', BASE_DIR]) != 0: # return value 8 | raise RuntimeError('Cannot compile lanms: {}'.format(BASE_DIR)) 9 | 10 | 11 | def merge_quadrangle_n9(polys, thres=0.3, precision=10000): 12 | from .adaptor import merge_quadrangle_n9 as nms_impl 13 | if len(polys) == 0: 14 | return np.array([], dtype='float32') 15 | p = polys.copy() 16 | p[:,:8] *= precision 17 | ret = np.array(nms_impl(p, thres), dtype='float32') 18 | ret[:,:8] /= precision 19 | return ret 20 | 21 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/__main__.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | from . import merge_quadrangle_n9 5 | 6 | if __name__ == '__main__': 7 | # unit square with confidence 1 8 | q = np.array([0, 0, 0, 1, 1, 1, 1, 0, 1], dtype='float32') 9 | 10 | print(merge_quadrangle_n9(np.array([q, q + 0.1, q + 2]))) 11 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/adaptor.cpp: -------------------------------------------------------------------------------- 1 | #include "pybind11/pybind11.h" 2 | #include "pybind11/numpy.h" 3 | #include "pybind11/stl.h" 4 | #include "pybind11/stl_bind.h" 5 | 6 | #include "lanms.h" 7 | 8 | namespace py = pybind11; 9 | 10 | 11 | namespace lanms_adaptor { 12 | 13 | std::vector> polys2floats(const std::vector &polys) { 14 | std::vector> ret; 15 | for (size_t i = 0; i < polys.size(); i ++) { 16 | auto &p = polys[i]; 17 | auto &poly = p.poly; 18 | ret.emplace_back(std::vector{ 19 | float(poly[0].X), float(poly[0].Y), 20 | float(poly[1].X), float(poly[1].Y), 21 | float(poly[2].X), float(poly[2].Y), 22 | float(poly[3].X), float(poly[3].Y), 23 | float(p.score), 24 | }); 25 | } 26 | 27 | return ret; 28 | } 29 | 30 | 31 | /** 32 | * 33 | * \param quad_n9 an n-by-9 numpy array, where first 8 numbers denote the 34 | * quadrangle, and the last one is the score 35 | * \param iou_threshold two quadrangles with iou score above this threshold 36 | * will be merged 37 | * 38 | * \return an n-by-9 numpy array, the merged quadrangles 39 | */ 40 | std::vector> merge_quadrangle_n9( 41 | py::array_t quad_n9, 42 | float iou_threshold) { 43 | auto pbuf = quad_n9.request(); 44 | if (pbuf.ndim != 2 || pbuf.shape[1] != 9) 45 | throw std::runtime_error("quadrangles must have a shape of (n, 9)"); 46 | auto n = pbuf.shape[0]; 47 | auto ptr = static_cast(pbuf.ptr); 48 | return polys2floats(lanms::merge_quadrangle_n9(ptr, n, iou_threshold)); 49 | } 50 | 51 | } 52 | 53 | PYBIND11_PLUGIN(adaptor) { 54 | py::module m("adaptor", "NMS"); 55 | 56 | m.def("merge_quadrangle_n9", &lanms_adaptor::merge_quadrangle_n9, 57 | "merge quadrangels"); 58 | 59 | return m.ptr(); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/include/clipper/clipper.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/include/clipper/clipper.cpp -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/include/pybind11/buffer_info.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/buffer_info.h: Python buffer object interface 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "common.h" 13 | 14 | NAMESPACE_BEGIN(pybind11) 15 | 16 | /// Information record describing a Python buffer object 17 | struct buffer_info { 18 | void *ptr = nullptr; // Pointer to the underlying storage 19 | ssize_t itemsize = 0; // Size of individual items in bytes 20 | ssize_t size = 0; // Total number of entries 21 | std::string format; // For homogeneous buffers, this should be set to format_descriptor::format() 22 | ssize_t ndim = 0; // Number of dimensions 23 | std::vector shape; // Shape of the tensor (1 entry per dimension) 24 | std::vector strides; // Number of entries between adjacent entries (for each per dimension) 25 | 26 | buffer_info() { } 27 | 28 | buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, 29 | detail::any_container shape_in, detail::any_container strides_in) 30 | : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim), 31 | shape(std::move(shape_in)), strides(std::move(strides_in)) { 32 | if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) 33 | pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length"); 34 | for (size_t i = 0; i < (size_t) ndim; ++i) 35 | size *= shape[i]; 36 | } 37 | 38 | template 39 | buffer_info(T *ptr, detail::any_container shape_in, detail::any_container strides_in) 40 | : buffer_info(private_ctr_tag(), ptr, sizeof(T), format_descriptor::format(), static_cast(shape_in->size()), std::move(shape_in), std::move(strides_in)) { } 41 | 42 | buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t size) 43 | : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}) { } 44 | 45 | template 46 | buffer_info(T *ptr, ssize_t size) 47 | : buffer_info(ptr, sizeof(T), format_descriptor::format(), size) { } 48 | 49 | explicit buffer_info(Py_buffer *view, bool ownview = true) 50 | : buffer_info(view->buf, view->itemsize, view->format, view->ndim, 51 | {view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}) { 52 | this->view = view; 53 | this->ownview = ownview; 54 | } 55 | 56 | buffer_info(const buffer_info &) = delete; 57 | buffer_info& operator=(const buffer_info &) = delete; 58 | 59 | buffer_info(buffer_info &&other) { 60 | (*this) = std::move(other); 61 | } 62 | 63 | buffer_info& operator=(buffer_info &&rhs) { 64 | ptr = rhs.ptr; 65 | itemsize = rhs.itemsize; 66 | size = rhs.size; 67 | format = std::move(rhs.format); 68 | ndim = rhs.ndim; 69 | shape = std::move(rhs.shape); 70 | strides = std::move(rhs.strides); 71 | std::swap(view, rhs.view); 72 | std::swap(ownview, rhs.ownview); 73 | return *this; 74 | } 75 | 76 | ~buffer_info() { 77 | if (view && ownview) { PyBuffer_Release(view); delete view; } 78 | } 79 | 80 | private: 81 | struct private_ctr_tag { }; 82 | 83 | buffer_info(private_ctr_tag, void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, 84 | detail::any_container &&shape_in, detail::any_container &&strides_in) 85 | : buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in)) { } 86 | 87 | Py_buffer *view = nullptr; 88 | bool ownview = false; 89 | }; 90 | 91 | NAMESPACE_BEGIN(detail) 92 | 93 | template struct compare_buffer_info { 94 | static bool compare(const buffer_info& b) { 95 | return b.format == format_descriptor::format() && b.itemsize == (ssize_t) sizeof(T); 96 | } 97 | }; 98 | 99 | template struct compare_buffer_info::value>> { 100 | static bool compare(const buffer_info& b) { 101 | return (size_t) b.itemsize == sizeof(T) && (b.format == format_descriptor::value || 102 | ((sizeof(T) == sizeof(long)) && b.format == (std::is_unsigned::value ? "L" : "l")) || 103 | ((sizeof(T) == sizeof(size_t)) && b.format == (std::is_unsigned::value ? "N" : "n"))); 104 | } 105 | }; 106 | 107 | NAMESPACE_END(detail) 108 | NAMESPACE_END(pybind11) 109 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/include/pybind11/complex.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/complex.h: Complex number support 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "pybind11.h" 13 | #include 14 | 15 | /// glibc defines I as a macro which breaks things, e.g., boost template names 16 | #ifdef I 17 | # undef I 18 | #endif 19 | 20 | NAMESPACE_BEGIN(pybind11) 21 | 22 | template struct format_descriptor, detail::enable_if_t::value>> { 23 | static constexpr const char c = format_descriptor::c; 24 | static constexpr const char value[3] = { 'Z', c, '\0' }; 25 | static std::string format() { return std::string(value); } 26 | }; 27 | 28 | template constexpr const char format_descriptor< 29 | std::complex, detail::enable_if_t::value>>::value[3]; 30 | 31 | NAMESPACE_BEGIN(detail) 32 | 33 | template struct is_fmt_numeric, detail::enable_if_t::value>> { 34 | static constexpr bool value = true; 35 | static constexpr int index = is_fmt_numeric::index + 3; 36 | }; 37 | 38 | template class type_caster> { 39 | public: 40 | bool load(handle src, bool convert) { 41 | if (!src) 42 | return false; 43 | if (!convert && !PyComplex_Check(src.ptr())) 44 | return false; 45 | Py_complex result = PyComplex_AsCComplex(src.ptr()); 46 | if (result.real == -1.0 && PyErr_Occurred()) { 47 | PyErr_Clear(); 48 | return false; 49 | } 50 | value = std::complex((T) result.real, (T) result.imag); 51 | return true; 52 | } 53 | 54 | static handle cast(const std::complex &src, return_value_policy /* policy */, handle /* parent */) { 55 | return PyComplex_FromDoubles((double) src.real(), (double) src.imag()); 56 | } 57 | 58 | PYBIND11_TYPE_CASTER(std::complex, _("complex")); 59 | }; 60 | NAMESPACE_END(detail) 61 | NAMESPACE_END(pybind11) 62 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/include/pybind11/eval.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/exec.h: Support for evaluating Python expressions and statements 3 | from strings and files 4 | 5 | Copyright (c) 2016 Klemens Morgenstern and 6 | Wenzel Jakob 7 | 8 | All rights reserved. Use of this source code is governed by a 9 | BSD-style license that can be found in the LICENSE file. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include "pybind11.h" 15 | 16 | NAMESPACE_BEGIN(pybind11) 17 | 18 | enum eval_mode { 19 | /// Evaluate a string containing an isolated expression 20 | eval_expr, 21 | 22 | /// Evaluate a string containing a single statement. Returns \c none 23 | eval_single_statement, 24 | 25 | /// Evaluate a string containing a sequence of statement. Returns \c none 26 | eval_statements 27 | }; 28 | 29 | template 30 | object eval(str expr, object global = globals(), object local = object()) { 31 | if (!local) 32 | local = global; 33 | 34 | /* PyRun_String does not accept a PyObject / encoding specifier, 35 | this seems to be the only alternative */ 36 | std::string buffer = "# -*- coding: utf-8 -*-\n" + (std::string) expr; 37 | 38 | int start; 39 | switch (mode) { 40 | case eval_expr: start = Py_eval_input; break; 41 | case eval_single_statement: start = Py_single_input; break; 42 | case eval_statements: start = Py_file_input; break; 43 | default: pybind11_fail("invalid evaluation mode"); 44 | } 45 | 46 | PyObject *result = PyRun_String(buffer.c_str(), start, global.ptr(), local.ptr()); 47 | if (!result) 48 | throw error_already_set(); 49 | return reinterpret_steal(result); 50 | } 51 | 52 | template 53 | object eval(const char (&s)[N], object global = globals(), object local = object()) { 54 | /* Support raw string literals by removing common leading whitespace */ 55 | auto expr = (s[0] == '\n') ? str(module::import("textwrap").attr("dedent")(s)) 56 | : str(s); 57 | return eval(expr, global, local); 58 | } 59 | 60 | inline void exec(str expr, object global = globals(), object local = object()) { 61 | eval(expr, global, local); 62 | } 63 | 64 | template 65 | void exec(const char (&s)[N], object global = globals(), object local = object()) { 66 | eval(s, global, local); 67 | } 68 | 69 | template 70 | object eval_file(str fname, object global = globals(), object local = object()) { 71 | if (!local) 72 | local = global; 73 | 74 | int start; 75 | switch (mode) { 76 | case eval_expr: start = Py_eval_input; break; 77 | case eval_single_statement: start = Py_single_input; break; 78 | case eval_statements: start = Py_file_input; break; 79 | default: pybind11_fail("invalid evaluation mode"); 80 | } 81 | 82 | int closeFile = 1; 83 | std::string fname_str = (std::string) fname; 84 | #if PY_VERSION_HEX >= 0x03040000 85 | FILE *f = _Py_fopen_obj(fname.ptr(), "r"); 86 | #elif PY_VERSION_HEX >= 0x03000000 87 | FILE *f = _Py_fopen(fname.ptr(), "r"); 88 | #else 89 | /* No unicode support in open() :( */ 90 | auto fobj = reinterpret_steal(PyFile_FromString( 91 | const_cast(fname_str.c_str()), 92 | const_cast("r"))); 93 | FILE *f = nullptr; 94 | if (fobj) 95 | f = PyFile_AsFile(fobj.ptr()); 96 | closeFile = 0; 97 | #endif 98 | if (!f) { 99 | PyErr_Clear(); 100 | pybind11_fail("File \"" + fname_str + "\" could not be opened!"); 101 | } 102 | 103 | #if PY_VERSION_HEX < 0x03000000 && defined(PYPY_VERSION) 104 | PyObject *result = PyRun_File(f, fname_str.c_str(), start, global.ptr(), 105 | local.ptr()); 106 | (void) closeFile; 107 | #else 108 | PyObject *result = PyRun_FileEx(f, fname_str.c_str(), start, global.ptr(), 109 | local.ptr(), closeFile); 110 | #endif 111 | 112 | if (!result) 113 | throw error_already_set(); 114 | return reinterpret_steal(result); 115 | } 116 | 117 | NAMESPACE_END(pybind11) 118 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/include/pybind11/functional.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/functional.h: std::function<> support 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "pybind11.h" 13 | #include 14 | 15 | NAMESPACE_BEGIN(pybind11) 16 | NAMESPACE_BEGIN(detail) 17 | 18 | template 19 | struct type_caster> { 20 | using type = std::function; 21 | using retval_type = conditional_t::value, void_type, Return>; 22 | using function_type = Return (*) (Args...); 23 | 24 | public: 25 | bool load(handle src, bool convert) { 26 | if (src.is_none()) { 27 | // Defer accepting None to other overloads (if we aren't in convert mode): 28 | if (!convert) return false; 29 | return true; 30 | } 31 | 32 | if (!isinstance(src)) 33 | return false; 34 | 35 | auto func = reinterpret_borrow(src); 36 | 37 | /* 38 | When passing a C++ function as an argument to another C++ 39 | function via Python, every function call would normally involve 40 | a full C++ -> Python -> C++ roundtrip, which can be prohibitive. 41 | Here, we try to at least detect the case where the function is 42 | stateless (i.e. function pointer or lambda function without 43 | captured variables), in which case the roundtrip can be avoided. 44 | */ 45 | if (auto cfunc = func.cpp_function()) { 46 | auto c = reinterpret_borrow(PyCFunction_GET_SELF(cfunc.ptr())); 47 | auto rec = (function_record *) c; 48 | 49 | if (rec && rec->is_stateless && 50 | same_type(typeid(function_type), *reinterpret_cast(rec->data[1]))) { 51 | struct capture { function_type f; }; 52 | value = ((capture *) &rec->data)->f; 53 | return true; 54 | } 55 | } 56 | 57 | value = [func](Args... args) -> Return { 58 | gil_scoped_acquire acq; 59 | object retval(func(std::forward(args)...)); 60 | /* Visual studio 2015 parser issue: need parentheses around this expression */ 61 | return (retval.template cast()); 62 | }; 63 | return true; 64 | } 65 | 66 | template 67 | static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) { 68 | if (!f_) 69 | return none().inc_ref(); 70 | 71 | auto result = f_.template target(); 72 | if (result) 73 | return cpp_function(*result, policy).release(); 74 | else 75 | return cpp_function(std::forward(f_), policy).release(); 76 | } 77 | 78 | PYBIND11_TYPE_CASTER(type, _("Callable[[") + 79 | argument_loader::arg_names() + _("], ") + 80 | make_caster::name() + 81 | _("]")); 82 | }; 83 | 84 | NAMESPACE_END(detail) 85 | NAMESPACE_END(pybind11) 86 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/include/pybind11/options.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/options.h: global settings that are configurable at runtime. 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "common.h" 13 | 14 | NAMESPACE_BEGIN(pybind11) 15 | 16 | class options { 17 | public: 18 | 19 | // Default RAII constructor, which leaves settings as they currently are. 20 | options() : previous_state(global_state()) {} 21 | 22 | // Class is non-copyable. 23 | options(const options&) = delete; 24 | options& operator=(const options&) = delete; 25 | 26 | // Destructor, which restores settings that were in effect before. 27 | ~options() { 28 | global_state() = previous_state; 29 | } 30 | 31 | // Setter methods (affect the global state): 32 | 33 | options& disable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = false; return *this; } 34 | 35 | options& enable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = true; return *this; } 36 | 37 | options& disable_function_signatures() & { global_state().show_function_signatures = false; return *this; } 38 | 39 | options& enable_function_signatures() & { global_state().show_function_signatures = true; return *this; } 40 | 41 | // Getter methods (return the global state): 42 | 43 | static bool show_user_defined_docstrings() { return global_state().show_user_defined_docstrings; } 44 | 45 | static bool show_function_signatures() { return global_state().show_function_signatures; } 46 | 47 | // This type is not meant to be allocated on the heap. 48 | void* operator new(size_t) = delete; 49 | 50 | private: 51 | 52 | struct state { 53 | bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings. 54 | bool show_function_signatures = true; //< Include auto-generated function signatures in docstrings. 55 | }; 56 | 57 | static state &global_state() { 58 | static state instance; 59 | return instance; 60 | } 61 | 62 | state previous_state; 63 | }; 64 | 65 | NAMESPACE_END(pybind11) 66 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/lanms/include/pybind11/typeid.h: -------------------------------------------------------------------------------- 1 | /* 2 | pybind11/typeid.h: Compiler-independent access to type identifiers 3 | 4 | Copyright (c) 2016 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #if defined(__GNUG__) 16 | #include 17 | #endif 18 | 19 | NAMESPACE_BEGIN(pybind11) 20 | NAMESPACE_BEGIN(detail) 21 | /// Erase all occurrences of a substring 22 | inline void erase_all(std::string &string, const std::string &search) { 23 | for (size_t pos = 0;;) { 24 | pos = string.find(search, pos); 25 | if (pos == std::string::npos) break; 26 | string.erase(pos, search.length()); 27 | } 28 | } 29 | 30 | PYBIND11_NOINLINE inline void clean_type_id(std::string &name) { 31 | #if defined(__GNUG__) 32 | int status = 0; 33 | std::unique_ptr res { 34 | abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status), std::free }; 35 | if (status == 0) 36 | name = res.get(); 37 | #else 38 | detail::erase_all(name, "class "); 39 | detail::erase_all(name, "struct "); 40 | detail::erase_all(name, "enum "); 41 | #endif 42 | detail::erase_all(name, "pybind11::"); 43 | } 44 | NAMESPACE_END(detail) 45 | 46 | /// Return a string representation of a C++ type 47 | template static std::string type_id() { 48 | std::string name(typeid(T).name()); 49 | detail::clean_type_id(name); 50 | return name; 51 | } 52 | 53 | NAMESPACE_END(pybind11) 54 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/locality_aware_nms.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from shapely.geometry import Polygon 3 | 4 | 5 | def intersection(g, p): 6 | g = Polygon(g[:8].reshape((4, 2))) 7 | p = Polygon(p[:8].reshape((4, 2))) 8 | if not g.is_valid or not p.is_valid: 9 | return 0 10 | inter = Polygon(g).intersection(Polygon(p)).area 11 | union = g.area + p.area - inter 12 | if union == 0: 13 | return 0 14 | else: 15 | return inter/union 16 | 17 | 18 | def weighted_merge(g, p): 19 | g[:8] = (g[8] * g[:8] + p[8] * p[:8])/(g[8] + p[8]) 20 | g[8] = (g[8] + p[8]) 21 | return g 22 | 23 | 24 | def standard_nms(S, thres): 25 | order = np.argsort(S[:, 8])[::-1] 26 | keep = [] 27 | while order.size > 0: 28 | i = order[0] 29 | keep.append(i) 30 | ovr = np.array([intersection(S[i], S[t]) for t in order[1:]]) 31 | 32 | inds = np.where(ovr <= thres)[0] 33 | order = order[inds+1] 34 | 35 | return S[keep] 36 | 37 | 38 | def nms_locality(polys, thres=0.3): 39 | ''' 40 | locality aware nms of EAST 41 | :param polys: a N*9 numpy array. first 8 coordinates, then prob 42 | :return: boxes after nms 43 | ''' 44 | S = [] 45 | p = None 46 | for g in polys: 47 | if p is not None and intersection(g, p) > thres: 48 | p = weighted_merge(g, p) 49 | else: 50 | if p is not None: 51 | S.append(p) 52 | p = g 53 | if p is not None: 54 | S.append(p) 55 | 56 | if len(S) == 0: 57 | return np.array([]) 58 | return standard_nms(np.array(S), thres) 59 | 60 | 61 | if __name__ == '__main__': 62 | # 343,350,448,135,474,143,369,359 63 | print(Polygon(np.array([[343, 350], [448, 135], 64 | [474, 143], [369, 359]])).area) 65 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/model.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | 4 | import tensorflow as tf 5 | import numpy as np 6 | 7 | from tensorflow.contrib import slim 8 | 9 | tf.app.flags.DEFINE_integer('text_scale', 512, '') 10 | 11 | from east_part.nets import resnet_v1 12 | 13 | FLAGS = tf.app.flags.FLAGS 14 | 15 | 16 | def unpool(inputs): 17 | return tf.image.resize_bilinear(inputs, size=[tf.shape(inputs)[1]*2, tf.shape(inputs)[2]*2]) 18 | 19 | 20 | def mean_image_subtraction(images, means=[123.68, 116.78, 103.94]): 21 | ''' 22 | image normalization 23 | :param images: 24 | :param means: 25 | :return: 26 | ''' 27 | num_channels = images.get_shape().as_list()[-1] 28 | if len(means) != num_channels: 29 | raise ValueError('len(means) must match the number of channels') 30 | channels = tf.split(axis=3, num_or_size_splits=num_channels, value=images) 31 | for i in range(num_channels): 32 | channels[i] -= means[i] 33 | return tf.concat(axis=3, values=channels) 34 | 35 | 36 | def model(images, weight_decay=1e-5, is_training=True): 37 | ''' 38 | define the model, we use slim's implemention of resnet 39 | ''' 40 | images = mean_image_subtraction(images) 41 | 42 | with slim.arg_scope(resnet_v1.resnet_arg_scope(weight_decay=weight_decay)): 43 | logits, end_points = resnet_v1.resnet_v1_50(images, is_training=is_training, scope='resnet_v1_50') 44 | 45 | with tf.variable_scope('feature_fusion', values=[end_points.values]): 46 | batch_norm_params = { 47 | 'decay': 0.997, 48 | 'epsilon': 1e-5, 49 | 'scale': True, 50 | 'is_training': is_training 51 | } 52 | with slim.arg_scope([slim.conv2d], 53 | activation_fn=tf.nn.relu, 54 | normalizer_fn=slim.batch_norm, 55 | normalizer_params=batch_norm_params, 56 | weights_regularizer=slim.l2_regularizer(weight_decay)): 57 | f = [end_points['pool5'], end_points['pool4'], 58 | end_points['pool3'], end_points['pool2']] 59 | for i in range(4): 60 | print('Shape of f_{} {}'.format(i, f[i].shape)) 61 | g = [None, None, None, None] 62 | h = [None, None, None, None] 63 | num_outputs = [None, 128, 64, 32] 64 | for i in range(4): 65 | if i == 0: 66 | h[i] = f[i] 67 | else: 68 | c1_1 = slim.conv2d(tf.concat([g[i-1], f[i]], axis=-1), num_outputs[i], 1) 69 | h[i] = slim.conv2d(c1_1, num_outputs[i], 3) 70 | if i <= 2: 71 | g[i] = unpool(h[i]) 72 | else: 73 | g[i] = slim.conv2d(h[i], num_outputs[i], 3) 74 | print('Shape of h_{} {}, g_{} {}'.format(i, h[i].shape, i, g[i].shape)) 75 | 76 | # here we use a slightly different way for regression part, 77 | # we first use a sigmoid to limit the regression range, and also 78 | # this is do with the angle map 79 | F_score = slim.conv2d(g[3], 1, 1, activation_fn=tf.nn.sigmoid, normalizer_fn=None) 80 | # 4 channel of axis aligned bbox and 1 channel rotation angle 81 | geo_map = slim.conv2d(g[3], 4, 1, activation_fn=tf.nn.sigmoid, normalizer_fn=None) * FLAGS.text_scale 82 | angle_map = (slim.conv2d(g[3], 1, 1, activation_fn=tf.nn.sigmoid, normalizer_fn=None) - 0.5) * np.pi/2 # angle is between [-45, 45] 83 | F_geometry = tf.concat([geo_map, angle_map], axis=-1) 84 | 85 | return F_score, F_geometry 86 | 87 | 88 | def dice_coefficient(y_true_cls, y_pred_cls, 89 | training_mask): 90 | ''' 91 | dice loss 92 | :param y_true_cls: 93 | :param y_pred_cls: 94 | :param training_mask: 95 | :return: 96 | ''' 97 | eps = 1e-5 98 | intersection = tf.reduce_sum(y_true_cls * y_pred_cls * training_mask) 99 | union = tf.reduce_sum(y_true_cls * training_mask) + tf.reduce_sum(y_pred_cls * training_mask) + eps 100 | loss = 1. - (2 * intersection / union) 101 | tf.summary.scalar('classification_dice_loss', loss) 102 | return loss 103 | 104 | 105 | 106 | def loss(y_true_cls, y_pred_cls, 107 | y_true_geo, y_pred_geo, 108 | training_mask): 109 | ''' 110 | define the loss used for training, contraning two part, 111 | the first part we use dice loss instead of weighted logloss, 112 | the second part is the iou loss defined in the paper 113 | :param y_true_cls: ground truth of text 114 | :param y_pred_cls: prediction os text 115 | :param y_true_geo: ground truth of geometry 116 | :param y_pred_geo: prediction of geometry 117 | :param training_mask: mask used in training, to ignore some text annotated by ### 118 | :return: 119 | ''' 120 | classification_loss = dice_coefficient(y_true_cls, y_pred_cls, training_mask) 121 | # scale classification loss to match the iou loss part 122 | classification_loss *= 0.01 123 | 124 | # d1 -> top, d2->right, d3->bottom, d4->left 125 | d1_gt, d2_gt, d3_gt, d4_gt, theta_gt = tf.split(value=y_true_geo, num_or_size_splits=5, axis=3) 126 | d1_pred, d2_pred, d3_pred, d4_pred, theta_pred = tf.split(value=y_pred_geo, num_or_size_splits=5, axis=3) 127 | area_gt = (d1_gt + d3_gt) * (d2_gt + d4_gt) 128 | area_pred = (d1_pred + d3_pred) * (d2_pred + d4_pred) 129 | w_union = tf.minimum(d2_gt, d2_pred) + tf.minimum(d4_gt, d4_pred) 130 | h_union = tf.minimum(d1_gt, d1_pred) + tf.minimum(d3_gt, d3_pred) 131 | area_intersect = w_union * h_union 132 | area_union = area_gt + area_pred - area_intersect 133 | L_AABB = -tf.log((area_intersect + 1.0)/(area_union + 1.0)) 134 | L_theta = 1 - tf.cos(theta_pred - theta_gt) 135 | tf.summary.scalar('geometry_AABB', tf.reduce_mean(L_AABB * y_true_cls * training_mask)) 136 | tf.summary.scalar('geometry_theta', tf.reduce_mean(L_theta * y_true_cls * training_mask)) 137 | L_g = L_AABB + 20 * L_theta 138 | 139 | return tf.reduce_mean(L_g * y_true_cls * training_mask) + classification_loss 140 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/nets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/front_all/east_part/nets/__init__.py -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/east_part/sort_cut_line_v2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | import os 4 | 5 | def check_dir(path): 6 | if not(os.path.exists(path) and os.path.isdir(path)): 7 | os.mkdir(path) 8 | 9 | def load_region(file_name): 10 | rect_list = [] 11 | with open(file_name, 'r') as reader: 12 | for line in reader.readlines(): 13 | rect_list.append([int(d) for d in line[:-1].split(',')]) 14 | return rect_list 15 | 16 | def rotate(image, angle, center=None, scale=1.0): 17 | # 获取图像尺寸 18 | (h, w) = image.shape[:2] 19 | # 若未指定旋转中心,则将图像中心设为旋转中心 20 | if center is None: 21 | center = (w / 2, h / 2) 22 | # 执行旋转 23 | M = cv2.getRotationMatrix2D(center, angle, scale) 24 | rotated = cv2.warpAffine(image, M, (w, h)) 25 | # 返回旋转后的图像 26 | return rotated 27 | 28 | def sort_rect(rect_list, over_lap_ratio=0.5): 29 | is_flap = False 30 | 31 | # filter the useless rect (currently not good idea) 32 | 33 | # assign only rect in id card is remain 34 | y_up_list = [d[1] for d in rect_list] 35 | y_down_list = [d[5] for d in rect_list] 36 | width_list = [d[2] - d[0] for d in rect_list] 37 | x_left_list = [d[0] for d in rect_list] 38 | 39 | max_width_index = np.argmax(width_list) 40 | 41 | id_num_y_up = y_up_list[max_width_index] 42 | 43 | if id_num_y_up == np.max(y_up_list): 44 | # the id num in the bottom not flap 45 | is_flap = False 46 | elif id_num_y_up == np.min(y_up_list): 47 | # the id num in the top must flap 48 | is_flap = True 49 | else: 50 | return None, None 51 | 52 | sorted_index = np.argsort(y_up_list).tolist()[::-1] 53 | 54 | line_rect_list = [] 55 | before_rects_index = sorted_index.pop(0) 56 | cache = [before_rects_index] 57 | 58 | while len(sorted_index) > 0: 59 | current_rects_index = sorted_index.pop(0) 60 | before_rects_y_down = y_up_list[before_rects_index] 61 | before_rects_y_up = y_down_list[before_rects_index] 62 | current_rects_y_down = y_up_list[current_rects_index] 63 | current_rects_y_up = y_down_list[current_rects_index] 64 | 65 | if float(current_rects_y_up - before_rects_y_down) / (float(before_rects_y_up - current_rects_y_down) + 0.00001) > over_lap_ratio: 66 | cache.append(current_rects_index) 67 | else: 68 | cache_x_left_list = [x_left_list[d] for d in cache] 69 | cache_index = np.argsort(cache_x_left_list) 70 | cache = [cache[d] for d in cache_index] 71 | line_rect_list.append(cache) 72 | cache = [current_rects_index] 73 | before_rects_index = current_rects_index 74 | line_rect_list.append(cache) 75 | 76 | return line_rect_list[::-1], is_flap 77 | 78 | # def process_sort_cut_line(src_path, txt_filename): 79 | def process_sort_cut_line(img, rect_list): 80 | # rect_list = load_region(os.path.join(src_path, txt_filename)) 81 | # rect_list = load_region(os.path.join(src_path, txt_filename)) 82 | 83 | line_rect_list, is_flap = sort_rect(rect_list) 84 | 85 | if line_rect_list is None and is_flap is None: 86 | print('noise in file: {}'.format(f)) 87 | return None 88 | 89 | return_img_dict = {} 90 | 91 | if is_flap: 92 | line_rect_list = line_rect_list[::-1] 93 | for i, line_list in enumerate(line_rect_list): 94 | for j, rect_index in enumerate(line_list): 95 | # img = cv2.imread(os.path.join(src_path, txt_filename.replace('.txt', '.png')), 1) 96 | x1, y1, x2, y2, x3, y3, x4, y4 = rect_list[rect_index] 97 | if i == 3 or i == len(line_rect_list) - 1: 98 | start = x1 - 1 if x1 - 1 >= 0 else 0 99 | end = x3 + 4 if x3 + 4 <= img.shape[1] else img.shape[1] 100 | # cutted_line = img[y1: y3, x1-3: x3+5] 101 | elif i == 1 or i == 2: 102 | continue 103 | else: 104 | start = x1 if x1 >= 0 else 0 105 | end = x3 if x3 <= img.shape[1] else img.shape[1] 106 | cutted_line = img[y1: y3, start: end] 107 | cutted_line = rotate(cutted_line, 180) 108 | # cv2.imwrite(os.path.join(save_path, '{}_{}.png'.format(i, j)), cutted_line) 109 | return_img_dict['{}_{}'.format(i, g)] = cutted_line 110 | else: 111 | for i, line_list in enumerate(line_rect_list): 112 | for j, rect_index in enumerate(line_list): 113 | # img = cv2.imread(os.path.join(src_path, txt_filename.replace('.txt', '.png')), 1) 114 | x1, y1, x2, y2, x3, y3, x4, y4 = rect_list[rect_index] 115 | if i == 3 or i == len(line_rect_list) - 1: 116 | start = x1 - 1 if x1 - 1 >= 0 else 0 117 | end = x3 + 4 if x3 + 4 <= img.shape[1] else img.shape[1] 118 | elif i == 1 or i == 2: 119 | continue 120 | else: 121 | start = x1 if x1 >= 0 else 0 122 | end = x3 if x3 <= img.shape[1] else img.shape[1] 123 | # cv2.imwrite(os.path.join(save_path, '{}_{}.png'.format(i, j)), img[y1: y3, start: end]) 124 | return_img_dict['{}_{}'.format(i, j)] = img[y1: y3, start: end] 125 | return return_img_dict 126 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | 4 | ### use model_loader 5 | from model_loader import id_card_model 6 | 7 | from rotate_cert.FaceDetection_usedto_adjust_rotate_via_MTCNN import detectFace 8 | # from east_part.east_segment_line import east_main 9 | from number_recognition.number_main import number_processing 10 | from other_recognize.other_main import other_propcessing 11 | import cv2 12 | 13 | import time 14 | 15 | def check_dir(path): 16 | if not(os.path.exists(path) and os.path.isdir(path)): 17 | os.mkdir(path) 18 | 19 | ### send models to cert_front_recog function ### 20 | def cert_front_recog(src_img, models, debug_flag=False): 21 | 22 | debug_path = './{}'.format(time.time()) 23 | if debug_flag: 24 | check_dir(debug_path) 25 | 26 | # 通过人脸旋转校正, face_count为检测到的人脸数量,若为0则代表没检测到,给予警告使用原始图片 27 | ### send mtcnn models to detectFace ### 28 | face_count, rotated_img = detectFace(src_img, models.mtcnn_models) 29 | if face_count < 1: 30 | print('warning: not find face use origin image!') 31 | if debug_flag: 32 | with open(os.path.join(debug_path, 'rotate_cert.txt'), 'w') as out: 33 | out.write('face_count: {}\n'.format(face_count)) 34 | cv2.imwrite(os.path.join(debug_path, 'rotate_cert.jpg'), rotated_img) 35 | 36 | # EAST检测切割行并将结果排序, im为EAST输出图片,east_outpoints是EAST输出的坐标,line_cutted_imgs是行切割后图片 37 | ### use model loader to do east predict ### 38 | im, east_outpoints, line_cutted_imgs = models.east_predict(rotated_img) 39 | # im, east_outpoints, line_cutted_imgs = east_main(rotated_img) 40 | 41 | # 识别行时出现漏或多识别文字行情况,无法确定当前哪行为姓名部分无法继续进行识别,直接返回None 42 | if line_cutted_imgs is None: 43 | return None 44 | else: 45 | line_index = [] 46 | line_content = [] 47 | 48 | for k, v in line_cutted_imgs.items(): 49 | row_index, column_index = k.split('_') 50 | line_index.append(row_index) 51 | line_content.append(v) 52 | 53 | sorted_index = np.argsort(line_index) 54 | sorted_line = [line_content[d] for d in sorted_index] 55 | 56 | if debug_flag: 57 | with open(os.path.join(debug_path, 'east_main_outpoint.txt'), 'w') as out: 58 | for line in east_outpoints: 59 | out.write('{}\n'.format(','.join([str(d) for d in line]))) 60 | cv2.imwrite(os.path.join(debug_path, 'east_output_img.jpg'), im) 61 | debug_line_img_path = os.path.join(debug_path, 'line_img') 62 | check_dir(debug_line_img_path) 63 | # save line 64 | for line_findex, line in enumerate(sorted_line): 65 | cv2.imwrite(os.path.join(debug_line_img_path, '{}.png'.format(line_findex)), line) 66 | 67 | # 分为两个部分,1. 识别身份卡号, 2. 识别姓名及地址 68 | 69 | # 处理身份证号部分 70 | id_number_src_img = sorted_line[-1] 71 | ### send loaded number model ### 72 | # id_number_results, number_characters = number_processing(id_number_src_img) 73 | id_number_results, number_characters = number_processing(id_number_src_img, models) 74 | 75 | if debug_flag: 76 | debug_id_character_path = os.path.join(debug_path, 'number_character') 77 | check_dir(debug_id_character_path) 78 | for number_findex, ch in enumerate(number_characters): 79 | cv2.imwrite(os.path.join(debug_id_character_path, '{}.png'.format(number_findex)), ch) 80 | 81 | # 处理姓名及地址部分 82 | other_part_src_imgs = sorted_line[:-1] 83 | ### send loaded model ### 84 | other_result, line_characters = other_propcessing(other_part_src_imgs, models) 85 | 86 | if debug_flag: 87 | 88 | debug_other_character_path = os.path.join(debug_path, 'other') 89 | check_dir(debug_other_character_path) 90 | 91 | for folder_index, character_list in enumerate(line_characters): 92 | save_level_1 = os.path.join(debug_other_character_path, str(folder_index)) 93 | check_dir(save_level_1) 94 | for findex, ch in enumerate(character_list): 95 | cv2.imwrite(os.path.join(save_level_1, '{}.png'.format(findex)), ch) 96 | 97 | return other_result + [str(id_number_results)] 98 | 99 | if __name__ == '__main__': 100 | 101 | ### first init all model ### 102 | models = id_card_model() 103 | 104 | 105 | start = time.time() 106 | 107 | src_img = cv2.imread('./test.png', 1) 108 | recog_result = cert_front_recog(src_img, models,debug_flag=True) 109 | with open('./test_result.txt', 'w', encoding='utf-8') as out: 110 | out.write('\n'.join(recog_result)) 111 | 112 | print('consume time: {}'.format(time.time() - start)) -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/model_loader.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | from rotate_cert import detect_face 4 | from east_part import locality_aware_nms as nms_locality 5 | from east_part import lanms 6 | from east_part.east_segment_line import resize_image, detect, sort_poly 7 | 8 | import cv2 9 | import time 10 | import math 11 | import os 12 | import numpy as np 13 | from keras.models import model_from_json 14 | 15 | from east_part import model 16 | from east_part.icdar import restore_rectangle 17 | 18 | # for sort east cutted lines 19 | from east_part.sort_cut_line_v2 import process_sort_cut_line 20 | 21 | class id_card_model(): 22 | def __init__(self): 23 | 24 | self._rotate_part = self._load_rotate_part() 25 | print('MTCNN model loaded!') 26 | self._east_part = self._load_east_part('./east_part/east_model') 27 | print('EAST model loaded!') 28 | self._keras_graph, self._number_model, self._other_model = None, None, None 29 | self._load_keras_models() 30 | print('Keras models loaded!') 31 | 32 | @property 33 | def mtcnn_models(self): 34 | return self._rotate_part 35 | 36 | def _load_rotate_part(self): 37 | mtcnn_graph = tf.Graph() 38 | 39 | with mtcnn_graph.as_default(): 40 | mtcnn_sess = tf.Session() 41 | return detect_face.create_mtcnn(mtcnn_sess, None) 42 | 43 | def _load_east_part(self, model_path): 44 | east_graph = tf.Graph() 45 | 46 | with east_graph.as_default(): 47 | input_images = tf.placeholder(tf.float32, shape=[None, None, None, 3], name='input_images') 48 | global_step = tf.get_variable('global_step', [], initializer=tf.constant_initializer(0), trainable=False) 49 | 50 | f_score, f_geometry = model.model(input_images, is_training=False) 51 | 52 | variable_averages = tf.train.ExponentialMovingAverage(0.997, global_step) 53 | saver = tf.train.Saver(variable_averages.variables_to_restore()) 54 | 55 | # with tf.Session(config=tf.ConfigProto(allow_soft_placement=True)) as sess: 56 | sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True)) 57 | # ckpt_state = tf.train.get_checkpoint_state(FLAGS.checkpoint_path) 58 | ckpt_state = tf.train.get_checkpoint_state(model_path) 59 | # model_path = os.path.join(FLAGS.checkpoint_path, os.path.basename(ckpt_state.model_checkpoint_path)) 60 | model_path = os.path.join(model_path, os.path.basename(ckpt_state.model_checkpoint_path)) 61 | print('Restore from {}'.format(model_path)) 62 | saver.restore(sess, model_path) 63 | 64 | self._east_sess = sess 65 | self._f_score = f_score 66 | self._f_geometry = f_geometry 67 | self._input_images = input_images 68 | 69 | def east_predict(self, src_image): 70 | # convert RGB 71 | im = cv2.cvtColor(src_image, cv2.COLOR_BGR2RGB) 72 | 73 | start_time = time.time() 74 | im_resized, (ratio_h, ratio_w) = resize_image(im) 75 | 76 | timer = {'net': 0, 'restore': 0, 'nms': 0} 77 | start = time.time() 78 | score, geometry = self._east_sess.run([self._f_score, self._f_geometry], feed_dict={self._input_images: [im_resized]}) 79 | timer['net'] = time.time() - start 80 | 81 | boxes, timer = detect(score_map=score, geo_map=geometry, timer=timer) 82 | print('net {:.0f}ms, restore {:.0f}ms, nms {:.0f}ms'.format( 83 | timer['net'] * 1000, timer['restore'] * 1000, timer['nms'] * 1000)) 84 | 85 | if boxes is not None: 86 | boxes = boxes[:, :8].reshape((-1, 4, 2)) 87 | boxes[:, :, 0] /= ratio_w 88 | boxes[:, :, 1] /= ratio_h 89 | 90 | duration = time.time() - start_time 91 | print('[timing] {}'.format(duration)) 92 | 93 | line_box = [] 94 | im_with_box = cv2.cvtColor(im, cv2.COLOR_RGB2BGR) 95 | 96 | # save to file 97 | if boxes is not None: 98 | for box in boxes: 99 | # to avoid submitting errors 100 | box = sort_poly(box.astype(np.int32)) 101 | if np.linalg.norm(box[0] - box[1]) < 5 or np.linalg.norm(box[3] - box[0]) < 5: 102 | continue 103 | line_box.append( 104 | [box[0, 0], box[0, 1], box[1, 0], box[1, 1], box[2, 0], box[2, 1], box[3, 0], box[3, 1]]) 105 | # f.write('{},{},{},{},{},{},{},{}\r\n'.format( 106 | # box[0, 0], box[0, 1], box[1, 0], box[1, 1], box[2, 0], box[2, 1], box[3, 0], box[3, 1], 107 | # )) 108 | cv2.polylines(im_with_box, [box.astype(np.int32).reshape((-1, 1, 2))], True, 109 | color=(255, 255, 0), thickness=1) 110 | 111 | line_box_img = process_sort_cut_line(src_image, line_box) 112 | 113 | return im_with_box, line_box, line_box_img 114 | 115 | def _load_keras_models(self): 116 | self._keras_graph = tf.Graph() 117 | 118 | with self._keras_graph.as_default(): 119 | self._number_model = model_from_json(open('number_recognition/model_structure_number.json').read()) 120 | self._number_model.load_weights('number_recognition/model_weight_number.h5') 121 | self._number_model._make_predict_function() 122 | 123 | self._other_model = model_from_json(open('other_recognize/combined_model_structure.json').read()) 124 | self._other_model.load_weights('other_recognize/combined_model_weight.h5') 125 | self._other_model._make_predict_function() 126 | 127 | def number_predict(self, data): 128 | 129 | with self._keras_graph.as_default(): 130 | 131 | predict_class = self._number_model.predict(data, batch_size=18, verbose=0) 132 | # predict_class = model.predict_classes(data, batch_size=batch_size,verbose=0) 133 | predict_list = np.argmax(predict_class, axis=1).tolist() 134 | # predict_list = predict_class.tolist() 135 | # 如果识别的是数字,类型10对应数字 'X' 136 | result = ['X' if i == '10' else i for i in predict_list] 137 | 138 | return result 139 | 140 | def other_predict_prob(self, data): 141 | 142 | with self._keras_graph.as_default(): 143 | prob = self._other_model.predict(data, batch_size=64, verbose=0) 144 | predict_class = np.argmax(prob, axis=1) 145 | 146 | return predict_class, prob.tolist() 147 | def other_predict_class(self, data, mappingList): 148 | 149 | with self._keras_graph.as_default(): 150 | predict_class = self._other_model.predict_classes(data, batch_size=64, verbose=0) 151 | predict_list = predict_class.tolist() 152 | result = [mappingList[i] for i in predict_list] 153 | 154 | return result 155 | 156 | if __name__ == '__main__': 157 | a = id_card_model() 158 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/number_recognition/CNN_test_number_v3_modify.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 1. 将传进来的图片进行预处理,包括灰度化,图像增强,二值化,切割边缘和统一分辨率 3 | # 2. 读取图片内容 4 | # 3. 导入识别模型 5 | # 4. 判断传进来的是姓名或数字图片,分别调用相关CNN模型进行识别,返回识别结果,即图片的内容 6 | 7 | from __future__ import absolute_import 8 | from __future__ import print_function 9 | import os 10 | import cv2 11 | import numpy as np 12 | import pandas as pd 13 | 14 | import time 15 | from keras.models import model_from_json 16 | from sklearn.metrics import confusion_matrix 17 | 18 | 19 | # 1. 图像预处理 20 | # 灰度化 - 图像增强(直方图均衡化)- 二值化 - 切割边缘 - 统一分辨率 21 | 22 | # 1.1 灰度化 23 | def gray_img_modified(imgscr): 24 | # 灰度化 25 | newimg = cv2.cvtColor(imgscr,cv2.COLOR_RGB2GRAY) 26 | # 返回灰度化后的图片 27 | return newimg 28 | 29 | # 1.2 图像增强 - 直方图均衡化 30 | def enhance_img(img): 31 | # 增强 32 | newimg=cv2.equalizeHist(img) 33 | return newimg 34 | 35 | # 1.3 二值化 自定义阈值 36 | def binary_img(img): 37 | 38 | # cv.Threshold(src, dst, threshold, maxValue, thresholdType) 39 | # threshold_type=CV_THRESH_TRUNC: 40 | # 如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y). 41 | # 二值化 42 | ret,newimg=cv2.threshold(img,127,255,cv2.THRESH_TRUNC) # 此种方法效果最好 43 | return newimg 44 | 45 | 46 | # 1.4 判断像素值是否小于阈值,小于则返回像素点对应坐标 47 | def indices(a, func): 48 | return [i for (i, val) in enumerate(a) if func(val)] 49 | 50 | # 1.5 切割边缘 51 | # cutThreahold = 70 默认切割时使用的像素阈值,当像素值<70,认为是字符像素点,而非噪声 52 | def cut_img(img,cutThreahold): 53 | 54 | # img.shape[0] -- 图像高度 img.shape[1] ---- 图像宽度 55 | # 得到每列最小像素值 ---图像横向 56 | width_val = np.min(img,axis=0) 57 | # 得到每行最小像素值 --- 图像纵向 58 | height_val = np.min(img,axis=1) 59 | 60 | # 获得截取部分的左边界 61 | left_point = np.min(indices(width_val,lambda x:x 18: 34 | print("more than 18") 35 | elif len(textline) < 18: 36 | print("less than 18") 37 | if len(textline) != 18: 38 | print("识别卡号不准") 39 | # 保存身份证号 40 | w_list = [text[2] for text in textline[len(textline) - 18:]] 41 | mean_w = np.mean(w_list) 42 | return_img_list = [] 43 | for i, text in enumerate(textline[len(textline) - 18:]): 44 | x, y, w, h = text 45 | if w < int(mean_w): 46 | diff = int((int(mean_w) - w) / 2) 47 | x = x - diff 48 | w = 2 * diff + w 49 | return_img_list.append(imgscr[y:y + h, x:x + w, :]) 50 | except: 51 | print('error happened in {}'.format(file)) 52 | return None 53 | return return_img_list 54 | 55 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/number_recognition/Preprocess.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 4 13:12:30 2017 4 | 5 | @author: yi.xiong 6 | """ 7 | 8 | 9 | import cv2 10 | import numpy as np 11 | 12 | 13 | 14 | # 灰度世界算法 15 | def grayWorld(imgscr): 16 | newimg = imgscr[::] 17 | # newimg = imgscr.copy() 18 | r = imgscr[:,:,0] 19 | g = imgscr[:,:,1] 20 | b = imgscr[:,:,2] 21 | avgR = np.mean(r) 22 | avgG = np.mean(g) 23 | avgB = np.mean(b) 24 | avgRGB = [avgR, avgG, avgB] 25 | grayValue = (avgR + avgG + avgB)/3 26 | scaleValue = [grayValue/avg for avg in avgRGB] 27 | newimg[:,:,0] = scaleValue[0] * r 28 | newimg[:,:,1] = scaleValue[1] * g 29 | newimg[:,:,2] = scaleValue[2] * b 30 | return newimg 31 | 32 | 33 | # 灰度化 34 | def gray_img(imgscr): 35 | return cv2.cvtColor(imgscr, cv2.COLOR_RGB2GRAY) 36 | 37 | 38 | # 图像增强 39 | def enhance_img(imgscr): 40 | return cv2.equalizeHist(imgscr) 41 | 42 | 43 | # 二值化 44 | def binary_img(imgscr): 45 | th2 = cv2.adaptiveThreshold(imgscr,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,29,15) 46 | #th3 = cv2.adaptiveThreshold(imgscr,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,29,20) 47 | return th2 48 | 49 | # 1.4 开操作 自定义结构元素 50 | def open_img(img): 51 | kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5)) # 矩形结构元素 52 | # kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3, 3)) # 椭圆结构元素 53 | # kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3, 3)) # 十字形结构元素 54 | # kernel = np.uint8(np.ones((3,3))) 55 | 56 | #开运算 57 | opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) 58 | return opened 59 | 60 | 61 | # 1.5 闭操作 自定义结构元素 62 | def close_img(img): 63 | kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3)) # 矩形结构元素 64 | # kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3, 3)) # 椭圆结构元素 65 | # kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3, 3)) # 十字形结构元素 66 | # kernel = np.uint8(np.ones((3,3))) 67 | 68 | #闭运算 69 | closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) 70 | return closed 71 | 72 | # 1.6 腐蚀膨胀 自定义结构元素 73 | def ed_img(img): 74 | #kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3)) # 矩形结构元素 75 | # kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3, 3)) # 椭圆结构元素 76 | kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5)) # 十字形结构元素 77 | # kernel = np.uint8(np.ones((3,3))) 78 | #腐蚀图像 79 | eroded = cv2.erode(img,kernel) 80 | #膨胀图像 81 | dilated = cv2.dilate(eroded,kernel) 82 | return eroded 83 | 84 | 85 | # 预处理 86 | def preprocess_full_img(imgscr): 87 | img_light = grayWorld(imgscr) 88 | # 灰度化 89 | img_gray = gray_img(img_light) 90 | 91 | img_binary = binary_img(img_gray) 92 | 93 | return img_binary 94 | 95 | 96 | # 预处理 97 | def preprocess_infor(imgscr): 98 | img_light = grayWorld(imgscr) 99 | # 灰度化 100 | img_gray = gray_img(img_light) 101 | 102 | img_binary = binary_img(img_gray) 103 | 104 | img_open = open_img(img_binary) 105 | 106 | img_ed = ed_img(img_binary) 107 | 108 | # 反色(为保证补白边) 109 | img = 255 - img_open 110 | 111 | return img 112 | 113 | # 预处理 114 | def preprocess(imgscr): 115 | img_light = grayWorld(imgscr) 116 | # 灰度化 117 | img_gray = gray_img(img_light) 118 | 119 | img_binary = binary_img(img_gray) 120 | 121 | return img_binary 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/number_recognition/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/number_recognition/model_structure_number.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "config": [{"class_name": "Convolution2D", "config": {"name": "convolution2d_1", "trainable": true, "activity_regularizer": null, "W_constraint": null, "subsample": [1, 1], "init": "glorot_uniform", "batch_input_shape": [null, 32, 32, 1], "activation": "linear", "b_constraint": null, "bias": true, "input_dtype": "float32", "dim_ordering": "tf", "nb_col": 5, "b_regularizer": null, "nb_filter": 6, "nb_row": 5, "border_mode": "valid", "W_regularizer": null}}, {"class_name": "Activation", "config": {"activation": "relu", "trainable": true, "name": "activation_1"}}, {"class_name": "MaxPooling2D", "config": {"strides": [2, 2], "trainable": true, "dim_ordering": "tf", "pool_size": [2, 2], "border_mode": "valid", "name": "maxpooling2d_1"}}, {"class_name": "Convolution2D", "config": {"name": "convolution2d_2", "activity_regularizer": null, "W_constraint": null, "subsample": [1, 1], "init": "glorot_uniform", "W_regularizer": null, "activation": "linear", "b_constraint": null, "bias": true, "b_regularizer": null, "dim_ordering": "tf", "nb_col": 5, "nb_filter": 16, "nb_row": 5, "border_mode": "valid", "trainable": true}}, {"class_name": "Activation", "config": {"activation": "relu", "trainable": true, "name": "activation_2"}}, {"class_name": "MaxPooling2D", "config": {"strides": [2, 2], "trainable": true, "dim_ordering": "tf", "pool_size": [2, 2], "border_mode": "valid", "name": "maxpooling2d_2"}}, {"class_name": "Convolution2D", "config": {"name": "convolution2d_3", "activity_regularizer": null, "W_constraint": null, "subsample": [1, 1], "init": "glorot_uniform", "W_regularizer": null, "activation": "linear", "b_constraint": null, "bias": true, "b_regularizer": null, "dim_ordering": "tf", "nb_col": 5, "nb_filter": 120, "nb_row": 5, "border_mode": "valid", "trainable": true}}, {"class_name": "Activation", "config": {"activation": "relu", "trainable": true, "name": "activation_3"}}, {"class_name": "Flatten", "config": {"trainable": true, "name": "flatten_1"}}, {"class_name": "Dense", "config": {"activity_regularizer": null, "W_constraint": null, "init": "normal", "W_regularizer": null, "activation": "linear", "output_dim": 120, "b_constraint": null, "input_dim": null, "b_regularizer": null, "trainable": true, "bias": true, "name": "dense_1"}}, {"class_name": "Activation", "config": {"activation": "relu", "trainable": true, "name": "activation_4"}}, {"class_name": "Dense", "config": {"activity_regularizer": null, "W_constraint": null, "init": "normal", "W_regularizer": null, "activation": "linear", "output_dim": 11, "b_constraint": null, "input_dim": null, "b_regularizer": null, "trainable": true, "bias": true, "name": "dense_2"}}, {"class_name": "Activation", "config": {"activation": "softmax", "trainable": true, "name": "activation_5"}}], "keras_version": "1.1.0"} -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/number_recognition/model_weight_number.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/front_all/number_recognition/model_weight_number.h5 -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/number_recognition/number_main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | 4 | from number_recognition.Overall_Process_input_line import segment_number 5 | from number_recognition.CNN_test_number_v3_modify import do_recognize 6 | 7 | ### send loaded model ### 8 | # def number_processing(id_num_src_img): 9 | def number_processing(id_num_src_img, model): 10 | character_img_list = segment_number(id_num_src_img) 11 | 12 | if character_img_list is None: 13 | return None 14 | 15 | ### send loaded model ### 16 | # return do_recognize(character_img_list), character_img_list 17 | return do_recognize(character_img_list, model), character_img_list 18 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/other_recognize/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/other_recognize/combined_model_structure.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "keras_version": "1.1.0", "config": [{"class_name": "Convolution2D", "config": {"subsample": [1, 1], "batch_input_shape": [null, 32, 32, 1], "trainable": true, "dim_ordering": "tf", "b_constraint": null, "activity_regularizer": null, "b_regularizer": null, "border_mode": "valid", "input_dtype": "float32", "nb_filter": 16, "W_constraint": null, "activation": "linear", "bias": true, "nb_col": 3, "name": "convolution2d_1", "init": "glorot_uniform", "W_regularizer": null, "nb_row": 3}}, {"class_name": "Activation", "config": {"name": "activation_1", "activation": "tanh", "trainable": true}}, {"class_name": "MaxPooling2D", "config": {"border_mode": "valid", "pool_size": [2, 2], "strides": [2, 2], "name": "maxpooling2d_1", "dim_ordering": "tf", "trainable": true}}, {"class_name": "Convolution2D", "config": {"nb_row": 3, "subsample": [1, 1], "init": "glorot_uniform", "dim_ordering": "tf", "b_constraint": null, "activity_regularizer": null, "b_regularizer": null, "border_mode": "valid", "nb_filter": 32, "activation": "linear", "bias": true, "nb_col": 3, "name": "convolution2d_2", "trainable": true, "W_regularizer": null, "W_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_2", "activation": "tanh", "trainable": true}}, {"class_name": "MaxPooling2D", "config": {"border_mode": "valid", "pool_size": [2, 2], "strides": [2, 2], "name": "maxpooling2d_2", "dim_ordering": "tf", "trainable": true}}, {"class_name": "Convolution2D", "config": {"nb_row": 3, "subsample": [1, 1], "init": "glorot_uniform", "dim_ordering": "tf", "b_constraint": null, "activity_regularizer": null, "b_regularizer": null, "border_mode": "valid", "nb_filter": 32, "activation": "linear", "bias": true, "nb_col": 3, "name": "convolution2d_3", "trainable": true, "W_regularizer": null, "W_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_3", "activation": "tanh", "trainable": true}}, {"class_name": "Flatten", "config": {"name": "flatten_1", "trainable": true}}, {"class_name": "Dense", "config": {"output_dim": 128, "trainable": true, "b_regularizer": null, "b_constraint": null, "activity_regularizer": null, "W_regularizer": null, "activation": "linear", "bias": true, "name": "dense_1", "init": "glorot_normal", "W_constraint": null, "input_dim": null}}, {"class_name": "Activation", "config": {"name": "activation_4", "activation": "tanh", "trainable": true}}, {"class_name": "Dropout", "config": {"name": "dropout_1", "trainable": true, "p": 0.5}}, {"class_name": "Dense", "config": {"output_dim": 7024, "trainable": true, "b_regularizer": null, "b_constraint": null, "activity_regularizer": null, "W_regularizer": null, "activation": "linear", "bias": true, "name": "dense_2", "init": "glorot_normal", "W_constraint": null, "input_dim": null}}, {"class_name": "Activation", "config": {"name": "activation_5", "activation": "softmax", "trainable": true}}]} -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/other_recognize/combined_model_weight.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/front_all/other_recognize/combined_model_weight.h5 -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/other_recognize/do_recognition_for_sorted_id_card.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # caculate accuracy of model by picture 4 | from __future__ import absolute_import 5 | from __future__ import print_function 6 | import os 7 | import cv2 8 | import numpy as np 9 | import shutil 10 | from keras.models import model_from_json 11 | import csv 12 | 13 | import time 14 | 15 | ## globale ## 16 | # 设置切割图片的像素阈值 17 | cutThreahold = 70 18 | # 设置分辨率的统一值 19 | size = 32 20 | # 设置导入图片的尺寸和通道 21 | img_rows, img_cols = 32, 32 22 | # 单通道(灰度图片)-1 RGB图片-3 23 | img_channels = 1 24 | # 设定每次进入计算的样本batch尺寸 25 | batch_size=50 26 | 27 | 28 | # 2. 图像预处理 29 | # 灰度化 - 图像增强(直方图均衡化)- 二值化 - 切割边缘 - 统一分辨率 30 | 31 | # 2.1 灰度化 32 | def gray_img_modified(imgscr): 33 | # 读入 34 | # imgscr = cv2.imread(filename) 35 | if imgscr is None: 36 | return 'Fault' 37 | # 灰度化 38 | newimg = cv2.cvtColor(imgscr,cv2.COLOR_RGB2GRAY) 39 | # 返回灰度化后的图片 40 | return newimg 41 | 42 | # 2.2 图像增强 - 直方图均衡化 43 | def enhance_img(img): 44 | # 增强 45 | newimg=cv2.equalizeHist(img) 46 | return newimg 47 | 48 | # 2.3 二值化 自定义阈值 49 | def binary_img(img): 50 | 51 | # cv.Threshold(src, dst, threshold, maxValue, thresholdType) 52 | # threshold_type=CV_THRESH_TRUNC: 53 | # 如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y). 54 | # 二值化 55 | ret,newimg=cv2.threshold(img,127,255,cv2.THRESH_TRUNC) # 此种方法效果最好 56 | return newimg 57 | 58 | 59 | # 2.4 判断像素值是否小于阈值,小于则返回像素点对应坐标 60 | def indices(a, func): 61 | return [i for (i, val) in enumerate(a) if func(val)] 62 | 63 | # 2.5 切割边缘 64 | # cutThreahold = 70 默认切割时使用的像素阈值,当像素值<70,认为是字符像素点,而非噪声 65 | def cut_img(img,cutThreahold): 66 | 67 | # img.shape[0] -- 图像高度 img.shape[1] ---- 图像宽度 68 | # 得到每列最小像素值 ---图像横向 69 | width_val = np.min(img,axis=0) 70 | # 得到每行最小像素值 --- 图像纵向 71 | height_val = np.min(img,axis=1) 72 | 73 | # 获得截取部分的左边界 74 | left_point = np.min(indices(width_val,lambda x:x 3: 65 | peak_left_pts.append(temp[-1] - 3) 66 | peak_right_pts.append(temp[0] - 3) 67 | temp = [pt] 68 | # add end two point 69 | peak_left_pts.append(temp[-1] - 3) 70 | peak_right_pts.append(temp[0] - 3) 71 | # remove the point when we first add (three zero) 72 | peak_left_pts.remove(peak_left_pts[-1]) 73 | peak_right_pts.remove(peak_right_pts[0]) 74 | # filter the noise peak 75 | region_width_list = [] 76 | updated_peak_left = [] 77 | updated_peak_right = [] 78 | 79 | for i in range(len(peak_left_pts)): 80 | current_width = peak_right_pts[i] - peak_left_pts[i] 81 | if current_width > peak_width_threshold: 82 | region_width_list.append(current_width) 83 | updated_peak_left.append(peak_left_pts[i]) 84 | updated_peak_right.append(peak_right_pts[i]) 85 | # if all peak is noise return None 86 | if len(region_width_list) == 0: 87 | return None 88 | # add two pixel because dilation have been extend 10 pixels 89 | left_point = updated_peak_left[0] + 2 90 | right_point = updated_peak_right[-1] -2 91 | # left and right cut down 92 | left_right_cutted_img = src_img[:, left_point:right_point] 93 | 94 | img = cv2.cvtColor(left_right_cutted_img, cv2.COLOR_BGR2GRAY) 95 | 96 | cv2.normalize(img, img, 0, 255, cv2.NORM_MINMAX) 97 | try: 98 | # do hog (because cutted image have possibly get error in HOG detect so use try and except 99 | fd, hog_img = hog(img, orientations=4, pixels_per_cell=(3, 3), visualise=True) 100 | 101 | hog_img = np.asarray(hog_img, dtype='uint8') 102 | 103 | cv2.normalize(hog_img, hog_img, 0, 255, cv2.NORM_MINMAX) 104 | res, binary_img = cv2.threshold(hog_img, 100, 255, cv2.THRESH_BINARY) 105 | 106 | y_map = np.mean(binary_img, axis=1) 107 | y_map /= np.max(y_map) 108 | 109 | # avoid error 110 | try: 111 | down_point = np.min(np.where(y_map > threshold_value)[0]) 112 | except: 113 | down_point = 0 114 | try: 115 | up_point = np.max(np.where(y_map > threshold_value)[0]) 116 | except: 117 | up_point = len(y_map) -1 118 | 119 | # adjust 1 pixel (make sure not cutted the text part) 120 | down_point = down_point - 1 if down_point > 0 else down_point 121 | up_point = up_point + 1 if up_point < src_img.shape[0] - 1 else up_point 122 | 123 | return src_img[down_point:up_point, left_point:right_point] 124 | except: 125 | return None 126 | 127 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/rotate_cert/FaceDetection_usedto_adjust_rotate_via_MTCNN.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 4 13:12:30 2017 4 | 5 | @author: li.kou 6 | """ 7 | 8 | import sys 9 | sys.path.append('../') 10 | 11 | import numpy as np 12 | import cv2 13 | import os 14 | from math import fabs, sin, cos, radians 15 | 16 | # MTCNN 17 | import tensorflow as tf 18 | # import detect_face 19 | from rotate_cert import detect_face 20 | import time 21 | import numpy as np 22 | 23 | # global init 24 | 25 | # sess = tf.Session() 26 | # pnet, rnet, onet = detect_face.create_mtcnn(sess, None) 27 | 28 | minsize = 40 # minimum size of face 29 | threshold = [0.6, 0.7, 0.9] # three steps's threshold 30 | factor = 0.709 # scale factor 31 | 32 | # end global init 33 | 34 | def check_dir(path): 35 | if not(os.path.isdir(path) and os.path.exists(path)): 36 | os.mkdir(path) 37 | 38 | def detectFace(imgscr, models): 39 | # global sess, pnet, rnet, onet, minsize, threshold, factor 40 | global minsize, threshold, factor 41 | pnet, rnet, onet = models 42 | # modified 43 | face_cascade = cv2.CascadeClassifier("./haarcascade_frontalface_default.xml") 44 | 45 | for angle in [0, 90, 180, 270]: 46 | img_rotated = rotate_img_alter(imgscr, angle) 47 | img_rotated = cv2.cvtColor(img_rotated, cv2.COLOR_BGR2RGB) 48 | bounding_boxes, points = detect_face.detect_face(img_rotated, minsize, pnet, rnet, onet, threshold, factor) 49 | 50 | ### very important reset tensorflow session ### 51 | tf.reset_default_graph() 52 | 53 | if len(bounding_boxes) > 0: 54 | return len(bounding_boxes), cv2.cvtColor(img_rotated, cv2.COLOR_RGB2BGR) 55 | 56 | for angle in [0,90,180,270]: 57 | img_rotated = rotate_img_alter(imgscr,angle) 58 | gray = cv2.cvtColor(img_rotated, cv2.COLOR_BGR2GRAY) 59 | faces = face_cascade.detectMultiScale(gray, 1.2, 5) #1.2和5是特征的最小、最大检测窗口,它改变检测结果也会改变 60 | if len(faces) > 0: 61 | return len(faces), img_rotated 62 | return 0, imgscr 63 | 64 | # # 将原图调整为正方形 65 | # def adjustsize_img(img): 66 | # width = img.shape[1] #图像宽度 67 | # height = img.shape[0] #图像高度 68 | # channel = img.shape[2] 69 | # if height > width: 70 | # size = height 71 | # else: 72 | # size = width 73 | # # 创建一个空白的新图片,尺寸为size*size*size 74 | # newimg = np.zeros((size,size,channel), np.uint8) 75 | # # 放入size*size大小图片的起始位置 76 | # offset_height = int(np.ceil((size - height) / 2)) 77 | # offset_width = int(np.ceil((size - width) / 2)) 78 | # # 将调整比例后的图片内容复制到空白图片 79 | # for x in range(height): 80 | # for y in range(width): 81 | # for i in range(channel): 82 | # newimg[x +offset_height, y +offset_width,i] = img[x, y,i] 83 | # # 返回预处理完成后的图片 84 | # return newimg 85 | # 86 | # 87 | # def rotate_img(imgscr,angle): 88 | # if angle == 0: 89 | # return imgscr 90 | # height,width = imgscr.shape[:2] 91 | # if height != width: 92 | # img_adjusted = adjustsize_img(imgscr) 93 | # else: 94 | # img_adjusted = imgscr 95 | # height,width = img_adjusted.shape[:2] 96 | # 97 | # #第一个参数旋转中心,第二个参数旋转角度,第三个参数:缩放比例 98 | # M = cv2.getRotationMatrix2D((width/2,height/2),angle,1) 99 | # return cv2.warpAffine(img_adjusted,M,(width,height)) #第三个参数:变换后的图像大小 width * height 100 | 101 | def rotate_img_alter(img, angle):#try this... 102 | height,width=img.shape[:2] 103 | 104 | heightNew=int(width*fabs(sin(radians(angle)))+height*fabs(cos(radians(angle)))) 105 | widthNew=int(height*fabs(sin(radians(angle)))+width*fabs(cos(radians(angle)))) 106 | 107 | matRotation=cv2.getRotationMatrix2D((width/2,height/2),angle,1) 108 | 109 | matRotation[0,2] +=(widthNew-width)/2 110 | matRotation[1,2] +=(heightNew-height)/2 111 | 112 | imgRotation=cv2.warpAffine(img,matRotation,(widthNew,heightNew),borderValue=(255,255,255)) 113 | return imgRotation 114 | 115 | if __name__ == '__main__': 116 | src_image_path = r'C:\Users\li.kou\Desktop\test_front' 117 | dst_image_path = r'C:\Users\li.kou\Desktop\dst' 118 | error_image_path = r'C:\Users\li.kou\Desktop\error' 119 | 120 | check_dir(dst_image_path) 121 | check_dir(error_image_path) 122 | 123 | consume_time_list = [] 124 | 125 | for f in os.listdir(src_image_path): 126 | start_time = time.time() 127 | face, img = detectFace(cv2.imread(os.path.join(src_image_path, f), 1)) 128 | consume_time_list.append(time.time() - start_time) 129 | # if len(face) < 1: 130 | if face < 1: 131 | print('error : {}'.format(f)) 132 | cv2.imwrite(os.path.join(error_image_path, f), img) 133 | else: 134 | print('finish : {}'.format(f)) 135 | cv2.imwrite(os.path.join(dst_image_path, f), img) 136 | 137 | print('average consume time: {}'.format(np.mean(consume_time_list))) 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/rotate_cert/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/rotate_cert/det1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/front_all/rotate_cert/det1.npy -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/rotate_cert/det2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/front_all/rotate_cert/det2.npy -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/rotate_cert/det3.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/front_all/rotate_cert/det3.npy -------------------------------------------------------------------------------- /code/ch25_身份证汉字和数字识别/front_all/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/code/ch25_身份证汉字和数字识别/front_all/test.png -------------------------------------------------------------------------------- /datasets/README.md: -------------------------------------------------------------------------------- 1 | 这是 datasets 文件夹的帮助文档。 2 | 3 | 本文件夹会保存相关的数据集,包括 csv 文件等。 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 这是 docs 文件夹的帮助文档。 2 | 3 | 本文件夹记录一些说明文档,包括勘误、读者反馈等。 4 | 5 | -------------------------------------------------------------------------------- /imgs/README.md: -------------------------------------------------------------------------------- 1 | 这是 imgs 文件夹的帮助文档。 2 | 3 | 本文件夹会保存本仓库中代码或文档中使用到的图片。 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /imgs/book-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/imgs/book-cover.jpg -------------------------------------------------------------------------------- /勘误.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinapnr/How-to-Python-and-Machine-Learning-book-code/fbc8a0cb507eec3c0758e238e5a2eb9b99f987b6/勘误.pdf --------------------------------------------------------------------------------