Contoh penggunaan ImageMagick -- penanganan video
- Kata pengantar dan indeks contoh penggunaan ImageMagick
- Video ke GIF, rangkuman optimisasi
- Menghilangkan interlace pada frame video
ImageMagick tidak terlalu cocok untuk menangani video digital, tetapi program ini umum dipakai untuk keperluan tersebut, terutama di lingkungan Linux. Di sini saya menelusuri teknik dan contoh yang khusus berkaitan dengan penanganan urutan video nyata (dan hasil raytracing).
Video ke GIF, rangkuman optimisasi
Seorang pengembang perangkat lunak yang memakai IM untuk membuat GIF film, Benoit Rouleau, dalam diskusi dengan saya, memberi saya sebuah video AVI berisi pesawat yang terbang melintas, agar kita bisa sama-sama menelusuri teknik konversi video IM. Namun meski file AVI-nya sendiri cukup kecil, video tanpa kompresinya berukuran raksasa, yaitu
byte, dan memuat
warna sepanjang
frame. Meski begitu IM tidak menemui kesulitan berarti dalam mengonversi video ini menjadi animasi GIF. Tetapi hati-hati, kemungkinan besar akan muncul beberapa error 'AVI chunk' yang tidak didukung; error itu bisa diabaikan dengan memakai "-quiet" sebagai pengaturan kendali. |
magick -quiet -delay 1 plane.avi plane.gif
![[IM Output]](../static/img/video/plane.gif)
Ini memakai metode kuantisasi warna dan dithering bawaan ImageMagick, sehingga menghasilkan konversi video yang cukup masuk akal. Masalah warna nyaris tidak ada, karena videonya memang hanya memakai sedikit warna sejak awal. Hal ini tidak selalu terjadi, apalagi karena GIF punya batas 256 warna per frame. Namun file animasinya berukuran
byte; walau hanya 1/5 ukuran semula berkat pengurangan warna dan kompresi data piksel GIF, ukuran itu masih tergolong besar. Selain itu, bila animasi hasilnya ditelaah lebih jauh, akan terlihat bahwa dari
frame dalam gambar tersebut,
frame mendapat tambahan tabel warna lokal tersendiri. Artinya, setiap frame dalam animasi GIF membutuhkan tabel indeks warnanya sendiri. Artinya pula, meski setiap frame memiliki kurang dari 256 warna (karena keterbatasan format GIF), seluruh animasi memakai total
warna. Sayangnya format GIF tidak mengompres tabel warna, sehingga semua tabel warna tambahan itu bisa memakan hingga: 256 warna * 3 byte per warna * 106 frame; atau 81.408 byte ruang file. Tidak banyak untuk video 1 Gbyte, tetapi tetap saja lumayan, apalagi ketika kita mengoptimisasi videonya lebih jauh. Ditambah lagi, animasi ini tidak akan bisa dioptimisasi frame-nya dengan baik dalam GIF. Bukan hanya karena latar belakangnya bergerak (kamera menyapu ke atas), tetapi juga karena IM memakai dithering koreksi error (Hilbert Curve Dither), yang menghasilkan pola warna acak semu yang berbeda dari frame ke frame. Contoh berikutnya akan membuat 'dither noise' ini jauh lebih kentara.
Tabel warna global bersama
Di sini saya membangkitkan satu tabel warna global untuk semua frame video tersebut. |
magick -quiet -delay 1 plane.avi +remap plane_cgc.gif
Hasilnya, tentu saja,
tabel warna lokal, dengan ukuran file
byte. ![[IM Output]](../static/img/video/plane_cgc.gif)
Seperti terlihat, animasi hasilnya tidak lagi punya tabel warna lokal tambahan. Sebagai gantinya IM membangkitkan satu tabel warna global berisi
warna 'terbaik' berdasarkan seluruh frame dalam animasi. Sayangnya hal ini juga membuat data pikselnya tidak terkompres sebaik sebelumnya, karena dibutuhkan dither yang lebih kuat. Hasilnya animasi yang tampak sedikit lebih buruk, dengan ukuran kurang lebih sama dengan yang sebelumnya. Untuk video tertentu berwarna terbatas ini, saya bahkan bisa memangkas jumlah warna lebih jauh lagi, katakanlah hanya 64 warna, tanpa terlalu banyak masalah, sehingga ukuran file animasinya makin kecil. Namun hal ini sangat bergantung pada urutan video yang dipakai, dan bisa saja hasilnya tidak enak dilihat. Video milik Anda sendiri bisa memberi hasil yang lebih baik atau lebih buruk, terutama bila videonya memakai jauh lebih banyak warna dan mungkin beberapa adegan sekaligus.
Tabel warna global universal
Cara yang lebih baik untuk menghasilkan animasi GIF yang 'lebih kecil' adalah cukup menyediakan rentang warna universal yang umum, alih-alih membangkitkan tabel warna global 'terbaik' bagi animasi tersebut. Pakailah peta warna yang mestinya bekerja baik apa pun warna yang ada dalam video aslinya. Alasan lain melakukan ini: video bisa dibuat lebih panjang tanpa efek buruk yang serius pada pemilihan warna, dan tanpa harus kembali memakai tabel warna lokal untuk setiap frame. Setiap frame di-dither ke peta warna yang sama, sepenuhnya lepas dari frame lain yang ada dalam animasi. Di sini saya memakai peta warna '332', yang biasanya dianggap sebagai peta warna standar yang sangat baik bila transparansi tidak diperlukan. Saya sering melihat peta warna ini (atau peta warna 'web-safe' berisi 219 warna) dipakai di berbagai format video. |
magick -quiet -delay 1 plane.avi -remap colormap_332.png plane_ugc.gif
![[IM Output]](../static/img/video/plane_ugc.gif)
Animasi ini punya
tabel warna lokal, dan hasilnya animasi menjadi lebih kecil, yaitu
byte. Masalahnya, sering terlihat 'noise' yang mencolok dan mengganggu di area berwarna konstan. Noise ini sebenarnya juga ada di SEMUA animasi video sebelumnya. Baru sekarang terlihat karena pemetaan warna yang dipakai lebih universal, sehingga tersebar lebih luas. Noise itu sebetulnya berasal dari dithering atas himpunan warna yang sudah dikurangi ketika gambarnya dibangkitkan ulang. Hanya saja, dithering itu menghasilkan pola warna acak semu yang berubah dari frame ke frame, sehingga muncul kesan noise latar belakang pada gambar. Lihat Masalah pada E-Dither untuk keterangan lebih rinci mengapa hal ini terjadi. Kita bisa saja mematikan dithering warna untuk menghilangkan 'dither noise' itu... |
magick -quiet -delay 1 plane.avi \
+dither -remap colormap_332.png plane_ugc_nd.gif
Hasilnya punya
tabel warna lokal, dan berukuran
byte. ![[IM Output]](../static/img/video/plane_ugc_nd.gif)
Animasi hasilnya sangat kecil, hanya 1/60 ukuran animasi aslinya, terutama karena bentangan luas warna polos menghasilkan kompresi piksel yang luar biasa baik. Tetapi walau dither noise-nya hilang dan ukuran file-nya jadi sangat kecil, yang muncul sebagai gantinya adalah color banding, dan itu umumnya dianggap sebagai pertukaran yang sangat buruk.
Video dengan ordered dither
Solusi yang sebenarnya adalah memakai teknik dithering warna lain yang tidak menghasilkan pola berbeda dari satu frame ke frame berikutnya. Sebagai contoh, di sini saya memakai ordered dither dengan level warna terposterisasi untuk men-dither peta warna '332' universal yang sama. |
magick -quiet -delay 1 plane.avi \
-ordered-dither o8x8,8,8,4 +remap plane_od.gif
Hasilnya punya
tabel warna lokal, dan berukuran
byte. ![[IM Output]](../static/img/video/plane_od.gif)
Perintah di atas juga memakai operator "+remap" untuk memastikan semua gambar memakai peta warna global yang persis sama (yang oleh ordered dither sudah dikurangi hingga maksimum 256 warna). Karena jumlah warnanya sudah optimal, operator "+remap" tidak melakukan dithering maupun pengurangan warna. Pola dither yang dihasilkan tidak acak, dan tidak banyak berubah dari satu frame ke frame berikutnya. Dengan begitu 'dither noise' sudah hilang dari animasi, dan yang tersisa adalah pola warna yang tetap dari frame ke frame. Polanya juga sangat berulang sehingga kompresinya jauh lebih baik. Terakhir, karena peta warnanya tetap, cara ini mestinya bekerja cukup baik apa pun video yang dipakai.
Video dengan ordered dither berkualitas lebih tinggi
Namun video yang satu ini hanya memakai rentang warna yang sempit, kebanyakan berbagai gradasi biru, sehingga banyak warna dari peta warna seragam umum yang sebenarnya tidak terpakai. Nyatanya hanya
warna yang terpakai pada animasi video terakhir tadi! Angka itu sangat rendah, dan karena itu pula cukup kentara. Tetapi ini juga berarti animasi yang satu ini bisa memperoleh manfaat dari pemakaian 'level warna' dalam jumlah besar pada operasi ordered dither, sehingga kualitas keseluruhannya membaik. Namun pertama-tama kita perlu menentukan berapa banyak level warna yang sanggup ditangani animasi ini sebelum menyentuh batas 256 warna yang dipaksakan baik oleh format file GIF maupun oleh pemetaan ulang peta warna global. Bagian yang rumit, semua itu harus ditentukan SEBELUM animasinya disimpan ke format GIF yang terbatas itu. Dan inilah perintah yang saya pakai...
magick -quiet plane.avi -ordered-dither o8x8,23 -append -format %k info:
Pada dasarnya saya menaikkan dan menurunkan jumlah level warna yang dipakai, sampai memperoleh angka yang pas berada di dalam batas 256 warna yang disyaratkan. Pilihan 'level warna' yang ditemukan itu lalu bisa saya terapkan pada animasi pesawat. |
magick -quiet -delay 1 plane.avi \
-ordered-dither o8x8,23 +remap plane_od2.gif
Hasilnya punya
tabel warna lokal, berukuran
byte, dan
warna. ![[IM Output]](../static/img/video/plane_od2.gif)
Seperti terlihat, yang dihasilkan adalah video ordered dither berkualitas sangat tinggi, setara dengan versi peta warna global 'peta warna terbaik' yang kita hasilkan tadi, tetapi ukurannya 1/3 lebih kecil, sementara 'dither noise'-nya kini jauh lebih sulit terlihat. Tentu saja, karena kualitasnya jauh lebih tinggi, ukuran file-nya pun lebih besar, sebab ia tidak terkompres sebaik versi berkualitas rendah. Di sisi lain, kini benar-benar tersedia kendali yang baik atas pertukaran antara kualitas dan ukuran file, yaitu lewat jumlah 'level warna' yang dipakai. Ingat saja bahwa teknik ini adalah kasus khusus , untuk animasi yang tidak memakai terlalu banyak warna. Dan memperpanjang video dengan menambah frame juga akan menambah warna, sehingga kendali kualitas 'level warna' harus diturunkan. Ini boleh dibilang metode optimisasi warna terbaik yang pernah saya lihat untuk animasi GIF pada umumnya. Metode ini menghilangkan 'dither noise', memberi sedikit kendali kualitas, dan tetap memungkinkan pemakaian metode optimisasi animasi GIF lain, seperti Optimisasi frame.
Optimisasi kompresi (transparansi)
Karena video ini memakai kamera yang bergerak menyapu (panning), latar belakang videonya berubah dari frame ke frame. Artinya animasi GIF ini tidak akan bisa dikenai Optimisasi frame dengan baik. Meski begitu kita masih bisa memakai Optimisasi transparansi sederhana untuk memperkecil lagi ukuran akhir animasi GIF-nya. |
magick plane_od2.gif -layers OptimizeTransparency +remap plane_opt.gif
Hasilnya berukuran
byte, dengan
warna. ![[IM Output]](../static/img/video/plane_opt.gif)
Artinya, satu warna tambahan berupa indeks warna transparan ditambahkan ke gambar, dan setiap piksel yang tidak mengubah warna yang sedang ditampilkan dijadikan transparan. Hal ini pada gilirannya menghasilkan segmen area transparan yang luas pada animasi aslinya, sekaligus pengulangan urutan piksel yang mirip, sehingga kompresi LZW pada gambar GIF akhir menjadi lebih baik. Lumayan: animasinya kini separuh dari hasil konversi langsung ke GIF, dan kualitasnya masih cukup tinggi. Jika Anda ingin menambahkan sesuatu pada uraian di atas, atau membahas teknik-teknik ini agar lebih baik lagi, silakan hubungi saya atau forum IM. Saya dengan senang hati mendengar pandangan, teknik, dan diskusi Anda, atau menengok masalah video/animasi tertentu yang sedang Anda hadapi. Salah satu diskusi semacam itu adalah Finding the "right levels" for quantization with anim GIF.
Optimisasi LZW dengan kompresi Giflossy
Sebuah program baru, GifLossy, yang merupakan fork dari program Gifsicle aslinya, mengubah warna setiap frame agar LZW bisa mengompres gambarnya jauh lebih baik. Sebagai contoh, di sini saya menerapkannya pada animasi GIF yang asli, sambil memintanya mengurangi warna menjadi satu tabel 256 warna. |
gifsicle -O3 --lossy=80 --colors 256 plane.gif -o plane_giflossy.gif
Ukurannya sungguh menakjubkan:
byte. Kualitasnya memang jauh di bawah apa yang kita capai dengan ordered dither, tetapi ukurannya kurang dari 1/2. ![[IM Output]](../static/img/video/plane_giflossy.gif)
Terdorong oleh hasil di atas, saya memutuskan memakai GifLossy pada hasil ordered dither terbaik yang kita peroleh, untuk melihat apakah ukurannya bisa lebih kecil lagi. |
gifsicle -O3 --lossy=80 plane_od2.gif -o plane_od2_giflossy.gif
Dan kita memang mendapat ukuran yang lebih kecil lagi, yaitu
byte. Sayangnya hasil ordered dither berkualitas tinggi yang susah payah kita raih tadi praktis hilang. Sungguh mengecewakan.
Menghilangkan interlace pada frame video
Tidak semua gambar berasal dari kamera digital. Sangat lazim orang mengekstrak gambar dari umpan video digital sebuah kamera video non-CCD. Gambar semacam ini di-interlace agar bisa langsung ditampilkan di TV, sehingga setiap baris kedua sebenarnya berasal dari frame gambar yang berbeda (interlacing). Untuk dua frame yang isinya tidak bergerak, interlacing biasanya tidak terlalu kentara. Paling-paling tepi gambarnya tampak sedikit buram. Tetapi bila ada objek yang bergerak cepat, gambar ter-interlace yang dihasilkan sangat mengganggu, karena dua frame tergabung menjadi satu. Wolfgang Hugemann Auto@Hugemann.de (Jerman) mengalami masalah ini dan mengirimi saya cuplikan sebuah uji tabrak yang diambil Wolfgang sendiri. Namun untuk peragaan saya akan memakai gambar yang lebih kecil, hasil pemangkasan dari gambar itu. Tekniknya sendiri tetap berlaku pada gambar berukuran penuh. |
magick video_frame.png -crop 100x100+200+470 +repage interlaced.png
![[IM Output]](../static/img/video/interlaced.png)
| Wolfgang Hugemann memakai format TIFF untuk frame video aslinya; saya mengonversinya ke PNG untuk dipakai di IM Examples. JANGAN tergoda memakai JPEG untuk gambar-gambar ini sebelum pemrosesannya selesai, karena format itu akan merusak kualitas detail halus yang dibutuhkan proses ini.
Seperti terlihat, interlacing itu menampilkan dua frame terpisah, karena gambarnya berasal dari urutan video digital PAL ter-interlace (kira-kira 50 setengah-frame per detik). Ya, mobilnya melaju sangat cepat dan kameranya memakai rana berkecepatan tinggi, sehingga gambar videonya berkualitas sangat tinggi. Gambar hasilnya berupa dua setengah-frame yang saling terjalin, dengan spion mobil berpindah cukup jauh selama selang waktu 1/50 detik di antara kedua setengah-frame itu. Di sini kita cukup mengganti salah satu setengah-frame ter-interlace (setiap baris kedua) dengan warna putih. Inilah metode penghilangan interlace yang standar, dikenal sebagai filter 'BoB'. Metode ini disumbangkan Wolfgang untuk IM Examples. |
magick interlaced.png -fx "floor(j/2)==j/2 ? u : 1" deinterlace_1.png
![[IM Output]](../static/img/video/deinterlace_1.png)
Nah, operator FX itu lambat, jadi alternatifnya adalah membuat 'gambar bergaris'. Gambar semacam itu bisa dibangkitkan dari gambar bawaan khusus "pattern:Horizontal2". Gambar tersebut lalu bisa ditimpakan pada gambar aslinya, memakai metode komposisi 'Screen' untuk menimpakan garis putih, atau 'Multiply' untuk menimpakan garis hitam. Sebagai contoh... |
magick -size 100x100 pattern:Horizontal2 \
interlaced.png -compose Multiply -composite deinterlace_2.png
![[IM Output]](../static/img/video/deinterlace_2.png)
Menegasikan pola tersebut bisa dipakai untuk memilih separuh lain dari gambar ter-interlace. Atau bila 'Multiply' diganti menjadi 'Screen', frame bisa diekstrak dengan latar belakang putih. Sebagai alternatif, saya mencoba mengisi baris frame yang hilang dengan cara menggandakan baris sebelumnya saja. |
magick interlaced.png -fx "u.p{i,j-j%2}" deinterlace_3.png
![[IM Output]](../static/img/video/deinterlace_3.png)
Teknik pikselisasi juga bisa dipakai untuk mengecilkan lalu membesarkan gambar sehingga setiap baris kedua tergandakan. |
magick interlaced.png -sample 100%x50% \
-sample 100%x200% deinterlace_4.png
![[IM Output]](../static/img/video/deinterlace_4.png)
Dan dengan sedikit variasi, baris di kedua sisi bisa digabungkan untuk memuluskan gambar setengah-frame itu secara vertikal, sekalian saat pembesaran ukurannya. |
magick interlaced.png -sample 100%x50% \
-resize 100%x200% deinterlace_5.png
![[IM Output]](../static/img/video/deinterlace_5.png)
Hasilnya adalah ekstraksi yang sangat rapi atas satu frame dari gambar video ter-interlace itu. Jika Anda ingin mengekstrak setengah-frame yang satu lagi dari gambar itu, 'sample:offset' bisa disesuaikan (sejak IM v6.8.4-7) (Catatan penerjemah: naskah asli menulis 'sampling:offset'; nama define yang benar adalah 'sample:offset', seperti pada contoh di bawah) |
magick interlaced.png -define sample:offset=75 \
-sample 100%x50% -resize 100%x200% deinterlace_6.png
![[IM Output]](../static/img/video/deinterlace_6.png)
Sebelum versi IM tersebut, gambarnya perlu digeser dengan "-roll" sebesar satu piksel untuk mendapatkan hasil yang sama.
![[IM Text]](../static/img/video/plane_od2_find.txt.gif)