写在前面
来到暗组好多年了,期间也是断断续续的,今天终于有时间发布一些东西。
下面就验证码破解,给大家分享一下keras的初步用法。
祝大家心想事成~
背景
机器学习,即Machine Learning,属于AI范畴,是深度学习的父集。 通过如何用机器学习(ML)在15分钟内破解图片验证码的一篇文章,看到了solving-captchas-code-examples项目。作者通过神经网络训练简单的model来破解了形如下图的图片验证码:
正文
根据该example,打算拿来练练手。当然不能止步于示例中的简单验证码,随手打开了几个网页,搜集了下如图所示的彩色的图片验证码:
选定了上述的带数学算数的图形验证码,接下来开始上路了。 大致的思路是:识别图片
->训练模型(model)
->生产验证
一、识别图片
这个识别图片不是OCR的意思,是相当于你教会计算机去认识图片中的数字。 比如,你拿一张写着数字1的图片,然后告诉计算机,这是数字1。
这些基础数据,是作为下一步训练模型的基础的,大家都知道,时下人工智能里,训练个好的模型至关重要。我曾一度认为,源代码是否暴露或者开源已无关紧要,但是经过了大数据的神经网络模型,才是真正的'核心源码'。
所以,我们要获取的验证码图片的基础数据当然越多越好。这里我的初衷是简单实验一下,也不是打造一个专业的破解系统,所以通过爬虫简单保存了二三十张图片,并按照图片内容手动重命名:
接下来,按照各个基础数据图片的文件名,识别、拆分图片中的各个字符,来分类。这里没有照搬example中的代码,通过循环各个图片,来拆分图片并分类存储:
def main(counts, captcha_image_file): # 结果输出dir OUTPUT_FOLDER = './result' # 基础数据dir captcha_image_files = glob.glob(os.path.join('./subject/', "*")) filename = os.path.basename(captcha_image_file) captcha_correct_text = os.path.splitext(filename)[0] # 加载图像并将其转换成灰度级 image = cv2.imread(captcha_image_file) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 在图像周围添加一些填充物 gray = cv2.copyMakeBorder(gray, 1, 1, 1, 1, cv2.BORDER_REPLICATE) # 阈值图像(转换为纯黑色和白色) # threshold: 输入图像,阈值,输出图像的最大值,阈值的类型,目标图像 # 转换第一次,有浅色情况所以下面转第二次 thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_TOZERO)[1] # 将图片转为黑白 thresh = cv2.threshold(thresh, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] # 找到图像的轮廓(连续像素块) contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # OpenCV版本适应 contours = contours[0] if imutils.is_cv2() else contours[1] letter_image_regions = [] # 遍历轮廓,并提取字母 for contour in contours: # 获取包含轮廓的矩形 (x, y, w, h) = cv2.boundingRect(contour) # 比较轮廓的宽度和高度来检测字母 if w / h > 1.25: # 轮廓太宽,把它分成两个字母区域 half_width = int(w / 2) letter_image_regions.append((x, y, half_width, h)) letter_image_regions.append((x + half_width, y, half_width, h)) else: # 正常字母 letter_image_regions.append((x, y, w, h)) # 根据X坐标对检测到的字母图像进行排序,以确保我们从左到右处理它们,因此我们将正确的图像与正确的字母匹配。 letter_image_regions = sorted(letter_image_regions, key=lambda x: x[0]) # Save image retStr = '' nCnt = 0 for letter_bounding_box, letter_text in zip(letter_image_regions, captcha_correct_text): nCnt += 1 # 抓取图像中字母的坐标 x, y, w, h = letter_bounding_box # 从原始图像中提取具有边缘的2像素边缘的字母 letter_image = gray[y - 2:y + h + 2, x - 2:x + w + 2] # 获取文件夹以保存图像 save_path = os.path.join(OUTPUT_FOLDER, letter_text) # 如果输出目录不存在,则创建它 if not os.path.exists(save_path): os.makedirs(save_path) # 将字母图像写入文件 count = counts.get(letter_text, 1) p = os.path.join(save_path, "{}.png".format(str(count).zfill(6))) retStr += letter_text cv2.imwrite(p, letter_image) counts[letter_text] = count + 1 return (retStr)
上述是主函数,我又写了遍历基础图片目录的方法,来循环调用它:
dicT = {} for (i, captcha_image_file) in enumerate(captcha_image_files): a = main(dicT, captcha_image_file) a = list(a) try: # 这里做个打印 if len(a)>=3: if a[1] == 'x': print(a[0]+'*'+a[2]+'='+str((int(a[0])*int(a[2])))) else: print(a[0]+'+'+a[2]+'='+str((int(a[0])+int(a[2])))) except: raise
执行后,将基础数据分门别类到result目录:
上图即按照各个字符,拆分图片,每个子文件夹下,是该字符的各种样子。 比如./result/9/
目录下,即各个形态的数字9。
二、训练模型
训练模型这里,使用的是keras库。摘一下文档中的简述:
Keras 是一个用 Python 编写的高级神经网络 API,它能够以 TensorFlow, CNTK, 或者 Theano 作为后端运行。Keras 的开发重点是支持快速的实验。能够以最小的时延把你的想法转换为实验结果,是做好研究的关键。 如果你在以下情况下需要深度学习库,请使用 Keras:
- 允许简单而快速的原型设计(由于用户友好,高度模块化,可扩展性)。
- 同时支持卷积神经网络和循环神经网络,以及两者的组合。
- 在 CPU 和 GPU 上无缝运行
该步骤处理的就是./result/
下的各个形态的字符文件了。 其大致思路是,先将各个图片统一大小,然后加入到keras的model进行训练并生成一个model文件。 该部分代码同example类似,可以当做utils来使用。
LETTER_IMAGES_FOLDER = "result" MODEL_FILENAME = "captcha_model.hdf5" MODEL_LABELS_FILENAME = "model_labels.dat" # 初始化 data = [] labels = [] # 循环result中的图片 for image_file in paths.list_images(LETTER_IMAGES_FOLDER): # 读取图片转灰度 image = cv2.imread(image_file) image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 重置图片大小,统一为20x20 pixel image = resize_to_fit(image, 20, 20) # 在原image数组的3位置添加数据,是为了给keras用 image = np.expand_dims(image, axis=2) # 获取文件名 label = image_file.split(os.path.sep)[-2] # 加入到数组以备后续训练模型 data.append(image) labels.append(label) # 下述模型训练的代码未做改动 data = np.array(data, dtype="float") / 255.0 labels = np.array(labels) (X_train, X_test, Y_train, Y_test) = train_test_split(data, labels, test_size=0.25, random_state=0) lb = LabelBinarizer().fit(Y_train) Y_train = lb.transform(Y_train) Y_test = lb.transform(Y_test) # one-hot编码保存映射文件 with open(MODEL_LABELS_FILENAME, "wb") as f: pickle.dump(lb, f) model = Sequential() model.add(Conv2D(20, (5, 5), padding="same", input_shape=(20, 20, 1), activation="relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) model.add(Conv2D(50, (5, 5), padding="same", activation="relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) model.add(Flatten()) model.add(Dense(500, activation="relu")) model.add(Dense(13, activation="softmax")) model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) model.fit(X_train, Y_train, validation_data=(X_test, Y_test), batch_size=13, epochs=10, verbose=1) model.save(MODEL_FILENAME)
运行后输出的captcha_model.hdf5
文件即所需的model模型文件。
三、开始验证
验证是,首先按照第一步开头的方式,载入图片。因为第一步我做了些改动来识别彩色的数字验证码,所以此刻也需将要破解的图片,做相同处理:
MODEL_FILENAME = "captcha_model.hdf5" MODEL_LABELS_FILENAME = "model_labels.dat" # 要破解的图片文件夹所在目录 CAPTCHA_IMAGE_FOLDER = "sc" # 载入映射文件和模型 with open(MODEL_LABELS_FILENAME, "rb") as f: lb = pickle.load(f) model = load_model(MODEL_FILENAME)
接下里使用模型来识别目标图片:
captcha_image_files = list(paths.list_images(CAPTCHA_IMAGE_FOLDER)) captcha_image_files = np.random.choice(captcha_image_files, size=(1,), replace=False) print("要识别的图:\n"+str(captcha_image_files)) # loop over the image paths for image_file in captcha_image_files: # Load the image and convert it to grayscale image = cv2.imread(image_file) image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) image = cv2.copyMakeBorder(image, 1, 1, 1, 1, cv2.BORDER_REPLICATE) # 转换第一次,有浅色情况所以下面转第二次 thresh = cv2.threshold(image, 200, 255, cv2.THRESH_TOZERO)[1] # 将图片转为黑白 thresh = cv2.threshold(thresh, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] # 找到图像的轮廓(连续像素块) contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # OpenCV版本适应 contours = contours[0] if imutils.is_cv2() else contours[1] # 遍历四个轮廓中的每一个并提取每个轮廓中的字母,这里封装统一函数,不作显示,详见example letter_image_regions = contours_regions(contrours) # 输出demo,做轮廓的标记 output = cv2.merge([image] * 3) predictions = [] for letter_bounding_box in letter_image_regions: x, y, w, h = letter_bounding_box letter_image = image[y - 2:y + h + 2, x - 2:x + w + 2] # 同样的resize letter_image = resize_to_fit(letter_image, 20, 20) letter_image = np.expand_dims(letter_image, axis=2) letter_image = np.expand_dims(letter_image, axis=0) prediction = model.predict(letter_image) letter = lb.inverse_transform(prediction)[0] predictions.append(letter) cv2.rectangle(output, (x - 2, y - 2), (x + w + 4, y + h + 4), (0, 255, 0), 1) cv2.putText(output, letter, (x - 5, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 255, 0), 2) # 至此predictions中即为破解解析后的各个字符的list了,接下来是一些便利性的打印,不作展示 print(predictions)
效果如图:
总结
- 通过cv2读入并转换图片,这部分在blog中有不少示例了,所以用的相对熟练。这也是能通过示例example的黑白字母验证码引申到彩色的算式验证码上的原因所在;
- keras是第一次使用,基本上是照搬的示例了,用的还比较顺手。虽不是第一次训练model,但也是第一次这么流程清晰的使用神经网络;