Cainvas

Signature Forgery Detection

Credit: AITS Cainvas Community

Photo by ForSureLetters on Dribbble

In [1]:
!pip install wget
import tqdm
import numpy as np 
import pandas as pd
import os
import matplotlib.pyplot as plt
import seaborn as sns
import cv2
from sklearn.model_selection import StratifiedShuffleSplit,train_test_split
import glob
import tensorflow as tf
from sklearn import metrics
from sklearn import preprocessing
from tensorflow import keras
sns.set_style('darkgrid')
!wget -N 'https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/signature.zip'
!unzip -qo signature.zip
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: wget in ./.local/lib/python3.7/site-packages (3.2)
WARNING: You are using pip version 20.3.1; however, version 21.1.3 is available.
You should consider upgrading via the '/opt/tljh/user/bin/python -m pip install --upgrade pip' command.
--2021-07-14 10:15:58--  https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/signature.zip
Resolving cainvas-static.s3.amazonaws.com (cainvas-static.s3.amazonaws.com)... 52.219.66.92
Connecting to cainvas-static.s3.amazonaws.com (cainvas-static.s3.amazonaws.com)|52.219.66.92|:443... connected.
HTTP request sent, awaiting response... 304 Not Modified
File ‘signature.zip’ not modified on server. Omitting download.

Each Image in Folder with suffix _forg are forged

Each Image in Folders without this suffix are Authentic or signed by the same person .

In [2]:
display(sorted(glob.glob('train_data/*'))[:4])
print('Each Folder has' ,len(glob.glob('train_data/001/*')),'Authentic Images and Folders with suffix forf have',len(glob.glob('train_data/001_forg/*')),'Images.')
print('Training Size is ',len(glob.glob('train_data/*/*')),'Validation Size is ',len(glob.glob('validation_data/*/*')),'Test Size is ',len(glob.glob('test_data/*/*')))
['train_data/001',
 'train_data/001_forg',
 'train_data/002',
 'train_data/002_forg']
Each Folder has 12 Authentic Images and Folders with suffix forf have 4 Images.
Training Size is  826 Validation Size is  413 Test Size is  410
In [3]:
plt.imshow(plt.imread('train_data/001/001_01.PNG'))
Out[3]:
<matplotlib.image.AxesImage at 0x7f3409febcc0>
In [4]:
def get_data(dir, labels):
    features = []
    targets = []
    for name in tqdm.tqdm(sorted(os.listdir(dir))):
        for image_name in sorted(os.listdir(os.path.join(dir,name))):
            img = cv2.imread(os.path.join(dir,name,image_name), cv2.IMREAD_GRAYSCALE)
            features.append(img)
            if labels:
                if 'forg' in name.lower():
                    targets.append(1)
                else:
                    targets.append(0)
    if labels:
        return np.array(features),np.array(targets)
    else:
        return np.array(features)
train_features, train_labels = get_data('train_data', True)
validation_features, validation_labels = get_data('validation_data', True)
test_features, test_labels = get_data('test_data', True)
print(train_features.shape,validation_features.shape,test_features.shape)
train_features = train_features.reshape((826, 268, 650, 1))
train_features = train_features.astype('float32') / 255
validation_features = validation_features.reshape((413, 268, 650, 1))
validation_features = validation_features.astype('float32') / 255
test_features = test_features.reshape((410, 268, 650, 1))
test_features = test_features.astype('float32') / 255
100%|██████████| 128/128 [00:01<00:00, 108.68it/s]
100%|██████████| 128/128 [00:00<00:00, 199.23it/s]
100%|██████████| 128/128 [00:00<00:00, 192.87it/s]
(826, 268, 650) (413, 268, 650) (410, 268, 650)
In [5]:
def get_model():
    keras.backend.clear_session()
    model = keras.models.Sequential()
    model.add(keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(268, 650, 1),padding='same'))
    model.add(keras.layers.MaxPooling2D((2, 2),padding='same'))
    model.add(keras.layers.Conv2D(32, (3, 3), activation='relu',padding='same'))
    model.add(keras.layers.MaxPooling2D((2, 2),padding='same'))
    model.add(keras.layers.Conv2D(64, (3, 3), activation='relu',padding='same'))
    model.add(keras.layers.MaxPooling2D((2, 2),padding='same'))
    model.add(keras.layers.Conv2D(64, (3, 3), activation='relu',padding='same'))
    model.add(keras.layers.MaxPooling2D((2, 2),padding='same'))
    model.add(keras.layers.Conv2D(64, (3, 3), activation='relu',padding='same'))
    model.add(keras.layers.MaxPooling2D((2, 2),padding='same'))
    model.add(keras.layers.Conv2D(64, (3, 3), activation='relu',padding='same'))
    model.add(keras.layers.MaxPooling2D((2, 2),padding='same'))
    model.add(keras.layers.Conv2D(64, (3, 3), activation='relu',padding='same'))
    model.add(keras.layers.MaxPooling2D((2, 2),padding='same'))
    model.add(keras.layers.Conv2D(64, (3, 3), activation='relu',padding='same'))
    model.add(keras.layers.MaxPooling2D((2, 2),padding='same'))
    model.add(keras.layers.Flatten())
    model.add(keras.layers.Dense(32, activation='relu'))
    model.add(keras.layers.Dense(1, activation='sigmoid'))
    model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['accuracy'])
    model.summary()
    return model
model = get_model()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 268, 650, 32)      320       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 134, 325, 32)      0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 134, 325, 32)      9248      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 67, 163, 32)       0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 67, 163, 64)       18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 34, 82, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 34, 82, 64)        36928     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 17, 41, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 17, 41, 64)        36928     
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 9, 21, 64)         0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 21, 64)         36928     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 5, 11, 64)         0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 5, 11, 64)         36928     
_________________________________________________________________
max_pooling2d_6 (MaxPooling2 (None, 3, 6, 64)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 3, 6, 64)          36928     
_________________________________________________________________
max_pooling2d_7 (MaxPooling2 (None, 2, 3, 64)          0         
_________________________________________________________________
flatten (Flatten)            (None, 384)               0         
_________________________________________________________________
dense (Dense)                (None, 32)                12320     
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 33        
=================================================================
Total params: 225,057
Trainable params: 225,057
Non-trainable params: 0
_________________________________________________________________
In [6]:
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['accuracy'])
early_stop = keras.callbacks.EarlyStopping(monitor= 'val_accuracy',patience=10,restore_best_weights=True)
red_lr = keras.callbacks.ReduceLROnPlateau(patience=3,monitor= 'val_accuracy',factor=0.75)
model_check = keras.callbacks.ModelCheckpoint(f"signature.h5",save_best_only=True)
history = model.fit(train_features, train_labels ,validation_data=(validation_features, validation_labels),epochs=30,callbacks = [early_stop,model_check,red_lr])
Epoch 1/30
 2/26 [=>............................] - ETA: 1s - loss: 0.7490 - accuracy: 0.4219WARNING:tensorflow:Callbacks method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0440s vs `on_train_batch_end` time: 0.0722s). Check your callbacks.
26/26 [==============================] - 5s 181ms/step - loss: 0.6967 - accuracy: 0.4988 - val_loss: 0.7205 - val_accuracy: 0.4625
Epoch 2/30
26/26 [==============================] - 3s 131ms/step - loss: 0.6958 - accuracy: 0.5109 - val_loss: 0.6713 - val_accuracy: 0.7579
Epoch 3/30
26/26 [==============================] - 3s 131ms/step - loss: 0.7141 - accuracy: 0.7906 - val_loss: 0.3346 - val_accuracy: 0.8547
Epoch 4/30
26/26 [==============================] - 3s 131ms/step - loss: 0.1062 - accuracy: 0.9613 - val_loss: 0.3020 - val_accuracy: 0.8910
Epoch 5/30
26/26 [==============================] - 3s 130ms/step - loss: 0.0582 - accuracy: 0.9818 - val_loss: 0.6940 - val_accuracy: 0.8935
Epoch 6/30
26/26 [==============================] - 3s 129ms/step - loss: 0.2546 - accuracy: 0.9576 - val_loss: 0.3755 - val_accuracy: 0.8959
Epoch 7/30
26/26 [==============================] - 3s 130ms/step - loss: 0.0400 - accuracy: 0.9843 - val_loss: 0.6922 - val_accuracy: 0.8983
Epoch 8/30
26/26 [==============================] - 3s 130ms/step - loss: 0.0650 - accuracy: 0.9770 - val_loss: 0.5701 - val_accuracy: 0.8959
Epoch 9/30
26/26 [==============================] - 3s 131ms/step - loss: 0.0314 - accuracy: 0.9891 - val_loss: 0.2554 - val_accuracy: 0.9080
Epoch 10/30
26/26 [==============================] - 3s 130ms/step - loss: 0.0173 - accuracy: 0.9976 - val_loss: 1.4482 - val_accuracy: 0.8935
Epoch 11/30
26/26 [==============================] - 3s 130ms/step - loss: 0.0498 - accuracy: 0.9867 - val_loss: 0.7330 - val_accuracy: 0.9007
Epoch 12/30
26/26 [==============================] - 3s 130ms/step - loss: 0.0062 - accuracy: 0.9964 - val_loss: 1.4886 - val_accuracy: 0.8959
Epoch 13/30
26/26 [==============================] - 3s 130ms/step - loss: 0.0058 - accuracy: 0.9988 - val_loss: 0.5737 - val_accuracy: 0.9177
Epoch 14/30
26/26 [==============================] - 3s 130ms/step - loss: 0.0272 - accuracy: 0.9939 - val_loss: 1.7399 - val_accuracy: 0.8959
Epoch 15/30
26/26 [==============================] - 3s 131ms/step - loss: 1.5213e-04 - accuracy: 1.0000 - val_loss: 2.8497 - val_accuracy: 0.8959
Epoch 16/30
26/26 [==============================] - 3s 131ms/step - loss: 0.0197 - accuracy: 0.9927 - val_loss: 2.3043 - val_accuracy: 0.8983
Epoch 17/30
26/26 [==============================] - 3s 131ms/step - loss: 1.7406e-04 - accuracy: 1.0000 - val_loss: 2.3739 - val_accuracy: 0.8983
Epoch 18/30
26/26 [==============================] - 3s 131ms/step - loss: 3.9943e-05 - accuracy: 1.0000 - val_loss: 2.5672 - val_accuracy: 0.8983
Epoch 19/30
26/26 [==============================] - 3s 131ms/step - loss: 9.0830e-06 - accuracy: 1.0000 - val_loss: 3.0231 - val_accuracy: 0.8983
Epoch 20/30
26/26 [==============================] - 3s 132ms/step - loss: 2.2442e-06 - accuracy: 1.0000 - val_loss: 3.3780 - val_accuracy: 0.8983
Epoch 21/30
26/26 [==============================] - 3s 132ms/step - loss: 7.4263e-07 - accuracy: 1.0000 - val_loss: 3.6413 - val_accuracy: 0.9007
Epoch 22/30
26/26 [==============================] - 3s 132ms/step - loss: 1.4938e-07 - accuracy: 1.0000 - val_loss: 4.2647 - val_accuracy: 0.8983
Epoch 23/30
26/26 [==============================] - 3s 133ms/step - loss: 1.6305e-08 - accuracy: 1.0000 - val_loss: 4.4710 - val_accuracy: 0.8983
In [7]:
fig = plt.figure()
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
fig.savefig('Accuracy vs Epoch.png')
In [8]:
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
fig.savefig('Loss vs Epoch.png')
In [9]:
model.evaluate(train_features, train_labels)
model.evaluate(validation_features, validation_labels)
model.evaluate(test_features, test_labels)
26/26 [==============================] - 1s 39ms/step - loss: 0.1574 - accuracy: 0.9661
13/13 [==============================] - 0s 38ms/step - loss: 0.5737 - accuracy: 0.9177
13/13 [==============================] - 0s 37ms/step - loss: 0.6449 - accuracy: 0.9000
Out[9]:
[0.6449013948440552, 0.8999999761581421]
In [10]:
indices = np.random.randint(len(test_features),size=15)
demo,demo_pred = test_features[indices],test_labels[indices]
predictions = (model.predict(demo)>.5).astype('int32')
In [11]:
fig,ax = plt.subplots(3,5,figsize=(15,7))
plt.tick_params(left = False, bottom = False)
for i in range(3):
    for j in range(5):
        ax[i][j].imshow(demo[i*5+j])
        ax[i][j].tick_params(left = False, bottom = False)
        ax[i][j].set_xticks([])
        ax[i][j].set_yticks([])
        ax[i][j].set_title("Truth Label "+str(demo_pred[i*5+j])+"   \n PREDICTED   "+str(predictions[i*5+j][0]))
fig.savefig('Predict.png')
In [12]:
model.save('signature.h5')

DeepCC

In [13]:
!deepCC signature.h5
[INFO]
Reading [keras model] 'signature.h5'
[SUCCESS]
Saved 'signature_deepC/signature.onnx'
[INFO]
Reading [onnx model] 'signature_deepC/signature.onnx'
[INFO]
Model info:
  ir_vesion : 5
  doc       : 
[WARNING]
[ONNX]: graph-node conv2d's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2d_1's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2d_2's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2d_3's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2d_4's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2d_5's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2d_6's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2d_7's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: terminal (input/output) conv2d_input's shape is less than 1. Changing it to 1.
[WARNING]
[ONNX]: terminal (input/output) dense_1's shape is less than 1. Changing it to 1.
WARN (GRAPH): found operator node with the same name (dense_1) as io node.
[INFO]
Running DNNC graph sanity check ...
[SUCCESS]
Passed sanity check.
[INFO]
Writing C++ file 'signature_deepC/signature.cpp'
[INFO]
deepSea model files are ready in 'signature_deepC/' 
[RUNNING COMMAND]
g++ -std=c++11 -O3 -fno-rtti -fno-exceptions -I. -I/opt/tljh/user/lib/python3.7/site-packages/deepC-0.13-py3.7-linux-x86_64.egg/deepC/include -isystem /opt/tljh/user/lib/python3.7/site-packages/deepC-0.13-py3.7-linux-x86_64.egg/deepC/packages/eigen-eigen-323c052e1731 "signature_deepC/signature.cpp" -D_AITS_MAIN -o "signature_deepC/signature.exe"
[RUNNING COMMAND]
size "signature_deepC/signature.exe"
   text	   data	    bss	    dec	    hex	filename
1094653	   3760	    760	1099173	 10c5a5	signature_deepC/signature.exe
[SUCCESS]
Saved model as executable "signature_deepC/signature.exe"