Bantal - Mengubah ukuran GIF

Saya memiliki gif yang ingin saya ubah ukurannya dengan pillow agar ukurannya mengecil. Ukuran gif saat ini adalah 2MB.

Saya sedang mencoba

  1. ubah ukurannya sehingga tinggi/lebarnya lebih kecil

  2. menurunkan kualitasnya.

Dengan JPEG, potongan kode berikut biasanya cukup untuk membuat ukuran gambar besar berkurang secara drastis.

from PIL import Image

im = Image.open("my_picture.jpg")
im = im.resize((im.size[0] // 2, im.size[1] // 2), Image.ANTIALIAS)  # decreases width and height of the image
im.save("out.jpg", optimize=True, quality=85)  # decreases its quality

Namun, dengan GIF, sepertinya ini tidak berfungsi. Potongan kode berikut bahkan membuat out.gif lebih besar dari gif awal:

im = Image.open("my_gif.gif")
im.seek(im.tell() + 1)  # loads all frames
im.save("out.gif", save_all=True, optimize=True, quality=10)  # should decrease its quality

print(os.stat("my_gif.gif").st_size)  # 2096558 bytes / roughly 2MB
print(os.stat("out.gif").st_size)  # 7536404 bytes / roughly 7.5MB

Jika saya menambahkan baris berikut, maka hanya bingkai pertama GIF yang disimpan, bukan seluruh bingkainya.

im = im.resize((im.size[0] // 2, im.size[1] // 2), Image.ANTIALIAS)  # should decrease its size

Saya telah berpikir untuk memanggil resize() pada im.seek() atau im.tell() tetapi tidak satu pun dari metode ini yang mengembalikan objek Gambar, dan oleh karena itu saya tidak dapat memanggil resize() pada keluarannya.

Tahukah Anda bagaimana saya bisa menggunakan Pillow untuk memperkecil ukuran GIF saya sambil mempertahankan semua bingkainya?

[sunting] Solusi parsial:

Mengikuti tanggapan Beruang Tua, saya telah melakukan perubahan berikut:

  • Saya menggunakan skrip BigglesZX untuk mengekstrak semua bingkai. Penting untuk dicatat bahwa ini adalah skrip Python 2, dan proyek saya ditulis dengan Python 3 (saya memang menyebutkan detail itu pada awalnya, tetapi telah diedit oleh Komunitas Stack Overflow). Menjalankan 2to3 -w gifextract.py membuat skrip tersebut kompatibel dengan Python 3.

  • Saya telah mengubah ukuran setiap frame satu per satu: frame.resize((frame.size[0] // 2, frame.size[1] // 2), Image.ANTIALIAS)

  • Saya telah menyimpan semua frame bersama-sama: img.save("out.gif", save_all=True, optimize=True).

GIF baru sekarang telah disimpan dan berfungsi, tetapi ada 2 masalah utama :

  • Saya tidak yakin metode pengubahan ukuran berfungsi, karena out.gif masih 7,5 MB. GIF awal adalah 2MB.

  • Kecepatan gif ditingkatkan dan gif tidak berputar. Itu berhenti setelah dijalankan pertama kali.

Contoh:

gif asli my_gif.gif:

Gif asli

Gif setelah diproses (out.gif) https://i.imgur.com/zDO4cE4.mp4 (I tidak dapat menambahkannya ke Stack Overflow). Imgur membuatnya lebih lambat (dan mengubahnya menjadi mp4). Saat saya membuka file gif dari komputer saya, keseluruhan gif berdurasi sekitar 1,5 detik.


person Pauline    schedule 18.01.2017    source sumber
comment
dapatkah Anda mengunggah file GIF yang ingin Anda ubah ukurannya?   -  person Jeru Luke    schedule 19.01.2017
comment
@JeruLuke Saya telah menambahkan file GIF.   -  person Pauline    schedule 23.01.2017


Jawaban (3)


Menggunakan skrip BigglesZX, saya telah membuat skrip baru yang mengubah ukuran GIF menggunakan Pillow.

GIF Asli (2,1 MB):

Gif asli

Keluaran GIF setelah diubah ukurannya (1,7 MB):

Keluaran gif

Saya telah menyimpan skrip di sini. Itu menggunakan metode thumbnail dari Bantal daripada metode resize karena menurut saya metode resize tidak berhasil.

Ini tidak sempurna jadi silakan bercabang dan memperbaikinya. Berikut beberapa masalah yang belum terselesaikan:

  • Meskipun GIF ditampilkan dengan baik saat dihosting oleh imgur, ada masalah kecepatan saat saya membukanya dari komputer di mana keseluruhan GIF hanya membutuhkan waktu 1,5 detik.
  • Demikian pula, meskipun imgur tampaknya mengatasi masalah kecepatan, GIF tidak ditampilkan dengan benar ketika saya mencoba mengunggahnya ke stack.imgur. Hanya frame pertama yang ditampilkan (Anda dapat melihatnya di sini).

Kode lengkap (jika inti di atas dihapus):

def resize_gif(path, save_as=None, resize_to=None):
    """
    Resizes the GIF to a given length:

    Args:
        path: the path to the GIF file
        save_as (optional): Path of the resized gif. If not set, the original gif will be overwritten.
        resize_to (optional): new size of the gif. Format: (int, int). If not set, the original GIF will be resized to
                              half of its size.
    """
    all_frames = extract_and_resize_frames(path, resize_to)

    if not save_as:
        save_as = path

    if len(all_frames) == 1:
        print("Warning: only 1 frame found")
        all_frames[0].save(save_as, optimize=True)
    else:
        all_frames[0].save(save_as, optimize=True, save_all=True, append_images=all_frames[1:], loop=1000)


def analyseImage(path):
    """
    Pre-process pass over the image to determine the mode (full or additive).
    Necessary as assessing single frames isn't reliable. Need to know the mode
    before processing all frames.
    """
    im = Image.open(path)
    results = {
        'size': im.size,
        'mode': 'full',
    }
    try:
        while True:
            if im.tile:
                tile = im.tile[0]
                update_region = tile[1]
                update_region_dimensions = update_region[2:]
                if update_region_dimensions != im.size:
                    results['mode'] = 'partial'
                    break
            im.seek(im.tell() + 1)
    except EOFError:
        pass
    return results


def extract_and_resize_frames(path, resize_to=None):
    """
    Iterate the GIF, extracting each frame and resizing them

    Returns:
        An array of all frames
    """
    mode = analyseImage(path)['mode']

    im = Image.open(path)

    if not resize_to:
        resize_to = (im.size[0] // 2, im.size[1] // 2)

    i = 0
    p = im.getpalette()
    last_frame = im.convert('RGBA')

    all_frames = []

    try:
        while True:
            # print("saving %s (%s) frame %d, %s %s" % (path, mode, i, im.size, im.tile))

            '''
            If the GIF uses local colour tables, each frame will have its own palette.
            If not, we need to apply the global palette to the new frame.
            '''
            if not im.getpalette():
                im.putpalette(p)

            new_frame = Image.new('RGBA', im.size)

            '''
            Is this file a "partial"-mode GIF where frames update a region of a different size to the entire image?
            If so, we need to construct the new frame by pasting it on top of the preceding frames.
            '''
            if mode == 'partial':
                new_frame.paste(last_frame)

            new_frame.paste(im, (0, 0), im.convert('RGBA'))

            new_frame.thumbnail(resize_to, Image.ANTIALIAS)
            all_frames.append(new_frame)

            i += 1
            last_frame = new_frame
            im.seek(im.tell() + 1)
    except EOFError:
        pass

    return all_frames
person Pauline    schedule 24.01.2017

Menurut Pillow 4.0x, fungsi Image.resize hanya berfungsi pada satu gambar/bingkai.

Untuk mencapai apa yang Anda inginkan, saya yakin Anda harus terlebih dahulu mengekstrak setiap frame dari file .gif, mengubah ukuran setiap frame satu per satu, lalu memasangnya kembali.

Untuk melakukan langkah pertama, tampaknya ada beberapa detail yang perlu diperhatikan. Misalnya. apakah setiap bingkai gif menggunakan palet lokal atau palet global diterapkan pada semua bingkai, dan apakah gif mengganti setiap gambar menggunakan bingkai penuh atau sebagian. BigglesZX telah mengembangkan skrip untuk mengatasi masalah ini sambil mengekstrak setiap frame dari file gif, jadi manfaatkan hal itu .

Selanjutnya, Anda harus menulis skrip untuk mengubah ukuran setiap frame yang diekstraksi dan merakit semuanya sebagai .gif baru menggunakan PIL.Image.resize() dan PIL.Image.save().

Saya perhatikan Anda menulis "im.seek(im.tell() + 1) # load all frames". Menurut saya ini tidak benar. Melainkan digunakan untuk menambah antar frame file .gif. Saya perhatikan Anda menggunakan quality=10 dalam fungsi penyimpanan untuk file .gif Anda. Saya tidak menemukan ini seperti yang disediakan dalam dokumentasi PIL. Anda dapat mempelajari lebih lanjut tentang atribut ubin yang disebutkan dalam skrip BiggleZX dengan membaca tautan

person Sun Bear    schedule 20.01.2017
comment
Anda benar untuk quality, ini adalah argumen yang valid untuk file jpg tetapi tidak untuk file gif. Saya lupa menghapusnya ketika saya mencoba mengubah kode saya dari mengubah ukuran gif menjadi mengubah ukuran jpg. Saya telah mengedit postingan saya untuk membalas elemen lain dari jawaban Anda karena saya tidak memiliki cukup ruang untuk membalas sebagai komentar. - person Pauline; 23.01.2017
comment
@Pauline jika Anda membandingkan ukuran masing-masing bingkai yang diekstraksi dengan bingkai yang diubah ukurannya setara, apakah menurut Anda ukuran bingkai yang diubah ukurannya lebih kecil? Kalau lebih kecil menurut saya berarti fungsi resize berfungsi dan pertambahan ukuran file berasal dari proses kompilasi ulang. Untuk masalah perulangan Anda, ada opsi perulangan yang disebutkan dalam perintah Image.save yang dapat mengatasi masalah Anda. Mengenai kecepatan, menurut saya opsi durasi mengontrolnya. Anda dapat melihatnya melalui tautan yang diberikan pada jawaban saya sebelumnya. - person Sun Bear; 23.01.2017
comment
Pikiran lain. Jika ada peningkatan ukuran dalam pengurangan ukuran file Anda, mungkinkah itu disebabkan oleh jenis filter Anda. Lihat pillow.readthedocs.io/en/4.0.x/handbook /concepts.html#filters. Saya perhatikan Anda menggunakan filter Image.ANTIALIAS, tetapi tidak menemukannya sebagai salah satu filter yang terdaftar untuk mengubah ukuran. Filter Hamming terlihat menarik. - person Sun Bear; 23.01.2017
comment
Saya menemukan bahwa metode resize tidak berfungsi, oleh karena itu saya beralih ke thumbnail yang berfungsi. Saya tidak tahu mengapa resize tidak berhasil. Saya sekarang menggunakan atribut perulangan tetapi pada awalnya saya menginginkan perulangan tak terbatas daripada jumlah perulangan yang tetap. Saya tidak menemukan cara untuk mencapai hal ini, jadi saya mengatur jumlah loop menjadi 1000 (itu hanya menambah beberapa byte ke ukuran total GIF. Saya belum menemukan solusi untuk kecepatan yang tidak akan mengharuskan saya memasukkan kecepatan secara manual. Idealnya saya akan membaca kecepatan dari GIF asli tetapi belum menemukan cara untuk mencapainya. - person Pauline; 24.01.2017
comment
Image.ANTIALIAS adalah versi lama (lihat catatan rilis ini) namun masih dapat digunakan pada bantal versi terbaru karena terhubung ke LANCZOS. Karena menggunakan LANCZOS di versi lama pillow akan menimbulkan pengecualian, dan karena menggunakan ANTIALIAS mencapai hasil yang sama seperti menggunakan LANCZOS, menurut saya mungkin lebih baik membiarkannya sebagai ANTIALIAS, tetapi saya mungkin salah, dan saya akan dengan senang hati melakukannya dikoreksi. Terima kasih atas bantuan Anda, saya telah menemukan cara untuk membuatnya berfungsi sekarang, dan mempostingnya sebagai jawaban. - person Pauline; 24.01.2017
comment
@Pauline Senang Anda mencapai tujuan Anda dan senang telah menjadi bagian dari perjalanan Anda. Tercatat di ANTIALIAS. Menemukan Dokumen PIL rekomendasi filter BOX untuk diperkecil pada skala tertentu. - person Sun Bear; 25.01.2017

Saya menggunakan fungsi di bawah ini untuk mengubah ukuran dan memotong gambar termasuk gambar animasi (GIF, WEBP) Sederhananya, kita perlu mengulangi setiap frame di gif atau webp.

from math import floor, fabs
from PIL import Image, ImageSequence

def transform_image(original_img, crop_w, crop_h):
  """
  Resizes and crops the image to the specified crop_w and crop_h if necessary.
  Works with multi frame gif and webp images also.

  args:
  original_img is the image instance created by pillow ( Image.open(filepath) )
  crop_w is the width in pixels for the image that will be resized and cropped
  crop_h is the height in pixels for the image that will be resized and cropped

  returns:
  Instance of an Image or list of frames which they are instances of an Image individually
  """
  img_w, img_h = (original_img.size[0], original_img.size[1])
  n_frames = getattr(original_img, 'n_frames', 1)

  def transform_frame(frame):
    """
    Resizes and crops the individual frame in the image.
    """
    # resize the image to the specified height if crop_w is null in the recipe
    if crop_w is None:
      if crop_h == img_h:
        return frame
      new_w = floor(img_w * crop_h / img_h)
      new_h = crop_h
      return frame.resize((new_w, new_h))

    # return the original image if crop size is equal to img size
    if crop_w == img_w and crop_h == img_h:
      return frame

    # first resize to get most visible area of the image and then crop
    w_diff = fabs(crop_w - img_w)
    h_diff = fabs(crop_h - img_h)
    enlarge_image = True if crop_w > img_w or crop_h > img_h else False
    shrink_image = True if crop_w < img_w or crop_h < img_h else False

    if enlarge_image is True:
      new_w = floor(crop_h * img_w / img_h) if h_diff > w_diff else crop_w
      new_h = floor(crop_w * img_h / img_w) if h_diff < w_diff else crop_h

    if shrink_image is True:
      new_w = crop_w if h_diff > w_diff else floor(crop_h * img_w / img_h)
      new_h = crop_h if h_diff < w_diff else floor(crop_w * img_h / img_w)

    left = (new_w - crop_w) // 2
    right = left + crop_w
    top = (new_h - crop_h) // 2
    bottom = top + crop_h

    return frame.resize((new_w, new_h)).crop((left, top, right, bottom))

  # single frame image
  if n_frames == 1:
    return transform_frame(original_img)
  # in the case of a multiframe image
  else:
    frames = []
    for frame in ImageSequence.Iterator(original_img):
      frames.append( transform_frame(frame) )
    return frames
person muratgozel    schedule 01.12.2018