使用IBM ART库生成交通信号牌的攻击样本

发布时间 2023-10-30 11:57:52作者: bonelee

目标:生成对抗样本,扰动图像,让原本是“停”的信号牌识别为“禁止驶入”:

 

代码如下(注意,因为我找的cnn原始模型支持的是灰度图像,所以彩色的对抗样本还需要修改代码):

import cv2, os
import numpy as np
import numpy as np
import tensorflow as tf
from keras.models import load_model
from tensorflow.keras.layers import Dense
from tensorflow.keras.utils import to_categorical
from art.estimators.classification import TensorFlowV2Classifier
from art.attacks.evasion import FastGradientMethod
import matplotlib.pyplot as plt
from tensorflow.keras.models import load_model
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split


def getCalssName(classNo):
    if   classNo == 0: return 'Speed Limit 20 km/h'
    elif classNo == 1: return 'Speed Limit 30 km/h'
    elif classNo == 2: return 'Speed Limit 50 km/h'
    elif classNo == 3: return 'Speed Limit 60 km/h'
    elif classNo == 4: return 'Speed Limit 70 km/h'
    elif classNo == 5: return 'Speed Limit 80 km/h'
    elif classNo == 6: return 'End of Speed Limit 80 km/h'
    elif classNo == 7: return 'Speed Limit 100 km/h'
    elif classNo == 8: return 'Speed Limit 120 km/h'
    elif classNo == 9: return 'No passing'
    elif classNo == 10: return 'No passing for vechiles over 3.5 metric tons'
    elif classNo == 11: return 'Right-of-way at the next intersection'
    elif classNo == 12: return 'Priority road'
    elif classNo == 13: return 'Yield'
    elif classNo == 14: return 'Stop'
    elif classNo == 15: return 'No vechiles'
    elif classNo == 16: return 'Vechiles over 3.5 metric tons prohibited'
    elif classNo == 17: return 'No entry'
    elif classNo == 18: return 'General caution'
    elif classNo == 19: return 'Dangerous curve to the left'
    elif classNo == 20: return 'Dangerous curve to the right'
    elif classNo == 21: return 'Double curve'
    elif classNo == 22: return 'Bumpy road'
    elif classNo == 23: return 'Slippery road'
    elif classNo == 24: return 'Road narrows on the right'
    elif classNo == 25: return 'Road work'
    elif classNo == 26: return 'Traffic signals'
    elif classNo == 27: return 'Pedestrians'
    elif classNo == 28: return 'Children crossing'
    elif classNo == 29: return 'Bicycles crossing'
    elif classNo == 30: return 'Beware of ice/snow'
    elif classNo == 31: return 'Wild animals crossing'
    elif classNo == 32: return 'End of all speed and passing limits'
    elif classNo == 33: return 'Turn right ahead'
    elif classNo == 34: return 'Turn left ahead'
    elif classNo == 35: return 'Ahead only'
    elif classNo == 36: return 'Go straight or right'
    elif classNo == 37: return 'Go straight or left'
    elif classNo == 38: return 'Keep right'
    elif classNo == 39: return 'Keep left'
    elif classNo == 40: return 'Roundabout mandatory'
    elif classNo == 41: return 'End of no passing'
    elif classNo == 42: return 'End of no passing by vechiles over 3.5 metric tons'


def grayscale(img):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    return img

def equalize(img):
    img =cv2.equalizeHist(img)
    return img

def preprocessing(img):
    # img = grayscale(img)
    img = equalize(img)
    img = img/255
    return img

def read_imgs(image_dir, label=0):
    # 读取图片
    image_files = os.listdir(image_dir)
    images = []
    labels = []
    for image_file in image_files:
        image_path = os.path.join(image_dir, image_file)
        image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)  # 以灰度模式读取图片
        image = cv2.resize(image, (30, 30)) 
        img = preprocessing(image)
        images.append(img)
        labels.append(label)
    return images, labels

def test_predict(model):
    # 读取图片
    image_dir = 'D:/welink/STOP/before'
    # images = read_imgs(image_dir)
    image_files = os.listdir(image_dir)
    images = []
    for image_file in image_files:
        image_path = os.path.join(image_dir, image_file)
        image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)  # 以灰度模式读取图片
        image = cv2.resize(image, (30, 30)) 
        img = preprocessing(image)
        img = img.reshape(1, 30, 30, 1)
        images.append(img)
        # PREDICT IMAGE
        predictions = model.predict(img)
        predict_x=model.predict(img)
        classIndex=np.argmax(predict_x)
        probabilityValue =np.amax(predictions)
        print("img path:", image_file, " ==> ", str(classIndex)+" "+str(getCalssName(classIndex)))
        print(str(round(probabilityValue*100,2) )+"%")

def refact_model(model):
    base_model = model
    # 移除最后的分类层
    base_model = Model(inputs=base_model.input, outputs=base_model.layers[-2].output)
    # 添加一个新的分类层
    output = Dense(2, activation='softmax', name='new_dense')(base_model.output)
    model = Model(inputs=base_model.input, outputs=output)
    # 编译模型
    model.compile(optimizer=Adam(), loss='categorical_crossentropy', metrics=['accuracy'])
    return model

def retrain_with2label(model):
    image_dir1 = 'D:/welink/STOP/14'
    image_dir2 = 'D:/welink/STOP/17'
    images1, labels1 = read_imgs(image_dir1, 0)
    images2, labels2 = read_imgs(image_dir2, 1)

    # 合并图片和标签
    images = images1 + images2
    labels = labels1 + labels2

    images = np.array(images, dtype='float32')
    # 如果模型的输入形状是(30, 30, 1),那么我们需要增加一个维度
    if model.input_shape[-1] == 1:
        images = np.expand_dims(images, axis=-1)

    labels = np.array(labels)
    labels = to_categorical(labels, num_classes=2)

    # 划分训练集和测试集
    train_images, test_images, train_labels, test_labels = train_test_split(images, labels, test_size=0.2)
    # 训练模型
    model.fit(train_images, train_labels, validation_data=(test_images, test_labels), epochs=10)

def test_predict2(model):
    # 选择stop的图像,扰动前的
    images, _ = read_imgs('D:/welink/STOP/before')
    if model.input_shape[-1] == 1:
        images = np.expand_dims(images, axis=-1)
    preds = model.predict(images)
    print('Predicted before:', preds.argmax(axis=1))
    return images

def run_art(images):
    # 创建一个目标标签(我们希望模型将0 stop识别为1 no entry)
    target_label = to_categorical(1, num_classes=2)
    target_label = np.tile(target_label, (len(images), 1))

    # 创建ART分类器
    classifier = TensorFlowV2Classifier(
        model=model,
        nb_classes=2,
        input_shape=(30, 30, 1),
        loss_object=tf.keras.losses.CategoricalCrossentropy(from_logits=True),
        clip_values=(0, 1)
    )
    # 创建FGSM实例
    attack = FastGradientMethod(estimator=classifier, targeted=True)

    # 初始化对抗样本为原始图像
    adv_images = np.copy(images)

    for i in range(100):  # 最多迭代100次
        # 生成对抗样本的扰动
        perturbations = attack.generate(x=adv_images, y=target_label) - adv_images

        # 计算所有样本的平均扰动
        avg_perturbation = np.mean(perturbations, axis=0)

        # 将平均扰动添加到所有对抗样本上
        adv_images += avg_perturbation

        # 使用模型对对抗样本进行预测
        preds = model.predict(adv_images)
        print('Iteration:', i, 'Predicted after:', preds.argmax(axis=1))

        # 如果所有的预测结果都为1,那么停止迭代
        if np.all(preds.argmax(axis=1) == 1):
            break

    # 保存对抗样本
    for i in range(len(adv_images)):
        # 将图像的数据类型转换为uint8,并且将图像的值范围调整到[0, 255]
        img = (adv_images[i] * 255).astype(np.uint8)
        # 保存图像
        cv2.imwrite(f'traffic_adv_image_{i}.png', img)

    # 归一化平均扰动并保存为图像
    avg_perturbation = (avg_perturbation - np.min(avg_perturbation)) / (np.max(avg_perturbation) - np.min(avg_perturbation))
    # 将平均扰动的值范围调整到[0, 255],并转换为uint8类型
    avg_perturbation = (avg_perturbation * 255).astype(np.uint8)
    # 将灰度图像转换为RGB图像
    avg_perturbation_rgb = cv2.cvtColor(avg_perturbation, cv2.COLOR_GRAY2RGB)
    # 保存图像
    cv2.imwrite('traffic_avg_perturbation.png', avg_perturbation_rgb)


if __name__ == "__main__":
    # 找到一个训练好的,识别交通信号牌的模型: https://github.com/Daulettulegenov/TSR_CNN
    model = load_model(r'D:\source\competition\TSR_CNN-main\CNN_model_3.h5')
    # 预测原始的输出类型,可以看到并不能正确的分类,因为是中文字幕 停!!!!而不是 STOP
    test_predict(model)
    # 因此,需要迁移训练,让其识别中文的“停”
    model = refact_model(model)
    # 测试是否可以识别中文的停
    retrain_with2label(model)
    # 预测新的输出类型,可以看到能正确的分类,即便是中文的停!!!
    images = test_predict2(model)
    # 生成扰动图像,让其扰动,识别为no entry,保存扰动图像
    run_art(images)

  

效果:

Predicted before: [0 0 0 0 0 0 0 0 0 0]
  output, from_logits = _get_logits(....
1/1 [==============================] - 0s 38ms/step
Iteration: 0 Predicted after: [1 1 0 1 0 0 1 0 1 0]
1/1 [==============================] - 0s 46ms/step
Iteration: 1 Predicted after: [1 1 1 1 1 1 1 1 1 1]
 
生成的对抗样本:

依稀可见“停”

扰动图像:

 

 
代码说明:这份代码的主要目的是使用深度学习模型来识别交通信号牌,并使用对抗性攻击来生成能够误导模型的对抗样本。
以下是代码的主要部分的解释:

1. getCalssName(classNo): 这个函数根据类别编号返回对应的交通信号牌的名称。

2. grayscale(img), equalize(img), preprocessing(img): 这些函数用于图像预处理,包括将图像转换为灰度图像,进行直方图均衡化,以及归一化。

3. read_imgs(image_dir, label=0): 这个函数读取指定目录下的所有图像,并返回图像数据和对应的标签。

4. test_predict(model): 这个函数读取指定目录下的所有图像,然后使用模型进行预测,并打印出预测结果。

5. refact_model(model): 这个函数修改模型的结构,移除最后的分类层,然后添加一个新的分类层。

6. retrain_with2label(model): 这个函数读取两个目录下的所有图像,然后使用这些图像和对应的标签来训练模型。

7. test_predict2(model): 这个函数读取指定目录下的所有图像,然后使用模型进行预测,并返回预测结果和图像数据。

8. run_art(images): 这个函数使用对抗性攻击来生成对抗样本,并保存对抗样本和平均扰动。

在if __name__ == "__main__"部分,代码首先加载一个预训练的模型,然后使用这个模型来预测原始的交通信号牌图像。然后,代码修改模型的结构,并使用新的数据来训练模型。最后,代码使用新的模型来预测原始的交通信号牌图像,并使用对抗性攻击来生成对抗样本。
 
样本下载:
交通信号灯 https://www.kaggle.com/datasets/meowmeowmeowmeowmeow/gtsrb-german-traffic-sign