【问题标题】:How to crop GIFs with a script?如何使用脚本裁剪 GIF?
【发布时间】:2019-06-07 22:45:03
【问题描述】:

有没有像这个页面一样在 python 中裁剪 gif 的脚本:https://www.iloveimg.com/crop-image

前段时间,我找到了Image Cropping using Python,但问题是你需要用光标绘制矩形。

我需要一个像 https://www.iloveimg.com/crop-image 这样的 GUI,它有一个可以移动到任何我想要的地方的矩形:

看到https://www.iloveimg.com/crop-image 将 GIF 裁剪成一个新的动画。而Image Cropping using Python 只裁剪 GIF 的第一帧。

我可以使用的一些模块是:

  • Tkinter(最好)
  • Pygame
  • 枕头/PIL
  • 其他

【问题讨论】:

    标签: python python-3.x user-interface crop gif


    【解决方案1】:

    在阅读了一些教程后,我想出了这个解决方案:

    import numpy as np
    import matplotlib.pyplot as plt
    
    from PIL import Image, ImageSequence
    from matplotlib.widgets import RectangleSelector
    
    class ImageCutter:
        def __init__(self, file):
            self.file = file
            self.img = Image.open(file)
            self.frames = [np.array(frame.copy().convert("RGB"))
                            for frame in ImageSequence.Iterator(self.img)]
    
            self.pos = np.array([0,0,0,0])
    
    
        def crop(self):
            self.pos = self.pos.astype(int)
            self.cropped_imgs =  [frame[self.pos[1]:self.pos[3], self.pos[0]:self.pos[2]]
                    for frame in self.frames]
            self.save()
    
        def save(self):
            self.imgs_pil = [Image.fromarray(np.uint8(img))
                             for img in self.cropped_imgs]
            self.imgs_pil[0].save(self.file+"_cropped.gif",
                         save_all=True,
                         append_images=self.imgs_pil[1:],
                         duration=16,
                         loop=0)
    
    
    data = ImageCutter("final.gif")
    
    fig, ax = plt.subplots(1)
    ax.axis("off")
    
    plt.imshow(data.frames[0])
    
    def onselect(eclick, erelease):
        "eclick and erelease are matplotlib events at press and release."
        data.pos = np.array([eclick.xdata, eclick.ydata, erelease.xdata, erelease.ydata])
    
    def onrelease(event):
        data.crop()
    
    cid = fig.canvas.mpl_connect('button_release_event', onrelease)
    RS = RectangleSelector(ax, onselect, drawtype='box')
    

    您将文件名放在ImageCutter 实例中,它将绘制第一帧,让您用鼠标选择一个框,该框定义了要裁剪的区域。定义区域后,单击图像中的某个位置,程序会将裁剪后的 gif 保存在您的工作文件夹中。

    您可以使用自己的Rectangle代替小部件

    【讨论】:

    • 有没有办法让 RectangleSelector 具有相同的像素?例如,窗口应该有一个 500x600 像素的矩形来裁剪图像,而不是使用鼠标。
    【解决方案2】:

    由于这仍然是谷歌在 python 中裁剪 gif 的热门话题,因此可能值得更新。

    如果我们像这样概括上面的方法,那么用法就更熟悉了:

    import io
    from dataclasses import dataclass
    from pathlib import Path
    from typing import List, Tuple, Union
    
    import numpy as np
    from PIL import Image, ImageSequence
    
    Left, Upper, Right, Lower = int, int, int, int
    Box = Tuple[Left, Upper, Right, Lower]
    Frames = List[np.ndarray]
    ImageArray = List[Image.Image]
    File = Union[str, bytes, Path, io.BytesIO]
    
    
    @dataclass
    class MultiFrameImage:
        fp: File
    
        @property
        def im(self):
            return Image.open(self.fp)
    
        @property
        def frames(self) -> Frames:
            return [
                np.array(frame.copy().convert("RGB"))
                for frame in ImageSequence.Iterator(self.im)
            ]
    
        def crop_frames(self, box: Box) -> List[np.ndarray]:
            left, upper, right, lower = box
            return [frame[upper:lower, left:right] for frame in self.frames]
    
        def image_array_from_frames(self, frames: Frames) -> ImageArray:
            return [Image.fromarray(np.uint8(frame)) for frame in frames]
    
        def crop_to_buffer(self, box: Box, **kwargs) -> io.BytesIO:
            cropped_frames = self.crop_frames(box)
            cropped_images = self.image_array_from_frames(cropped_frames)
            buffer = io.BytesIO()
            cropped_images[0].save(
                buffer,
                save_all=True,
                format="GIF",
                append_images=cropped_images[1:],
                duration=16,
                loop=0,
                **kwargs
            )
            return buffer
    
        def crop(self, box: Box, **kwargs) -> Image.Image:
            return Image.open(self.crop_to_buffer(box, **kwargs))
    
    

    这里的crop 方法将返回一个PIL 图像,就像Image.crop 一样。

    用法如下:

    image = MultiFrameImage(io.BytesIO(avatar_bytes))
    buffer = image.crop_to_buffer((left, upper, right, lower))
    
    # or if you need the image instead of the buffer
    cropped_image = image.crop((left, upper, right, lower))
    

    如果你赶时间,忽略这部分并复制上面的代码

    另一种选择(只是为了好玩,我提出的第一个可能更干净)是从 PIL 猴子修补 open 函数并递归我们的 crop 方法,如下所示:

    import io
    from dataclasses import dataclass
    from pathlib import Path
    from typing import List, Tuple, Union, cast
    
    import numpy as np
    from PIL import Image, ImageSequence
    
    Left, Upper, Right, Lower = int, int, int, int
    Box = Tuple[Left, Upper, Right, Lower]
    Frames = List[np.ndarray]
    ImageArray = List[Image.Image]
    File = Union[str, bytes, Path, io.BytesIO]
    
    
    @dataclass
    class MultiFrameImage:
        fp: File
    
        @property
        def im(self):
            return Image.open(self.fp)
    
        @property
        def frames(self) -> Frames:
            return [
                np.array(frame.copy().convert("RGB"))
                for frame in ImageSequence.Iterator(self.im)
            ]
    
        def crop_frames(self, box: Box) -> List[np.ndarray]:
            left, upper, right, lower = box
            return [frame[upper:lower, left:right] for frame in self.frames]
    
        def image_array_from_frames(self, frames: Frames) -> ImageArray:
            return [Image.fromarray(np.uint8(frame)) for frame in frames]
    
        def crop_to_buffer(self, box: Box, **kwargs) -> io.BytesIO:
            cropped_frames = self.crop_frames(box)
            cropped_images = self.image_array_from_frames(cropped_frames)
            buffer = io.BytesIO()
            cropped_images[0].save(
                buffer,
                save_all=True,
                format="GIF",
                append_images=cropped_images[1:],
                duration=16,
                loop=0,
                **kwargs
            )
            return buffer
    
        def crop(self, box: Box, **kwargs) -> "MultiFrameImage":
            return open_multiframe_image(self.crop_to_buffer(box, **kwargs))
    
    
    class MonkeyPatchedMultiFrameImage(Image.Image, MultiFrameImage):
        pass
    
    
    def open_multiframe_image(fp):
        multi_frame_im = MultiFrameImage(fp)
        im = multi_frame_im.im
        setattr(im, "frames", multi_frame_im.frames)
        setattr(im, "crop_frames", multi_frame_im.crop_frames)
        setattr(im, "image_array_from_frames", multi_frame_im.image_array_from_frames)
        setattr(im, "crop_to_buffer", multi_frame_im.crop_to_buffer)
        setattr(im, "crop", multi_frame_im.crop)
        return cast(MonkeyPatchedMultiFrameImage, im)
    

    这给人一种我们实际上是在使用 PIL Image 类的错觉。这很危险,除非您打算同时覆盖所有其他 Image 方法。对于大多数用例,我给出的第一个代码块就足够了

    【讨论】:

      猜你喜欢
      • 2012-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-24
      • 1970-01-01
      • 2011-08-14
      • 2021-01-21
      • 1970-01-01
      相关资源
      最近更新 更多