Beberapa tahun yang lalu saya menulis implementasi kompresi fraktal gambar yang sangat sederhana untuk pekerjaan siswa dan memposting kode di
github .
Yang mengejutkan saya, repositori ternyata cukup populer, jadi saya memutuskan untuk memperbarui kode dan menulis artikel yang menjelaskannya dan teorinya.
Teori
Bagian ini cukup teoretis, dan jika Anda hanya tertarik pada dokumentasi kode, Anda dapat melewatinya.
Pemetaan Kompresi
Biarkan
(E,d) Apakah
ruang metrik penuh , dan
f:E rightarrowE - pemetaan dari
E pada
E .
Kami mengatakan itu
f adalah
pemetaan kompresif jika ada
0<s<1 sedemikian rupa sehingga:
forallx,y dalamE,d(f(x),f(y)) leqsd(x,y)
Berdasarkan ini,
f akan menunjukkan pemetaan kompresi dengan rasio kompresi
s .
Ada dua teorema penting tentang
pembuatan peta kontrak:
teorema titik tetap Banach dan
teorema kolase .
Teorema titik tetap :
f memiliki titik tetap yang unik
x0 .
Tunjukkan buktiPertama kita buktikan urutannya
(un) ditetapkan sebagai
$ inline $ \ left \ {\ begin {alignat *} {2} u_0 & = x \\ u_ {n + 1} & = f (u_n) \ end {alignat *} \ kanan. $ inline $ konvergen untuk semua
x dalamE .
Untuk semua
m<n in mathbbN :
\ begin {alignat *} {2} d (u_m, u_n) & = d (f ^ m (x), f ^ n (x)) \\ & \ leq s ^ md (x, f ^ {nm} (x)) \ teks {sejak} f \ teks {adalah peta kontraksi} \\ & \ leq s ^ m \ kiri (\ sum_ {i = 0} ^ {nm-1} {d (f ^ i (x ), f ^ {i + 1} (x))} \ kanan) \ teks {dari ketidaksetaraan segitiga} \\ & \ leq s ^ m \ kiri (\ sum_ {i = 0} ^ {nm-1} {s ^ id (x, f (x))} \ kanan) \ teks {karena} f \ teks {adalah peta kontraksi} \\ & = s ^ m \ kiri (\ frac {1 - s ^ {nm}} {1 - s} d (x, f (x)) \ kanan) \\ & \ leq \ frac {s ^ m} {1 - s} d (x, f (x)) \ underset {m \ rightarrow \ infty} {\ rightarrow} 0 \ end {alignat *}
\ begin {alignat *} {2} d (u_m, u_n) & = d (f ^ m (x), f ^ n (x)) \\ & \ leq s ^ md (x, f ^ {nm} (x)) \ teks {sejak} f \ teks {adalah peta kontraksi} \\ & \ leq s ^ m \ kiri (\ sum_ {i = 0} ^ {nm-1} {d (f ^ i (x ), f ^ {i + 1} (x))} \ kanan) \ teks {dari ketidaksetaraan segitiga} \\ & \ leq s ^ m \ kiri (\ sum_ {i = 0} ^ {nm-1} {s ^ id (x, f (x))} \ kanan) \ teks {karena} f \ teks {adalah peta kontraksi} \\ & = s ^ m \ kiri (\ frac {1 - s ^ {nm}} {1 - s} d (x, f (x)) \ kanan) \\ & \ leq \ frac {s ^ m} {1 - s} d (x, f (x)) \ underset {m \ rightarrow \ infty} {\ rightarrow} 0 \ end {alignat *}
Oleh karena itu
(un) adalah
urutan Cauchy , dan
E adalah ruang penuh, yang artinya
(un) konvergen. Biarkan dia menjadi batasnya
x0 .
Selain itu, karena peta kontraksi adalah
kontinu sebagai peta Lipschitz ,
peta kontraksi juga kontinu, mis.
f(un) rightarrowf(x0) . Karena itu, jika
n cenderung tak terhingga di
un+1=f(un) kita dapatkan
x0=f(x0) . Yaitu
x0 adalah titik tetap
f .
Kami telah menunjukkan itu
f memiliki titik tetap. Mari kita tunjukkan dengan kontradiksi bahwa itu unik. Biarkan
y0 - titik tetap lainnya. Lalu:
d(x0,y0)=d(f(x0),f(y0)) leqsd(x0,y0)<d(x0,y0)
Ada kontradiksi.
Selanjutnya kami akan menyatakan sebagai
x0 titik tetap
f .
Teorema kolase : jika
d(x,f(x))< epsilon lalu
d(x,x0)< frac epsilon1βs .
Tunjukkan buktiDalam bukti sebelumnya, kami menunjukkan itu d(um,un) leq fracsm1βsd(x,f(x))= fracsm1βs epsilon .
Jika kita perbaiki m masuk 0 lalu kita dapatkan d(x,un) leq frac epsilon1βs .
Saat berusaha n hingga tak terbatas kami mendapatkan hasil yang diinginkan.
Teorema kedua memberi tahu kita bahwa jika kita menemukan peta kontraksi
f sedemikian rupa
f(x) dekat dengan
x , maka bisa dipastikan titik itu tetap
f juga dekat dengan
x .
Hasil ini akan menjadi dasar untuk pekerjaan kami di masa depan. Dan pada kenyataannya, alih-alih menyimpan gambar, itu sudah cukup bagi kita untuk menyimpan tampilan kompresif, titik tetap yang dekat dengan gambar.
Tampilan kompresi untuk gambar
Pada bagian ini saya akan menunjukkan cara membuat tampilan kompresif sehingga titik tetap dekat dengan gambar.
Pertama, mari kita atur set gambar dan jarak. Kami akan memilih
E=[0,1]h kaliw .
E Apakah banyak matriks dengan
h dalam baris
w kolom dan dengan koefisien dalam interval
[0,1] . Maka kita akan ambil
d(x,y)= kiri( sumhi=1 sumwj=1(xijβyij)2 kanan)0,5 .
d Apakah jarak yang didapat dari
norma Frobenius .
Biarkan
x dalamE Apakah gambar yang ingin kita kompres.
Kami akan membagi gambar menjadi blok dua kali:
- Pertama, kami membagi gambar menjadi blok terbatas atau interval R1,...,RL . Blok-blok ini dipisahkan dan menutupi seluruh gambar.
- Lalu kami membagi gambar menjadi blok sumber atau domain D1,...,DK . Blok-blok ini tidak harus dipisahkan dan tidak harus mencakup seluruh gambar.
Misalnya, kita dapat mengelompokkan gambar sebagai berikut:
Kemudian untuk setiap blok interval
Rl kami memilih blok domain
Dkl dan pemetaan
fl:[0,1]Dkl rightarrow[0,1]Rl .
Selanjutnya kita dapat mendefinisikan suatu fungsi
f seperti:
f(x)ij=fl(xDkl)ij textif(i,j) dalamRl
Persetujuan : jika
fl membuat kontrak pemetaan, lalu dan
f juga pemetaan kompresif.
Tunjukkan buktiBiarkan
x,y dalamE dan anggap itu semua
fl adalah pemetaan kompresi dengan rasio kompresi
sl . Lalu kami mendapatkan yang berikut:
\ begin {alignat *} {2} d (f (x), f (y)) ^ 2 & = \ sum_ {i = 1} ^ {h} {\ sum_ {j = 1} ^ {w} { (f (x) _ {ij} -f (y) _ {ij}) ^ 2}} \ text {menurut definisi} d \\ & = \ sum_ {l = 1} ^ L {\ sum _ {(i, j) \ dalam R_l} {(f (x) _ {ij} -f (y) _ {ij}) ^ 2}} \ teks {sejak} (R_l) \ teks {adalah partisi} \\ & = \ sum_ {l = 1} ^ L {\ jumlah _ {(i, j) \ dalam R_l} {(f_l (x_ {D_ {k_l}}) _ {ij} -f_l (y_ {D_ {k_l}}) _ {ij }) ^ 2}} \ teks {dengan definisi} f \\ & = \ sum_ {l = 1} ^ L {d (f_l (x_ {D_ {k_l}}), f_l (y_ {D_ {k_l}})) ) ^ 2} \ teks {dengan definisi} d \\ & \ leq \ sum_ {l = 1} ^ L {s_l ^ 2d (x_ {D_ {k_l}}, y_ {D_ {k_l}}) ^ 2} \ teks {sejak} (f_l) \ teks {adalah pemetaan kontraksi} \\ & \ leq \ underset {l} {\ max} {s_l ^ 2} \ sum_ {l = 1} ^ L {d (x_ {D_ {k_l }}, y_ {D_ {k_l}}) ^ 2} \\ & = \ underset {l} {\ max} {s_l ^ 2} \ sum_ {l = 1} ^ L {\ sum _ {(i, j) \ dalam R_l} {(x_ {ij} -y_ {ij}) ^ 2}} \ teks {menurut definisi} d \\ & = \ underset {l} {\ max} {s_l ^ 2} \ sum_ {i = 1} ^ {h} {\ sum_ {j = 1} ^ {w} {(x_ {ij} -y_ {ij}) ^ 2}} \ text {since} (R_l) \ teks {adalah partisi} \\ & = \ underset {l} {\ max} {s_l ^ 2} d (x, y) ^ 2 \ teks {menurut definisi} d \\ \ end {alignat *}
\ begin {alignat *} {2} d (f (x), f (y)) ^ 2 & = \ sum_ {i = 1} ^ {h} {\ sum_ {j = 1} ^ {w} { (f (x) _ {ij} -f (y) _ {ij}) ^ 2}} \ text {menurut definisi} d \\ & = \ sum_ {l = 1} ^ L {\ sum _ {(i, j) \ dalam R_l} {(f (x) _ {ij} -f (y) _ {ij}) ^ 2}} \ teks {sejak} (R_l) \ teks {adalah partisi} \\ & = \ sum_ {l = 1} ^ L {\ jumlah _ {(i, j) \ dalam R_l} {(f_l (x_ {D_ {k_l}}) _ {ij} -f_l (y_ {D_ {k_l}}) _ {ij }) ^ 2}} \ teks {dengan definisi} f \\ & = \ sum_ {l = 1} ^ L {d (f_l (x_ {D_ {k_l}}), f_l (y_ {D_ {k_l}})) ) ^ 2} \ teks {dengan definisi} d \\ & \ leq \ sum_ {l = 1} ^ L {s_l ^ 2d (x_ {D_ {k_l}}, y_ {D_ {k_l}}) ^ 2} \ teks {sejak} (f_l) \ teks {adalah pemetaan kontraksi} \\ & \ leq \ underset {l} {\ max} {s_l ^ 2} \ sum_ {l = 1} ^ L {d (x_ {D_ {k_l }}, y_ {D_ {k_l}}) ^ 2} \\ & = \ underset {l} {\ max} {s_l ^ 2} \ sum_ {l = 1} ^ L {\ sum _ {(i, j) \ dalam R_l} {(x_ {ij} -y_ {ij}) ^ 2}} \ teks {menurut definisi} d \\ & = \ underset {l} {\ max} {s_l ^ 2} \ sum_ {i = 1} ^ {h} {\ sum_ {j = 1} ^ {w} {(x_ {ij} -y_ {ij}) ^ 2}} \ text {since} (R_l) \ teks {adalah partisi} \\ & = \ underset {l} {\ max} {s_l ^ 2} d (x, y) ^ 2 \ teks {menurut definisi} d \\ \ end {alignat *}
Masih ada satu pertanyaan yang perlu dijawab: bagaimana memilih
Dkl dan
fl ?
Teorema kolase menawarkan cara untuk memilihnya: jika
xRl dekat dengan
f(xDkl) untuk semua
l lalu
x dekat dengan
f(x) dan oleh teorema kolase
x dan
x0 juga dekat.
Jadi kami independen untuk semua orang
l kita dapat membuat satu set pemetaan dari masing-masing
Dk pada
Rl dan pilih yang terbaik. Di bagian selanjutnya, kami menunjukkan semua detail operasi ini.
Implementasi
Di setiap bagian saya akan menyalin fragmen kode yang menarik, dan seluruh skrip dapat ditemukan di
sini .
Partisi
Saya memilih pendekatan yang sangat sederhana. Blok sumber dan blok daun mengelompokkan gambar pada kisi, seperti yang ditunjukkan pada gambar di atas.
Ukuran balok sama dengan kekuatan dua dan ini sangat menyederhanakan pekerjaan. Blok sumber adalah 8 oleh 8 dan blok akhir adalah 4 oleh 4.
Ada skema partisi yang lebih kompleks. Sebagai contoh, kita dapat menggunakan pohon quadtree untuk memecah area dengan banyak detail lebih kuat.
Konversi
Di bagian ini, saya akan menunjukkan cara membuat pemetaan kompresi
Dk pada
Rl .
Ingatlah bahwa kami ingin membuat pemetaan seperti itu
fl untuk
f(xDk) dekat
xRl . Artinya, semakin banyak pemetaan yang kami hasilkan, semakin besar kemungkinan untuk menemukan yang baik.
Namun, kualitas kompresi tergantung pada jumlah bit yang diperlukan untuk menyimpan
fl . Artinya, jika banyak fungsi terlalu besar, maka kompresi akan buruk. Dibutuhkan kompromi di sini.
Saya memutuskan itu
fl akan terlihat seperti ini:
fl(xDk)=s kalirotate theta(flipd(kurangi(xDk)))+b
dimana
kurangi - ini adalah fungsi untuk bergerak dari blok 8 ke 8 ke blok 4 ke 4,
flip dan
rotate - transformasi affine,
s mengubah kontras, dan
b - kecerahan.
Fungsi pengurangan mengurangi ukuran gambar dengan rata-rata lingkungan:
def reduce(img, factor): result = np.zeros((img.shape[0] // factor, img.shape[1] // factor)) for i in range(result.shape[0]): for j in range(result.shape[1]): result[i,j] = np.mean(img[i*factor:(i+1)*factor,j*factor:(j+1)*factor]) return result
Fungsi
rotate
memutar gambar dengan sudut tertentu:
def rotate(img, angle): return ndimage.rotate(img, angle, reshape=False)
Untuk mempertahankan bentuk sudut gambar
theta hanya bisa mengambil nilai
\ {0 ^ {\ circ}, 90 ^ {\ circ}, 180 ^ {\ circ}, 270 ^ {\ circ} \}\ {0 ^ {\ circ}, 90 ^ {\ circ}, 180 ^ {\ circ}, 270 ^ {\ circ} \} .
Fungsi
flip
mencerminkan gambar jika
direction
-1 dan tidak mencerminkan jika nilainya 1:
def flip(img, direction): return img[::direction,:]
Konversi lengkap dilakukan oleh fungsi
apply_transformation
:
def apply_transformation(img, direction, angle, contrast=1.0, brightness=0.0): return contrast*rotate(flip(img, direction), angle) + brightness
Kita perlu 1 bit untuk mengingat jika mirroring diperlukan, dan 2 bit untuk sudut rotasi. Apalagi jika kita menabung
s dan
b Menggunakan 8 bit untuk setiap nilai, kita hanya perlu 11 bit untuk menyimpan konversi.
Selain itu, kita harus memeriksa apakah fungsi-fungsi ini adalah pemetaan kontraksi. Buktinya sedikit membosankan, dan tidak terlalu yang kita butuhkan. Mungkin nanti saya akan menambahkannya sebagai lampiran artikel.
Kompresi
Algoritma kompresi sederhana. Pertama, kami membuat semua transformasi affine yang mungkin dari semua blok sumber menggunakan fungsi
generate_all_transformed_blocks
:
def generate_all_transformed_blocks(img, source_size, destination_size, step): factor = source_size // destination_size transformed_blocks = [] for k in range((img.shape[0] - source_size) // step + 1): for l in range((img.shape[1] - source_size) // step + 1):
Kemudian, untuk setiap blok terakhir, kami memeriksa semua blok sumber yang diubah sebelumnya dibuat. Untuk masing-masing, kami mengoptimalkan kontras dan kecerahan menggunakan metode
find_contrast_and_brightness2
, dan jika konversi yang diuji adalah yang terbaik dari yang sejauh ini ditemukan, maka simpanlah:
def compress(img, source_size, destination_size, step): transformations = [] transformed_blocks = generate_all_transformed_blocks(img, source_size, destination_size, step) for i in range(img.shape[0] // destination_size): transformations.append([]) for j in range(img.shape[1] // destination_size): print(i, j) transformations[i].append(None) min_d = float('inf')
Untuk menemukan kontras dan kecerahan terbaik, metode
find_contrast_and_brightness2
cukup memecahkan masalah kuadrat terkecil:
def find_contrast_and_brightness2(D, S):
Membongkar
Algoritma dekompresi bahkan lebih sederhana. Kami mulai dengan gambar yang benar-benar acak, dan kemudian menerapkan gambar pemerasan beberapa kali
f :
def decompress(transformations, source_size, destination_size, step, nb_iter=8): factor = source_size // destination_size height = len(transformations) * destination_size width = len(transformations[0]) * destination_size iterations = [np.random.randint(0, 256, (height, width))] cur_img = np.zeros((height, width)) for i_iter in range(nb_iter): print(i_iter) for i in range(len(transformations)): for j in range(len(transformations[i])):
Algoritma ini berfungsi karena peta kompresi memiliki titik tetap yang unik, dan apa pun sumber gambar yang kita pilih, kami akan berusaha keras untuk itu.
Saya pikir waktunya telah tiba untuk contoh kecil. Saya akan mencoba mengompres dan membuka ritsleting gambar monyet:
Fungsi
test_greyscale
memuat gambar, mengompresnya, mendekompresinya, dan menunjukkan setiap iterasi dari dekompresi:
Tidak buruk sama sekali untuk implementasi yang begitu sederhana!
Gambar RGB
Pendekatan yang sangat naif untuk mengompresi gambar RGB adalah dengan mengompresi ketiga saluran secara terpisah:
def compress_rgb(img, source_size, destination_size, step): img_r, img_g, img_b = extract_rgb(img) return [compress(img_r, source_size, destination_size, step), \ compress(img_g, source_size, destination_size, step), \ compress(img_b, source_size, destination_size, step)]
Dan untuk membongkar, kami cukup membongkar data tiga saluran secara terpisah dan mengumpulkannya dalam tiga saluran gambar:
def decompress_rgb(transformations, source_size, destination_size, step, nb_iter=8): img_r = decompress(transformations[0], source_size, destination_size, step, nb_iter)[-1] img_g = decompress(transformations[1], source_size, destination_size, step, nb_iter)[-1] img_b = decompress(transformations[2], source_size, destination_size, step, nb_iter)[-1] return assemble_rbg(img_r, img_g, img_b)
Solusi lain yang sangat sederhana adalah dengan menggunakan tampilan kompresi yang sama untuk ketiga saluran, karena mereka seringkali sangat mirip.
Jika Anda ingin memeriksa cara kerjanya, maka jalankan fungsi
test_rgb
:
Artefak muncul. Metode ini mungkin terlalu naif untuk menghasilkan hasil yang baik.
Ke mana harus pergi selanjutnya
Jika Anda ingin tahu lebih banyak tentang kompresi gambar fraktal, saya dapat merekomendasikan Anda membaca artikel
Teknik Kompresi Gambar Fraktal dan Wavelet oleh Stephen Welsted. Mudah dibaca dan menjelaskan teknik yang lebih canggih.