利用OpenCV-Python实现视频拆帧(组帧),也可以用于组合实现视频格式的转换

文章目录

  • 前言
  • 一、脚本运行依赖
  • 二、参数解释与必需参数
    • 1.视频拆帧
    • 2.视频组帧
  • 三、源代码
  • 源码下载地址


前言

前一段时间写了一个简单的视频拆帧脚本,这次添加了一个将图片转为视频的组件,实现一个脚本内通用的视频拆帧(组帧)。


一、脚本运行依赖

OpenCV-Python >= 4.5.5

二、参数解释与必需参数

1.视频拆帧

定义视频拆帧的函数video_to_image()中各参数含义如下:

video_path: ROOT -> 视频路径(或视频所在文件目录)
step: int=None -> 间隔帧率,默认不间隔取帧
fps: int=25 -> 视频帧率,默认25帧
start: str=None -> 开始时间(00:00:00),默认视频开始时间
end: str=None -> 结束时间(00:00:00),默认视频结束时间
use_file_name: bool=False -> 是否使用视频文件名作为命名规范
img_format: str=‘jpg’ -> 保存的图片格式
save_path: ROOT=‘./images’ -> 保存的文件路径

调用时,必需传入video_path参数,该参数可以是某一个视频文件,也可以是包含一些视频的文件夹。

2.视频组帧

定义的视频组帧方法Video()中各参数含义如下:

images: ROOT -> 图片所在目录
video_name: str=None -> 保存的视频名
video_format: str=‘mp4’ -> 保存的视频格式
image_format: str=‘jpg’ -> 选取的图片格式
size: list=None -> 合并图片的resize大小
fps: int=25 -> 合成视频的帧率
output: ROOT=‘./output’ -> 视频输出目录

实例化该方法后,通过调用该方法中的make_video()方法,实现图片到视频的转化。
与拆帧函数一样,实例化时必需传入的参数为images,该参数表示图片所在目录。


三、源代码

'''
Descripttion: 
version: 1.0
Author: UniFind
Date: 2022-07-30 11:24:17
LastEditors: UniDome
LastEditTime: 2022-07-30 16:33:12
'''

__author__ = 'UniDome'
__version__ = '1.0.1'

import cv2
import os, sys
from pathlib import Path
import glob
from tqdm import tqdm

FILE = Path(__file__).resolve() # 当前文件所在绝对路径
ROOT = FILE.parents[0]

if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # 当前文件所在相对路径

IMG_FORMATS = ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'dng', 'webp', 'mpo']
VID_FORMATS = ['mov', 'avi', 'mp4', 'mpg', 'mpeg', 'm4v', 'wmv', 'mkv']
VID_MKFORMATS = ['avi', 'mp4']
VID = ['.' + x for x in VID_FORMATS]

class Frame(object):
	'''
		该方法重新定义了一个迭代器,视频地址、图片、该图片的内部编号、图片命名
	'''
    def __init__(
        self, 
        video_path:ROOT, 
        step:int=None, 
        fps:int=25, 
        start:int=None, 
        end:int=None, 
        use_file_name:bool=False,
        img_format:str='jpg'
        ) -> None:
		# 参数含义同video_to_image()
        self.step = step if step is not None else 1
        self.fps = fps 
        self.start = start
        self.end = end
        self.use_file_name = use_file_name
        self.img_format = img_format
        
        p = str(Path(video_path).resolve())
        if os.path.isdir(p):
            dir_file = os.listdir(video_path)
            files_v = [pa for pa in dir_file if os.path.splitext(pa)[-1] in VID]
            files = [os.path.join(p, file) for file in files_v]
        elif os.path.isfile(p):
            assert os.path.splitext(p)[-1] in [ x for x in VID], f'Supported formats are:\nimages: {VID_FORMATS}'
            files = [p]
        else:
            raise Exception(f'ERROR: {p} does not exist')

        self.nv = len(files)
        self.count = 0
        self.videos = files
        self.pps = 0	# 当前帧所处位号
        self.frame = 0

        assert self.nv > 0, f'No images or videos found in {p}. ' \
                            f'Supported formats are:\nvideos: {VID_FORMATS}'
        assert self.img_format in IMG_FORMATS, f'Supported formats are:\nimages: {IMG_FORMATS}'
        assert self.step > 0, f'FPS {self.step} is illegal'
        assert self.fps > 0, f'FPS {self.fps} is illegal'

        print(f'INFORMATION\ntotal find {self.nv} files:\n{self.videos}') 

        self.new_video(self.videos[0])

        if self.start is not None:
            self.start = self.time_str_to_sec(self.start) * self.fps
        else:
            self.start = 0

        if self.end is not None:
            self.end = self.time_str_to_sec(self.end) * self.fps
        else:
            self.end = self.frames

        if self.use_file_name:
            self.file_name = Path(self.videos[0]).stem + '_'
        else:
            self.file_name = ''

        print(f'make images\t{self.start} --> {self.end}')     

    def __iter__(self):
        return self

    def __next__(self):
        if self.count == self.nv:
            raise StopIteration
        path = self.videos[self.count]
        ret, img = self.cap.read()
        if not ret:
            self.check_and_get_new()
            ret, img = self.cap.read()

        if self.frames < self.end:
            print('Video duration is too short')
            self.check_and_get_new()
            ret, img = self.cap.read()
        
        while self.pps < self.start:
            print(f'Loading Video File {path}', end='\r')
            self.pps += 1
            ret, img = self.cap.read()
            if not ret:
                self.check_and_get_new()
                ret, img = self.cap.read()
        if self.pps == self.start:
            print('\nLoad Complete\nSaving Images\nPlease Waiting···')

        while self.pps % self.step != 0:
            self.pps += 1
            ret, img = self.cap.read()
            if not ret:
                self.check_and_get_new()
                ret, img = self.cap.read()

        if self.pps >= self.start and self.pps < self.end:
            self.frame += 1
            self.pps += 1
            img_name = '{:0>8d}.{}'.format(self.frame - 1, self.img_format)
            img_name = self.file_name + img_name
            print(f'video {self.count + 1}/{self.nv} ({self.frame}/{(self.end - self.start)/self.fps}) {img_name}: ', end='')
            return path, img, self.frame, img_name
        else:
            print()
            return self.check_next()

    def check_next(self):
    	# 获取下一个视频中符合要求的第一帧图片
        self.check_and_get_new()
        path = self.videos[self.count]
        ret, img = self.cap.read()
        if not ret:
            self.check_and_get_new()
            ret, img = self.cap.read()

        if self.frames < self.end:
            print('Video duration is too short')
            self.check_and_get_new()
            ret, img = self.cap.read()
        
        while self.pps < self.start:
            print(f'Loading Video File {path}', end='\r')
            self.pps += 1
            ret, img = self.cap.read()
            if not ret:
                self.check_and_get_new()
                ret, img = self.cap.read()
        if self.pps == self.start:
            print('\nLoad Complete\nSaving Images\nPlease Waiting···')

        while self.pps % self.step != 0:
            self.pps += 1
            ret, img = self.cap.read()
            if not ret:
                self.check_and_get_new()
                ret, img = self.cap.read()

        if self.pps >= self.start and self.pps < self.end:
            self.frame += 1
            self.pps += 1
            img_name = '{:0>8d}.{}'.format(self.frame - 1, self.img_format)
            img_name = self.file_name + img_name
            print(f'video {self.count + 1}/{self.nv} ({self.frame}/{(self.end - self.start)/self.fps}) {img_name}: ', end='')
            return path, img, self.frame, img_name
        else:
            return self.check_next()

    def check_and_get_new(self):
    	# 检查是否已经读取完成,如果队列中还有视频,则开始读取新的视频
        self.count += 1
        self.cap.release()
        if self.count == self.nv:
            raise StopIteration
        else:
            path = self.videos[self.count]
            if self.use_file_name:
                self.file_name = Path(path).stem + '_'
            self.new_video(path)

    def time_str_to_sec(self, time):
    	# 转换时间格式
        time = time.replace(':',':')
        time_list = time.split(':')
        if len(time_list) == 1:
            return int(time_list[0])
        elif len(time_list) == 2:
            return int(time_list[0]) * 60 + int(time_list[1])
        elif len(time_list) == 3:
            return int(time_list[0]) * 3600 + int(time_list[1]) * 60 + int(time_list[2])

    def new_video(self, path):
    	# 读取一个视频文件
        self.frame = 0
        self.cap = cv2.VideoCapture(path)
        self.frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.pps = 0
        print(f'\nOpen Video {path}')

    def __len__(self):
        return self.nv

class Video():

    def __init__(
        self, 
        images:ROOT,
        video_name:str=None, 
        video_format:str='mp4',
        image_format:str='jpg',
        size:list=None,
        fps:int=25,
        output:str='./output'
        ):

        '''
            images: 图片所在目录  
            video_name: 保存的视频名
            video_format: 保存的视频格式 
            image_format: 选取的图片格式
            size: 合并图片的resize大小
            fps: 合成视频的帧率
            output: 视频输出目录      
        '''

        print('Checking···\nINFORMATION')
        images = Path(images)
        size = size if size is not None else ['auto']

        assert os.path.isdir(images), 'Images path is invalid'
        # assert video_name is not None, 'Video name is None'
        assert video_format in VID_FORMATS, f'Supported video formats are:\nimages: {VID_MKFORMATS}'
        assert image_format in IMG_FORMATS, f'Supported images formats are:\nimages: {IMG_FORMATS}'

        if video_name is None:
            video_name=os.path.split(Path(images).resolve())[-1]

        imgs = os.listdir(images)
        imgs = [img for img in imgs if os.path.splitext(img)[-1]==('.' + image_format)]
        imgs.sort()
        self.imgs = [os.path.join(Path(images), img) for img in imgs]

        self.size = size
        self.fps = fps
        self.output = output

        fourcc = {'mp4':'mp4v', 'avi':'DIVX'}
        self.fourcc = fourcc[video_format]

        if os.path.splitext(video_name)[-1] == ('.' + video_format):
            self.video = os.path.join(Path(output), video_name)
        else:
            video_name += '.' + video_format
            self.video = os.path.join(Path(output), video_name)

        print(f'Images from {images}\tVideo name: {video_name}\nImage Format: {image_format}\tVideo Format: {video_format}\nImage Size: {size}\tFPS: {fps}')
        print(f'Video will Save to {self.video}')

    def make_video(self):
        if not os.path.exists(self.output):
            os.makedirs(self.output)
        fourcc = cv2.VideoWriter_fourcc(*self.fourcc)
        if self.size[0] == 'auto':
            img = self.imgs[0]
            self.size = cv2.imread(img).shape[::-1][1:]
        else:
            assert len(self.size)==2, 'Size of video is illegal'

        video = cv2.VideoWriter(self.video, fourcc, self.fps, self.size)
        print('pls waiting···')
        with tqdm(self.imgs,desc='making video',ncols=80,unit='img') as t:
            for item in t:
                img = cv2.imread(item)
                video.write(img)
        video.release()
        cv2.destroyAllWindows()
        print('video Done!\nsave to {}'.format(self.video))

def video_to_image(
    video_path:ROOT, 
    step:int=None, 
    fps:int=25, 
    start:int=None, 
    end:int=None, 
    use_file_name:bool=False,
    img_format:str='jpg',
    save_path:ROOT='./images'
    ):
    # 视频拆帧函数
    '''
        video_path: ROOT -> 视频路径(或视频所在文件目录)
        step: int -> 间隔帧率,默认不间隔取帧
        fps: int -> 视频帧率,默认25帧
        start: str -> 开始时间(00:00:00),默认视频开始时间
        end: str -> 结束时间(00:00:00),默认视频结束时间
        use_file_name: bool -> 是否使用视频文件名作为命名规范
        img_format: str -> 保存的图片格式
        save_path: ROOT -> 保存的文件路径
    '''
    
	# 实例化迭代器
    video  = Frame(video_path, step=step, fps=fps,start=start,end=end, use_file_name=use_file_name,img_format=img_format)

    for path, img, frame, name in video:
        dir_path = os.path.join(Path(save_path), Path(path).stem)
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)
        save_img_path = os.path.join(dir_path, name)
        cv2.imwrite(save_img_path, img)
        print(f'Save to {save_img_path}', end='\r')
    print('\nSave Done')

if __name__ == '__main__':
    video_path = './video/ground/ppelab.mp4'
    video_to_image(video_path, step=1)	# 视频拆帧
    
    images_path = './images/ppelabt'
    video = Video(images_path, video_format='avi')	# 实例化Video
    video.make_video() # 视频组帧
    # 测试代码时实现了将视频mp4格式转化为avi格式
    

源码下载地址

video_frame.py文件下载

你可能感兴趣的:(opencv,python,音视频)