[Python] Implementing Variable-Length Bit Packing for LZW

Discussão em 'Python' iniciado por Stack, Março 9, 2025.

  1. Stack

    Stack Membro Participativo

    roblem is that when we compress a 508×371 BMP color image using LZW (by splitting it into R, G, and B channels), the resulting compressed file is larger than the original. This happens because:

    We use a fixed 32-bit code for every LZW code, which adds overhead. The color image lacks enough repetitive patterns for LZW to achieve significant compression. Although decompression is lossless (restored image matches the original), the compression ratio is unfavorable (expansion rather than reduction).

    from PIL import Image
    import numpy as np
    import os
    import math
    import struct
    from collections import Counter


    # --- LZW Sıkıştırma ve Açma Fonksiyonları (Gray-Scale için) ---

    def lzw_compress_gray(data):
    dictionary = {bytes(): i for i in range(256)}
    dict_size = 256
    max_dict_size = 65536 # 16-bit sınırı
    w = bytes()
    compressed = []
    for pixel in data:
    wc = w + bytes([pixel])
    if wc in dictionary:
    w = wc
    else:
    compressed.append(dictionary[w])
    # Sözlüğün boyutunu sınırla:
    if dict_size < max_dict_size:
    dictionary[wc] = dict_size
    dict_size += 1
    w = bytes([pixel])
    if w:
    compressed.append(dictionary[w])
    return compressed



    def lzw_decompress_gray(codes):
    """
    LZW algoritmasıyla sıkıştırılmış kod listesini açar.
    Return: Açılmış veriyi (piksel değerleri listesi) elde eder.
    """
    dictionary = {i: bytes() for i in range(256)}
    dict_size = 256
    # İlk kodu alıp sözlüğe bakıyoruz.
    w = bytes([codes.pop(0)])
    result = [w]
    for code in codes:
    if code in dictionary:
    entry = dictionary
    Código:
            elif code == dict_size:
                entry = w + w[:1]
            else:
                raise ValueError("Geçersiz sıkıştırılmış kod: %s" % code)
            result.append(entry)
            dictionary[dict_size] = w + entry[:1]
            dict_size += 1
            w = entry
        # Sonuçları tek bir bytes dizisine dönüştürüp liste haline getiriyoruz.
        return list(b''.join(result))
    
    
    # --- Renkli Görüntü için Sıkıştırma ve Açma Fonksiyonları ---
    
    def compress_color_image(image_path, compressed_file):
        """
        1. Renkli resmi açar ve RGB bileşenlerine ayırır (her biri bir gri seviye görüntüsüdür).
        2. Her kanal için LZW ile sıkıştırma uygular.
        3. Sıkıştırılmış kodları ve görüntü boyut bilgisini tek bir binary dosyada saklar.
        4. Ayrıca her kanal için metrikler hesaplanabilir.
        """
        # Resmi aç ve RGB'ye çevir
        img = Image.open(image_path).convert("RGB")
        # Görüntü boyutları (tüm kanallar aynı boyutta)
        rows, cols = img.size[1], img.size[0]  # Pillow'da (width, height) olduğundan dikkat!
    
        # Kanal ayrımı (R, G, B)
        r, g, b = img.split()
        channels = {'R': np.array(r, dtype=np.uint8),
                    'G': np.array(g, dtype=np.uint8),
                    'B': np.array(b, dtype=np.uint8)}
    
        # Her kanal için sıkıştırılmış kodları saklamak üzere bir yapı oluşturuyoruz
        compressed_channels = {}
    
        for key in channels:
            # Düzleştir: 2D görüntüyü 1D listeye çeviriyoruz.
            data = channels[key].flatten().tolist()
            codes = lzw_compress_gray(data)
            compressed_channels[key] = codes
    
        # Sıkıştırılmış veriyi dosyaya yazıyoruz.
        # Dosya formatı:
        # 1) 4 byte: rows (unsigned int, big endian)
        # 2) 4 byte: cols (unsigned int)
        # Sonra her kanal için sırasıyla:
        # 3) 4 byte: Kanalın kod sayısı (N)
        # 4) N adet 2 byte: Her kod (unsigned short, big endian)
        with open(compressed_file, "wb") as f:
            f.write(struct.pack(">I", rows))
            f.write(struct.pack(">I", cols))
            # Kanallar R, G, B sırasıyla yazılıyor.
            for key in ['R', 'G', 'B']:
                codes = compressed_channels[key]
                f.write(struct.pack(">I", len(codes)))  # Kanal kod sayısı
                for code in codes:
                    f.write(struct.pack(">I", code))  # Her kodu 2 bayt olarak yazıyoruz.
        print("Sıkıştırma tamamlandı. Sıkıştırılmış dosya:", compressed_file)
    
    
    def decompress_color_image(compressed_file, restored_image_path):
        """
        Sıkıştırılmış dosyayı açar, içindeki bilgiden RGB kanallarını LZW açma yöntemiyle geri getirir,
        orijinal boyutlara yeniden şekillendirir ve restore edilmiş renkli resmi oluşturur.
        """
        with open(compressed_file, "rb") as f:
            # Görüntü boyutlarını oku.
            rows = struct.unpack(">I", f.read(4))[0]
            cols = struct.unpack(">I", f.read(4))[0]
            channels_restored = {}
            for key in ['R', 'G', 'B']:
                # Her kanal için kod sayısını oku.
                num_codes = struct.unpack(">I", f.read(4))[0]
                codes = [struct.unpack(">I", f.read(4))[0] for _ in range(num_codes)]
                # LZW açma işlemi
                decompressed = lzw_decompress_gray(codes)
                # Yeniden 2D'ye dönüştür (uint8 formatına çeviriyoruz)
                channel_array = np.array(decompressed, dtype=np.uint8).reshape((rows, cols))
                channels_restored[key] = channel_array
    
        # Kanalları birleştirip renkli görüntü oluşturuyoruz.
        restored_img = Image.merge("RGB", (Image.fromarray(channels_restored['R']),
                                           Image.fromarray(channels_restored['G']),
                                           Image.fromarray(channels_restored['B'])))
        restored_img.save(restored_image_path)
        print("Açma tamamlandı. Restore edilmiş resim:", restored_image_path)
        return restored_img
    
    
    def calculate_color_metrics(image_path, compressed_file):
        """
        Orijinal resim ve sıkıştırılmış dosya üzerinden metrikleri hesaplar:
        - Entropi (her kanal için ayrı ayrı hesaplanıp ortalaması alınabilir)
        - Ortalama kod uzunluğu (tüm kanalların toplam kod sayısı üzerinden)
        - Sıkıştırılmış dosya boyutu
        - Sıkıştırma oranı
        """
        # Orijinal resmin dosya boyutunu alıyoruz
        original_size = os.path.getsize(image_path)
        compressed_size = os.path.getsize(compressed_file)
    
        # Açık resmi numpy array'e çevir (RGB)
        img = np.array(Image.open(image_path).convert("RGB"))
        # Entropi: Her kanalın histogramı hesaplanıp ortalaması alınabilir.
        entropies = []
        for i in range(3):
            channel = img[:, :, i].flatten()
            hist = Counter(channel)
            total = len(channel)
            entropy = -sum((count / total) * math.log2(count / total) for count in hist.values())
            entropies.append(entropy)
        avg_entropy = sum(entropies) / 3
    
        # Ortalama kod uzunluğunu hesaplamak için,
        # dosyadaki toplam bit sayısı bölünmüş toplam sembol sayısı (R+G+B)
        total_codes = 0
        with open(compressed_file, "rb") as f:
            f.read(8)  # rows, cols
            for _ in range(3):
                num_codes = struct.unpack(">I", f.read(4))[0]
                total_codes += num_codes
                f.read(num_codes * 4)
        # Her kodu 2 bayt = 16 bit olarak sakladığımızı varsayarsak:
        avg_code_length = (total_codes * 16) / (img.shape[0] * img.shape[1] * 3)  # bit/piksel
    
        compression_ratio = compressed_size / original_size
    
        print(f"Orijinal Dosya Boyutu: {original_size} byte")
        print(f"Sıkıştırılmış Dosya Boyutu: {compressed_size} byte")
        print(f"Compression Ratio: {compression_ratio:.4f}")
        print(f"Ortalama Kod Uzunluğu: {avg_code_length:.4f} bit/piksel")
        print(f"Ortalama Entropi: {avg_entropy:.4f} bit/piksel")
    
        return avg_entropy, avg_code_length, compressed_size, compression_ratio
    
    
    # --- Ana Program Bloğu ---
    
    if __name__ == "__main__":
        image_path = "color_image.bmp"  # Renkli görüntü dosyanızın yolu
        compressed_file = "compressed_level4.bin"
        restored_image_path = "restored_level4.bmp"
    
        # Sıkıştırma aşaması
        compress_color_image(image_path, compressed_file)
        calculate_color_metrics(image_path, compressed_file)
    
        # Açma (Decompression) aşaması
        restored_img = decompress_color_image(compressed_file, restored_image_path)
    
        # Karşılaştırma: Orijinal ve açılmış görüntüleri piksel bazında kontrol edelim.
        original_img = Image.open(image_path).convert("RGB")
        if np.array_equal(np.array(original_img), np.array(restored_img)):
            print("Başarılı: Orijinal ve restore edilmiş görüntüler aynı!")
        else:
            print("Hata: Görüntüler arasında fark var.")
    
    
    
    I'm excepting .bin file should be have KB less than .bmp file
    
    [url="https://stackoverflow.com/questions/79495991/implementing-variable-length-bit-packing-for-lzw"]Continue reading...[/url]

Compartilhe esta Página