1.视频转化为"图像"
首先我们的视频不能直接输入神经网络,必须要采样转化成帧。一般利用opencv来采样。
python
def process_video(self, video, action_name, save_dir):
# Initialize a VideoCapture object to read video data into a numpy array
video_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
EXTRACT_FREQUENCY = 4
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1
count = 0
i = 0
retaining = True
while (count < frame_count and retaining):
retaining, frame = capture.read()
if frame is None:
continue
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 += 1
count += 1
# Release the VideoCapture once it is no longer needed
capture.release()
1.参数定义
self
:类的实例对象,用于访问类的属性和方法。
video
:要处理的视频文件名,是一个字符串。
action_name
:视频所在的动作类别名称,用于定位视频文件的路径。
save_dir
:提取的帧图像要保存的目录
2.初始化操作
video_filename
:通过 split('.')[0]
提取视频文件名(不包含扩展名)。用.当分割符
检查是否存在以视频文件名命名的目录,如果不存在则创建该目录,用于保存提取的帧图像。
cv2.VideoCapture
:使用 OpenCV 的 VideoCapture
类打开视频文件,视频文件的完整路径由 self.root_dir
、action_name
和 video
组合而成。
3.获取操作
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))
4.采样帧
python
# Make sure splited video has at least 16 frames
EXTRACT_FREQUENCY = 4
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1 #整除
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1
这个代码无疑是证明为 检查按照当前频率提取的帧数是否小于等于 16,如果是,则将提取频率减 1,最多进行三次调整,以确保提取的帧数不少于 16 帧。
5.逐帧提取
count
:记录当前读取的帧数。i
:记录保存的帧图像的编号。retaining
:表示是否成功读取到帧。capture.read()
:逐帧读取视频,返回一个布尔值retaining
和当前帧frame
。- 如果
frame
为None
,则跳过当前循环,继续读取下一帧。- 如果当前帧数
count
是EXTRACT_FREQUENCY
的倍数,则进行以下操作:
- 检查当前帧的尺寸是否与
self.resize_height
和self.resize_width
一致,如果不一致,则使用cv2.resize
函数将帧图像调整为指定尺寸。- 使用
cv2.imwrite
函数将帧图像保存为 JPEG 文件,文件名格式为0000{}.jpg
,其中{}
是帧图像的编号i
。i
加 1。count
加 1。
2.3D卷积神经网络
经过上面的转化是不是感觉,2D也可以处理视频了,毕竟处理的图像2D也是一把手。
下面我来解说一下2D.
python
conv2d()-输入[B,inchannel,H,W] 返回也是[B,outchannel,H,W]
你可以理解为只是对一个图像进行处理!!!
对于一个视频而言,2D卷积网络在提取局部的空间信息有非常强大的功能,但是图像只是一部分(你可以理解为空间信息),但是一个视频往往蕴含十分重要的时间信息!!! ,所以3D网络也就出来了。

在三维卷积操作中,我们处理的是三维张量,其形状为 [T,H,W],其中:
-
T 表示时间维度(例如视频中的帧数)。
-
H 和 W 分别表示图像的高度和宽度。
这种数据结构非常适合处理视频或其他时间序列图像数据。通过三维卷积,我们可以在时间维度和空间维度上同时提取特征,从而捕捉到动态变化和局部信息。
三维卷积的卷积核
假设我们使用一个 3×3×3 的卷积核进行操作,这意味着:
-
时间维度:卷积核的大小为 3,表示它会同时覆盖 3 个连续的帧。可以将其视为一个长度为 3 的滑动窗口,每次选取 3 个连续的帧进行卷积操作。
-
空间维度:在每个时间窗口内,卷积核还会在 3×3 的空间区域内滑动,提取局部特征。
步长(Stride)和填充(Padding)
在卷积操作中,步长(stride)和填充(padding)是两个重要的参数,它们会影响输出的形状和特征提取的效果:
-
步长(Stride):定义了卷积核在时间维度和空间维度上滑动的步长。步长越大,输出的特征图越小,但计算效率更高。
-
填充(Padding):通过在输入张量的边界上添加额外的零值像素(或帧),可以控制输出特征图的大小,甚至保持输入和输出的尺寸一致。
示例说明
假设输入张量的形状为 [T=10,H=32,W=32],表示有 10 帧图像,每帧的大小为 32×32。我们使用一个 3×3×3 的卷积核,并设置以下参数:
-
步长(Stride):时间维度 ST=1,空间维度 SH=SW=1。
-
填充(Padding):时间维度 PT=1,空间维度 PH=PW=1。
输出形状的计算
根据卷积操作的公式,输出的形状可以通过以下方式计算:

因此,最终的输出张量形状为 [10,32,32]。
步长和填充的作用
-
步长:通过调整步长,可以控制卷积核滑动的速度。较大的步长会减少输出特征图的大小,但可以减少计算量,适合捕捉更全局的特征。
-
填充:通过添加零填充,可以保持输入和输出的尺寸一致,或者根据需要调整输出的大小。这在处理边界信息时尤为重要,避免因卷积核超出边界而导致信息丢失。
总结
在三维卷积操作中,卷积核不仅在空间维度上滑动,还在时间维度上滑动,从而同时捕捉时间和空间上的特征。通过合理设置步长和填充,可以灵活控制输出特征图的大小和特征提取的效果。你的理解非常准确:三维卷积核就像是一个长度为 3 的滑动窗口,每次选取 3 个连续的帧进行卷积操作,而步长和填充则进一步优化了这一过程。
3.构建一个3D卷积神经网络
python
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 logits