使用模型前不需要缩放图片,程序内部会自动进行操作
特征提取使用Sports-1M上训练的C3D v1.0模型,caffe框架
主要参考教程
官网
官方User Guide
官方github
个人caffe安装教程
层 特征长度:fc6-1 4096 fc7-1 4096 fc8-1 487 prob 487
可能用到的文件或压缩包:
链接:https://pan.baidu.com/s/1vIRxSJHRoBnrnsjczISOPg?pwd=7asr
提取码:7asr
复制这段内容后打开百度网盘手机App,操作更方便哦
下面所有命令均在root权限下执行,若提示权限不足,则在前面加sudo
主要参考教程若网址失效,可以在网盘里看到“主要参考教程网页备份”
本文所介绍的是C3D v1.0,若想使用C3D v1.1,则其和caffe安装的过程一模一样,跟着“个人caffe安装教程”走一遍,然后再安装C3D v1.1即可
原论文
C3D: Convolutional 3D
实验表明,三维卷积深度网络是很好的特征学习机,可以同时建模外观和运动。
我们根据经验发现,所有层的3 × 3 × 3卷积核在有限的探索架构中工作得最好。
在4个不同的任务和6个不同的基准上,用简单的线性模型提出的特征优于或接近目前最好的方法。它们也很紧凑,计算效率很高。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pB6rj3ig-1663554505307)(C:/Users/shang/AppData/Roaming/Typora/typora-user-images/image-20220919093158716.png)]
Sports-1M:目前最大的视频分类基准数据集,包含1.1million运动视频,487个运动类别。
UCF101: 一个现实动作视频的动作识别数据集,收集自YouTube,提供了来自101个动作类别的13320个视频。
2D卷积存在的问题:输入一个图片 -> 输出一个图片;输入多个图片(看做不同的channel),输出一个图片。仅一次conv就丢失了全部的时间信息。
clip shape[c,l,h,w]
c: channels l:frames h:height w:width
conv and pool kernel: shape[d, k, k]
图片的预处理:
训练:resize(128, 171) random 抽取16帧 crop(112, 112)实现抖动 50%水平翻转
测试:resize(128, 171) 均匀采样16 中心裁剪(112, 112) 无翻转
shape[3, 16, 112, 112],l为16,经过4次2->1的池化操作后,时间信息就会完全丢失,所以pool1没有进行在时间上的池化,pool2到pool5共4次在时间上的池化
安装CUDA 10.2及对应的CUDNN(参考“主要参考教程”),为了简化操作作者使用了“矩池云平台”:
已经配置好了CUDA和cuDNN,就不用自己配置了。开一个1元/时的环境就够用
apt-get update
apt-get install -y libprotobuf-dev libleveldb-dev libsnappy-dev libopencv-dev libhdf5-serial-dev protobuf-compiler
apt-get install --no-install-recommends libboost-all-dev
apt-get install -y libopenblas-dev liblapack-dev libatlas-base-dev libgflags-dev libgoogle-glog-dev liblmdb-dev git cmake build-essential
# 打开环境配置文件
vim ~/.bashrc
# 在打开的文件末尾添加下面两行
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
# 使环境配置文件生效
source ~/.bashrc
将opencv-3.4.10.zip复制到/root目录下
unzip opencv-3.4.10.zip
删除opencv-3.4.10.zip(为了节省空间)
执行下面的命令
# 进入文件夹
cd opencv-3.4.10
# 创建build文件夹
mkdir build
# 进入build文件夹
cd build
# 编译
cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local ..
make
# 安装
make install
# 查看是否安装成功 成功将输出:3.4.10
pkg-config --modversion opencv
alias python=python2
## 输出python版本为2.?则为成功
python -V
将boost_1_72_0.7z复制到/root目录下
apt-get install p7zip-full
7z x boost_1_72_0.7z
删除boost_1_72_0.7z(为了节省空间)
执行下面的命令
# 进入文件夹
cd boost_1_72_0.7z
./bootstrap.sh --with-libraries=all
./b2
./b2 install
将C3D-master.zip复制到/root目录下
unzip C3D-master.zip
删除C3D-master.zip(为了节省空间)
执行下面的命令
# 进入文件夹
cd C3D-master/C3D-v1.0/python
# 安装依赖包
for req in $(cat requirements.txt); do pip install $req; done
# 返回上一级目录
cd ..
# 复制文件
cp Makefile.config.example Makefile.config
修改文件
1) 修改Makefile.config
CUDA_ARCH := -gencode arch=compute_20,code=sm_20 \
-gencode arch=compute_20,code=sm_21 \
-gencode arch=compute_30,code=sm_30 \
-gencode arch=compute_35,code=sm_35 \
-gencode=arch=compute_50,code=sm_50 \
#-gencode=arch=compute_50,code=compute_50
修改为
CUDA_ARCH := #-gencode arch=compute_20,code=sm_20 \
#-gencode arch=compute_20,code=sm_21 \
#-gencode arch=compute_30,code=sm_30 \
#-gencode arch=compute_35,code=sm_35 \
-gencode=arch=compute_50,code=sm_50 \
-gencode=arch=compute_50,code=compute_50
# OPENCV_VERSION := 3
修改为
OPENCV_VERSION := 3
INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/local/include
LIBRARY_DIRS := $(PYTHON_LIB) /usr/local/lib /usr/lib
修改为
INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/local/include /usr/include/hdf5/serial
LIBRARY_DIRS := $(PYTHON_LIB) /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu/hdf5/serial
2) 修改Makefile
NVCCFLAGS +=-ccbin=$(CXX) -Xcompiler-fPIC $(COMMON_FLAGS)
修改为
NVCCFLAGS += -D_FORCE_INLINES -ccbin=$(CXX) -Xcompiler -fPIC $(COMMON_FLAGS)
LIBRARIES := cudart cublas curand \
pthread \
glog protobuf leveldb snappy \
boost_system \
hdf5_hl hdf5 \
opencv_core opencv_highgui opencv_imgproc
修改为
LIBRARIES := cudart cublas curand \
pthread \
glog protobuf leveldb snappy \
boost_system \
hdf5_serial_hl hdf5_serial \
opencv_core opencv_highgui opencv_imgproc
3) 修改/src/caffe/test/test_gradient_check_util.hpp
Dtype scale = std::max(
std::max(fabs(computed_gradient), fabs(estimated_gradient)), 1.);
修改为
Dtype scale = std::max(
std::max(fabs(computed_gradient), fabs(estimated_gradient)),
Dtype(1.));
make all
# 可能有错误:recipe for target 'build/examples/mnist/convert_mnist_data.o' failed
# 不用管
make test
make runtest
# 可能有错误,不用管
# Makefile:285: recipe for target 'runtest' failed
# make: *** [runtest] Error 127
将文件conv3d_deepnetA_sport1m_iter_1900000复制到C3D-master/C3D-v1.0/examples/c3d_feature_extraction/
sh c3d_sport1m_feature_extraction_frm.sh
出现错误:…/…/build/tools/extract_image_features.bin: error while loading shared libraries: libopencv_core.so.3.4: cannot open shared object file: No such file or directory
解决:在/etc/ld.so.conf.d/下创建OpenCV.conf,内容为/usr/local/lib,然后执行命令sudo ldconfig
sh c3d_sport1m_feature_extraction_frm.sh
执行成功,E0707 14:27:41.620281 36381 extract_image_features.cpp:121] Successfully extracted 16 features!
将百度网盘中malloclu_c3d_feature_extraction复制到C3D-master目录下,使用下面的程序提取特征,输入为视频(.mp4,.avi),输出为特征向量字典(.pkl)
"""
功能:
使用c3d v1.0提取视频特征,执行完成后得到output//c3d____.pkl,
内容为一个字典,key为(视频名),value为list内含多个特征向量
其中第i个特征向量(从0开始)所表示视频中的帧为 [startFrame + i * step, startFrame + i * step + stride * 16 - 1]
例如:python main.py --videoDir testVideo
参数:
videoDir: 多个视频所在文件夹
frameSize=None: 是否缩放帧图片,例如(320, 240)
startFrame=1: 开始帧(标号从1开始)
stride=1: 采样间隔(后采样帧-前采样帧=stride)(stride * 16即为获得的特征向量所表示的 视频片段长度)
step=16: 步幅,stride的整数倍,本次的startFrm与下次的startFrm之间的间隔帧数
keepCookie=False: 是否保存中间文件
layer='fc7-1': 获取c3d特征向量的层数,可选fc6-1 fc7-1 fc8-1 prob
if not keepCookie:
删除产生的除 c3dfreature-.pkl 之外所有的中间文件
程序执行流程:
截取视频帧保存到 output//frame//
写prototxt/input_list_frm.txt
写prototxt/output_list_prefix.txt
执行c3d,每个视频特征暂存到 output//feature//
所有特征保存到 output//c3d____.pkl
"""
import argparse
import datetime
import os
import cv2
import subprocess
import math
import numpy as np
import pickle
import shutil
def createDirs(dirs):
"""
不存在时,创建多级目录
存在时,无操作
:param dirs: 多级目录
:return: None
"""
if not os.path.exists(dirs):
os.makedirs(dirs)
def saveFrame(videoPath, frameSize, startFrame, stride, saveDir):
"""
保存视频帧图片,保存名为 递增的f'{:06d}.jpg'
从startFrame开始每隔stride保存一张,图片可选缩放
:param videoPath: 视频路径
:param frameSize: 是否缩放帧图片,例如(320, 240)
:param startFrame: 开始帧(标号从1开始)
:param stride: 采样间隔(后采样帧-前采样帧=stride)(stride * 16即为获得的特征向量所表示的 视频片段长度)
:param saveDir: 保存文件夹
:return: 保存图片数/-1
"""
vidcap = cv2.VideoCapture(videoPath)
len = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
if not os.path.exists(saveDir):
os.makedirs(saveDir)
if os.listdir(saveDir).__len__() > 0:
print(f'【x 保存文件夹{saveDir}不为空】')
return -1
cnt = 1
for i in range(len):
ret, frame = vidcap.read()
if not ret:
print("读取帧时发生错误")
return -1
if i + 1 >= startFrame and (i + 1 - startFrame) % stride == 0:
if frameSize:
frame = cv2.resize(frame, frameSize)
cv2.imwrite(os.path.join(saveDir, f'{cnt:06d}.jpg'), frame)
cnt = cnt + 1
return cnt - 1
def read_binary_blob(fn):
"""
读取c3d提取的特征文件->numpy array float32
:param fn: 文件名
:return:
"""
s = np.fromfile(fn, dtype=np.dtype('int32'), count=5)
# s contains size of the blob e.g. num x chanel x length x height x width
m = s[0] * s[1] * s[2] * s[3] * s[4]
# data is the blob binary data in single precision (e.g float in C++)
data = np.fromfile(fn, dtype=np.dtype('float32'), count=m, offset=20)
return s, data
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--videoDir', type=str, required=True)
parser.add_argument('--frameSize', type=tuple, default=None)
parser.add_argument('--startFrame', type=int, default=1)
parser.add_argument('--stride', type=int, default=1)
parser.add_argument('--step', type=int, default=16)
parser.add_argument('--keepCookie', type=bool, default=False)
parser.add_argument('--layer', type=str, default='fc7-1', choices=['fc6-1', 'fc7-1', 'fc8-1', 'prob'])
args = parser.parse_args()
print(args)
print('--------------------------------------------------------------')
if not args.step % args.stride == 0:
print('【x 参数step需为参数stride的整数倍】')
exit(1)
curTime = datetime.datetime.strftime(datetime.datetime.now(), '%Y%m%d%H%M%S')
# 截取视频帧
cntDict = {}
videoList = os.listdir(args.videoDir)
for item in videoList:
if item.endswith('.avi') or item.endswith('.mp4'):
createDirs(os.path.join('output', curTime, 'frame', item))
createDirs(os.path.join('output', curTime, 'feature', item))
tcnt = saveFrame(os.path.join(args.videoDir, item), None,
args.startFrame, args.stride, os.path.join('output', curTime, 'frame', item))
cntDict[item] = tcnt
createDirs('prototxt')
# 写prototxt//input_list_frm.txt
# 写prototxt//output_list_prefix.txt
sStep = args.step // args.stride
wlinput = []
wloutput = []
for item in videoList:
if item.endswith('.avi') or item.endswith('.mp4'):
sStart = 1
while sStart + 15 <= cntDict[item]:
wlinput.append(f'output/{curTime}/frame/{item}/ {sStart} 0\n')
wloutput.append(f'output/{curTime}/feature/{item}/{sStart:06d}\n')
sStart = sStart + sStep
with open('prototxt/input_list_frm.txt', 'w') as f:
f.writelines(wlinput)
with open('prototxt/output_list_prefix.txt', 'w') as f:
f.writelines(wloutput)
# 执行c3d
status = subprocess.getstatusoutput('GLOG_logtosterr=1 ../C3D-v1.0/build/tools/extract_image_features.bin '
'c3d_sport1m_feature_extractor_frm.prototxt '
'conv3d_deepnetA_sport1m_iter_1900000 0 '
f'50 {math.ceil(len(wlinput) / 50)} '
f'prototxt/output_list_prefix.txt {args.layer}')[0]
if status != 0:
print('【x c3d运行失败】')
exit(1)
# 保存特征
result = {}
for item in wloutput:
item = item.strip('\n')
_, data = read_binary_blob(item + f'.{args.layer}')
name = item.split('/')[-2]
if name not in result.keys():
result[name] = []
result[name].append(data)
saveName = f'c3d_{args.startFrame}_{args.stride}_{args.step}_{args.layer}.pkl'
saveName = os.path.join('output', curTime, saveName)
with open(saveName, 'wb') as f:
pickle.dump(result, f)
# 删除缓存
if not args.keepCookie:
if os.path.exists(f'output/{curTime}/frame'):
shutil.rmtree(f'output/{curTime}/frame')
if os.path.exists(f'output/{curTime}/feature'):
shutil.rmtree(f'output/{curTime}/feature')
if os.path.exists('prototxt'):
shutil.rmtree('prototxt')
print(f'【v 运行成功 保存名{saveName}】')
exit(0)
name: "DeepConv3DNet_Sport1M_Val"
layers {
name: "data"
type: VIDEO_DATA
top: "data"
top: "label"
image_data_param {
source: "prototxt/input_list_frm.txt"
use_image: true
mean_file: "sport1m_train16_128_mean.binaryproto"
batch_size: 50
crop_size: 112
mirror: false
show_data: 0
new_height: 128
new_width: 171
new_length: 16
shuffle: false
}
}
# ----------- 1st layer group -----------
...
# ----------- 2nd layer group -----------
...
# ----------- 3rd layer group -----------
...
# ----------- 4th layer group -----------
...
# ----------- 5th layer group -----------
...
# ----------- fc layers -----------------
layers {
name: "fc6-1"
num_output: 4096
}
layers {
name: "relu6"
}
layers {
name: "drop6"
}
layers {
name: "fc7-1"
num_output: 4096
}
layers {
name: "relu7"
}
layers {
name: "drop7"
}
layers {
name: "fc8-1"
num_output: 487
}
layers {
name: "prob"
type: SOFTMAX
}
layers {
name: "accuracy"
type: ACCURACY
}