(一)选题背景
首先,验证码是最初的设定是通过验证码对人类和非人类行为进行区分;大多数的网站在进行注册或者登录时都需要用到图片验证码,这都是为了防止用户通过机器人或者脚本进行自动注册,登录等设计网络安全的问题,所以破解这种技术,提高网站的信息安全就显得很有必要。验证码图片的识别可能就是破解技术的核心,希望通过这次课设能对机器学习有更好的了解。
(二)机器学习案例设计方案
通过增加干扰线和噪点自主生成验证码图片,然后对自动生成的数据集进行降噪和灰度处理,然后建立训练模型,卷积神经网络池化然后通过全连接层进行处理,最后将图片与机器学习预测的结果进行比对。
参考来源:https://gitee.com/kdldbq/verification-decoder
数据集参考来源:
(三)机器学习实现步骤
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.总结
这次的图片验证码识别流程已经走通,但是验证码降噪处理的代码比较局限,要是更换数据集就没有办法复用了,虽然添加了三个卷积层,但是从结果中可以看出,机器学习的准确度还是有待提高的。另外这次生成的验证码是纯数字的预测,要是加上字母的验证码才更完美,但这也是我遇到的难点,在数字跟字母转换类型的过程中,有些函数不支持字符型变量的调用。感谢老师本学期的教学,让我对机器学习的概念有了更进一步的学习,但掌握的还不够,学海无涯,我以后会更努力的学习!