FCA项目踩坑记录

本文是《FCA: Learning a 3D Full-coverage Vehicle Camouflage for Multi-view Physical Adversarial Attack》论文代码复现过程中的踩坑记录

准备阶段

  • 论文地址

  • Guthub项目地址

  • 将项目clone下来之后,还需要clone另一个项目neural_renderer,这是原项目地址,原项目在高版本的pytorch需要做一些修改,而作者也提供了对其进行修改后的项目地址。

环境配置

由于neural_renderer环境配置比较麻烦,这部分主要说明我在服务器上具体环境下的配置方法。

  • 环境:Ubuntu 18.04,gcc 7.5.0,NVIDIA GeForce RTX 3090,cuda 11.3

  • 上述环境中cuda版本最重要,可通过nvcc -V命令查看。如果服务器上有多个cuda版本,可以使用ls /usr/local | grep cuda查看,并通过修改~/.bashrc文件指定具体版本。

  • 下面进行conda环境配置

conda create -n fca python=3.6 # 只在python=3.6成功配置过neural_renderer
source activate fca
pip install torch==1.10.2+cu113 torchvision==0.11.3+cu113 -f https://download.pytorch.org/whl/torch_stable.html # 只在该pytorch版本下成功配置过neural_renderer
# 在安装完成pytorch后,必须先安装neural_renderer
cd neural_renderer
python setup.py install
# 如果可以成功安装,问题就基本解决了
# 如果出现类似load_textures.cpython-38-x86_64-linux-gnu.so: undefined symbol: _ZNK3c107SymBool10guard_boolEPKcl的报错,说明前面的步骤没走对
# 安装完成后,可以运行测试程序
pip install imageio skikit-image
python examples/example1.py # 如果顺利运行,可以看到一个进度条
ls -al examples/data
# 此时应该可以看到出现名为example1.gif的文件,且修改时间应该是最新的,如果到达这里,那nueral_renderer的安装完成
cd .. # 返回src目录下
pip install -r requirements.txt # 通过文件自动安装大多数重要的包
# opencv包可能会安装失败,此时应通过conda安装,并且在requirements.txt中注释掉opencv-python>=4.1.2这一行
conda install py-opencv
# 安装完成之后,检查opencv是否可以使用
python -c "import cv2; print(cv2.__version__)" # -c参数会使得后面的字符串作为程序输入,若成功输出结果,说明opencv安装成功。
  • 如果顺利通过上面步骤,环境的配置基本已经完成。Congratulations!

数据集、模型下载

  • 本文使用前作的数据集,可以直接在这里drive.google.com下载其中文件,并且将其移动到carla_dataset目录下,《Dual Attention Attack》项目也提供了网盘的下载链接。

  • 执行bash ./weights/download_weights.sh文件,下载yolov3目标检测模型

  • 如果想使用其他的目标检测模型、其他的数据集,可以利用data目录下各种yaml文件并通过其提示使用train.py训练目标检测模型。

  • 在模型进行训练时(train_camouflage_yolov3.py)可能会发现在每轮训练只读取一个文件,位于carla_dataset/train_new/data1.png,我使用的处理方式如下:

    1. 删除carla_dataset目录下所有.cache缓存文件(这一步在每次重新训练前都建议使用)
    2. 编写脚本,从原数据集.npz文件(其实应该是一个字典)中提取KEY==img的VALUE,npz2img.py如下:
import numpy as np
import os
from PIL import Image

def npz2img(input_path, output_path):
    # 获取input_path下所有的.npz文件
    for file_name in os.listdir(input_path):
        if file_name.endswith('.npz'):
            print(input_path + file_name)
            
            # 加载.npz文件
            npz_data = np.load(os.path.join(input_path, file_name))
            
            # 获取'img'这个KEY值对应的VALUE
            image_data = npz_data['img']
            
            # 将numpy数组转为PIL图像
            image = Image.fromarray(image_data.astype(np.uint8))
            
            # 创建输出目录,如果不存在
            if not os.path.exists(output_path):
                os.makedirs(output_path)
                
            # 构建输出文件名(将.npz更改为.jpg)
            output_file_name = file_name.replace('.npz', '.png')
            output_file_path = os.path.join(output_path, output_file_name)
            
            # 保存图片
            image.save(output_file_path)
            
            print(output_file_path)
# 使用示例
input_path = './carla_dataset/train/'  # 请替换为您的.npz文件所在路径
output_path = './carla_dataset/train_new/'  # 请替换为您希望保存图片的路径
npz2img(input_path, output_path)
  • 执行该脚本即可,约需2小时可以将所有.npz文件处理成.png图片并保存在train_new目录下。

模型训练

  • 下面就可以执行train_camouflage_yolov3.py文件训练对抗纹理了。执行前需要调整超参数,注意GPU似乎只能使用单卡,多卡训练会报错。对12500张图片进行训练每一轮大概需耗时3.5小时。

结果处理

  • 训练完成后,重点关注得出的纹理texture.npy文件。下面讲解如何使用训练得到的纹理文件,将其渲染到3d物体模型上。这个步骤不止在该项目中可以使用,更可以应用于非常多的方面。
    • 使用如下示例脚本读取并生成新的物体(修改于nmr_test.py)
    def example():
    	obj_file = './carassets/audi_et_te.obj'
    	data_path = './carla_dataset/train/'
    	img_save_dir = './render_test_res/'
    
    	vertices, faces = neural_renderer.load_obj(obj_file)
    
    	texture_mask = np.zeros((faces.shape[0], 2, 2, 2, 3), 'int8')
    	with open('./carassets/exterior_face.txt', 'r') as f:
        	face_ids = f.readlines()
        	for face_id in face_ids:
            	texture_mask[int(face_id) - 1, :, :, :, :] = 1
    	texture_mask = torch.from_numpy(texture_mask).cuda(device=0).unsqueeze(0)
    	print(texture_mask.size())
    	faces_var = torch.autograd.Variable(faces.cuda(device=0))
    	vertices_var = vertices.cuda(device=0)
    	# Textures
    	texture_size = 2
    	textures = np.ones((1, faces.shape[0], texture_size, texture_size, texture_size, 3), 'float32')
    	textures = torch.from_numpy(textures).cuda(device=0)
    	print(textures.size())
    	textures = textures * texture_mask
    	neural_renderer.save_obj(img_save_dir + 'saveTmp.obj', vertices_var, faces_var, textures[0], texture_size)
    
  • 下面会提到一些运行这部分代码会出现的报错以及处理意见:
    1. TypeError: expected np.ndarray (got Tensor): 不需要torch.from_numpy()操作,画蛇添足,删去即可。
    2. assert vertices.ndimension() == 2 AssertionError: 不需要在vertices或faces后进行[None, :, :]维度扩增操作,删去
    3. RuntimeError: CUDA error: an illegal memory access was encountered: 这个错误非常危险也难以察觉,其实是在调用save_obj函数时,一定要注意删去vertices, faces, textures这三个参数的batch_size维度,这一点非常非常关键,也导致了很多neural_renderer官方issue的“悬案”。这个问题是这样被测试出来的:直接load_obj后用得到的三个参数save_obj,同时观察其所有的shape,发现这三个参数的第一维都不是batch_size而是面的数量。
  • 如果可以成功运行测试程序,并生成三个文件:.obj, .mtl, .png。在该项目下,执行example应该生成的是一个车辆模型,其车窗、底盘等位置是黑色的,而车身主体是白色的。哪里为黑哪里为白是由exterior_face.txt文件确定的。
  • 在此基础上,再进行一些修改就可用于读取.npy文件并渲染到.obj物体上的操作。

测试纹理

  • 在通过上述方法训练得到伪装纹理之后,可以通过作者提供的generated_and_test.py脚本,对伪装纹理进行测试。测试的思路是这样的:首先读取carla测试集,将伪装纹理通过set_texture方法贴到2D场景中,得到有伪装纹理的场景图片,再把该图片放进yolov5x权重中进行测试,至于为什么是yolov5仍存疑(因为训练时使用的是yolov3)。通过这样的操作可以获得有bounding box、label、置信度的图片,可以清晰的看出能否识别出汽车,显然,如果图片中心带有伪装纹理的汽车没有被识别出来,就说明对抗成功了。其实应该有更简单的方法,可以直接读出对抗成功率,可以作进一步研究。
  • 部分修改后的generated_and_test.py文件如下:
import torch
from torch.utils.data import DataLoader
from data_loader import MyDatasetTestAdv
from tqdm import tqdm
import numpy as np
import sys
import argparse
from PIL import Image
import os

sys.path.append("./neural_renderer/")
import neural_renderer

parser = argparse.ArgumentParser()

parser.add_argument("--batchsize", type=int, default=1)
parser.add_argument("--obj", type=str, default='carassets/audi_et_te.obj')
parser.add_argument("--faces", type=str, default='carassets/exterior_face.txt') # exterior_face   all_faces
# parser.add_argument("--textures", type=str, default='textures/texture_camouflage.npy')
parser.add_argument('--textures', type=str, default='texture_in_5_epochs.npy')
parser.add_argument("--datapath", type=str, default="carla_dataset/")
args = parser.parse_args()


BATCH_SIZE = args.batchsize
mask_dir = os.path.join(args.datapath, 'masks/')

obj_file =args.obj
texture_size = 6

vertices, faces, textures = neural_renderer.load_obj(filename_obj=obj_file, texture_size=texture_size, load_texture=True)


# Camouflage Textures
texture_content_adv = torch.from_numpy(np.load(args.textures)).cuda(device=0)

texture_origin =textures[None, :, :, :, :, :].cuda(device=0)
texture_mask = np.zeros((faces.shape[0], texture_size, texture_size, texture_size, 3), 'int8')
with open(args.faces, 'r') as f:
    face_ids = f.readlines()
    for face_id in face_ids:
        if face_id != '\n':
            texture_mask[int(face_id) - 1, :, :, :, :] = 1
texture_mask = torch.from_numpy(texture_mask).cuda(device=0).unsqueeze(0)



def cal_texture(texture_content, CONTENT=False):
    textures = 0.5 * (torch.nn.Tanh()(texture_content) + 1)
    return texture_origin * (1 - texture_mask) + texture_mask * textures


@torch.no_grad()
def run_cam(data_dir, batch_size=BATCH_SIZE):
    print(data_dir)
    dataset = MyDatasetTestAdv(data_dir, input_size, texture_size, faces, vertices, distence=None, mask_dir=mask_dir, ret_mask=True)
    loader = DataLoader(
        dataset=dataset,
        batch_size=batch_size,
        shuffle=False,
        # num_workers=2,
    )

    print(len(dataset))
    tqdm_loader = tqdm(loader)
    
    textures_adv = cal_texture(texture_content_adv, CONTENT=True)
    dataset.set_textures(textures_adv)
    for i, (index, total_img, texture_img, _,  filename) in enumerate(tqdm_loader):
            texture_img_np = total_img.data.cpu().numpy()[0]
            texture_img_np = Image.fromarray(np.transpose(texture_img_np, (1, 2, 0)).astype('uint8'))
            filename = filename[0].split('.')[0]
            save_path = 'savedImage_test'
            if not os.path.exists(save_path):
                os.makedirs(save_path)
            if not os.path.exists(os.path.join(save_path, filename)):
                os.makedirs(os.path.join(save_path, filename))
            texture_img_np.save(fr'{save_path}/{filename}/{filename}.png')
            
            save_path = 'savedImage_test'
            # Yolo-v5 detection
            results = net(texture_img_np)

            results.save(fr'{save_path}/{filename}/')




if __name__ == "__main__":
    data_dir = f"{args.datapath}test/"
    batch_size = 1
    input_size = 800
    # net = torch.hub.load('ultralytics/yolov5', 'yolov5x')  # or yolov5m, yolov5x, custom
    net = torch.hub.load('./', 'custom', './yolov5x.pt', source='local')
    net.eval()
    if torch.cuda.is_available():
        net = net.cuda()


    run_cam(data_dir)

Attention! 这部分仍存在问题,持续更新中

其它

  • 如何设置tensorboard转发:在训练开始之后,另起一个shell并在src目录下执行tensorboard --logdir runs/train,具体的目录会在程序运行过程中打印到终端,执行代码后即在服务器上默认的6006端口启用tensorboard。然后在本地cmd、shell中执行ssh -L 16006:localhost:6006 -p 33 user@remote_server,这一段代码首先链接远程服务器33号端口,然后将6006端口转发到本地的16006端口。此后在浏览器中打开http://localhost:16006就可以打开TensorBoard界面。执行完上面所有步骤后,两个shell可以不关闭,他们只是负责开启tensorboard服务和转发端口的任务。

你可能感兴趣的:(项目踩坑记录,安全,3d,深度学习)