C3D源码解读(基于3D卷积的动作识别)

article/2025/10/14 15:50:09

UCF数据集下载地址:https://www.crcv.ucf.edu/data/UCF101.php

1.推理效果与项目配置

        执行inference.py,需要指定3个参数,第一个是标签文件地址,存储了各个标签的含义,第二个是权重文件地址,第三个是要进行推理的视频文件地址

 

 

2.训练配置参数 

        mypath.py 需要修改的参数,root_dir为UCF-101数据集存放的地址,output_dir为保存中间数据的地址,在数据预处理时,需要对视频截取为小片段,并进行数据增强,在时间和空间上进行抖动。

         训练参数:

        另外需要注意的是,在dataset.py文件中,第一次运行模型需要对数据进行预处理,因此第一次执行需要将preprocess=True,而在后续操作,保存了数据中间结果,可以将preprocess=False

 

3.视频数据读取

(1).视频数据预处理操作

        首先,对于UCF数据集,每一个文件夹包含了一个类别行为的视频。因此,对于每一个类别的文件夹的视频,划分训练集、测试集与验证集,同时对视频进行切分。返回图像序列。具体切分方法为:

  • 首先读取视频,并返回视频的帧数量、画面宽度和高度
  • 相邻的帧比较相似,不是特征有必要,因此每隔4帧采样一帧,但是需要保证输入图片序列至少有16帧,对采样不满16帧的视频,降低间隔
  • 将每一帧的图片resize到指定大小

(2)数据batch制作

        首先,读取视频预处理生成的图像序列,并返回到缓存中,然后对图片和图片序列进行截取,最终图像序列由16帧图片组成,每张图片大小为112*112.最后,将图像序列数据进行正规化并转换为tensor格式,并得到标签。

整体代码如下:

class VideoDataset(Dataset):r"""A Dataset for a folder of videos. Expects the directory structure to bedirectory->[train/val/test]->[class labels]->[videos]. Initializes with a listof all file names, along with an array of labels, with label being automaticallyinferred from the respective folder names.Args:dataset (str): Name of dataset. Defaults to 'ucf101'.split (str): Determines which folder of the directory the dataset will read from. Defaults to 'train'.clip_len (int): Determines how many frames are there in each clip. Defaults to 16.preprocess (bool): Determines whether to preprocess dataset. Default is False."""# 注意第一次要预处理数据的def __init__(self, dataset='ucf101', split='train', clip_len=16, preprocess=True):# 数据路径/视频预处理结果存储路径self.root_dir, self.output_dir = Path.db_dir(dataset)# train/val/test 文件夹folder = os.path.join(self.output_dir, split)# 视频序列长度self.clip_len = clip_lenself.split = split # train/val/test# The following three parameters are chosen as described in the paper section 4.1self.resize_height = 128self.resize_width = 171self.crop_size = 112if not self.check_integrity():raise RuntimeError('Dataset not found or corrupted.' +' You need to download it from official website.')# 视频数据预处理if (not self.check_preprocess()) or preprocess:print('Preprocessing of {} dataset, this will take long, but it will be done only once.'.format(dataset))self.preprocess()# Obtain all the filenames of files inside all the class folders# Going through each class folder one at a timeself.fnames, labels = [], []for label in sorted(os.listdir(folder)):for fname in os.listdir(os.path.join(folder, label)):self.fnames.append(os.path.join(folder, label, fname))labels.append(label)assert len(labels) == len(self.fnames)print('Number of {} videos: {:d}'.format(split, len(self.fnames)))# Prepare a mapping between the label names (strings) and indices (ints)self.label2index = {label: index for index, label in enumerate(sorted(set(labels)))}# Convert the list of label names into an array of label indicesself.label_array = np.array([self.label2index[label] for label in labels], dtype=int)if dataset == "ucf101":if not os.path.exists('dataloaders/ucf_labels.txt'):with open('dataloaders/ucf_labels.txt', 'w') as f:for id, label in enumerate(sorted(self.label2index)):f.writelines(str(id+1) + ' ' + label + '\n')elif dataset == 'hmdb51':if not os.path.exists('dataloaders/hmdb_labels.txt'):with open('dataloaders/hmdb_labels.txt', 'w') as f:for id, label in enumerate(sorted(self.label2index)):f.writelines(str(id+1) + ' ' + label + '\n')def __len__(self):return len(self.fnames)#需要重写__getitem__方法def __getitem__(self, index):# Loading and preprocessing.# 读取图像,并返回到缓存中buffer = self.load_frames(self.fnames[index]) #一共有8460个文件夹# 对图片和图片序列进行截取,最终返回16,3,112,112的图片序列buffer = self.crop(buffer, self.clip_len, self.crop_size)# 标签文件labels = np.array(self.label_array[index])if self.split == 'test':# Perform data augmentationbuffer = self.randomflip(buffer)buffer = self.normalize(buffer) # 正规化buffer = self.to_tensor(buffer) # 转化为tensor格式return torch.from_numpy(buffer), torch.from_numpy(labels)def check_integrity(self):if not os.path.exists(self.root_dir):return Falseelse:return Truedef check_preprocess(self):# TODO: Check image size in output_dirif not os.path.exists(self.output_dir):return Falseelif not os.path.exists(os.path.join(self.output_dir, 'train')):return Falsefor ii, video_class in enumerate(os.listdir(os.path.join(self.output_dir, 'train'))):for video in os.listdir(os.path.join(self.output_dir, 'train', video_class)):video_name = os.path.join(os.path.join(self.output_dir, 'train', video_class, video),sorted(os.listdir(os.path.join(self.output_dir, 'train', video_class, video)))[0])image = cv2.imread(video_name)if np.shape(image)[0] != 128 or np.shape(image)[1] != 171:return Falseelse:breakif ii == 10:breakreturn True# 视频数据预处理def preprocess(self):# 文件夹不存在/创建文件夹if not os.path.exists(self.output_dir):os.mkdir(self.output_dir)os.mkdir(os.path.join(self.output_dir, 'train'))os.mkdir(os.path.join(self.output_dir, 'val'))os.mkdir(os.path.join(self.output_dir, 'test'))#-----------------------------------------------------------------------------------------------------## Split train/val/test sets# 对于每一个文件夹(类别)的视频,划分训练集、测试集、验证集、#-----------------------------------------------------------------------------------------------------#for file in os.listdir(self.root_dir):file_path = os.path.join(self.root_dir, file)video_files = [name for name in os.listdir(file_path)]train_and_valid, test = train_test_split(video_files, test_size=0.2, random_state=42)train, val = train_test_split(train_and_valid, test_size=0.2, random_state=42)train_dir = os.path.join(self.output_dir, 'train', file)val_dir = os.path.join(self.output_dir, 'val', file)test_dir = os.path.join(self.output_dir, 'test', file)if not os.path.exists(train_dir):os.mkdir(train_dir)if not os.path.exists(val_dir):os.mkdir(val_dir)if not os.path.exists(test_dir):os.mkdir(test_dir)#------------------------------------------------## 对训练集、测试集、验证集的视频进行切分,得到图像序列,一般# 每隔4帧采样1帧#------------------------------------------------#for video in train:self.process_video(video, file, train_dir)for video in val:self.process_video(video, file, val_dir)for video in test:self.process_video(video, file, test_dir)print('Preprocessing finished.')#------------------------------------------------------------## 将视频切分为图片序列#------------------------------------------------------------#def process_video(self, video, action_name, save_dir):# Initialize a VideoCapture object to read video data into a numpy arrayvideo_filename = video.split('.')[0]if not os.path.exists(os.path.join(save_dir, video_filename)):os.mkdir(os.path.join(save_dir, video_filename))# ------------------------------------------------------------## 读取视频,并返回视频的帧数量、画面宽度和高度# ------------------------------------------------------------#capture = cv2.VideoCapture(os.path.join(self.root_dir, action_name, video))frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))frame_width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))frame_height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))#------------------------------------------------------## Make sure splited video has at least 16 frames# 相邻的帧比较相似,不是特征有必要,因此每隔4帧采样一帧,但是需要# 保证输入图片序列至少有16帧,对采样不满16帧的视频,降低间隔#------------------------------------------------------#EXTRACT_FREQUENCY = 4if frame_count // EXTRACT_FREQUENCY <= 16:EXTRACT_FREQUENCY -= 1if frame_count // EXTRACT_FREQUENCY <= 16:EXTRACT_FREQUENCY -= 1if frame_count // EXTRACT_FREQUENCY <= 16:EXTRACT_FREQUENCY -= 1count = 0i = 0retaining = Truewhile (count < frame_count and retaining):retaining, frame = capture.read()if frame is None:continue# 将图片resize到指定大小if count % EXTRACT_FREQUENCY == 0:if (frame_height != self.resize_height) or (frame_width != self.resize_width):frame = cv2.resize(frame, (self.resize_width, self.resize_height))cv2.imwrite(filename=os.path.join(save_dir, video_filename, '0000{}.jpg'.format(str(i))), img=frame)i += 1count += 1# Release the VideoCapture once it is no longer neededcapture.release()def randomflip(self, buffer):"""Horizontally flip the given image and ground truth randomly with a probability of 0.5."""if np.random.random() < 0.5:for i, frame in enumerate(buffer):frame = cv2.flip(buffer[i], flipCode=1)buffer[i] = cv2.flip(frame, flipCode=1)return bufferdef normalize(self, buffer):for i, frame in enumerate(buffer):frame -= np.array([[[90.0, 98.0, 102.0]]])buffer[i] = framereturn bufferdef to_tensor(self, buffer):return buffer.transpose((3, 0, 1, 2))#----------------------------------------------------------## 读取每一帧的图像,并返回到缓存中#----------------------------------------------------------#def load_frames(self, file_dir):frames = sorted([os.path.join(file_dir, img) for img in os.listdir(file_dir)])frame_count = len(frames)buffer = np.empty((frame_count, self.resize_height, self.resize_width, 3), np.dtype('float32'))for i, frame_name in enumerate(frames):frame = np.array(cv2.imread(frame_name)).astype(np.float64)buffer[i] = framereturn buffer#---------------------------------------------------------------## 随机选取一个index值,截取16帧的图片序列作为输入,同时对图片进行截取,最终返回# 3,16,112,112的图片序列作为输入#---------------------------------------------------------------#def crop(self, buffer, clip_len, crop_size):# randomly select time index for temporal jitteringtime_index = np.random.randint(buffer.shape[0] - clip_len)# Randomly select start indices in order to crop the videoheight_index = np.random.randint(buffer.shape[1] - crop_size)width_index = np.random.randint(buffer.shape[2] - crop_size)# Crop and jitter the video using indexing. The spatial crop is performed on# the entire array, so each frame is cropped in the same location. The temporal# jitter takes place via the selection of consecutive framesbuffer = buffer[time_index:time_index + clip_len,height_index:height_index + crop_size,width_index:width_index + crop_size, :]return buffer

 4.网络结构

        整个网络结构模型与论文描述一致,输入尺寸为3×16×128×171。在训练期间,使用3×16×112×112的随机裁剪进行抖动。网络有5个卷积层和5个池化层(每个卷积层紧是一个池化层),2个全连接层和一个softmax损失层来预测动作标签。从1层到5层的5个卷积层的滤波器数分别为64、128、256、256、256。所有卷积核的大小都为d,其中d是核的时间深度。所有这些卷积层都应用了适当的填充(空间和时间)和步幅1,因此从这些卷积层的输入到输出的大小没有变化。所有的池化层都是最大池化,内核大小为2×2×2(第一层除外),步幅为1,这意味着输出信号的大小比输入信号减少了8倍。第一个池化层的内核大小为1×2×2,目的是不过早合并时间信号,并满足16帧的序列长度

代码如下:

class C3D(nn.Module):"""The C3D network."""def __init__(self, num_classes, pretrained=False):super(C3D, self).__init__()self.conv1 = nn.Conv3d(3, 64, kernel_size=(3, 3, 3), padding=(1, 1, 1))self.pool1 = nn.MaxPool3d(kernel_size=(1, 2, 2), stride=(1, 2, 2))self.conv2 = nn.Conv3d(64, 128, kernel_size=(3, 3, 3), padding=(1, 1, 1))self.pool2 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=(2, 2, 2))self.conv3a = nn.Conv3d(128, 256, kernel_size=(3, 3, 3), padding=(1, 1, 1))self.conv3b = nn.Conv3d(256, 256, kernel_size=(3, 3, 3), padding=(1, 1, 1))self.pool3 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=(2, 2, 2))self.conv4a = nn.Conv3d(256, 512, kernel_size=(3, 3, 3), padding=(1, 1, 1))self.conv4b = nn.Conv3d(512, 512, kernel_size=(3, 3, 3), padding=(1, 1, 1))self.pool4 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=(2, 2, 2))self.conv5a = nn.Conv3d(512, 512, kernel_size=(3, 3, 3), padding=(1, 1, 1))self.conv5b = nn.Conv3d(512, 512, kernel_size=(3, 3, 3), padding=(1, 1, 1))self.pool5 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=(2, 2, 2), padding=(0, 1, 1))self.fc6 = nn.Linear(8192, 4096)self.fc7 = nn.Linear(4096, 4096)self.fc8 = nn.Linear(4096, num_classes)self.dropout = nn.Dropout(p=0.5)self.relu = nn.ReLU()self.__init_weight()if pretrained:self.__load_pretrained_weights()def forward(self, x):#print ('1:',x.size())x = self.relu(self.conv1(x))#print ('2:',x.size())x = self.pool1(x)#print ('3:',x.size())x = self.relu(self.conv2(x))#print ('4:',x.size())x = self.pool2(x)#print ('5:',x.size())x = self.relu(self.conv3a(x))#print ('6:',x.size())x = self.relu(self.conv3b(x))#print ('7:',x.size())x = self.pool3(x)#print ('8:',x.size())x = self.relu(self.conv4a(x))#print ('9:',x.size())x = self.relu(self.conv4b(x))#print ('10:',x.size())x = self.pool4(x)#print ('11:',x.size())x = self.relu(self.conv5a(x))#print ('12:',x.size())x = self.relu(self.conv5b(x))#print ('13:',x.size())x = self.pool5(x)#print ('14:',x.size())x = x.view(-1, 8192)#print ('15:',x.size())x = self.relu(self.fc6(x))#print ('16:',x.size())x = self.dropout(x)x = self.relu(self.fc7(x))x = self.dropout(x)logits = self.fc8(x)#print ('17:',logits.size())return logitsdef __load_pretrained_weights(self):"""Initialiaze network."""corresp_name = {# Conv1"features.0.weight": "conv1.weight","features.0.bias": "conv1.bias",# Conv2"features.3.weight": "conv2.weight","features.3.bias": "conv2.bias",# Conv3a"features.6.weight": "conv3a.weight","features.6.bias": "conv3a.bias",# Conv3b"features.8.weight": "conv3b.weight","features.8.bias": "conv3b.bias",# Conv4a"features.11.weight": "conv4a.weight","features.11.bias": "conv4a.bias",# Conv4b"features.13.weight": "conv4b.weight","features.13.bias": "conv4b.bias",# Conv5a"features.16.weight": "conv5a.weight","features.16.bias": "conv5a.bias",# Conv5b"features.18.weight": "conv5b.weight","features.18.bias": "conv5b.bias",# fc6"classifier.0.weight": "fc6.weight","classifier.0.bias": "fc6.bias",# fc7"classifier.3.weight": "fc7.weight","classifier.3.bias": "fc7.bias",}p_dict = torch.load(Path.model_dir())s_dict = self.state_dict()for name in p_dict:if name not in corresp_name:continues_dict[corresp_name[name]] = p_dict[name]self.load_state_dict(s_dict)def __init_weight(self):for m in self.modules():if isinstance(m, nn.Conv3d):# n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels# m.weight.data.normal_(0, math.sqrt(2. / n))torch.nn.init.kaiming_normal_(m.weight)elif isinstance(m, nn.BatchNorm3d):m.weight.data.fill_(1)m.bias.data.zero_()

 

 

 


http://chatgpt.dhexx.cn/article/Koyc06IH.shtml

相关文章

C3D代码总结(Pytorch)

C3D代码总结&#xff08;Pytorch&#xff09; github&#xff1a;https://github.com/Niki173/C3D 介绍数据介绍文件介绍具体操作流程运行结果 介绍&#xff1a; 本次C3D模型用的是pytorch框架&#xff0c;我们在UCF101和HMDB51数据集上训练这些模型&#xff0c;本次实验以U…

C3D论文笔记

论文链接&#xff1a;http://vlg.cs.dartmouth.edu/c3d/c3d_video.pdf 代码链接&#xff1a;https://github.com/jfzhang95/pytorch-video-recognition 1. C3D是什么&#xff1f; C3D&#xff0c;全称Convolutional 3D&#xff0c;即3D卷积。3D卷积方法是把视频划分成很多固定…

C3D网络介绍

1. 模型简介 C3D模型广泛用于3D视觉任务。C3D网络的构造类似于常见的2D卷积网&#xff0c;主要区别在于C3D使用像卷积3D这样的3D操作&#xff0c;而2D卷积网则是通常的2D架构。要了解有关C3D网络的更多信息&#xff0c;您可以阅读原始论文学习3D卷积网络的时空特征。 3D卷积图…

视频分析模型(行为识别):C3D

C3D 文章目录 C3D1. 简介1.1 背景1.2 C3D特点1.3 视频描述符1.4 C3D的结果 2. 架构2.1 工作流程2.2 网络结构2.3 3D卷积和池化2.4 kernel 的时间深度 3. 可视化3.1 特征图3.2 特征嵌入 4. 应用场景4.1 动作识别4.2 动作相似度标注4.3 场景和目标识别4.4 运行时间分析 1. 简介 …

C3D论文精读

论文地址:https://vlg.cs.dartmouth.edu/c3d/c3d_video.pdf Abstract 作者的研究结果有三个方面&#xff1a; 1)与二维相比&#xff0c;三维卷积网更适合时空特征学习&#xff1b;2)所有层具有333的小卷积核的同构架构是3D卷积网的最佳架构之一&#xff1b;3)学习到的特征&am…

基于C3D网络的视频分析与动作识别

卷积神经网络&#xff08;CNN&#xff09;被广泛应用于计算机视觉中&#xff0c;包括分类、检测、分割等任务。这些任务一般都是针对图像进行的&#xff0c;使用的是二维卷积&#xff08;即卷积核的维度为二维&#xff09;。而对于基于视频分析的问题&#xff0c;2D convolutio…

《QDebug 2022年12月》

一、Qt Widgets 问题交流 二、Qt Quick 问题交流 1、在 C 中关联 QQuickWindow 的 closing 信号提示 "使用了未定义类型QQuickCloseEvent" 因为 closing 信号中的参数类型是 private 模块中定义的&#xff0c;但是通过第二句提示我们知道找到了完整定义才能使用 Q_…

4.4 案例8 用qDebug()输出信息

本案例对应的源代码目录&#xff1a;src/chapter04/ks04_04。 在开发C/S&#xff08;Client/Server&#xff0c;客户端/服务端&#xff09;模式的软件时&#xff0c;服务端程序&#xff08;有时也称作服务&#xff09;经常运行在两种模式下。 &#xff08;1&#xff09;终端模…

Qt扫盲-QDebug理论总结

QDebug理论使用总结 一、概述二、使用1. 基础使用2. 格式化选项3.将自定义类型写入流 一、概述 每当开发人员需要将调试或跟踪信息写入设备、文件、字符串或控制台时&#xff0c;都会使用QDebug。这个就可以方便我们调试&#xff0c;基本上Qt所有的内容都能通过调试打印出来&a…

Qt重定向QDebug,自定义一个简易的日志管理类

0.前言 相对于第三方的日志库&#xff0c;在 Qt 中使用 QDebug 打印更便捷&#xff0c;有时候也需要对 QDebug 输出进行重定向&#xff0c;如写入文件等。 在 Qt4 中使用 qInstallMsgHandler 函数设置重定向的函数指针&#xff1a; typedef void (*QtMsgHandler)(QtMsgType,…

qDebug 控制台输出

做个小笔记:qDebug 控制台输出 Ⅰ&#xff1a;*.pro文件中添加 win32:CONFIG console Ⅱ&#xff1a;配置项目运行设置&#xff0c;将Run in terminal 复选框打勾 Ⅲ&#xff1a;添加头文件 #include <QDebug> Ⅳ&#xff1a;用qDebug()<<"xxxx";输…

Qt ——debug调试

程序调试&#xff1a; 方法一&#xff1a;断点调试法方法二&#xff1a;使用qDebug()函数 方法一&#xff1a;断点调试法 我们可以在程序加断点&#xff0c;然后再利用单步调试查看变量的值是否异常。 1. 设置断点。 可以左击相应的代码行前的区域&#xff08;下图用红色框标…

jadx反编译—下载和使用(傻瓜教程,非常详细)

原文地址 一、在GitHub上直接下载 下载地址 可以下这个版本&#xff1a; 二、运行图形化界面 1、将zip文件解压后定位到在lib文件夹中&#xff0c;在此处打开命令行 2、运行jadx-gui-0.7.1.jar&#xff08;前提是已经装好了JDK1.8&#xff09; 命令如下&#xff1a; <sp…

Android APK 反编译工具 JADX

文章目录 JADX 介绍JADX 安装JADX 使用补充APK 目录结构含义APK 打包流程 JADX 介绍 GitHub 地址&#xff1a;https://github.com/skylot/jadx JADX 支持将 APK, dex, aar, zip 中的 dalvik 字节码反编译为 Java 代码&#xff0c;也支持反编译 AndroidManifest.xml 和 resource…

jadx-gui 重命名功能

jad-gui 是大家常用的一款反编译工具&#xff0c;其中有些小使用技巧可以帮助大家更快的“学习”知识。 安装 方法参考项目GitHub主页 重命名 最新的 1.2.0 版本支持了方法、类、字段的重命名&#xff0c;这是一个非常有用的功能&#xff0c;之前反编译出来的都是混淆后的名…

Android 反编译神器jadx的使用

一、前言 今天介绍一个非常好用的反编译的工具 jadx 。jadx 的功能非常的强大&#xff0c;对我而言&#xff0c;基本上满足日常反编译需求。 jadx 优点&#xff1a; 图形化的界面。拖拽式的操作。反编译输出 Java 代码。导出 Gradle 工程。 这些优点都让 jadx 成为我反编译…

jadx工具windows下载

Release v1.2.0 skylot/jadx GitHubDex to Java decompiler. Contribute to skylot/jadx development by creating an account on GitHub.https://github.com/skylot/jadx/releases/tag/v1.2.0选择第三个jadx-gui-1.2.0-with-jre-win.zip解压exe可正常使用

jadx反编译—下载和使用

一、在GitHub上直接下载 https://github.com/skylot/jadx 可以下这个版本&#xff1a; 二、运行图形化界面 1、将zip文件解压后定位到在lib文件夹中&#xff0c;在此处打开命令行 2、运行jadx-gui-0.7.1.jar&#xff08;前提是已经装好了JDK1.8&#xff09; 命令如下&#xf…

jadx初识

一.jadx介绍 一款相对流行的反编译工具 下载&#xff1a;https://github.com/skylot/jadx/releases/tag/v1.0.0 解压后得到这么几个文件&#xff1a; 启动&#xff1a;&#xff08;以下来两个文件都可以&#xff09; 启动后的界面&#xff1a; 二.使用 打开之后&#xff0c;你可…

反编译工具之jadx

反编译神器之 - jadx git网址&#xff1a; https://github.com/skylot/jadx简介 首先推荐功能强大的jadx&#xff0c;官方网站为&#xff1a;https://github.com/skylot/jadx&#xff0c;可以直接在releases页面下载其最新版&#xff0c;解压即可使用。 安装 Windows下安装 …