通过机器学习破解验证码图片 Web安全

fls 2020-2-19 2997

写在前面

来到暗组好多年了,期间也是断断续续的,今天终于有时间发布一些东西。

下面就验证码破解,给大家分享一下keras的初步用法。

祝大家心想事成~

背景

机器学习,即Machine Learning,属于AI范畴,是深度学习的父集。 通过如何用机器学习(ML)在15分钟内破解图片验证码的一篇文章,看到了solving-captchas-code-examples项目。作者通过神经网络训练简单的model来破解了形如下图的图片验证码:

eg1

正文

根据该example,打算拿来练练手。当然不能止步于示例中的简单验证码,随手打开了几个网页,搜集了下如图所示的彩色的图片验证码:

rs0

选定了上述的带数学算数的图形验证码,接下来开始上路了。 大致的思路是:识别图片->训练模型(model)->生产验证

一、识别图片

这个识别图片不是OCR的意思,是相当于你教会计算机去认识图片中的数字。 比如,你拿一张写着数字1的图片,然后告诉计算机,这是数字1。

这些基础数据,是作为下一步训练模型的基础的,大家都知道,时下人工智能里,训练个好的模型至关重要。我曾一度认为,源代码是否暴露或者开源已无关紧要,但是经过了大数据的神经网络模型,才是真正的'核心源码'。

所以,我们要获取的验证码图片的基础数据当然越多越好。这里我的初衷是简单实验一下,也不是打造一个专业的破解系统,所以通过爬虫简单保存了二三十张图片,并按照图片内容手动重命名:

rs01

接下来,按照各个基础数据图片的文件名,识别、拆分图片中的各个字符,来分类。这里没有照搬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目录:

rs1

rs11

上图即按照各个字符,拆分图片,每个子文件夹下,是该字符的各种样子。 比如./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模型文件。

rs2

三、开始验证

验证是,首先按照第一步开头的方式,载入图片。因为第一步我做了些改动来识别彩色的数字验证码,所以此刻也需将要破解的图片,做相同处理:

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)

效果如图:

rs3

总结

  • 通过cv2读入并转换图片,这部分在blog中有不少示例了,所以用的相对熟练。这也是能通过示例example的黑白字母验证码引申到彩色的算式验证码上的原因所在;
  • keras是第一次使用,基本上是照搬的示例了,用的还比较顺手。虽不是第一次训练model,但也是第一次这么流程清晰的使用神经网络;
最新回复 (0)
全部楼主
返回
发新帖