A---Yang

导航

 

  (一)选题背景

  首先,验证码是最初的设定是通过验证码对人类和非人类行为进行区分;大多数的网站在进行注册或者登录时都需要用到图片验证码,这都是为了防止用户通过机器人或者脚本进行自动注册,登录等设计网络安全的问题,所以破解这种技术,提高网站的信息安全就显得很有必要。验证码图片的识别可能就是破解技术的核心,希望通过这次课设能对机器学习有更好的了解。

 

  (二)机器学习案例设计方案

  通过增加干扰线和噪点自主生成验证码图片,然后对自动生成的数据集进行降噪和灰度处理,然后建立训练模型,卷积神经网络池化然后通过全连接层进行处理,最后将图片与机器学习预测的结果进行比对。

参考来源:https://gitee.com/kdldbq/verification-decoder

数据集参考来源:

https://blog.csdn.net/LLC25802580/article/details/123103381?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-123103381-blog-90695681.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-123103381-blog-90695681.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=2

 

  (三)机器学习实现步骤

1.打开 jupyter notebook

导入需要的包

1 import random
2 import os
3 from PIL import Image,ImageDraw,ImageFont,ImageFilter
4 from PIL import Image
5 import numpy as np
6 import tensorflow as tf
7 from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics

2.绘制验证码图片

 1 # 设置验证码图片的宽和高
 2 width = 80
 3 height = 20
 4 
 5 def getRandomColor(is_light = True):
 6    #设置随机的颜色
 7     r = random.randint(0, 127) +int(is_light)* 128
 8     g = random.randint(0, 127) +int(is_light)* 128
 9     b = random.randint(0, 127) +int(is_light)* 128
10     return (r, g, b)
11 
12 # 这里设置生成的验证码,字母通过ascii码值进行生成
13 def getRandomChar():
14     random_num = str(random.randint(0, 9))      # 数字 0~9
15     random_lower = chr(random.randint(97, 122)) # 小写字母a~z
16     random_upper = chr(random.randint(65, 90))  # 大写字母A~Z
17     random_char = random.choice([random_num, random_upper])
18     return random_num
19 
20 #对生成的验证码图片增加干扰线并且设置随机的颜色
21 def drawLine(draw):
22     for i in range(4):
23         x1 = random.randint(0, width)
24         x2 = random.randint(0, width)
25         y1 = random.randint(0, height)
26         y2 = random.randint(0, height)
27         draw.line((x1, y1, x2, y2), fill=getRandomColor(is_light=True))
28 
29 #对生成的验证码图片增加噪点,并且对噪点设置随机的颜色        
30 def drawPoint(draw):
31     
32     for i in range(80):
33         x = random.randint(0, width)
34         y = random.randint(0, height)
35         draw.point((x,y), fill=getRandomColor(is_light=True))
36 
37 #设置生成的验证码背景图片
38 def createImg(folder):
39     # 随机生成一个颜色为背景色颜色
40     bg_color = getRandomColor(is_light=True)
41 
42     # 创建一张随机背景色的图片,大小为上面定义的宽和高
43     img = Image.new(mode="RGB", size=(width, height), color=bg_color)
44 
45     # 获取图片画笔,用于描绘字
46     draw = ImageDraw.Draw(img)
47 
48     # 修改验证码的字体
49     font = ImageFont.truetype(font="arial.ttf", size=18)
50 
51     # 保存验证码的图片名字
52     file_name = ''
53 
54     # 这里生成4位随机数,就循环4次
55     for i in range(4):
56         # 随机生成4种字符+4种颜色
57         random_txt = getRandomChar()
58         txt_color = getRandomColor(is_light=False)
59 
60         # 避免文字颜色和背景色一致重合
61         while txt_color == bg_color:
62             txt_color = getRandomColor(is_light=False)
63         # 根据坐标填充文字
64         draw.text((15 + 15 * i, 0), text=random_txt, fill=txt_color, font=font)
65         file_name +=random_txt
66         
67     # 调用干扰线和画噪点的方法
68     drawLine(draw)
69     drawPoint(draw)
70     
71     #显示生成的随机验证码
72     print(file_name)
73 
74     # 打开图片操作,并保存在当前文件夹下
75     with open("./{}/{}.png".format(folder,file_name), "wb") as f:
76 
77         img.save(f, format="png")
78 
79 if __name__ == '__main__':
80     # 创建10000张验证码
81     num = 10000
82 
83     # 创建train和test文件夹,划分训练集跟测试集
84     os.path.exists('train') or  os.makedirs('train')
85     os.path.exists('test') or os.makedirs('test')
86 
87     # 通过循环,在对应的文件夹生成验证码
88     for i in range(num):
89         createImg('train')
90     for i in range(2000):
91         createImg('test')

3.查看其中的一个图片验证码

1 from PIL import Image
2 import matplotlib.pyplot as plt
3 img=Image.open('./test/0196.png')
4 plt.imshow(img)
5 plt.show()

 

4.查看train下的文件

1 import os
2 filePath=r'F:\泉州信息工程学院\大二(上)\Python高级应用\期末资料\train'
3 #查看train目录下的图片名字
4 print(os.listdir(filePath))

 

5.接下来需要对生成的图片验证码进行降噪处理,使得机器能够更好的识别

 1 threshold = 160  # 通过设置阈值,去除干扰物
 2 
 3 for i in range(image.width):
 4     for j in range(image.height):
 5         #获取图片中像素点的颜色值
 6         r,g,b = image.getpixel((i,j))
 7         if (r > threshold or g >threshold or b > threshold):
 8             r=255
 9             g=255
10             b=255
11             image.putpixel((i,j),(r,g,b))
12         else:
13             r = 0
14             g = 0
15             b = 0
16             image.putpixel((i, j), (r, g, b))
17 
18 # 灰度图片
19 image = image.convert('L')
20 plt.imshow(image)
21 plt.show()

 

6.定义路径

1 #设置警告信息,定义训练集,数据集,模型文件的位置
2 os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
3 train_data_dir = r'F:\泉州信息工程学院\大二(上)\Python高级应用\期末资料\train'
4 test_data_dir = r'F:\泉州信息工程学院\大二(上)\Python高级应用\期末资料\test'
5 model_dir = r'F:\泉州信息工程学院\大二(上)\Python高级应用\期末资料\model_dir\model.h5'

7.构建模型

 1 model = Sequential([
 2     # 第一个卷积层
 3     layers.Conv2D(32, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
 4     layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
 5     # layers.Dropout(0.25),
 6     # 第二个卷积层
 7     layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
 8     layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
 9     
10     # 第三个卷积层
11     layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
12     layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
13     # layers.Dropout(0.25),
14     layers.Flatten(),
15 
16     # 全连接
17     layers.Dense(128),
18     layers.Dense(40), # 因为这里我们4个数字,所以也就4*10
19     layers.Reshape([4,10])
20 ])
21 
22 model.build(input_shape=[None, 20, 80, 1])
23 #输出模型结构信息
24 model.summary()

 

8.对构建的模型进行训练

 1 for epoch in range(5):
 2         for step, (x, y) in enumerate(train_db):
 3             # 有的时候验证码不是这种格式,就没处理所以就不是的直接过滤
 4             if x.shape == (10, 20, 80, 1):
 5                 with tf.GradientTape() as tape:
 6                     # logits
 7                     logits = model(x)
 8                     # 真实值就行one_hot编码来对比
 9                     y_onehot = tf.one_hot(y, depth=10)
10                     # 设置loss
11                     loss_ce = tf.losses.MSE(y_onehot, logits)
12                     loss_ce = tf.reduce_mean(loss_ce)
13                 # 不断更新梯度
14                 grads = tape.gradient(loss_ce, model.trainable_variables)
15                 optimizer.apply_gradients(zip(grads, model.trainable_variables))
16 
17                 #输出训练次数,损失函数
18                 if step % 10 == 0:
19                     print(epoch, step, 'loss:', float(loss_ce))
20     # 因为5次就已经很高了,所以直接保存模型
21     model.save('model.h5')

9.训练产生的数据进行保存

1 # 如果存在模型,就拿以前的继续训练,不用再从头开始
2 if os.path.exists(model_dir):
3 
4     model = tf.keras.models.load_model('model.h5', compile=False)

10.对图片验证码进行预测

1 model = tf.keras.models.load_model('model.h5', compile=False)
2 for step, (x, y) in enumerate(test_db):
3     if x.shape == (1, 20, 80, 1):
4         logits = model(x)
5         logits = tf.nn.softmax(logits)
6         pred = tf.cast(tf.argmax(logits,axis=2),dtype=tf.int32)
7         print('预测值:',pred[0].numpy(),'真实值:',y[0].numpy(),'是否相同:',int(tf.reduce_sum(tf.cast(tf.equal(pred,y),dtype=tf.int32)))==4)
8         

 

11.全部代码:

  1 import random
  2 import os
  3 from PIL import Image,ImageDraw,ImageFont,ImageFilter
  4 
  5 # 设置验证码图片的宽和高
  6 width = 80
  7 height = 20
  8 
  9 def getRandomColor(is_light = True):
 10    #设置随机的颜色
 11     r = random.randint(0, 127) +int(is_light)* 128
 12     g = random.randint(0, 127) +int(is_light)* 128
 13     b = random.randint(0, 127) +int(is_light)* 128
 14     return (r, g, b)
 15 
 16 # 这里设置生成的验证码,字母通过ascii码值进行生成
 17 def getRandomChar():
 18     random_num = str(random.randint(0, 9))      # 数字 0~9
 19     random_lower = chr(random.randint(97, 122)) # 小写字母a~z
 20     random_upper = chr(random.randint(65, 90))  # 大写字母A~Z
 21     random_char = random.choice([random_num, random_upper])
 22     return random_num
 23 
 24 #对生成的验证码图片增加干扰线并且设置随机的颜色
 25 def drawLine(draw):
 26     for i in range(4):
 27         x1 = random.randint(0, width)
 28         x2 = random.randint(0, width)
 29         y1 = random.randint(0, height)
 30         y2 = random.randint(0, height)
 31         draw.line((x1, y1, x2, y2), fill=getRandomColor(is_light=True))
 32 
 33 #对生成的验证码图片增加噪点,并且对噪点设置随机的颜色        
 34 def drawPoint(draw):
 35     
 36     for i in range(80):
 37         x = random.randint(0, width)
 38         y = random.randint(0, height)
 39         draw.point((x,y), fill=getRandomColor(is_light=True))
 40 
 41 #设置生成的验证码背景图片
 42 def createImg(folder):
 43     # 随机生成一个颜色为背景色颜色
 44     bg_color = getRandomColor(is_light=True)
 45 
 46     # 创建一张随机背景色的图片,大小为上面定义的宽和高
 47     img = Image.new(mode="RGB", size=(width, height), color=bg_color)
 48 
 49     # 获取图片画笔,用于描绘字
 50     draw = ImageDraw.Draw(img)
 51 
 52     # 修改验证码的字体
 53     font = ImageFont.truetype(font="arial.ttf", size=18)
 54 
 55     # 保存验证码的图片名字
 56     file_name = ''
 57 
 58     # 这里生成4位随机数,就循环4次
 59     for i in range(4):
 60         # 随机生成4种字符+4种颜色
 61         random_txt = getRandomChar()
 62         txt_color = getRandomColor(is_light=False)
 63 
 64         # 避免文字颜色和背景色一致重合
 65         while txt_color == bg_color:
 66             txt_color = getRandomColor(is_light=False)
 67         # 根据坐标填充文字
 68         draw.text((15 + 15 * i, 0), text=random_txt, fill=txt_color, font=font)
 69         file_name +=random_txt
 70         
 71     # 调用干扰线和画噪点的方法
 72     drawLine(draw)
 73     drawPoint(draw)
 74     
 75     #显示生成的随机验证码
 76     print(file_name)
 77 
 78     # 打开图片操作,并保存在当前文件夹下
 79     with open("./{}/{}.png".format(folder,file_name), "wb") as f:
 80 
 81         img.save(f, format="png")
 82 
 83 if __name__ == '__main__':
 84     # 创建10000张验证码
 85     num = 10000
 86 
 87     # 创建train和test文件夹,划分训练集跟测试集
 88     os.path.exists('train') or  os.makedirs('train')
 89     os.path.exists('test') or os.makedirs('test')
 90 
 91     # 通过循环,在对应的文件夹生成验证码
 92     for i in range(num):
 93         createImg('train')
 94     for i in range(2000):
 95         createImg('test')
 96 
 97 from PIL import Image
 98 import matplotlib.pyplot as plt
 99 img=Image.open('./test/0196.png')
100 plt.imshow(img)
101 plt.show()
102 
103 import os
104 filePath=r'F:\泉州信息工程学院\大二(上)\Python高级应用\期末资料\train'
105 #查看train目录下的图片名字
106 print(os.listdir(filePath))
107 
108 image=Image.open('./test/0196.png')
109 threshold = 160  # 通过设置阈值,去除干扰物
110 
111 for i in range(image.width):
112     for j in range(image.height):
113         #获取图片中像素点的颜色值
114         r,g,b = image.getpixel((i,j))
115         if (r > threshold or g >threshold or b > threshold):
116             r=255
117             g=255
118             b=255
119             image.putpixel((i,j),(r,g,b))
120         else:
121             r = 0
122             g = 0
123             b = 0
124             image.putpixel((i, j), (r, g, b))
125 
126 # 灰度图片
127 image = image.convert('L')
128 plt.imshow(image)
129 plt.show()
130 
131 import numpy as np
132 import tensorflow as tf
133 from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics
134 #设置警告信息,定义训练集,数据集,模型文件的位置
135 os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
136 train_data_dir = r'F:\泉州信息工程学院\大二(上)\Python高级应用\期末资料\train'
137 test_data_dir = r'F:\泉州信息工程学院\大二(上)\Python高级应用\期末资料\test'
138 model_dir = r'F:\泉州信息工程学院\大二(上)\Python高级应用\期末资料\model_dir\model.h5'
139 
140 def denoising(image):
141     threshold = 160  # 通过设置阈值,去除不必要的干扰物
142 
143     for i in range(image.width):
144         for j in range(image.height):
145             r,g,b = image.getpixel((i,j))
146             if (r > threshold or g >threshold or b > threshold):
147                 r=255
148                 g=255
149                 b=255
150                 image.putpixel((i,j),(r,g,b))
151             else:
152                 r = 0
153                 g = 0
154                 b = 0
155                 image.putpixel((i, j), (r, g, b))
156 
157     # 灰度图片
158     image = image.convert('L')
159     return image
160 
161 def gen_train_data(filePath):
162 
163     #返回指定的文件夹包含的文件或文件夹的名字的列表。
164     train_file_name_list = os.listdir(filePath)
165     # 返回值
166     x_data = []
167     y_data = []
168 
169     # 对每个图片单独处理
170     for selected_train_file_name in train_file_name_list:
171         if selected_train_file_name.endswith('.png'):
172 
173             # 获取图片对象
174             captcha_image = Image.open(os.path.join(filePath, selected_train_file_name))
175 
176             # 对图片去噪音,利用上面的方面进行处理
177             captcha_image = denoising(captcha_image)
178             # captcha_image = captcha_image.convert('L') # 对于简单的不用去噪,灰度反而更有利
179             captcha_image_np = np.array(captcha_image)
180             img_np = np.array(captcha_image_np)
181             # 把每个处理后的数据,放进x_data,y_data
182             x_data.append(img_np)
183             y_data.append(np.array(list(selected_train_file_name.split('.')[0])).astype(np.int))
184             
185 
186     x_data = np.array(x_data).astype(np.float)
187     y_data = np.array(y_data)
188     return x_data,y_data
189 
190 # 生成训练集
191 (x,y) = gen_train_data(train_data_dir)
192 # 生成测试集
193 (x_test,y_test) = gen_train_data(test_data_dir)
194 print(x.shape,y.shape) #(num个图片验证码, 20宽, 80高) 
195 
196 
197 def preprocess(x,y):
198   
199     x = 2*tf.cast(x,dtype=tf.float32)/255.-1
200     x = tf.expand_dims(x,-1)
201     y = tf.cast(y,dtype=tf.int32)
202     return x,y
203 
204 batch_size = 10
205 train_db = tf.data.Dataset.from_tensor_slices((x,y))
206 train_db = train_db.map(preprocess).batch(batch_size)
207 
208 test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
209 test_db = test_db.map(preprocess).batch(1)
210 
211 
212 model = Sequential([
213     # 第一个卷积层
214     layers.Conv2D(32, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
215     layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
216     # layers.Dropout(0.25),
217     # 第二个卷积层
218     layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
219     layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
220     
221     # 第三个卷积层
222     layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
223     layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
224     # layers.Dropout(0.25),
225     layers.Flatten(),
226 
227     # 全连接
228     layers.Dense(128),
229     layers.Dense(40), # 因为这里我们4个数字,所以也就4*10可能性
230     layers.Reshape([4,10])
231 ])
232 
233 model.build(input_shape=[None, 20, 80, 1])
234 #输出模型结构信息
235 model.summary()
236 # 设置学习率
237 optimizer = optimizers.Adam(lr=1e-3)
238 
239 
240 def train():
241     global model
242     # 如果存在模型,就拿以前的继续训练,不用再从头开始
243     if os.path.exists(model_dir):
244         model = tf.keras.models.load_model('model.h5', compile=False)
245 
246     # 进行5次重复训练
247     for epoch in range(5):
248         for step, (x, y) in enumerate(train_db):
249             # 有的时候验证码不是这种格式,就没处理所以就不是的直接过滤
250             if x.shape == (10, 20, 80, 1):
251                 with tf.GradientTape() as tape:
252                     # logits
253                     logits = model(x)
254                     # 真实值就行one_hot编码来对比
255                     y_onehot = tf.one_hot(y, depth=10)
256                     # 设置loss
257                     loss_ce = tf.losses.MSE(y_onehot, logits)
258                     loss_ce = tf.reduce_mean(loss_ce)
259                 # 不断更新梯度
260                 grads = tape.gradient(loss_ce, model.trainable_variables)
261                 optimizer.apply_gradients(zip(grads, model.trainable_variables))
262 
263                 #输出训练次数,损失函数
264                 if step % 10 == 0:
265                     print(epoch, step, 'loss:', float(loss_ce))
266     # 因为5次就已经很高了,所以直接保存模型
267     model.save('model.h5')
268 
269 def test():
270     model = tf.keras.models.load_model('model.h5', compile=False)
271     for step, (x, y) in enumerate(test_db):
272         if x.shape == (1, 20, 80, 1):
273             logits = model(x)
274             logits = tf.nn.softmax(logits)
275             pred = tf.cast(tf.argmax(logits,axis=2),dtype=tf.int32)
276             print('预测值:',pred[0].numpy(),'真实值:',y[0].numpy(),'是否相同:',
277                   int(tf.reduce_sum(tf.cast(tf.equal(pred,y),dtype=tf.int32)))==4)
278 
279 if __name__ == '__main__':
280 
281     #判断是否存在模型文件,没有则训练生成
282     choice_flag = 1 # 0训练 1测试
283     if os.path.exists(model_dir) and choice_flag==1:
284         test()
285     else:
286         train()

 

12.总结

  这次的图片验证码识别流程已经走通,但是验证码降噪处理的代码比较局限,要是更换数据集就没有办法复用了,虽然添加了三个卷积层,但是从结果中可以看出,机器学习的准确度还是有待提高的。另外这次生成的验证码是纯数字的预测,要是加上字母的验证码才更完美,但这也是我遇到的难点,在数字跟字母转换类型的过程中,有些函数不支持字符型变量的调用。感谢老师本学期的教学,让我对机器学习的概念有了更进一步的学习,但掌握的还不够,学海无涯,我以后会更努力的学习!

posted on 2022-12-20 20:19  混饭的  阅读(1240)  评论(0编辑  收藏  举报