APS capcha crack with LeNet CNN

  • here the aim is recognize the image of Einstein or Marie Curie within the set of images out of APS captcha.
![](output/images/real_world/1.png) ![](output/images/real_world/19.png) ![](output/images/real_world/62.png) ![](output/images/real_world/40.png) ![](output/images/real_world/15.png) ![](output/images/real_world/5.png)
![](output/images/real_world/3.png) ![](output/images/real_world/33.png) ![](output/images/real_world/60.png) ![](output/images/real_world/11.png) ![](output/images/real_world/55.png) ![](output/images/real_world/51.png)
  • python files structure:
|--- output
    |--- core
    |   |--- __init__.py
    |   |--- lenet.py
    |--- train_aps.py
    |--- aug_train_aps.py
    |--- test_lenet.py

the __init__.py file only import the LeNet class:

# import the necessary packages
from lenet import LeNet, load_images, deshear, normalize_image
  • the sets of images are placed into the output folder in 3 categories:
|--- images
   |   |--- train
   |   |--- test
   |   |--- real_world

Setting LeNet layers and the editting images functions

lenet.py :


In [1]:
# import the necessary packages
from keras.models import Sequential
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Activation, Flatten, Dense
import os
import numpy as np
from skimage import io

class LeNet:

    def __init__(self, input_shape, conv_1, pool_1, conv_2, pool_2, hidden,
                 classes):
        self.model = Sequential()
        # first set of CONV => RELU => POOL
        self.model.add(Conv2D(*conv_1, padding='same', activation='relu',
                              data_format='channels_last',
                              input_shape=input_shape))
        self.model.add(MaxPooling2D(pool_1[0], pool_1[1]))
        # second set of CONV => RELU => POOL
        self.model.add(Conv2D(*conv_2, padding='same', activation='relu',
                              data_format='channels_last'))
        self.model.add(MaxPooling2D(pool_2[0], pool_2[1]))
        # set of FC => RELU layers
        self.model.add(Flatten())
        self.model.add(Dense(hidden, activation='relu'))
        # softmax classifier
        self.model.add(Dense(classes, activation='softmax'))

def load_images(folder):
    images = []
    labels = []
    for file in os.listdir(folder):
        if file.endswith(".png"):
            images.append(io.imread(folder + file, as_grey=True))
            if file.find("einstein") > -1:
                labels.append(1)
            elif file.find("curie") > -1:
                labels.append(2)
            elif os.path.splitext(file)[0].isdigit():
                labels.append(int(os.path.splitext(file)[0]))
            else:
                labels.append(0)
    return images, labels

def deshear(filename):
    image = io.imread(filename)
    distortion = image.shape[1] - image.shape[0]
    shear = tf.AffineTransform(shear=math.atan(distortion/image.shape[0]))
    return tf.warp(image, shear)[:, distortion:]


def normalize_images(images):
    for i in range(len(images)):
        images[i] = images[i][0:100, 0:100]
        images[i] = images[i]/np.amax(images[i])
    return np.array(images)


Using Theano backend.

Training the CNN with Augmentation

aug_train_aps.py :


In [1]:
# append the package folder to sys.path
import sys
sys.path.append("core")

# import the necessary packages
from core import LeNet, load_images, deshear, normalize_images
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import SGD
from keras.utils import np_utils
import numpy as np

# Loading image data sets and normalizing color scale
training_set, training_labels = load_images("output/images/train/")
test_set, test_labels = load_images("output/images/test/")
rw_set, rw_file_labels = load_images("output/images/real_world/")
training_set = normalize_images(training_set)
training_set = training_set[..., np.newaxis]
test_set = normalize_images(test_set)
test_set = test_set[..., np.newaxis]
rw_set = normalize_images(rw_set)
rw_set = rw_set[..., np.newaxis]
rw_set = np.array([x for (y, x) in sorted(zip(rw_file_labels, rw_set))])

# Getting labels for real world set from file
f = open('output/images/real_world/labels.txt', "r")
lines = f.readlines()
rw_labels = []
for x in lines:
    rw_labels.append(int((x.split('	')[1]).replace('\n', '')))
f.close()

# Augmenting basic data set to improve performance in real world set
datagen = ImageDataGenerator(rotation_range=10, shear_range=0.3,
                             zoom_range=0.2, width_shift_range=0.15,
                             height_shift_range=0.15, fill_mode='constant',
                             cval=1)

# Parameters for LeNet convolutional network
classes = 3  # number of classes to identify
hidden = 500  # number of nuerons in hidden layer
conv_1 = (20, (15, 15))  # (num of filters in first layer, filter size)
conv_2 = (50, (15, 15))  # (num of filters in second layer, filter size)
pool_1 = ((6, 6), (6, 6))  # (size of pool matrix, stride)
pool_2 = ((6, 6), (6, 6))  # (size of pool matrix, stride)

# Converting integer labels to categorical labels
training_labels = np_utils.to_categorical(training_labels, classes)
test_labels = np_utils.to_categorical(test_labels, classes)
rw_labels = np_utils.to_categorical(rw_labels, classes)

# Initialize the optimizer and model for training
print("[INFO] compiling model...")
aps = LeNet(training_set[1].shape, conv_1, pool_1, conv_2, pool_2, hidden,
            classes)
aps.model.compile(loss='categorical_crossentropy', optimizer='adam',
                  metrics=["accuracy"])

# Training the CNN
print("[INFO] training...")
aps.model.fit_generator(datagen.flow(training_set, training_labels, batch_size=10),
                        steps_per_epoch=len(training_set), epochs=50, verbose=1)

# Testing aps model of both sets
print("[INFO] Test model in both sets...")
test_probs = aps.model.predict(test_set)
test_prediction = test_probs.argmax(axis=1)
rw_probs = aps.model.predict(rw_set)
rw_prediction = rw_probs.argmax(axis=1)

# show the accuracy on the testing set
print("[INFO] evaluating test set...")
(loss, accuracy) = aps.model.evaluate(test_set, test_labels, verbose=0)
print("[INFO] accuracy: {:.2f}%".format(accuracy * 100) +
      " - loss: {:.2f}".format(loss))

# show the accuracy on the real world
print("[INFO] evaluating real world set...")
(loss, accuracy) = aps.model.evaluate(rw_set, rw_labels, verbose=0)
print("[INFO] accuracy: {:.2f}%".format(accuracy * 100) +
      " - loss: {:.2f}".format(loss))

# Save weights trained
print("[INFO] dumping weights to file...")
aps.model.save_weights('output/aug_lenet_weights.hdf5', overwrite=True)

print("[INFO] Done!")


Using Theano backend.
[INFO] compiling model...
[INFO] training...
Epoch 1/50
25/25 [==============================] - 4s - loss: 0.4781 - acc: 0.9238     
Epoch 2/50
25/25 [==============================] - 4s - loss: 0.3195 - acc: 0.9240     
Epoch 3/50
25/25 [==============================] - 4s - loss: 0.3152 - acc: 0.9122     
Epoch 4/50
25/25 [==============================] - 4s - loss: 0.2712 - acc: 0.9161     
Epoch 5/50
25/25 [==============================] - 4s - loss: 0.2281 - acc: 0.9317     
Epoch 6/50
25/25 [==============================] - 4s - loss: 0.2122 - acc: 0.9241     
Epoch 7/50
25/25 [==============================] - 5s - loss: 0.1618 - acc: 0.9361     
Epoch 8/50
25/25 [==============================] - 4s - loss: 0.2663 - acc: 0.9201     
Epoch 9/50
25/25 [==============================] - 4s - loss: 0.1752 - acc: 0.9435     
Epoch 10/50
25/25 [==============================] - 4s - loss: 0.1770 - acc: 0.9478     
Epoch 11/50
25/25 [==============================] - 4s - loss: 0.1630 - acc: 0.9438     
Epoch 12/50
25/25 [==============================] - 5s - loss: 0.1492 - acc: 0.9479     
Epoch 13/50
25/25 [==============================] - 5s - loss: 0.1275 - acc: 0.9482     
Epoch 14/50
25/25 [==============================] - 4s - loss: 0.1054 - acc: 0.9597     
Epoch 15/50
25/25 [==============================] - 4s - loss: 0.2163 - acc: 0.9280     
Epoch 16/50
25/25 [==============================] - 4s - loss: 0.2112 - acc: 0.9518     
Epoch 17/50
25/25 [==============================] - 4s - loss: 0.1768 - acc: 0.9560     
Epoch 18/50
25/25 [==============================] - 5s - loss: 0.1190 - acc: 0.9439     
Epoch 19/50
25/25 [==============================] - 4s - loss: 0.1086 - acc: 0.9560     
Epoch 20/50
25/25 [==============================] - 4s - loss: 0.0734 - acc: 0.9720     
Epoch 21/50
25/25 [==============================] - 4s - loss: 0.0752 - acc: 0.9560     
Epoch 22/50
25/25 [==============================] - 5s - loss: 0.0833 - acc: 0.9683     
Epoch 23/50
25/25 [==============================] - 4s - loss: 0.2811 - acc: 0.9201     
Epoch 24/50
25/25 [==============================] - 5s - loss: 0.1803 - acc: 0.9397     
Epoch 25/50
25/25 [==============================] - 8s - loss: 0.1890 - acc: 0.9482     - ETA: 2s - loss: 0.2313 - a
Epoch 26/50
25/25 [==============================] - 6s - loss: 0.1442 - acc: 0.9478     
Epoch 27/50
25/25 [==============================] - 5s - loss: 0.1234 - acc: 0.9483     
Epoch 28/50
25/25 [==============================] - 5s - loss: 0.0905 - acc: 0.9679     
Epoch 29/50
25/25 [==============================] - 6s - loss: 0.0584 - acc: 0.9718     
Epoch 30/50
25/25 [==============================] - 5s - loss: 0.1857 - acc: 0.9679     
Epoch 31/50
25/25 [==============================] - 4s - loss: 0.0856 - acc: 0.9681     
Epoch 32/50
25/25 [==============================] - 4s - loss: 0.0928 - acc: 0.9601     - ETA: 2s - loss: 0.125
Epoch 33/50
25/25 [==============================] - 4s - loss: 0.0901 - acc: 0.9445     
Epoch 34/50
25/25 [==============================] - 4s - loss: 0.0673 - acc: 0.9841     
Epoch 35/50
25/25 [==============================] - 7s - loss: 0.0870 - acc: 0.9720     
Epoch 36/50
25/25 [==============================] - 7s - loss: 0.0586 - acc: 0.9841     
Epoch 37/50
25/25 [==============================] - 5s - loss: 0.0265 - acc: 0.9919     
Epoch 38/50
25/25 [==============================] - 5s - loss: 0.0433 - acc: 0.9839     
Epoch 39/50
25/25 [==============================] - 4s - loss: 0.0772 - acc: 0.9721     
Epoch 40/50
25/25 [==============================] - 5s - loss: 0.1152 - acc: 0.9599     
Epoch 41/50
25/25 [==============================] - 5s - loss: 0.0398 - acc: 0.9879     
Epoch 42/50
25/25 [==============================] - 4s - loss: 0.0271 - acc: 0.9919     
Epoch 43/50
25/25 [==============================] - 5s - loss: 0.1049 - acc: 0.9641     
Epoch 44/50
25/25 [==============================] - 5s - loss: 0.1750 - acc: 0.9559     
Epoch 45/50
25/25 [==============================] - 5s - loss: 0.0885 - acc: 0.9598     
Epoch 46/50
25/25 [==============================] - 5s - loss: 0.0979 - acc: 0.9522     
Epoch 47/50
25/25 [==============================] - 5s - loss: 0.0501 - acc: 0.9839     
Epoch 48/50
25/25 [==============================] - 4s - loss: 0.0284 - acc: 0.9881     
Epoch 49/50
25/25 [==============================] - 5s - loss: 0.0111 - acc: 1.0000     
Epoch 50/50
25/25 [==============================] - 5s - loss: 0.0099 - acc: 0.9960     
[INFO] Test model in both sets...
[INFO] evaluating test set...
[INFO] accuracy: 100.00% - loss: 0.02
[INFO] evaluating real world set...
[INFO] accuracy: 100.00% - loss: 0.00
[INFO] dumping weights to file...
[INFO] Done!

Testing the trained model

test_aps.py :


In [1]:
# append the package folder to sys.path
import sys
sys.path.append("core")

# import the necessary packages
from core import LeNet, load_images, deshear, normalize_images
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import SGD
from keras.utils import np_utils
import numpy as np
import cv2
# Loading image data sets and normalizing color scale
rw_set, rw_file_labels = load_images("output/images/real_world/")
rw_set = normalize_images(rw_set)
rw_set = rw_set[..., np.newaxis]
rw_set = np.array([x for (y, x) in sorted(zip(rw_file_labels, rw_set))])

# Getting labels for real world set from file
f = open('output/images/real_world/labels.txt', "r")
lines = f.readlines()
rw_labels = []
for x in lines:
    rw_labels.append(int((x.split('	')[1]).replace('\n', '')))
f.close()

# Parameters for LeNet convolutional network
classes = 3  # number of classes to identify
hidden = 500  # number of nuerons in hidden layer
conv_1 = (20, (15, 15))  # (num of filters in first layer, filter size)
conv_2 = (50, (15, 15))  # (num of filters in second layer, filter size)
pool_1 = ((6, 6), (6, 6))  # (size of pool matrix, stride)
pool_2 = ((6, 6), (6, 6))  # (size of pool matrix, stride)

# Converting integer labels to categorical labels
rw_labels = np_utils.to_categorical(rw_labels, classes)

# Initialize the optimizer and model for training
print("[INFO] compiling model...")
aps = LeNet(rw_set[1].shape, conv_1, pool_1, conv_2, pool_2, hidden,
            classes)
aps.model.compile(loss='categorical_crossentropy', optimizer='adam',
                  metrics=["accuracy"])
aps.model.load_weights('output/aug_lenet_weights.hdf5')

# Testing aps model of both sets
print("[INFO] Test model in real world set...")
rw_probs = aps.model.predict(rw_set)
rw_prediction = rw_probs.argmax(axis=1)

# show the accuracy on the real world
print("[INFO] evaluating real world set...")
(loss, accuracy) = aps.model.evaluate(rw_set, rw_labels, verbose=0)
print("[INFO] accuracy: {:.2f}%".format(accuracy * 100) +
      " - loss: {:.2f}".format(loss))

imlabel = ['any', 'einstein', 'curie']
for i in np.random.choice(np.arange(0, len(rw_labels)), size=(10,)):
    print("[INFO] Predicted: {}, Actual: {}".format(rw_prediction[i],
		np.argmax(rw_labels[i])))
    image = (rw_set[i] * 255).astype("uint8")
    image = cv2.merge([image] * 3)
    cv2.putText(image, str(imlabel[rw_prediction[i]]), (5, 20),
	 	cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 255, 0), 2)
    cv2.imshow("Digit", image)
    cv2.waitKey(0)


Using Theano backend.
[INFO] compiling model...
[INFO] Test model in real world set...
[INFO] evaluating real world set...
[INFO] accuracy: 100.00% - loss: 0.00
[INFO] Predicted: 0, Actual: 0
[INFO] Predicted: 0, Actual: 0
[INFO] Predicted: 0, Actual: 0
[INFO] Predicted: 1, Actual: 1
[INFO] Predicted: 0, Actual: 0
[INFO] Predicted: 0, Actual: 0
[INFO] Predicted: 0, Actual: 0
[INFO] Predicted: 0, Actual: 0
[INFO] Predicted: 0, Actual: 0
[INFO] Predicted: 0, Actual: 0

In [ ]: