习惯了C++语言的OpenCV突然用Python语言OpenCV还是感觉有点不适应,但是慢慢在写的过程中,觉得Python语言的风格也挺美的(但自己的写的还是很丑…),晚上回宿舍的剩余时间,记录一下最近用PythonOpenCV实现的视频抽帧小工具。

实现代码

2022/9/26更新:将while循环中连续帧读取再根据条件保存关键帧的代码改为通过OpenCVVideoCapture::set()函数传入cv2.CAP_PROP_POS_FRAMES参数和帧间隔实现每次循环读取直接跳到关键帧并保存(其实就是快进功能)

参数解读:
cv2.CAP_PROP_POS_FRAMES: 接下来要解码/捕获的帧(基于0的索引)

代码就是逻辑关系,简单记录一下:

import cv2
import os
import numpy as np


# video所在的根目录
video_base_path = "F:/my_Video/RowVideo"
# processVideo所在根目录
save_base_path = "F:/my_Video/ProcessVideo"
# 保存抽取的图像帧时所在的根目录
JPEGImage_base_path = "./JPEGImage"

# ******************指定提取图像帧的模式********************* #
# model = 0 以帧间隔提取, model = 1以秒间隔提取
extract_frame_model = 0

def form_single_channel_video(video_name: str, save_name: str, channel: int):
    """
    提取RGB中某通道形成一个单通道图像
    :param video_name: 输入根目录文件夹下video_name,eg. test.mp4
    :param save_name: 输入保存视频的name,eg. protest.mp4
    :param channel: 需要的提取的channel -->(0, 1, 2)
    :return: None
    """
    video_path = os.path.join(video_base_path, video_name)
    save_path = os.path.join(save_base_path, save_name)

    inputVideo = cv2.VideoCapture(video_path)
    if not inputVideo.isOpened():
        raise IOError("current video path is not exist!")

    ex = int(inputVideo.get(cv2.CAP_PROP_FOURCC))
    Size = (int(inputVideo.get(cv2.CAP_PROP_FRAME_WIDTH)), int(inputVideo.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    fps = int(inputVideo.get(cv2.CAP_PROP_FPS))
    total_frame = inputVideo.get(cv2.CAP_PROP_FRAME_COUNT)
    # 写入视频的文件路径 opencv的Size参数是(width,height)
    outputVideo = cv2.VideoWriter(save_path, ex, fps, Size, True)
    if not outputVideo.isOpened():
        raise IOError("no way to open the save path, please restart")

    print("input frame resolution: width=%d  height=%d, fps=%d, the total frame=%d"
          % (Size[0], Size[1], fps, total_frame))

    while inputVideo.isOpened():
        ret, frame = inputVideo.read()
        if not ret:
            break

        (B, G, R) = cv2.split(frame)
        zeros = np.zeros(np.shape(frame)[:2], dtype="uint8")
        output = np.zeros(np.shape(frame), dtype="uint8")
        if channel == 0:
            output = cv2.merge([B, zeros, zeros])
        elif channel == 1:
            output = cv2.merge([zeros, G, zeros])
        else:
            output = cv2.merge([zeros, zeros, R])

        # 将帧写入视频
        outputVideo.write(output)

        cv2.imshow("frame", frame)
        cv2.imshow("output", output)
        key = cv2.waitKey(1)
        if key == ord('q'):
            break

    print("clearing up!")
    cv2.destroyAllWindows()
    inputVideo.release()
    outputVideo.release()


def plus_shot_video_frame(video_name: str, image_save_path: str = None, frequency: int = 1, second: int = 1):
    """
    根据mode实现的不同形式的抽帧方式,当mode==0,按指定的帧间隔抽帧,mode==1,按指定的秒间隔进行抽帧
    :param video_name: 输入根目录文件夹下的video name,eg. test.mp4
    :param image_save_path: 当为None时默认在video所在文件中创建一个同名一个与video同名的文件中用于保存图像帧,否则输入绝对路径
    :param frequency: 当mode为0时,该参数启用,表示设置的帧间隔
    :param second: 当mode为1时,该参数启用,表示设置的秒间隔
    :return: None
    """
    actual_video_name = video_name.split('.')[0]  # 提取真正的文件夹名称
    if image_save_path is None:
        image_save_path = os.path.join(video_base_path, actual_video_name)

    if not os.path.exists(image_save_path):
        os.makedirs(image_save_path)

    video_path = os.path.join(video_base_path, video_name)
    inputVideo = cv2.VideoCapture(video_path)
    if not inputVideo.isOpened():
        raise IOError("current video path is not exist!")

    Size = (int(inputVideo.get(cv2.CAP_PROP_FRAME_WIDTH)), int(inputVideo.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    fps = int(inputVideo.get(cv2.CAP_PROP_FPS))
    total_frame = inputVideo.get(cv2.CAP_PROP_FRAME_COUNT)
    print("input frame resolution: width=%d  height=%d, fps=%d, the total frame=%d"
          % (Size[0], Size[1], fps, total_frame))

    num_frame = 0  # 帧计数
    if extract_frame_model == 0:
        frame_gap = frequency
    else:
        frame_gap = int(second * fps)

    start_time = time.time()                # 时间测算
    # 循环读取每一帧,根据model来确定抽帧模式
    while True:
        ret_success, frame = inputVideo.read()
        if not ret_success:
            print("extract frame from video fail, current frame=%d" % num_frame)
            break
            
        # frame = cv2.resize(frame, dsize=None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR)
        image_name = actual_video_name + str(round(num_frame / frame_gap)) + '.jpg'
        cv2.imwrite(os.path.join(image_save_path, image_name), frame)
        print("saved " + str(round(num_frame / frame_gap)) + '.jpg')

        num_frame += frame_gap  # 更新帧计数
        inputVideo.set(cv2.CAP_PROP_POS_FRAMES, num_frame)      # 隔帧读取

    end_time = time.time()
    print("time consuming: {:.2f}second".format(end_time - start_time))

    print("cleaning up!")
    inputVideo.release()



if __name__ == "__main__":
    video_name = 'shotTestVideo.MP4'
    shot_video_frame(video_name, frequency=20)

实现结果

终端打印

opencv获取视频的所有帧数 opencv 抽帧_opencv获取视频的所有帧数

图片文件夹

opencv获取视频的所有帧数 opencv 抽帧_ide_02

总结

发现对于视频流的解码过程几乎不了解,对于倍数播放,当视频分辨率过大(4K)时,几乎没有效果,要想达到potplayer的效果还有很长的一段路要走。