一、目的

兵马未动、粮草先行。

cv领域,在设计、训练你的模型之前,最重要的事情莫过于处理数据了。

很多时候,我们获取到的原始数据并非别人整理好的图片,而是一段一段的视频;这个时候,就需要我们自己抽帧、过滤、然后标注,才能制作一批可用于训练的数据集。

其中,视频抽帧又是第一步。本文的目的,就是提供一个视频抽帧的脚本,可以同时处理多个视频,并可视化抽帧过程,同时还支持对每个视频设置是否旋转。

之所以加上旋转的功能,是由于有些时候我们拿到的视频画面不是正常的,而是顺时针、逆时针旋转了90°,或者180°,这对我们后续的数据处理带来了不便。

在此,提供我经常使用的视频抽帧方法,供大家参考。

二、主要特点及用法

主要特点:

  • 可批量处理多个视频文件夹中的视频;
  • 每个视频新建一个文件夹,保存该视频的帧;
  • 每个视频可以手动设置是否旋转以及旋转方式;
  • 可视化抽帧过程;

用法:

需要设置四个参数:if_rota 、paths、img_save_path、freq

分别指定:是否手动设置旋转、视频文件夹路径(类型为list,每个元素为一个视频文件夹路径)、所有视频抽帧后的图片保存路径根目录、抽帧频率(帧/s).

三、代码

# -*- coding: utf-8 -*-
"""

从文件夹读取视频列表,并逐个抽帧,保存到相应文件夹(每个视频一个文件夹,抽帧频率:1fps)

"""

import cv2
import os


# 顺时针旋转90度
def RotateClockWise90(img):
    trans_img = cv2.transpose(img)
    new_img = cv2.flip(trans_img, 1)
    return new_img


# 逆时针旋转90度
def RotateAntiClockWise90(img):
    trans_img = cv2.transpose(img)
    new_img = cv2.flip(trans_img, 0)
    return new_img


def list_dir(path, list_name):  # 传入存储的list
    for file in os.listdir(path):
        file_path = os.path.join(path, file)
        if os.path.isdir(file_path):
            list_dir(file_path, list_name)
        else:
            list_name.append(file_path)


def get_frames(video_path, save_path, frames_per_second, if_rota=False):
    cap = cv2.VideoCapture(video_path)
    cv2.namedWindow("Capture")

    fps = cap.get(5)  # 获取视频帧率
    freq = fps / frames_per_second
    total_frames = cap.get(7)
    digit_num = len(str(int(total_frames)))

    rotate_flag = 'n'
    while cap.isOpened():

        ok, frame = cap.read()
        if not ok:
            break

        frame_index = cap.get(1)  # 帧序
        h, w, _ = frame.shape

        cv2.imshow("Capture", cv2.resize(frame, (w // 2, h // 2)))

        if if_rota:
            if int(frame_index) == 1:
                if cv2.waitKey(0) & 0xFF == ord('n'):
                    print('不旋转')
                    pass
                elif cv2.waitKey(0) & 0xFF == ord('a'):
                    rotate_flag = 'a'
                    print('逆时针旋转90度')
                elif cv2.waitKey(0) & 0xFF == ord('c'):
                    rotate_flag = 'c'
                    print('顺时针旋转90度')
            if rotate_flag == 'n':
                pass
            elif rotate_flag == 'a':
                frame = RotateAntiClockWise90(frame)
            elif rotate_flag == 'c':
                frame = RotateClockWise90(frame)

        if frame_index % int(freq) != 0:
            continue
        else:
            # 保存当前帧
            img_name = video_path.split('/')[-1].split('.')[0] + '_frame_' + \
                       str(int(frame_index)).zfill(digit_num) + '.jpg'
            #            frame = cv2.resize(frame, (1080, 1920))
            print("saving frame:", img_name)
            cv2.imwrite(os.path.join(save_path, img_name), frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cap.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':

    if_rota = False
    paths = ["video_root_path1", "video_root_path2"]
    img_save_path = 'path_to_save_imgs'  # 本批次图片保存路径
    freq = 1  # 抽帧频率:freq张/s

    if not os.path.isdir(img_save_path):
        os.mkdir(img_save_path)

    list_videos = []
    # 获取路径中的视频列表
    for path in paths:
        path = path.replace('\\', '/')
        list_dir(path, list_videos)
    list_videos = [v for v in list_videos if v[-4:] in ['.mp4', '.avi']]

    #    # 针对每个视频,分别按照指定频率抽取帧,保存在文件夹中
    for video_path in list_videos:
        video_path = video_path.replace('\\', '/')
        print('正在处理:', video_path.split('\\')[-1].split('.')[0], '...')
        if if_rota:
            print('不旋转:单击“n”;逆时针旋转90度:双击“a”;顺时针旋转90度:三击“c”')
            save_path = os.path.join(img_save_path, video_path.split('\\')[-1].split('.')[0])

        if not os.path.isdir(save_path):
            os.mkdir(save_path)

        # 调用视频抽帧方法
        get_frames(video_path, save_path, frames_per_second=freq, if_rota=if_rota)