1.0 ВВЕДЕНИЕ

В области анализа цифровых изображений насущной необходимостью стала разработка надежных методов, позволяющих отличить подлинные изображения от поддельных. С появлением сложных инструментов редактирования изображений определение подлинности визуальных доказательств стало сложной задачей. В этой статье мы отправляемся в увлекательное путешествие в процесс обучения сверточной нейронной сети (CNN) для обнаружения манипулируемых изображений.

Наша цель — оснастить нашу нейронную сеть способностью тщательно изучать каждый пиксель, обнаруживая тонкие подсказки, которые выявляют следы несанкционированного доступа. Используя возможности TensorFlow, передовой среды машинного обучения, мы обучаем сверточные нейронные сети на изображениях ELA (анализ уровня ошибок), чтобы уловить тонкую разницу между подлинными и поддельными изображениями.

Анализ уровня ошибок (ELA)Анализ уровня ошибок — это метод, используемый для выявления областей изображения, которые были изменены или подделаны. Он работает на основе того принципа, что при сжатии или редактировании изображения уровень ошибок, возникающих в измененных областях, отличается от остальной части изображения. Извлекая разницу в уровнях ошибок в новое изображение, которое мы будем называть изображением ELA, и используя эти изображения для обучения CNN, мы можем определить, было ли изображение потенциально изменено.

Сверточные нейронные сети (CNN)Сверточные нейронные сети — это тип модели глубокого обучения, обычно используемый для анализа изображений и задач классификации. CNN способны изучать сложные шаблоны и особенности изображений, что делает их хорошо подходящими для обнаружения фальсифицированных изображений. Обучая CNN на большом наборе данных изображений ELA как из подлинных, так и из обработанных изображений, он может научиться различать две категории и делать точные прогнозы.

2.0 ИМПОРТ БИБЛИОТЕК

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import os
from PIL import Image, ImageEnhance, ImageChops
import random
from shutil import copyfile
from tensorflow.keras import layers
from tensorflow.keras import models
from tensorflow.keras.metrics import Precision, Recall
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import tensorflow as tf
import matplotlib.pyplot as plt
import pickle as pkl
import warnings
warnings.filterwarnings("ignore")

3.0 ОБЗОР НАБОР ДАННЫХ

Набор данных для этого проекта — это набор данных обнаружения закалки изображений CASIA v2, который можно найти здесь на kaggle.

Набор данных содержит 7492 аутентичных изображения, сохраненных в подпапке Au, и 5125 закаленных изображений, сохраненных в подпапке Tp.

3.1 Распределение форматов изображений в наборе данных

def get_format_counts(path):
    ''' gets the counts of each image format in the specified path'''
    counts = {}

    for image in os.listdir(path):
        img_type = image.split('.')[-1]
        if img_type in counts:
            counts[img_type] += 1
        else:
            counts.update({img_type: 1})
    
    return counts

# Get the counts of formats in the authentic images folder
print(get_format_counts('/kaggle/input/casia-20-image-tampering-detection-dataset/CASIA2/Au'))

# Get the counts of formats in the tempered images folder
print(get_format_counts('/kaggle/input/casia-20-image-tampering-detection-dataset/CASIA2/Tp'))

На основе анализа наборов данных изображений было установлено, что набор данных аутентичных изображений состоит из 7 427 изображений JPG, а остальные 55 файлов считаются бесполезными для текущего проекта. С другой стороны, набор данных темперированных изображений содержит 2064 изображения JPG. Однако формат TIF, хотя и может быть прочитан приложением, также считается бесполезным для этого конкретного варианта использования из-за его природы без потерь.

Решение исключить изображения в формате TIF основано на том факте, что методология обнаружения поддельных изображений, используемая в этом проекте, в значительной степени зависит от различий в сжатии между исходными и повторно сохраненными изображениями. Поскольку формат TIF не имеет потерь, это может создать проблемы для эффективного функционирования алгоритма при обучении на таких изображениях.

В результате обучающий и проверочный набор для этого проекта будет состоять из 7427 изображений из аутентичного набора и 2064 изображений из темперированного набора. Важно отметить, что такое распределение приводит к сильно несбалансированному набору данных.

3.2 Примеры аутентичных изображений

def show_images(path):
    # Get random 8 images from the path
    image_files = random.sample(os.listdir(path), 8)

    # Create a 2x4 grid of subplots
    fig, axes = plt.subplots(2, 4, figsize=(10, 6))

    # Iterate over the image files and plot them in the subplots
    for i, ax in enumerate(axes.flatten()):
        # Load and plot the image
        image_path = os.path.join(path, image_files[i])
        image = plt.imread(image_path)
        ax.imshow(image)
        ax.set_title(image_files[i][:10])  # Set the title as the image filename
        ax.axis('off')  # Turn off axis labels

    # Adjust the spacing between subplots
    plt.tight_layout()

    # Show the plot
    plt.show()

# Path to the authentic images directory
image_dir = '/kaggle/input/casia-20-image-tampering-detection-dataset/CASIA2/Au'
show_images(image_dir)

3.3 Примеры закаленных изображений

# Path to the authentic images directory
image_dir = '/kaggle/input/casia-20-image-tampering-detection-dataset/CASIA2/Tp'
show_images(image_dir)

4.0 СОЗДАНИЕ ИЗОБРАЖЕНИЙ ELA ИЗ АУТЕНТИЧНЫХ И ЗАКАЛЕННЫХ ИЗОБРАЖЕНИЙ

В этой части мы создаем образы ELA из аутентичных и модифицированных файлов в разные папки. Мы гарантируем отфильтровывать изображения, отличные от jpeg, jpg и png.

def image_to_ela(path, quality, resave_path):
    ''' Gets images specified as path and resaves it at a new path resave_path at specified quality'''
    try:
        # Check if the file format is supported (JPEG or PNG)
        if path.endswith('jpg') or path.endswith('jpeg') or path.endswith('png'):
            # Open the image and convert it to RGB mode
            image = Image.open(path).convert('RGB')
            
            # Resave the image with the specified quality
            image.save('resaved.jpg', 'JPEG', quality=quality)
            resaved = Image.open('resaved.jpg')

            # Calculate the ELA (Error Level Analysis) image by taking the difference between the original and resaved image
            ela_image = ImageChops.difference(image, resaved)

            # Get the minimum and maximum pixel values in the ELA image
            band_values = ela_image.getextrema()
            max_value = max([val[1] for val in band_values])

            # If the maximum value is 0, set it to 1 to avoid division by zero
            if max_value == 0:
                max_value = 1

            # Scale the pixel values of the ELA image to the range [0, 255]
            scale = 255.0 / max_value
            ela_image = ImageEnhance.Brightness(ela_image).enhance(scale)

            # Save the ELA image with the same filename in the specified resave path
            ela_image.save(os.path.join(resave_path, os.path.basename(path)), 'JPEG')
    except Exception as e:
        print(f'Could not convert {path} to ELA: {str(e)}')
def preprocess_data(parent_path, files, resave_path):
    ''' Loops through a directory and applies the image_to_ela function to each image in the directory'''
    for file in files:
        image_to_ela(os.path.join(parent_path, file), 90, resave_path)
# get list of all authentic images
auth_files = os.listdir('/kaggle/input/casia-20-image-tampering-detection-dataset/CASIA2/Au')

# get list of all Tempered images
doc_files = os.listdir('/kaggle/input/casia-20-image-tampering-detection-dataset/CASIA2/Tp')

# create the all_reals and all_docs where we save the real and doctored ELA images
os.mkdir('/kaggle/working/all_reals')
os.mkdir('/kaggle/working/all_Doc')

# converting  validation real images to ela
parent_path = '/kaggle/input/casia-20-image-tampering-detection-dataset/CASIA2/Au'
preprocess_data(parent_path, auth_files, '/kaggle/working/all_reals')

# converting training doctored images to ela
parent_path = '/kaggle/input/casia-20-image-tampering-detection-dataset/CASIA2/Tp'
preprocess_data(parent_path, doc_files, '/kaggle/working/all_Doc')

print('There are ' + str(len(os.listdir('/kaggle/working/all_reals'))) + ' supported Authentic Images')
print('There are ' + str(len(os.listdir('/kaggle/working/all_Doc'))) + ' supported Tempered Images')

5.0 ОБЗОР ИЗОБРАЖЕНИЙ ELA

Давайте посмотрим на изображения ELA аутентичных изображений и закаленных изображений, которые мы построили ранее.

# Path to the authentic ela images directory
image_dir = '/kaggle/working/all_reals'
show_images(image_dir)

# Path to the tempered ela images directory
image_dir = '/kaggle/working/all_Doc'
show_images(image_dir)

6.0 РАЗДЕЛЕНИЕ ИЗОБРАЖЕНИЙ ELA НА ОБУЧАЮЩИЕ И ПРОВЕРОЧНЫЕ НАБОРЫ

Модели сверточной нейронной сети будут обучаться на изображениях, передаваемых из каталога, с использованием встроенного генератора изображений с тензорным потоком. ImageGenerator автоматически помечает изображения в подкаталогах, используя имя каталога. Чтобы использовать функцию автоматической маркировки ImageDataGenerator, вам необходимо придерживаться определенной структуры каталогов. Вот разбивка необходимого формата:

  • Каталог тренировочных данных:

Этот каталог служит основным каталогом для набора обучающих данных. Внутри каталога обучающих данных мы создадим подкаталоги, соответствующие классам или категориям, которые мы хотим классифицировать. Например, поскольку нас интересуют классификации Real и Doctored, мы создаем два подкаталога с именами «Real» и «Doctored» в каталоге обучающих данных. Все изображения, принадлежащие к классу «Настоящий», будут помещены в подкаталог «Настоящий», а все изображения, принадлежащие к классу «Подправленный», будут помещены в подкаталог «Подправленный».

  • Каталог данных проверки

Подобно каталогу обучающих данных, каталог данных проверки служит отдельным каталогом для набора данных проверки. Он должен отражать структуру каталога обучающих данных с подкаталогами, соответствующими каждому классу. Изображения для проверки должны быть помещены в соответствующие подкаталоги классов в каталоге данных проверки.

Организовав данные таким образом, ImageDataGenerator будет автоматически назначать метки изображениям на основе имен подкаталогов. Эта структурированная организация упрощает управление изображениями, маркировку и легко интегрируется с ImageDataGenerator во время обучения CNN.

# creating the parent directory for both the Training and Validation datasets
ela_path = os.path.join(os.getcwd(), 'ela_images')

# create path for training set and the different labels
auth_path_train = os.path.join(ela_path, 'train',  'Real')
temp_path_train = os.path.join(ela_path, 'train', 'Doctored')
os.makedirs(auth_path_train)
os.makedirs(temp_path_train)

# create path for validation set and its different labels
auth_path_val = os.path.join(ela_path, 'Val',  'Real')
temp_path_val = os.path.join(ela_path, 'Val', 'Doctored')
os.makedirs(auth_path_val)
os.makedirs(temp_path_val)
# Get the list of files in the 'all_reals' directory
real_files = os.listdir('/kaggle/working/all_reals')

# Get the list of files in the 'all_Doc' directory
doc_files = os.listdir('/kaggle/working/all_Doc')

# Calculate the total length for training and validation sets for doctored images
total_len_train_doc = int((90 / 100) * len(doc_files))
total_len_val_doc = int((10 / 100) * len(doc_files))

# Randomly select files for validation from the authentic image files
val_auth = random.choices(real_files, k=total_len_val_doc)

# Select the remaining files for training from the authentic image files
train_auth = [f for f in real_files if f not in val_auth][:total_len_train_doc]

# Randomly select files for validation from the doctored image files
val_doc = random.choices(doc_files, k=total_len_val_doc)

# Select the remaining files for training from the doctored image files
train_doc = [f for f in doc_files if f not in val_doc][:total_len_train_doc]

# Path to the destination directories for training and validation authentic images
auth_path_train = '/kaggle/working/ela_images/train/Real'
auth_path_val = '/kaggle/working/ela_images/Val/Real'

# Path to the destination directories for training and validation doctored images
temp_path_train = '/kaggle/working/ela_images/train/Doctored'
temp_path_val = '/kaggle/working/ela_images/Val/Doctored'

# Copy the training authentic images to ela_images/train/Real
for item in train_auth:
    copyfile('/kaggle/working/all_reals/{}'.format(item), "{}/{}".format(auth_path_train, os.path.basename(item)))

# Copy the validation authentic images to ela_images/Val/Real
for item in val_auth:
    copyfile('/kaggle/working/all_reals/{}'.format(item), "{}/{}".format(auth_path_val, os.path.basename(item)))

# Copy the training doctored images to ela_images/train/Doctored
for item in train_doc:
    copyfile('/kaggle/working/all_Doc/{}'.format(item), "{}/{}".format(temp_path_train, os.path.basename(item)))

# Copy the validation doctored images to ela_images/Val/Doctored
for item in val_doc:
    copyfile('/kaggle/working/all_Doc/{}'.format(item), "{}/{}".format(temp_path_val, os.path.basename(item)))

# Print the lengths of the validation and training sets for authentic and doctored images
print("There are a total of " + str(len(train_auth)) + "Authentic Images in the training set")
print("There are a total of " + str(len(train_doc)) + "Doctored Images in the training set")
print("There are a total of " + str(len(val_auth)) + "Authentic Images in the Validation set")
print("There are a total of " + str(len(val_doc)) + "Doctored Images in the Validation set")

7.0 СОЗДАНИЕ ГЕНЕРАТОРОВ ИЗОБРАЖЕНИЙ ДЛЯ ПОТОКОВОЙ ПЕРЕДАЧИ ИЗОБРАЖЕНИЙ ДАННЫЕ ДЛЯ ОБУЧЕНИЯ И ПРОВЕРКИ

Мы передаем путь к каталогу набора данных для обучения и проверки объекту ImageDataGenerator, чтобы создать два соответствующих генератора данных изображения. Мы также указываем размер передаваемых изображений 150 x 150 и размер пакета 8.

# Create an instance of the ImageDataGenerator
datagen = ImageDataGenerator()

# Generate training images
train_images = datagen.flow_from_directory(
    '/kaggle/working/ela_images/train',
    target_size=(150, 150),
    batch_size=8,
    class_mode='binary'
)

# Generate validation images
validation_images = datagen.flow_from_directory(
    '/kaggle/working/ela_images/Val',
    target_size=(150, 150),
    batch_size=8,
    class_mode='binary'
)

8.0. СЕТЕВАЯ АРХИТЕКТУРА

def train_model():
    model = tf.keras.models.Sequential([
        tf.keras.layers.Conv2D(filters = 32, kernel_size = (5, 5), padding = 'valid', activation = 'relu', input_shape = (150, 150, 3)),
        tf.keras.layers.Conv2D(filters = 32, kernel_size = (5, 5), padding = 'valid', activation = 'relu'),
        tf.keras.layers.MaxPool2D(pool_size = (2, 2)),
        tf.keras.layers.Dropout(0.25),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(150, activation = 'relu'),
        tf.keras.layers.Dropout(0.5),
        tf.keras.layers.Dense(1, activation = 'sigmoid')
    ])
    
    return model
model = train_model()
model.summary()

model.compile(
    optimizer = tf.keras.optimizers.Adam(learning_rate = 0.0001),
    loss = tf.keras.losses.BinaryCrossentropy(),
    metrics=['accuracy', Precision(), Recall()]
)
history = model.fit(
    train_images,  
    steps_per_epoch=len(train_images) / 8,
    epochs=30,
    validation_data = validation_images
)

def plot_accuracy(history):
    # Create a 2x2 grid of subplots
    fig, axes = plt.subplots(2, 2, figsize=(10, 6))
        
    # Get the training and validation accuracy values from the history object
    train_acc = history.history['accuracy']
    val_acc = history.history['val_accuracy']
    
    # Get the training and validation loss values from the history object
    train_loss = history.history['loss']
    val_loss = history.history['val_loss']
    
    # Get the training and validation precision values from the history object
    train_precision = history.history['precision_1']
    val_precision = history.history['val_precision_1']
    
    # Get the training and validation recall values from the history object
    train_recall = history.history['recall_1']
    val_recall = history.history['val_recall_1']
    
    # Get the number of epochs
    epochs = range(1, len(train_acc) + 1)
    
    # Plot the training and validation accuracy
    axes[0, 0].plot(epochs, train_acc, 'b', label='Training Accuracy')
    axes[0, 0].plot(epochs, val_acc, 'r', label='Validation Accuracy')
    axes[0, 0].set_title('Training and Validation Accuracy')
    axes[0, 0].set_xlabel('Epochs')
    axes[0, 0].set_ylabel('Accuracy')
    axes[0, 0].legend()
    
    # Plot the training and validation loss
    axes[0, 1].plot(epochs, train_loss, 'b', label='Training Loss')
    axes[0, 1].plot(epochs, val_loss, 'r', label='Validation Loss')
    axes[0, 1].set_title('Training and Validation Loss')
    axes[0, 1].set_xlabel('Epochs')
    axes[0, 1].set_ylabel('Loss')
    axes[0, 1].legend()
    
    # Plot the training and validation precision
    axes[1, 0].plot(epochs, train_precision, 'b', label='Training Precision')
    axes[1, 0].plot(epochs, val_precision, 'r', label='Validation Precision')
    axes[1, 0].set_title('Training and Validation Precision')
    axes[1, 0].set_xlabel('Epochs')
    axes[1, 0].set_ylabel('Precision')
    axes[1, 0].legend()
    
    # Plot the training and validation recall
    axes[1, 1].plot(epochs, train_recall, 'b', label='Training Recall')
    axes[1, 1].plot(epochs, val_recall, 'r', label='Validation Recall')
    axes[1, 1].set_title('Training and Validation Recall')
    axes[1, 1].set_xlabel('Epochs')
    axes[1, 1].set_ylabel('Recall')
    axes[1, 1].legend()
    
    # Adjust the layout
    fig.tight_layout()
    
    # Show the plot
    plt.show()


plot_accuracy(history)

Для нашего варианта использования приложения мы хотели бы, чтобы приложение одинаково хорошо работало как с точностью, так и с отзывом. Мы хотели бы, чтобы он пытался идентифицировать все темперированные изображения, при этом уменьшая ошибки классификации подлинных изображений как темперированных. Таким образом, точность является отличным показателем для нас. Благодаря обучению мы можем достичь точности от 96 до 98% на обучающем наборе данных и от 90 до 92% на тестовом наборе данных.