Identification de numéros de carte bancaire par traitement d'images OpenCV

  1. Introduction

Ce projet de fin d'études présente un système de reconnaissance automatique des numéros de carte bancaire à partir d'images. L'approche combine la localisation de la zone du numéro, la segmentation des caractères et leur reconnaissance à l'aide de techniques de vision par ordinateur et d'apprentissage profond.

L'évaluation du projet (sur 5) : difficulté 3, charge de travail 3, originalité 4.

  1. Architecture algorithmique

2.1 Prétraitement de l'image

Les informations de couleur étant inutiles pour la reconnaissance des chiffres, l'image est convertie en niveaux de gris. Cette étape réduit la dimensionnalité et élimine les perturbations liées aux motifs de fond de la carte.

2.2 Détection des contours

La zone contenant le numéro est extraite par seuillage adaptatif. Une fois la région d'intérêt localisée, les caractères sont isolés par analyse de projection.

2.3 Correspondance par modèle (template matching)

Chaque caractère segmenté est comparé à une base de modèles de chiffres prédéfinis. La similarité est calculée par corrélation normalisée, et le modèle le plus proche est retenu.

2.4 Reconnaissance finale

Les caractères étant souvent en relief sur les cartes, un algorithme de glissement de fenêtre (taille 64×48 pixels) balaie l'image segmentée de gauche à droite. Si la meilleure correspondance dépasse un seuil donné, le chiffre est validé ; sinon, la zone est considérée comme vide.

  1. Localisation du numéro de carte

La localisation est réalisée à l'aide d'un réseau Faster R-CNN entraîné sur des images de cartes bancaires. Le code suivant illustre l'étape de détection et de découpage de la région du numéro.

#!/usr/bin/env python
from __future__ import absolute_import, division, print_function
import os, cv2, numpy as np, tensorflow as tf
import matplotlib.pyplot as plt
from lib.config import config as cfg
from lib.utils.nms_wrapper import nms
from lib.utils.test import im_detect
from lib.nets.vgg16 import vgg16
from lib.utils.timer import Timer

os.environ["CUDA_VISIBLE_DEVICES"] = '0'
config_tf = tf.ConfigProto()
config_tf.gpu_options.per_process_gpu_memory_fraction = 0.8
config_tf.gpu_options.allow_growth = True
sess = tf.Session(config=config_tf)

CLASSES = ('__background__', 'lb')
NETS = {'vgg16': ('vgg16_faster_rcnn_iter_70000.ckpt',)}
DATASETS = {'pascal_voc': ('voc_2007_trainval',)}

def draw_detections(im, class_name, dets, threshold=0.5):
    inds = np.where(dets[:, -1] >= threshold)[0]
    if len(inds) == 0: return
    im_rgb = im[:, :, (2, 1, 0)]
    fig, ax = plt.subplots(figsize=(12,12))
    ax.imshow(im_rgb, aspect='equal')
    scores = [dets[i, -1] for i in inds]
    max_score = max(scores)
    for i in inds:
        score = dets[i, -1]
        if score == max_score:
            bbox = dets[i, :4].astype(int)
            img = cv2.imread("data/demo/" + filename)
            h, w = img.shape[:2]
            x1, y1, x2, y2 = bbox
            margin = 20
            x1_c = max(0, x1 - margin)
            x2_c = min(w, x2 + margin)
            cropped = img[y1:y2, x1_c:x2_c]
            resized = cv2.resize(cropped, (1000, 100), interpolation=cv2.INTER_CUBIC)
            cv2.imwrite('cut1/' + str(i) + filename, resized)
            ax.add_patch(plt.Rectangle((x1, y1), x2-x1, y2-y1, fill=False, edgecolor='red', linewidth=3.5))
            ax.text(x1, y1-2, '{:s} {:.3f}'.format(class_name, score),
                    bbox=dict(facecolor='blue', alpha=0.5), fontsize=14, color='white')
    plt.axis('off')
    plt.show()

def demo(sess, net, image_name):
    im_file = os.path.join(cfg.FLAGS2["data_dir"], 'demo', image_name)
    im = cv2.imread(im_file)
    timer = Timer(); timer.tic()
    scores, boxes = im_detect(sess, net, im)
    timer.toc()
    print('Detection took {:.3f}s for {:d} proposals'.format(timer.total_time, boxes.shape[0]))
    CONF_THRESH, NMS_THRESH = 0.1, 0.1
    for cls_ind, cls in enumerate(CLASSES[1:]):
        cls_ind += 1
        cls_boxes = boxes[:, 4*cls_ind:4*(cls_ind+1)]
        cls_scores = scores[:, cls_ind]
        dets = np.hstack((cls_boxes, cls_scores[:, np.newaxis])).astype(np.float32)
        keep = nms(dets, NMS_THRESH)
        dets = dets[keep, :]
        draw_detections(im, cls, dets, threshold=CONF_THRESH)

if __name__ == "__main__":
    tfmodel = './default/voc_2007_trainval/cut1/vgg16_faster_rcnn_iter_8000.ckpt'
    if not os.path.isfile(tfmodel + '.meta'):
        raise IOError('Model not found.')
    net = vgg16(batch_size=1)
    net.create_architecture(sess, "TEST", 2, tag='default', anchor_scales=[8,16,32])
    saver = tf.train.Saver()
    saver.restore(sess, tfmodel)
    filename = '0001.jpg'
    demo(sess, net, filename)

  1. Segmentation des caractères

Après etxraction de la zone du numéro, une segmentation par projection verticale est appliquée pour isoler chaque chiffre. Les paramètres de seuil sont ajustés en fonction de la largeur moyenne des caractères.

  1. Reconnaissance des chiffres

Un réseau de neurones AlexNet (adapté pour la reconnaissance de 10 classes) est entraîné sur des images de chiffres segmentés. Le code ci-dessous montre l'étape de prédiction.

import os, tensorflow as tf, numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from nets2 import nets_factory

CHAR_SET_LEN = 10
IMAGE_HEIGHT, IMAGE_WIDTH = 60, 160
BATCH_SIZE = 1
TFRECORD_FILE = "C:/workspace/Python/Bank_Card_OCR/demo/test_result/tfrecords/1.tfrecords"

x = tf.placeholder(tf.float32, [None, 224, 224])
os.environ["CUDA_VISIBLE_DEVICES"] = '0'
config = tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.5
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)

def read_and_decode(filename):
    filename_queue = tf.train.string_input_producer([filename])
    reader = tf.TFRecordReader()
    _, serialized_example = reader.read(filename_queue)
    features = tf.parse_single_example(serialized_example,
        features={'image': tf.FixedLenFeature([], tf.string),
                  'label0': tf.FixedLenFeature([], tf.int64)})
    image = tf.decode_raw(features['image'], tf.uint8)
    image = tf.reshape(image, [224, 224])
    image_raw = tf.reshape(image, [224, 224])
    image = tf.cast(image, tf.float32) / 255.0
    image = tf.subtract(image, 0.5)
    image = tf.multiply(image, 2.0)
    label0 = tf.cast(features['label0'], tf.int32)
    return image, image_raw, label0

image, image_raw, label0 = read_and_decode(TFRECORD_FILE)
image_batch, image_raw_batch, label_batch0 = tf.train.shuffle_batch(
    [image, image_raw, label0], batch_size=BATCH_SIZE,
    capacity=50000, min_after_dequeue=10000, num_threads=1)

train_network_fn = nets_factory.get_network_fn(
    'alexnet_v2', num_classes=CHAR_SET_LEN * 1,
    weight_decay=0.0005, is_training=False)

with tf.Session() as sess2:
    X = tf.reshape(x, [BATCH_SIZE, 224, 224, 1])
    logits, end_points = train_network_fn(X)
    logits0 = tf.slice(logits, [0,0], [-1,10])
    predict0 = tf.argmax(logits0, 1)
    sess2.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
    saver.restore(sess2, '../Cmodels/model/crack_captcha1.model-6000')
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess=sess2, coord=coord)
    for i in range(6):
        b_image, b_image_raw, b_label0 = sess2.run([image_batch, image_raw_batch, label_batch0])
        img = Image.fromarray(b_image_raw[0], 'L')
        plt.imshow(img); plt.axis('off'); plt.show()
        print('label:', b_label0)
        pred = sess2.run(predict0, feed_dict={x: b_image})
        print('predict:', pred[0])
    coord.request_stop()
    coord.join(threads)

Les résultats montrent une reconnaissance corercte des numéros de carte avec une précision satisfaisante.

  1. Synthèse du pipeline simplifié

  • Prétraitement : conversion en niveaux de gris, détection de contours
  • Localisation : Faster R-CNN
  • Segmentation : projection verticale
  • Reconnaissance : réseau AlexNet

Étiquettes: OpenCV TensorFlow faster r-cnn Reconnaissance de caractères Cartes bancaires

Publié le 16 juin à 20h52