コード例 #1
0
def test_get_aspect_ratio(test_video_file):
    """ Test get_aspect_ratio function. """
    expected_value = 1.0
    epsilon = 0.01

    video_manager = VideoManager([test_video_file])
    assert abs(get_aspect_ratio(video_manager) - expected_value) < epsilon

    # Ensure non-open VideoCapture returns 1.0.
    blank_cap = cv2.VideoCapture()
    assert abs(get_aspect_ratio(blank_cap) - expected_value) < epsilon
コード例 #2
0
def save_images(scene_list, video_manager, num_images=3, frame_margin=1,
                image_extension='jpg', encoder_param=95,
                image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER',
                output_dir=None, downscale_factor=1, show_progress=False,
                scale=None, height=None, width=None):
    # type: (List[Tuple[FrameTimecode, FrameTimecode]], VideoManager,
    #        Optional[int], Optional[int], Optional[str], Optional[int],
    #        Optional[str], Optional[str], Optional[int], Optional[bool],
    #        Optional[float], Optional[int], Optional[int])
    #       -> Dict[List[str]]
    """ Saves a set number of images from each scene, given a list of scenes
    and the associated video/frame source.

    Arguments:
        scene_list: A list of scenes (pairs of FrameTimecode objects) returned
            from calling a SceneManager's detect_scenes() method.
        video_manager: A VideoManager object corresponding to the scene list.
            Note that the video will be closed/re-opened and seeked through.
        num_images: Number of images to generate for each scene.  Minimum is 1.
        frame_margin: Number of frames to pad each scene around the beginning
            and end (e.g. moves the first/last image into the scene by N frames).
            Can set to 0, but will result in some video files failing to extract
            the very last frame.
        image_extension: Type of image to save (must be one of 'jpg', 'png', or 'webp').
        encoder_param: Quality/compression efficiency, based on type of image:
            'jpg' / 'webp':  Quality 0-100, higher is better quality.  100 is lossless for webp.
            'png': Compression from 1-9, where 9 achieves best filesize but is slower to encode.
        image_name_template: Template to use when creating the images on disk. Can
            use the macros $VIDEO_NAME, $SCENE_NUMBER, and $IMAGE_NUMBER. The image
            extension is applied automatically as per the argument image_extension.
        output_dir: Directory to output the images into.  If not set, the output
            is created in the working directory.
        downscale_factor: Integer factor to downscale images by.  No filtering
            is currently done, only downsampling (thus requiring an integer).
        show_progress: If True, shows a progress bar if tqdm is installed.
        scale: Optional factor by which to rescale saved images.A scaling factor of 1 would
            not result in rescaling. A value <1 results in a smaller saved image, while a
            value >1 results in an image larger than the original. This value is ignored if
            either the height or width values are specified.
        height: Optional value for the height of the saved images. Specifying both the height
            and width will resize images to an exact size, regardless of aspect ratio.
            Specifying only height will rescale the image to that number of pixels in height
            while preserving the aspect ratio.
        width: Optional value for the width of the saved images. Specifying both the width
            and height will resize images to an exact size, regardless of aspect ratio.
            Specifying only width will rescale the image to that number of pixels wide
            while preserving the aspect ratio.


    Returns:
        Dict[List[str]]: Dictionary of the format { scene_num : [image_paths] },
        where scene_num is the number of the scene in scene_list (starting from 1),
        and image_paths is a list of the paths to the newly saved/created images.

    Raises:
        ValueError: Raised if any arguments are invalid or out of range (e.g.
        if num_images is negative).
    """

    if not scene_list:
        return {}
    if num_images <= 0 or frame_margin < 0:
        raise ValueError()

    # TODO: Validate that encoder_param is within the proper range.
    # Should be between 0 and 100 (inclusive) for jpg/webp, and 1-9 for png.
    imwrite_param = [get_cv2_imwrite_params()[image_extension],
                     encoder_param] if encoder_param is not None else []

    video_name = video_manager.get_video_name()

    # Reset video manager and downscale factor.
    video_manager.release()
    video_manager.reset()
    video_manager.set_downscale_factor(downscale_factor)
    video_manager.start()

    # Setup flags and init progress bar if available.
    completed = True
    logging.info('Generating output images (%d per scene)...', num_images)
    progress_bar = None
    if show_progress and tqdm:
        progress_bar = tqdm(
            total=len(scene_list) * num_images,
            unit='images',
            dynamic_ncols=True)

    filename_template = Template(image_name_template)

    scene_num_format = '%0'
    scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'
    image_num_format = '%0'
    image_num_format += str(math.floor(math.log(num_images, 10)) + 2) + 'd'

    timecode_list = dict()

    fps = scene_list[0][0].framerate

    timecode_list = [
        [
            FrameTimecode(int(f), fps=fps) for f in [
                # middle frames
                a[len(a)//2] if (0 < j < num_images-1) or num_images == 1

                # first frame
                else min(a[0] + frame_margin, a[-1]) if j == 0

                # last frame
                else max(a[-1] - frame_margin, a[0])

                # for each evenly-split array of frames in the scene list
                for j, a in enumerate(np.array_split(r, num_images))
            ]
        ]
        for i, r in enumerate([
            # pad ranges to number of images
            r
            if 1+r[-1]-r[0] >= num_images
            else list(r) + [r[-1]] * (num_images - len(r))
            # create range of frames in scene
            for r in (
                range(start.get_frames(), end.get_frames())
                # for each scene in scene list
                for start, end in scene_list
                )
        ])
    ]

    image_filenames = {i: [] for i in range(len(timecode_list))}
    aspect_ratio = get_aspect_ratio(video_manager)
    if abs(aspect_ratio - 1.0) < 0.01:
        aspect_ratio = None

    for i, scene_timecodes in enumerate(timecode_list):
        for j, image_timecode in enumerate(scene_timecodes):
            video_manager.seek(image_timecode)
            ret_val, frame_im = video_manager.read()
            if ret_val:
                file_path = '%s.%s' % (
                    filename_template.safe_substitute(
                        VIDEO_NAME=video_name,
                        SCENE_NUMBER=scene_num_format % (i + 1),
                        IMAGE_NUMBER=image_num_format % (j + 1),
                        FRAME_NUMBER=image_timecode.get_frames()),
                    image_extension)
                image_filenames[i].append(file_path)
                if aspect_ratio is not None:
                    frame_im = cv2.resize(
                        frame_im, (0, 0), fx=aspect_ratio, fy=1.0,
                        interpolation=cv2.INTER_CUBIC)
                
                # Get frame dimensions prior to resizing or scaling
                frame_height = frame_im.shape[0]
                frame_width = frame_im.shape[1]

                # Figure out what kind of resizing needs to be done
                if height and width:
                    frame_im = cv2.resize(
                        frame_im, (width, height), interpolation=cv2.INTER_CUBIC)
                elif height and not width:
                    factor = height / float(frame_height)
                    width = int(factor * frame_width)
                    frame_im = cv2.resize(
                        frame_im, (width, height), interpolation=cv2.INTER_CUBIC)
                elif width and not height:
                    factor = width / float(frame_width)
                    height = int(factor * frame_height)
                    frame_im = cv2.resize(
                        frame_im, (width, height), interpolation=cv2.INTER_CUBIC)
                elif scale:
                    frame_im = cv2.resize(
                        frame_im, (0, 0), fx=scale, fy=scale,
                        interpolation=cv2.INTER_CUBIC)

                cv2.imwrite(
                    get_and_create_path(file_path, output_dir),
                    frame_im, imwrite_param)
            else:
                completed = False
                break
            if progress_bar:
                progress_bar.update(1)

    if not completed:
        logging.error('Could not generate all output images.')

    return image_filenames