Save frames from video files as still images with OpenCV in Python
This article describes how to capture and save frames from video files such as mp4 and avi as still image files with OpenCV in Python.
If you simply want to save a video as a frame-by-frame still image, you can do so with ffmpeg commands, but if you want to do some image processing before saving, Python and OpenCV are useful.
The following sample code is shown here.
- Save all frames of the video as image files
- Save given frames as image files
- Specify a single frame
- Specify any range of frames
- Specify by time in seconds
- Save by pressing the key while playing the video
If you want to capture and save still images from the real-time video of a camera (built-in camera, USB camera, or webcam) instead of video files, see the following article.
See the following article for basic information on how to handle video in OpenCV.
The following sample video is used as an example. Sorry for the Japanese site, but if you check the checkbox in the lower right corner, you can download the file from the blue button.
Save all frames of the video as image files
The sample code to save all frames of a video file as still image files is as follows.
Save files in the specified directory with the file name <basename>_<number>.<extension>
.
import cv2
import os
def save_all_frames(video_path, dir_path, basename, ext='jpg'):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return
os.makedirs(dir_path, exist_ok=True)
base_path = os.path.join(dir_path, basename)
digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))
n = 0
while True:
ret, frame = cap.read()
if ret:
cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
n += 1
else:
return
save_all_frames('data/temp/sample_video.mp4', 'data/temp/result', 'sample_video_img')
save_all_frames('data/temp/sample_video.mp4', 'data/temp/result_png', 'sample_video_img', 'png')
Create a directory with os.makedirs()
.
You can join path strings with os.path.join()
.
Get the total number of frames with CAP_PROP_FRAME_COUNT
(CV_CAP_PROP_FRAME_COUNT
in OpenCV2) and fill frame counts with zeros by zfill()
according to its number of digits.
Save given frames as image files
Specify a single frame
The sample code to save any frame as a still image file is as follows.
In this example, the directory, file name, and extension are specified together as the path.
def save_frame(video_path, frame_num, result_path):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return
os.makedirs(os.path.dirname(result_path), exist_ok=True)
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
ret, frame = cap.read()
if ret:
cv2.imwrite(result_path, frame)
save_frame('data/temp/sample_video.mp4', 100, 'data/temp/result_single/sample_100.jpg')
Get the directory of the destination path with os.path.dirname()
and create it with os.makedirs()
.
Specify any range of frames
The sample code to save any range of frames as still image files is as follows.
def save_frame_range(video_path, start_frame, stop_frame, step_frame,
dir_path, basename, ext='jpg'):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return
os.makedirs(dir_path, exist_ok=True)
base_path = os.path.join(dir_path, basename)
digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))
for n in range(start_frame, stop_frame, step_frame):
cap.set(cv2.CAP_PROP_POS_FRAMES, n)
ret, frame = cap.read()
if ret:
cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
else:
return
save_frame_range('data/temp/sample_video.mp4',
200, 300, 10,
'data/temp/result_range', 'sample_video_img')
You can also set step_frame
to, for example, save every 100 frames as a still image, etc. The start_frame
, stop_frame
and step_frame
are passed directly to range()
.
Specify by time in seconds
The previous examples are specified by frame number. If you want to specify the time in seconds, you can calculate it based on FPS (Frames Per Second).
Since FPS = frame / time[sec]
, time[sec] = frame / FPS
. The frame number must be an integer, so it must be rounded with round()
. Note that it does not exactly match the specified time.
Specify a single frame.
Here, the path to the output destination is specified as an argument. If you want to add a frame number or the actual number of seconds, see the next example.
def save_frame_sec(video_path, sec, result_path):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return
os.makedirs(os.path.dirname(result_path), exist_ok=True)
fps = cap.get(cv2.CAP_PROP_FPS)
cap.set(cv2.CAP_PROP_POS_FRAMES, round(fps * sec))
ret, frame = cap.read()
if ret:
cv2.imwrite(result_path, frame)
save_frame_sec('data/temp/sample_video.mp4', 10, 'data/temp/result_10sec.jpg')
Specify a range.
Since only integer int
can be used for range()
, use a while
statement so that the number of seconds can be specified as a floating point number float
.
def save_frame_range_sec(video_path, start_sec, stop_sec, step_sec,
dir_path, basename, ext='jpg'):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return
os.makedirs(dir_path, exist_ok=True)
base_path = os.path.join(dir_path, basename)
digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))
fps = cap.get(cv2.CAP_PROP_FPS)
fps_inv = 1 / fps
sec = start_sec
while sec < stop_sec:
n = round(fps * sec)
cap.set(cv2.CAP_PROP_POS_FRAMES, n)
ret, frame = cap.read()
if ret:
cv2.imwrite(
'{}_{}_{:.2f}.{}'.format(
base_path, str(n).zfill(digit), n * fps_inv, ext
),
frame
)
else:
return
sec += step_sec
save_frame_range_sec('data/temp/sample_video.mp4',
2, 10, 2,
'data/temp/result_range_sec', 'sample_video_img')
The output file name includes the frame number and the corresponding number of seconds. Note that the number of seconds is only an approximation. In this example, the output is to two decimal places.
Save by pressing the key while playing the video
The sample code to save the frame as a still image file at the timing when the keyboard is pressed while playing the video is as follows.
The movie will be played repeatedly, and pressing q
on the keyboard will end the movie, and pressing c
will save a still image. If delay
passed to waitKey()
is set to 0, the frame will advance every time a key other than q
or c
is pressed.
import cv2
import os
def save_frame_play(video_path, dir_path, basename, ext='jpg', delay=1, window_name='frame'):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return
os.makedirs(dir_path, exist_ok=True)
base_path = os.path.join(dir_path, basename)
digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))
n = 0
while True:
ret, frame = cap.read()
if ret:
cv2.imshow(window_name, frame)
key = cv2.waitKey(delay) & 0xFF
if key == ord('c'):
cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
elif key == ord('q'):
break
n += 1
else:
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
n = 0
cv2.destroyWindow(window_name)
save_frame_play('data/temp/sample_video.mp4', 'data/temp', 'sample_video_cap', delay=0)
See the following article for details about playing videos. The images read with read()
are just displayed with imshow()
.