ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

对抗性服装与NoRecognition:用PGD生成目标检测对抗贴图

对抗性服装与NoRecognition:用PGD生成目标检测对抗贴图 对抗性服装Adversarial Clothing是目标检测对抗样本研究中的一个重要分支也是 NoRecognition 这类项目关注的核心场景通过在服装表面印制特定图案让检测模型把画面中的人体识别成低置信度目标或者干脆“看不见”。这个方向研究的是深度学习视觉模型在物理世界中的鲁棒性缺陷不是玄学而是可复现、可量化、可用梯度计算解释的安全问题。这篇文章会从对抗样本的基本概念讲起逐步完成一个最小可运行的对抗性衣物贴图生成实验并给出验证、排错和防御评估的完整思路。适合阅读这篇文章的读者有三类一是做目标检测落地担心模型在真实场景中被图案干扰的算法工程师二是研究对抗攻击与防御方向的学生需要跑通一个物理世界攻击的简化闭环三是对 NoRecognition 这类名词好奇想理解它背后的技术原理而非停留在概念层面的开发者。学完后你能独立写出一套基于 PyTorch 的对抗贴图生成脚本理解扰动约束、优化迭代和检测置信度评估之间的关系也知道哪些做法只适合研究环境、哪些问题在生产环境里更值得防范。1. NoRecognition 是什么从对抗样本到对抗性服装1.1 目标检测的“盲点”图像分类里的对抗扰动对抗样本这个概念出现得很早核心发现也很反直觉给一张正常的图片加上人眼几乎无法察觉的微小噪声深度学习模型的分类结果就会彻底改变。例如一张熊猫图片叠加少量噪声后模型可能以极高置信度把它识别成长臂猿。噪声不是随意加的而是通过模型反向传播梯度计算出来的方向是让损失函数朝错误方向快速变化。目标检测同样存在这个问题。检测模型不仅要做分类还要输出边界框。攻击者可以让模型在目标位置输出空结果消失攻击也可以让模型把目标识别成另一个类别伪装攻击还可以让边界框偏移到错误位置。当这类攻击从数字图片迁移到物理世界时就出现了对抗性服装。关键点在于数字域对抗样本只需要考虑像素层面的扰动而物理世界对抗样本还要考虑拍摄角度、光照变化、打印色差、遮挡和距离。所以对抗性服装本质上是一个从数字优化到物理约束的工程问题不是简单的“给衣服加噪声”。1.2 为什么衣服会成为对抗攻击的载体衣服是人身上面积最大的视觉平面也是检测模型最关注的目标区域之一。相比在脸上贴纸或手持特殊道具衣服覆盖面大更容易承载高频纹理图案。从攻击者视角看衣服图案可以提前制作穿戴者不需要做太明显的动作就能完成“隐身”效果。从技术视角看衣服图案的优化目标相对明确让检测器在包含人的图片中无法输出高置信度的人体检测框。整个过程可以分解成三步。第一步选定一个可微的检测模型例如 YOLO 系列第二步把衣服区域当作可训练参数初始化成普通 T 恤图案第三步利用检测损失的反向梯度不断更新图案直到检测置信度降到阈值以下。NoRecognition 这个名字描述的就是这种效果检测模型不再能“识别”出画面中的人。注意这里的识别是指视觉目标检测的识别不是身份识别。很多初学者会把对抗性服装和人脸识别对抗混淆实际上两者针对的检测任务不同优化目标也不同。1.3 NoRecognition 的研究目标和实验边界NoRecognition 类项目在学术上主要回答三个问题检测模型在物理世界中是否存在容易被图案欺骗的盲区。对抗图案的迁移性如何针对一个模型生成的图案是否也能骗过另一个模型。防御手段是否能有效抵御这类攻击比如输入预处理、对抗训练或检测后处理。这类实验必须在受控环境、合规研究框架下进行。论文和开源项目中的实验数据是公开的但实验室环境下跑通的效果不等于生产环境、复杂背景中的效果。把对抗性服装用在非法目的比如逃避安全检测、规避公共场所的必要管理措施都是不被允许的。写这篇文章的目的是让防御方和安全研究人员理解检测模型的弱点从而改进模型鲁棒性而不是提供攻击工具。2. 对抗性服装的生成原理2.1 攻击目标函数让检测器“看不见人”以 YOLO 系列检测器为例它的输出可以拆成两部分目标存在的置信度和边界框坐标。要让检测器“看不见人”最直接的办法是压低包含人目标位置的类别置信度。对于一个输入图像 x模型的输出可以记为 f(x) (bboxes, scores, classes)。检测损失包含分类损失和回归损失。在对抗贴图生成中攻击者会定义一个攻击损失 L_adv核心目标是让目标物体的类别得分最小化或者让检测框的置信度低于阈值。用公式表达数字域的攻击目标可以写为L_adv max(0, score_person - threshold)也可以使用负对数似然形式L_adv -log(score_person eps)不断最小化 L_adv就相当于不断把检测器对“人”的响应压到阈值以下。这个梯度方向和训练检测器时是相反的训练是让模型更准确地识别人的类别和位置攻击是让模型在固定位置下丢失对人的响应。2.2 基于梯度的扰动优化FGSM 与 PGD对抗性服装生成中最常用的两种基准方法是 FGSMFast Gradient Sign Method和 PGDProjected Gradient Descent。FGSM 是一次性沿梯度符号方向扰动x_adv x epsilon * sign(grad_x L(x, y))其中 epsilon 是扰动幅度上限。FGSM 简单但只迭代一次成功率有限尤其面对检测这种复杂任务效果不稳定。PGD 是 FGSM 的迭代加强版多次小步更新每次更新后把扰动拉回 epsilon 范围内x_{t1} clip(x_t alpha * sign(grad_x L(x_t)), x - epsilon, x epsilon)alpha 是每次迭代的步长通常小于 epsilon。PGD 生成的对抗样本在攻击成功率上远高于 FGSM也是评估模型鲁棒性的常用基准。在对抗性服装场景中被优化的对象不是整张图而是衣服上的贴图。衣物区域作为可训练参数背景区域保持固定。每次迭代时把贴图区域反投影到检测图像中计算梯度再更新贴图像素。2.3 从数字域到物理世界图案打印和适应性数字域生成的对抗贴图放到物理世界后效果会大幅衰减。原因有三个打印机和屏幕存在色域差异RGB 像素无法精确还原。实际拍摄时存在光照不均、阴影和透视形变。人体运动导致衣服发生非刚性形变图案位置发生偏移。所以物理世界的对抗服装研究通常不会追求像素级攻击而是让扰动在视觉上形成“可打印纹理”或“对抗性补丁”。常见做法包括 TV 损失Total Variation Loss平滑相邻像素让图案打印出来不出现明显的椒盐噪声再用四边形透视变换模拟衣服在不同角度下的形变。L L_adv lambda_tv * L_tvTV 损失的作用是限制相邻像素差异使生成的图案更平滑更容易被打印和拍摄识别。lambda_tv 越大图案越平滑但攻击成功率也会下降。这是对抗性服装生成中必须考虑的取舍。3. 实验环境准备3.1 依赖和版本下面这套环境适合在本地或 GPU 服务器上跑通最小实验。示例版本是常见组合实际安装时以你当前项目的依赖锁定结果为准不需要严格追求最新版。组件建议版本用途Python3.10运行脚本PyTorch2.1.0 或以上自动微分和对抗优化torchvision0.16.0 或以上图像预处理ultralytics8.xYOLO 检测和推理OpenCV4.8 或以上图像读取和叠加NumPy1.24 或以上数据处理Pillow10.x贴图保存和打印预览安装命令pip install torch torchvision ultralytics opencv-python numpy pillow如果使用 GPU需要先安装与 CUDA 版本匹配的 PyTorch。检测模型可以先用 ultralytics 提供的 YOLOv8n 权重训练时间短适合先跑通流程。3.2 项目结构建议按下面的目录组织代码便于把贴图生成、检测评估和实验结果分开adversarial_clothing/ ├── data/ │ ├── person.jpg # 实验用含人图片 │ └── cloth_mask.png # 衣服区域掩码 ├── models/ │ └── yolo_loader.py # 检测模型加载封装 ├── attacks/ │ ├── pgd_cloth.py # 对抗贴图生成逻辑 │ └── transforms.py # 贴图叠加与透视变换 ├── evaluate/ │ └── detect_eval.py # 检测置信度评估 ├── output/ │ ├── adv_cloth.png # 生成的对抗贴图 │ └── eval_result.json # 评价结果 └── run_attack.py # 主入口data 目录放一张包含完整人体的图片和一张表示衣服区域的掩码图。掩码图是黑白图白色区域代表将要生成对抗贴图的位置黑色区域保持原图不变。3.3 准备基础数据实验不依赖大型数据集只需要一张分辨率足够的全身人像图。建议选择纯色或浅色背景人体占图片面积 50% 左右避免多人场景。衣服区域掩码可以用标注工具手动生成也可以在代码里通过分割模型获得。为了保持实验可控最简单的方式是人工标记。掩码示例python -c from PIL import Image; Image.new(L, (640, 640), 0).save(data/cloth_mask.png)上面命令生成全黑掩码。真正实验前需要用图像编辑工具在黑色画布中把衣服区域涂白或者用 OpenCV 脚本从原图中按坐标填充白色矩形。掩码精度决定了对抗贴图的叠加范围建议花时间标仔细。4. 最小实现用 PGD 生成对抗性衣物贴图4.1 定义贴图叠加函数贴图叠加是核心操作。每次迭代时把一张与衣物区域同样大小的贴图通过掩码覆盖到原始图片上。import cv2 import torch import numpy as np def apply_cloth_patch(image_tensor, mask_tensor, patch_tensor): 将 patch 贴到 image 的掩码区域。 image_tensor: [1, 3, H, W]归一化到 [0,1] 的张量 mask_tensor: [1, 1, H, W]0 或 1 patch_tensor: [1, 3, H, W] 或与衣物区域同尺寸 mask mask_tensor.to(image_tensor.device) # 掩码扩展成三通道方便在 RGB 上做融合 mask_3 mask.expand_as(image_tensor) adv_image image_tensor * (1 - mask_3) patch_tensor * mask_3 return adv_image关键点有两个。第一掩码需要和图片尺寸一致使用 expand_as 才能让加法广播正确。第二优化过程中 image_tensor 和 mask_tensor 要保持不变只有 patch_tensor 作为 requires_gradTrue 的参数更新。如果不想让贴图占据整张图片可以从 patch_tensor 中裁剪出衣物区域后再叠加这样 patch 的尺寸不至于过大也更容易调整。4.2 加载检测模型并构造攻击损失使用 ultralytics 的 YOLOv8n 作为目标检测模型。为了能计算梯度需要从模型输出中拿到类别置信度并构造对“人”这个类别的压制损失。from ultralytics import YOLO import torch import torch.nn.functional as F class PersonSuppressionAttack: def __init__(self, model_pathyolov8n.pt, target_class0): # 0 对应 COCO 数据集里的 person self.model YOLO(model_path) self.target_class target_class # 切换到训练模式保证梯度能回传 self.model.model.train() # 模型整体不更新参数只计算梯度 for param in self.model.model.parameters(): param.requires_grad False def attack_loss(self, adv_image): # ultralytics 的 forward 在 train 模式下返回损失字典 # 但为了简化这里使用预测结果中的置信度 results self.model.predict( adv_image, verboseFalse, conf0.001, iou0.5, ) # 从 results 取出检测框和 scores # 不同版本 API 略有差异以实际返回为准 boxes results[0].boxes if boxes is None or len(boxes) 0: return torch.tensor(0.0, requires_gradTrue, deviceadv_image.device) cls boxes.cls.to(adv_image.device) conf boxes.conf.to(adv_image.device) person_mask (cls self.target_class) if not person_mask.any(): return torch.tensor(0.0, requires_gradTrue, deviceadv_image.device) person_conf conf[person_mask] # 目标是让 person 置信度尽量低 # 使用 clamp置信度低于 0.01 就不再提供梯度 loss person_conf.clamp(min0.01).mean() return -loss这里有一个实际项目里容易踩的问题检测模型的推理接口通常为预测做了很多后处理会把低于置信度阈值的框过滤掉导致梯度为 0。所以在计算攻击损失时要把 conf 设置为极小值让模型尽量输出候选框这样才能拿到可优化的梯度。如果使用更底层的模型架构可以直接读取模型的原始输出跳过 NMS 后处理。NMS 等后处理是不可微的如果依赖它计算梯度会导致梯度中断。4.3 PGD 迭代更新贴图下面实现完整的 PGD 对抗贴图生成流程。def pgd_cloth_attack( image_tensor, mask_tensor, cloth_patch, attack, epsilon0.1, alpha0.01, iterations30, tv_lambda0.01, ): # 初始化贴图参数从原始衣服区域复制 patch cloth_patch.clone().detach().requires_grad_(True) optimizer torch.optim.Adam([patch], lralpha) for i in range(iterations): optimizer.zero_grad() adv_image apply_cloth_patch(image_tensor, mask_tensor, patch) adv_loss attack.attack_loss(adv_image) tv_loss total_variation_loss(patch) loss adv_loss tv_lambda * tv_loss loss.backward() optimizer.step() # 投影到 epsilon 邻域限制扰动幅度 with torch.no_grad(): patch.clamp_(image_tensor - epsilon, image_tensor epsilon) patch.clamp_(0.0, 1.0) if (i 1) % 10 0: print(fiter {i 1}, adv_loss{adv_loss.item():.4f}, tv_loss{tv_loss.item():.4f}) return patch.detach(), apply_cloth_patch(image_tensor, mask_tensor, patch)需要说明的是这里的 epsilon 表示每个像素允许偏离原始衣物区域的最大值。设置成 0.1 意味着像素值最多改变约 25 个灰度等级肉眼仍能明显看到纹理变化。epsilon 越大攻击成功率越高图案也会越怪异。TV 损失实现如下def total_variation_loss(img): 计算图像的总变差损失值越小图案越平滑。 img img.squeeze(0) h img.size(1) - 1 w img.size(2) - 1 tv torch.sum(torch.abs(img[:, :h, :] - img[:, 1:, :])) \ torch.sum(torch.abs(img[:, :, :w] - img[:, :, 1:])) return tv / (h * w)注意 TV 损失计算的是贴图自身的邻域像素差而不是整张图片的差异。这个损失会让图案在视觉上更接近纹理而不是纯噪声。4.4 验证检测置信度对比生成对抗贴图后需要做定量验证不是肉眼看图片是否变化。def evaluate_confidence(model, image_tensor, labeloriginal): results model.predict(image_tensor, verboseFalse, conf0.001, iou0.5) boxes results[0].boxes if boxes is None or len(boxes) 0: print(f[{label}] person detection: none) return 0.0 cls boxes.cls.cpu().numpy() conf boxes.conf.cpu().numpy() person_conf conf[cls 0] if len(person_conf) 0: print(f[{label}] person confidence: none) return 0.0 max_conf float(person_conf.max()) print(f[{label}] person confidence: {max_conf:.4f}) return max_conf评估指标建议记录三个值对抗前人的最大置信度、对抗后人的最大置信度、置信度下降百分比。效果较好的实验通常能让人物置信度从 0.8 以上降到 0.1 以下甚至直接不输出人物框。5. 关键参数与实验结果分析5.1 扰动阈值 epsilon 的影响epsilon 是影响攻击效果和图案可视性的首要参数。epsilon 值像素灰度变化攻击成功率图案可视性适用场景0.01约 2.5低几乎无效肉眼很难察觉数字域隐蔽攻击0.05约 12.75中等部分场景有效轻微纹理打印测试0.1约 25.5较高明显纹理常见实验基准0.2约 51高图案很花哨物理世界测试初步验证epsilon 调得越大生成的贴图越接近“对抗性花衬衫”越容易被肉眼识别。真实场景中要不要控制 epsilon取决于实验目的。如果是评估检测器最坏情况可以把 epsilon 放大如果研究隐蔽攻击就需要结合噪声感知评估指标。5.2 迭代次数和优化器选择PGD 迭代次数越多次攻击效果通常越好但收益会衰减。30 到 50 次迭代足够观察明显变化100 次以后图案可能出现大量高频噪声TV 损失也会显著上升。优化器选择上Adam 收敛快但容易出现震荡SGD 收敛慢但更稳定。在贴图生成实验中Adam 是常用的默认选择配合较小的学习率例如 alpha 0.005 到 0.01。如果发现置信度反复跳动可以降低学习率或增加迭代次数。5.3 多角度、多尺度与多场景的适配物理世界中的相机不会永远正对衣服拍摄。如果只在单一视角下优化贴图生成的图案换一个角度就可能失效。改进方法是在训练循环中做视角扰动def random_perspective_patch(patch, mask, image_size): 对贴图做随机透视变换模拟不同拍摄角度。 h, w image_size src_points np.float32([[0, 0], [w, 0], [0, h], [w, h]]) # 随机偏移几个角点模拟视角变化 offset np.random.uniform(-0.1, 0.1, size(4, 2)) dst_points src_points offset * (w, h) matrix cv2.getPerspectiveTransform(src_points, dst_points) warped cv2.warpPerspective(patch, matrix, (w, h)) return warped多尺度适配也有必要。检测模型在不同输入尺寸下的感受野不同贴图如果只在固定分辨率下生成缩放后可能失效。建议在每次迭代时随机缩放贴图再叠加到图片上。5.4 防御侧的检测效果对比对抗性服装研究不只要看攻击效果还要看防御后的模型表现。常规评估方式是把对抗贴图输入到不同防御策略下的模型里对比遮挡率Occlusion Rate和精准率。下面是一个典型的对比记录表防御方式对抗前人物置信度对抗后人物置信度检测框是否完整无防御0.890.04完全丢失Gaussian 模糊预处理0.860.42部分保留对抗训练模型0.880.73基本保留多模型集成0.870.61保留但置信度降低需要注意预处理防御会降低正常场景下的检测精度并不是免费的。对抗训练能提升鲁棒性但训练成本高且对不同攻击强度泛化能力有限。6. 常见问题、错误现象和排查路径6.1 模型输出没有变化对抗前后置信度几乎相同这是最常见的失败场景。可能原因模型被设置为 eval 模式且后处理把低置信度框过滤掉了。掩码位置不正确贴图没有与图片中的人体重合。学习率太小30 次迭代内几乎没有更新。检查方式打印 patch 更新前后的像素均值确认 patch 是否真的在变化。打印每一轮 loss确认 loss 是否稳定下降。保存对抗后的图片直接目视检查衣服区域是否出现明显纹理。处理建议计算攻击损失时把检测器的 conf 阈值调到 0.001。检查掩码是否使用expand_as扩展到三通道。尝试增大学习率到 0.02并增加迭代次数到 100。6.2 生成的贴图看起来是纯噪声打印后效果很差可能原因TV 损失权重太小没有约束相邻像素。初始化使用的是随机噪声而不是原衣服区域。处理建议将 tv_lambda 从 0.01 调整到 0.1 或 0.5。贴图初始值应该使用原图衣服区域或者加上少量随机噪声不要直接使用全噪点。生成后对贴图做中值滤波再保存能显著改善可打印性。6.3 数字域攻击成功但打印成实物后失效可能原因打印颜色和屏幕颜色存在差异。图案没有考虑透视形变。拍摄距离与训练时的尺度不一致。处理建议在优化循环中加入随机亮度、对比度扰动。用透视变换模拟不同拍摄角度。在评估时拍摄多组照片统计成功率的分布而不是只拍一张就下结论。6.4 训练和推理模式不一致导致梯度为 0ultralytics 的 predict 方法默认做完整后处理返回的 boxes 已经经过 NMS。如果拿 NMS 后的置信度构造损失模型前面的卷积层虽然能接受到梯度但实际梯度信号非常稀疏甚至为 0。检查方式在 loss.backward() 后打印 patch.grad 是否存在非零值。用torch.autograd.grad_check或手动计算中间特征梯度。处理建议使用 model.model 底层的 forward 输出在特征图和候选框阶段构造损失。或者使用支持端到端近似的攻击库例如 torchattacks 中针对检测模型的方法。如果必须走 predict至少把 conf 设到 0.001并确认 boxes 列表不为空。7. 如何把对抗性服装用于防御与鲁棒性研究7.1 对抗训练让模型见过更多“花衣服”对抗训练是最直接的防御思路。做法是在训练检测模型时把对抗贴图作为数据增强的一部分每批次按一定比例把对抗贴图叠加到训练图上并要求模型依然预测出人物边界框。这样模型会逐步学会忽略衣服上的高频纹理。对抗训练的成本较高。加载预训练权重继续训练时建议冻结主干网络前几层只微调检测头和后面几层能显著减少训练时间。评估时不能只用生成这批贴图的攻击方法否则会得到过于乐观的鲁棒性结果。7.2 输入预处理防御预处理防御的价格比对抗训练低实现也简单。常见做法高斯模糊能抑制高频扰动但也会让图像变模糊影响正常小目标检测。JPEG 压缩能去除部分高频噪声但打印后的图案本身已经是高频纹理压缩不一定有效。颜色域变换例如把 RGB 转换到 HSV 后再转回可以减少打印色差带来的影响但对基于梯度的攻击效果有限。建议不要把预处理当作唯一防线。更好的做法是预处理加检测后跟踪当目标置信度骤降时结合前一帧的位置信息做补偿。7.3 防御评估清单在验证防御效果时建议按下面的清单逐项确认检查项操作通过标准攻击基线数字域攻击成功率无防御时攻击应达到较高成功率迁移性用模型 A 生成的贴图测模型 B防御模型应降低跨模型迁移成功率正常精度在标准测试集上评估干净图片防御后 mAP 下降不超过 5%多角度稳定拍摄 3 个角度以上防御后多人框稳定误检测检查在没有人的图片中叠贴图不应出现大量新误检生产环境落地时还要额外考虑防御方案是否影响推理速度。有些防御模块看起来效果不错但单帧耗时增加一半就无法用于实时系统。8. 最佳实践、伦理边界与扩展方向8.1 学术实验的合规边界对抗性服装是目标检测鲁棒性研究的一部分但它天然带有“规避检测”的使用场景。做实验时建议只在自己的数据集、自己的相机、受控测试环境中进行。不要把他人的照片、真实监控画面或任何未经授权的图像作为对抗贴图的测试对象。发布代码时也要注明适用范围不让项目变成攻击工具。NoRecognition 这类研究中最有价值的部分不是让检测模型失效而是让模型失效的机制被公开后检测系统的设计者能据此改进训练和部署策略。作为开发者更值得关注的是防御评估方法而不是盲目追求更高的攻击成功率。8.2 可复现性建议对抗样本实验的复现性很容易被细节破坏。为了保证自己的实验可以复现至少要固定这几项固定随机种子包括 Python、NumPy、PyTorch 的随机种子。固定模型权重版本最好用哈希值记录权重文件。记录 epsilon、alpha、迭代次数、tv_lambda 全组参数。保存每次实验的输入图、掩码、生成的贴图和评估日志。建议把上面涉及的配置集中到一个 yaml 文件里model: path: yolov8n.pt input_size: 640 attack: method: PGD epsilon: 0.1 alpha: 0.01 iterations: 50 tv_lambda: 0.1 confidence_threshold: 0.001 data: image: data/person.jpg mask: data/cloth_mask.png output: patch: output/adv_cloth.png log: output/eval_result.json使用配置文件后跑实验只需要维护一个 yaml不需要改代码逻辑。也方便在 Git 提交时记录每一次实验参数的变化。8.3 下一步学习方向跑通最小实验后可以从三个方向继续深入。第一物理世界迁移。把生成的贴图打印在布料上用真实相机拍摄统计不同光照、角度、距离下的检测置信度找出数字环境与物理环境之间的差距。第二面向检测模型的攻击方法升级。当前实验只压制了 person 类别的置信度还可以尝试类别混淆攻击让模型把人物识别成背包、椅子和桌子等其它类别观察模型决策边界的混乱程度。第三防御机制研究。把重点从生成贴图转移到如何检测异常图案、如何利用时间序列信息维持跟踪稳定性、怎样在模型部署阶段加入轻量级防护模块。这一方向对工业界更有实际价值。对于刚开始接触对抗样本的读者建议先不要急着换复杂算法。把 FGSM、PGD 在单目标检测上的梯度闭环彻底理解清楚再扩展到几何变换、多模型攻击和物理世界测试会顺利得多。判断自己是否掌握的标准很简单不看文章能独立复现出一张让检测置信度显著下降的贴图并说清楚每一步梯度去了哪里就算入门了。
返回列表