视频异常检测数据集 (ShanghaiTech) 及其I3D特征转换

本文介绍了如何将ShanghaiTech异常视频数据集转换为适应MIL场景的I3D特征包,包括下载Torch-I3D模型,视频预处理,以及使用InceptionI3d进行特征提取。通过实例展示了单个视频和帧级输入的代码测试过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 概述

ShanghaiTech是一个中型数据集,基本信息如下:

  1. 训练集:330个正常视频;
  2. 测试集:107个异常视频,已被划分为多个帧,包含13个异常事件,且带有。
    该数据集的一个示意如下图:

为了使得数据集适应MIL的场景,Zhong等人依据类别平衡的准则将整个数据集重新划分。划分的索引如下:
https://github.com/jx-zhong-for-academic-purpose/GCN-Anomaly-Detection

2 视频数据转换为I3D包

这里使用的预训练模型作为特征提取器,其中Mixed_5c层作为返回特征。

2.1 下载Torch-I3D模型:

地址如下:
https://github.com/piergiaj/pytorch-i3d
下载之后打开models:

这里注意flow和rgb的区别:

  1. rgb:原始视频作为输入,通道为3;
  2. flow:视频的光流作为输入,通道为2;

关于光流的使用,可以参照我的博客:
https://inkiyinji.blog.csdn.net/article/details/127622063
这里使用的flownet而非flownet2,因为我的电脑没有GPU。。。

2.2 将视频转换为包

这里以单个视频为示意。设置划分后的最大视频片段数为32,每个片段的帧数固定为16:

  • 对于视频总帧数低于或者等于512帧的视频,从第1帧开始以每16帧为单位划分,最后一帧如果不足16帧,则替换为视频的后16帧;
  • 对于其它情况,将视频平分为32份,其中每一个片段在帧数维resize为16。

具体代码如下:

import decord
import os
import numpy as np
import torch
from imageio.v2 import imread
from gluoncv.torch.data.transforms.videotransforms import video_transforms, volume_transforms
from pytorch_i3d import InceptionI3d


class Video2I3D:

    def __init__(self, path, num_snippet=32, snippet_size=16, input_type="video", transformer=None):
        """
        Args:
            path: 视频存储路径
            num_snippet: 视频划分后的最大片段数
            snippet_size: 每个片段的数量,不得超过16,否则无法得到单向量;不得小于9,否则无法完成卷积
            input_type: 输入的数据类型:原始视频 (video) 或者视频帧 (frame)
            transformer: 视频转换器
        """
        self.path = path
        self.num_snippet = num_snippet
        self.snippet_size = snippet_size
        assert 9 <= self.snippet_size <= 16

        if input_type == "video":
            self.video = self.__load_video__()
        else:
            self.video = self.__load__frame()
        # self.video = np.transpose(self.video, [0, 3, 1, 2])

        self.transformer = self.__get_transformer__() if transformer is None else transformer
        self.i3d_net = self.__get_i3d_extractor()

    def fit(self):
        self.video = self.transformer(self.video)

        """Split each video"""
        # The frame number less than the split requirement
        if self.num_frame <= self.num_snippet * self.snippet_size:
            start_idx = np.arange(0, self.num_frame, self.snippet_size).tolist()
            end_idx = start_idx[1:] + [self.num_frame]
            if end_idx[-1] - start_idx[-1] < self.snippet_size:
                start_idx[-1] = end_idx[-1] - self.snippet_size
        else:
            start_idx = np.arange(0, self.num_frame, int(np.ceil(self.num_frame / self.num_snippet))).tolist()
            end_idx = start_idx[1:] + [self.num_frame]
            new_video = []
            for i, j in zip(start_idx, end_idx):
                video = self.video[:, i: j]
                video = video.resize_([3, self.snippet_size, video.shape[2], video.shape[3]])
                new_video.append(video)
            self.video = torch.hstack(new_video)
            start_idx = np.arange(0, self.num_snippet * self.snippet_size, self.snippet_size).tolist()
            end_idx = start_idx[1:] + [self.num_snippet * self.snippet_size]

        self.video = self.video.unsqueeze(0)
        bag = []
        for i, j in zip(start_idx, end_idx):
            video = self.video[:, :, i: j]
            if video.shape[2] == self.snippet_size:
                ins = self.i3d_net.extract_features(video).reshape(1, 1024)
                bag.append(ins)

        return torch.vstack(bag)

    def __load_video__(self):
        vr = decord.VideoReader(self.path)
        self.num_frame = vr.num_frame
        frame_id_list = np.arange(0, vr.num_frame).tolist()
        video = vr.get_batch(frame_id_list).asnumpy()
        # video_data = np.transpose(video_data, [0, 3, 1, 2])
        return video

    def __load__frame(self):
        frame_list = os.listdir(self.path)
        self.num_frame = len(frame_list)
        video = []
        for frame_name in frame_list:
            frame_path = os.path.join(self.path, frame_name)
            frame = imread(frame_path)
            frame = frame.reshape([1, frame.shape[0], frame.shape[1], frame.shape[2]])
            video.append(frame)

        video = np.vstack(video)

        return video

    @staticmethod
    def __get_transformer__():
        transform_fn = video_transforms.Compose([video_transforms.Resize(256, interpolation='bilinear'),
                                                 video_transforms.CenterCrop(size=(224, 224)),
                                                 volume_transforms.ClipToTensor(),
                                                 video_transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                                                            std=[0.229, 0.224, 0.225])])
        return transform_fn

    @staticmethod
    def __get_i3d_extractor():
        net = InceptionI3d(name="Mixed_5c")
        net.load_state_dict(torch.load("models/rgb_imagenet.pt"))
        return net

2.3 代码测试

以视频作为输入的代码:

if __name__ == '__main__':
    vi = Video2I3D(path="D:/Data/VAD/ShanghaiTech/training/videos/01_001.avi")
    print(vi.fit().shape)

以视频帧作为输入的代码:

if __name__ == '__main__':
    vi = Video2I3D(path="D:/Data/VAD/ShanghaiTech/testing/frames/01_0014/", input_type="frame")
    print(vi.fit().shape)

输出如下:

torch.Size([17, 1024])
<think>好的,我现在需要帮助用户解答关于使用2D和I3D特征进行视频异常检测的问题。首先,我得先理清楚这两个特征的区别以及它们在视频分析中的应用。 用户问的是方法及关键考量因素。我需要先回忆一下2D特征和I3D的基本概念。2D特征通常指的是从单个帧中提取的特征,比如使用CNN提取的空间特征,比如ResNet、VGG这些网络。而I3D(Inflated 3D ConvNets)则是处理视频片段的,同时捕捉空间和时间信息,可能使用预训练的光流或者RGB帧。 接下来,要分析两者的方法。基于2D的方法可能侧重于每一帧的静态特征异常检测可能通过分析帧间的变化或者使用时序模型如LSTM来处理。而I3D本身已经包含了时序信息,所以可能直接利用其时空特征进行分类。 然后考虑关键因素。首先是计算效率,2D特征处理单帧,计算量较低,但可能忽略时序信息;I3D需要处理视频片段,计算量大,但能捕捉运动信息。其次是数据量,I3D可能需要更多数据训练,而2D方法如果使用预训练模型,可能在小数据上表现更好。还有时空信息的结合,如何融合两者可能是一个考量点。此外,异常类型也很重要,如果是静态异常(如物体遗留),2D特征可能足够;动态异常(如突然奔跑)需要I3D的时间特征。 还需要考虑模型融合的方法,比如早期融合、晚期融合或者混合模型。另外,实时性要求也是一个因素,I3D可能更耗资源,影响实时检测。 可能还需要提到一些具体的研究,比如使用C3D、Two-Stream网络等,但用户特别提到I3D,所以应侧重于此。同时,引用相关文献,比如I3D的原始论文,或者其他结合2D和3D特征的工作。 最后,生成相关问题时要覆盖方法的比较、适用场景、优化方向、实时性挑战以及数据需求等方面,确保问题能引导用户进一步探索。</think>### 基于2D特征与I3D特征视频异常检测方法 #### 1. **方法概述** - **2D特征方法**: 从单帧图像中提取空间特征(如ResNet、VGG),通过时序建模(如LSTM或Transformer)捕捉帧间关系,适用于静态异常检测(如物体遗留)[^1]。 $$ \text{特征}_\text{2D} = f_\text{CNN}(I_t) $$ - **I3D特征方法**: 通过3D卷积同时提取时空特征,直接建模短时序运动模式(如人体动作异常),对动态异常(如突然奔跑)更敏感[^2]。 $$ \text{特征}_\text{I3D} = f_\text{3D-CNN}(I_{t-k:t}) $$ #### 2. **关键考量因素** | 因素 | 2D特征 | I3D特征 | |---------------------|---------------------------------------------|----------------------------------------------| | **时空信息捕捉** | 需额外时序模型(如LSTM) | 内建时空建模能力 | | **计算效率** | 单帧处理,资源占用低 | 需连续帧输入,计算成本高 | | **异常类型适配** | 静态异常(纹理/物体异常) | 动态异常(运动模式异常) | | **预训练数据依赖** | ImageNet预训练模型易迁移 | 需视频数据集(如Kinetics)预训练 | | **实时性** | 易于实现实时检测 | 受限于片段长度和GPU显存 | #### 3. **典型融合策略** - **早期融合**:将2D特征与I3D特征拼接后输入分类器 $$ \text{特征}_\text{fused} = [\text{特征}_\text{2D}; \text{特征}_\text{I3D}] $$ - **双流架构**:并行处理两种特征,通过注意力机制加权融合[^3] - **级联模型**:先用2D特征筛选可疑片段,再用I3D精细分析 #### 4. **实施挑战** - **数据标注成本**:视频异常检测需帧级标注,I3D训练需大规模视频数据[^4] - **场景泛化性**:静态特征易受光照/视角变化干扰,动态特征对摄像头抖动敏感 - **阈值设定**:需动态调整异常评分阈值以适应不同场景(如拥挤vs稀疏场景) ```python # 2D+I3D混合特征示例(PyTorch伪代码) class HybridModel(nn.Module): def __init__(self): super().__init__() self.cnn_2d = resnet18(pretrained=True) self.i3d = InceptionI3D() self.fc = nn.Linear(2048+1024, 1) # 联合特征维度 def forward(self, frames): # 提取2D特征(单帧) feat_2d = [self.cnn_2d(frame) for frame in frames] # 提取I3D特征(片段) clip = torch.stack(frames[-16:]) # 取最后16帧 feat_i3d = self.i3d(clip) # 特征融合 combined = torch.cat([feat_2d[-1], feat_i3d], dim=1) return self.fc(combined) ```
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值