用OpenCV精准切割视频

拯救强迫症

ffmpeg 切割视频

使用 ffmpeg 直接切割视频的命令

1
ffmpeg -i test.mp4 -ss 00:00:00 -t 00:00:30 -c:v copy -c:a copy output.mp4

或者

1
ffmpeg -i test.mp4 -ss 00:00:00 -to 00:00:30 -c:v copy -c:a copy output.mp4

其中

1
2
3
4
-i  指定输入文件
-ss 指定开始时间
-to 指定结束时间
-t 指定需要截取的时长

上述命令将从视频开头截取一段 30s 的视频。但是,如果截取的开始时间和结束时间不是关键帧,那么得到的视频开头画面通常会卡住,而声音是正常的。这时候可以对视频流进行重编码,命令如下:

1
ffmpeg -ss 00:00:00 -to 00:00:30 -i test.mp4 -c:v h264 -c:a copy output.mp4

上述方法基本上可以满足大部分人的需求,时间精确到了秒。对于强迫症来说可能还不够准确,需要精确到帧。

OpenCV 精准切割视频

首先,使用 ffmpeg 粗略的切割视频

1
ffmpeg -ss START -to END -i INPUT -c:v copy -c:a copy cut.mp4

这里 START 建议设置在目标开始时间前几秒,END 设置在目标结束时间后几秒,以保证待截取的所有视频帧都包含在输出文件内。

接下来使用 Python+OpenCV 库实现以帧为单位的精准切割视频。首先将 cut.mp4 逐帧保存为图片,这里可以使用 OpenCV 或者 ffmpeg 生成(在当前目录下新建一个frames文件夹,否则无法保存):

1
ffmpeg -i cut.mp4 frames/frames_%05d.jpg

或者使用 OpenCV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import cv2

cap = cv2.VideoCapture('cut.mp4')
success, image = cap.read()
count = 0
success = True
while success:
## save frame as JPEG file
cv2.imwrite("frames/frame%d.jpg" % count, image)
success, image = cap.read()
print('Read a new frame: ', count)
count += 1

frames 文件夹下,我们可以看到视频帧都转换成了图像文件。接下来,找出需要截取的视频开始帧和结束帧对应的文件,并记下编号(下一步用到)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import cv2

cap = cv2.VideoCapture('cut.mp4')
outpath = 'cut_1.mp4'

start_frame = 372 ## 开始帧
end_frame = 1230 ## 结束帧

## 获取视频分辨率
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))

## 输出文件编码,Linux下可选X264
fourcc = cv2.VideoWriter_fourcc(*'MJPG')

## 视频帧率
fps = cap.get(cv2.CAP_PROP_FPS)

success, image = cap.read()
count = 0
success = True

## 从视频开头获取每一帧,直到到达开始帧
while success:
success, image = cap.read()
count += 1
if (count==start_frame):
success = False

## 开始帧的时间(单位ms),相当于ffmpeg的ss参数
ss = int(cap.get(cv2.CAP_PROP_POS_MSEC))
## 输出
out = cv2.VideoWriter(outpath, fourcc, fps, size)

## 读取开始帧到结束帧的每一帧并写入新视频
while (count<end_frame):
success, image = cap.read()
out.write(image)
count+=1

## 结束帧的时间,相对于ffmpeg的to参数
to = int(cap.get(cv2.CAP_PROP_POS_MSEC))

print(ss, to)
cap.release()
out.release()

以上程序将生产一个精确到帧的视频文件,但是没有声音,而 ffmpeg 在分割音频的时候是很精确的。接下来将用 ffmpeg 来切割音频,并和上述视频合并。

ffmpeg 切割音频并合并视频

1
ffmpeg -i cut_1.mp4 -i cut.mp4 -ss TIME_OFF -to TIME_STOP -c copy output.mkv

这里TIME_OFFTIME_STOP与前一节的ssto相对应,但是 ffmpeg 里面时间单位是 s,因此应先除以1000