使用PyTorch识别简单验证码

前言

在这篇文章中,我们将演示如何使用PyTorch来识别简单的数字图形CAPTCHA。示例比较简单,主要演示图片预处理及简单的CNN网络。

环境准备

安装依赖包

conda install pytorch torchvision torchaudio cpuonly -c pytorch

sudo apt-get install libgl1  # for opencv
pip install requests matplotlib opencv-python

下载验证码图片(我们将验证码的值放在HTTP头中返回,方便的对原始数据集进行标注,更一般的情况需要对图片进行人工标注):

CAPTCHA_URL = 'https://captcha.tomo.wang'
r = requests.get(CAPTCHA_URL)
captcha = r.headers['X-Captcha']
with open('{}.png'.format(captcha), 'wb') as f:
    f.write(r.content)

图像处理及训练

准备

首先我们导入需要用到的包

import os
import re
import sys
import argparse
import glob
from io import BytesIO

import requests
import numpy as np
import cv2
import matplotlib.pyplot as plt

import torch
from torch.autograd import Variable
import torch.nn.functional as F

定义程序运行的相关常量

  • 字符个数
  • 验证码宽高
  • 裁剪后字符的宽高
NUM_CHARS = 4
CAPTCHA_WIDTH = 200
CAPTCHA_HEIGHT = 62
CH_WIDTH = 20
CH_HEIGHT = 28

CAPTCHA_DIR = './images'
TORCH_NET_PATH = 'captcha.torch'
BG_COLOR = (243, 251, 254)  # captcha backgroud color
BG_THRESHOLD = 245

BLANK_THRESHHOLD = 1
DOTS_THRESHOLD = 3
CH_MIN_WIDTH = 8

获取验证码图片并展示

def get_captcha():
    CAPTCHA_URL = 'https://captcha.tomo.wang'
    r = requests.get(CAPTCHA_URL)
    return r.content
img = get_captcha()
plt.imshow(plt.imread(BytesIO(img)))

<matplotlib.image.AxesImage at 0x7f1fcc4d7c40>

原始图片

一个验证码图片包含背景和不同颜色的字符,共四个字符,在训练前需要对其进行灰化处理,并进行切割

img_array = np.asarray(bytearray(img), dtype=np.uint8)
img = cv2.imdecode(img_array, cv2.IMREAD_GRAYSCALE)
assert img.shape == (CAPTCHA_HEIGHT, CAPTCHA_WIDTH)
img = cv2.threshold(img, BG_THRESHOLD, 255, cv2.THRESH_BINARY_INV)[1]
plt.imshow(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR))

<matplotlib.image.AxesImage at 0x7f1fcc3e7e80>

灰化处理后的图片

切割

将图片切割成四个独立的字符

def _denoise(img):
    img = cv2.threshold(img, BG_THRESHOLD, 255, cv2.THRESH_BINARY_INV)[1]
    return img


def _preprocess(img):
    img = img.copy()
    img = _denoise(img)
    return img


def find_filled_row(rows):
    for i, row in enumerate(rows):
        dots = np.sum(row) // 255
        if dots >= DOTS_THRESHOLD:
            return i
    assert False, 'cannot find filled row'


def pad_ch(ch):
    pad_w = CH_WIDTH - ch.shape[1]
    assert pad_w >= 0, 'bad char width'
    pad_w1 = pad_w // 2
    pad_w2 = pad_w - pad_w1
    pad_h = CH_HEIGHT - ch.shape[0]
    assert pad_h >= 0, 'bad char height'
    pad_h1 = pad_h // 2
    pad_h2 = pad_h - pad_h1
    return np.pad(ch, ((pad_h1, pad_h2), (pad_w1, pad_w2)), 'constant')


def segment(img):
    # Search blank intervals.
    img = _preprocess(img)
    dots_per_col = np.apply_along_axis(lambda row: np.sum(row) // 255, 0, img)
    blanks = []
    was_blank = False
    first_ch_x = None
    prev_x = 0
    x = 0
    while x < CAPTCHA_WIDTH:
        if dots_per_col[x] >= DOTS_THRESHOLD:
            if first_ch_x is None:
                first_ch_x = x
            if was_blank:
                # Skip first blank.
                if prev_x:
                    blanks.append((prev_x, x))
                # Don't allow too tight chars.
                x += CH_MIN_WIDTH
                was_blank = False
        elif not was_blank:
            was_blank = True
            prev_x = x
        x += 1
    blanks = [b for b in blanks if b[1] - b[0] >= BLANK_THRESHHOLD]
    # Add last (imaginary) blank to simplify following loop.
    blanks.append((prev_x if was_blank else CAPTCHA_WIDTH, 0))

    # Get chars.
    chars = []
    x1 = first_ch_x
    widest = 0, 0
    for i, (x2, next_x1) in enumerate(blanks):
        width = x2 - x1
        # Don't allow more than CH_WIDTH * 2.
        extra_w = width - CH_WIDTH * 2
        extra_w1 = extra_w // 2
        extra_w2 = extra_w - extra_w1
        x1 = max(x1, x1 + extra_w1)
        x2 = min(x2, x2 - extra_w2)
        ch = img[:CAPTCHA_HEIGHT, x1:x2]

        y1 = find_filled_row(ch[::])
        y2 = CAPTCHA_HEIGHT - find_filled_row(ch[::-1])
        ch = ch[y1:y2]

        chars.append(ch)
        if width > widest[0]:
            widest = x2 - x1, i
        x1 = next_x1

    # Fit chars into boxes.
    chars2 = []
    for i, ch in enumerate(chars):
        widest_w, widest_i = widest
        # Split glued chars.
        if len(chars) < NUM_CHARS and i == widest_i:
            ch1 = ch[:, 0:widest_w // 2]
            ch2 = ch[:, widest_w // 2:widest_w]
            chars2.append(pad_ch(ch1))
            chars2.append(pad_ch(ch2))
        else:
            ch = ch[:, 0:CH_WIDTH]
            chars2.append(pad_ch(ch))

    assert len(chars2) == NUM_CHARS, 'bad number of chars'
    return chars2
chars2 = segment(cv2.imdecode(img_array, cv2.IMREAD_GRAYSCALE))
fig = plt.figure()
for i, char in enumerate(chars2):
    a = fig.add_subplot(1, 4, i+1)
    plt.imshow(cv2.cvtColor(char, cv2.COLOR_GRAY2BGR))

切割后的图片

其他图片相关处理函数

def check_image(img):
    assert img is not None, 'cannot read image'
    assert img.shape == (CAPTCHA_HEIGHT, CAPTCHA_WIDTH), 'bad image dimensions'


def read_image_file(fpath):
    with open(fpath, 'rb') as f:
        return decode_image(f.read())


def decode_image(data):
    data = np.frombuffer(data, np.uint8)
    img = cv2.imdecode(data, cv2.IMREAD_GRAYSCALE)
    check_image(img)
    return img


def get_ch_data(img):
    data = img.flatten() & 1
    assert len(data) == NUM_INPUT, 'bad data size'
    return data

神经网络以及训练

# nn net define
NUM_INPUT = CH_WIDTH * CH_HEIGHT
NUM_NEURONS_HIDDEN = NUM_INPUT // 3
NUM_OUTPUT = 10


class Net(torch.nn.Module):
    def __init__(self, n_feature, n_hidden, n_output):
        super(Net, self).__init__()
        self.hidden = torch.nn.Linear(n_feature, n_hidden)   # hidden layer
        self.out = torch.nn.Linear(n_hidden, n_output)   # output layer

    def forward(self, x):
        x = F.relu(self.hidden(x))      # activation function for hidden layer
        x = self.out(x)
        return x

基于之前获取的字符集开始训练

def train(captchas_dir):
    net = Net(n_feature=NUM_INPUT, n_hidden=NUM_NEURONS_HIDDEN, n_output=NUM_OUTPUT)

    optimizer = torch.optim.SGD(net.parameters(), lr=0.02, momentum=0.9)
    loss_func = torch.nn.CrossEntropyLoss()

    captchas_dir = os.path.abspath(captchas_dir)
    captchas = glob.glob(captchas_dir + '/*.png')

    x, y = [], []
    for i, name in enumerate(captchas):
        answer = re.match(r'.*(\d{4})\.png$', name)
        if not answer:
            continue
        answer = answer.group(1)
        fpath = os.path.join(captchas_dir, name)
        try:
            img = read_image_file(fpath)
            ch_imgs = segment(img)
            for ch_img, digit in zip(ch_imgs, answer):
                x.append(get_ch_data(ch_img))
                y.append(int(digit))
        except Exception as e:
            print('Error occured while processing {}: {}'.format(name, e))
        else:
            if (i + 1) % 25 == 0:
                print('{}/{}'.format(i + 1, len(captchas)))

    x, y = torch.from_numpy(np.array(x)).type(torch.FloatTensor), torch.from_numpy(np.array(y)).type(torch.LongTensor)
    x, y = Variable(x), Variable(y)

    for t in range(100):
        out = net(x)                 # input x and predict based on x
        loss = loss_func(out, y)     # must be (1. nn output, 2. target), the target label is NOT one-hotted

        optimizer.zero_grad()   # clear gradients for next train
        loss.backward()         # backpropagation, compute gradients
        optimizer.step()        # apply gradients

    return net
net = train(CAPTCHA_DIR)
25/400
50/400
75/400
100/400
125/400
150/400
175/400
200/400
225/400
250/400
275/400
300/400
325/400
350/400
375/400
400/400
print(net)
Net(
  (hidden): Linear(in_features=560, out_features=186, bias=True)
  (out): Linear(in_features=186, out_features=10, bias=True)
)

预测新图形

def predict(net, img_content):
    def get_digit(ch_img):
        x = torch.from_numpy(get_ch_data(ch_img)).type(torch.FloatTensor)
        output = net(Variable(x))
        _, predicted = torch.max(output.data, 0)
        # return str(Variable(predicted).data[0])
        return str(predicted.item())

    img = decode_image(img_content)
    ch_imgs = segment(img)
    return ''.join(map(get_digit, ch_imgs))
img_content = get_captcha()
plt.imshow(plt.imread(BytesIO(img_content)))
result = predict(net, img_content)
plt.title(result)
Text(0.5, 1.0, '1707')

预测结果

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 验证识别是指通过计算机程序对验证码进行自动识别的过程。而PyTorch,则是一种基于Torch的开源机器学习框架,具有强大的计算能力和丰富的神经网络模型。 对于验证识别任务,使用PyTorch是一种有效的方法。首先,我们可以使用PyTorch构建一个卷积神经网络(CNN)模型。CNN模型在图像识别任务中表现出色,能够提取图像特征并进行分类。我们可以使用PyTorch提供的各种层和函数来构建模型的结构,如卷积层、池化层、全连接层等。 在模型构建完成后,我们需要准备训练数据集。可以通过收集一些包含验证码样本和对应标签(即验证码正确的值)的数据集。接着,使用PyTorch提供的数据加载器和图像处理函数,将数据集加载到模型中进行训练。在训练过程中,PyTorch提供了自动求导功能,可以方便地计算损失函数,并进行梯度更新。 为了提高验证识别的准确率,可以采用一些常用的优化技巧。例如,使用学习率调度器、正则化技术、数据增强等方法来防止过拟合问题,并加快训练过程。此外,还可以使用预训练的模型权重进行初始化,如在ImageNet数据集上预训练好的模型权重等。 在训练完成后,我们可以使用训练好的模型对新的验证码进行预测。通过将验证码图像输入到模型中,PyTorch会输出识别的结果。根据模型输出的结果,我们可以判断验证码是否被正确识别。 总结来说,验证识别是一个复杂的问题,但通过使用PyTorch这样的强大工具,我们可以轻松地构建、训练和应用深度学习模型,提高验证识别的准确率。 ### 回答2: 验证pytorch识别是一种利用深度学习框架PyTorch实现的验证识别技术。验证码是一种防止计算机自动化操作的措施,常用于网站登录、注册等环节。由于验证码具有一定的难度,传统的图像处理方法往往效果不佳。 PyTorch作为一种开源的深度学习框架,具有强大的计算能力和丰富的工具库,非常适合用于验证识别。其基于Python语言开发,易于学习和使用验证pytorch识别的主要步骤如下: 1. 数据收集和预处理:收集大量的验证码图片并进行预处理,包括图片大小调整、灰度化、二值化等操作,以便输入神经网络进行训练。 2. 神经网络设计:使用PyTorch构建一个深度学习模型,可以选择卷积神经网络(CNN)等结构。网络的输入为预处理后的验证码图片,输出为验证码的识别结果。 3. 数据集划分和训练:将收集的验证码图片划分为训练集和验证集,使用训练集对神经网络进行训练,并根据验证集的表现进行调参,以提高模型的准确率。 4. 模型评估和优化:使用测试集对训练好的模型进行评估,计算识别准确率和错误率。根据评估结果对模型进行优化,可以尝试调整网络结构、增加数据量、调整超参数等方式。 5. 验证识别:经过训练和优化的模型可以应用于实际验证码的识别任务,输入验证码图片,通过模型的预测输出识别结果。 总之,验证pytorch识别利用PyTorch这一强大的深度学习框架,通过数据收集、神经网络设计、训练评估等步骤,可以实现对验证码的识别,提高验证识别的准确率和效率。 ### 回答3: 验证识别是指通过计算机视觉技术,利用pytorch深度学习框架对验证码进行自动化识别的过程。 验证码是为了防止机器人或恶意程序的攻击而设计的一种安全机制。在互联网应用中,常用的验证码形式包括文字、数字、图形、滑块等,其目的是要求用户识别并输入,以证明其为人类而非机器。 pytorch作为一种开源、基于Python的深度学习框架,具备了处理图像和模式识别的能力。通过使用pytorch框架,可以利用深度学习的算法和神经网络,来对验证码进行自动识别验证识别的一般步骤包括数据预处理、模型训练和验证识别三个主要过程。 首先,对验证码进行数据预处理是非常重要的步骤。预处理包括图片的灰度化、二值化、滤波以及去噪等,以提高识别准确度和模型训练速度。 其次,建立合适的神经网络模型,并使用pytorch进行模型训练。训练数据集通常需要手动标注,包括正确的验证码标识和对应的标签。在模型训练的过程中,通过反向传播算法更新模型参数,提高模型对验证码的识别能力。 最后,利用训练好的模型对新的验证码进行识别。通过输入验证码图片,并运行训练好的模型,即可获得验证码的识别结果。 因为验证码种类繁多,存在一定的识别难度。对于复杂的验证码,可能需要进一步改进模型或者采用其他的算法进行增强识别能力。 总之,验证识别是应用pytorch深度学习框架来实现的一项技术,通过数据预处理、模型训练和识别等步骤,可以有效地对验证码进行准确和自动化的识别
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值