آزمایشگاه فصل ۸: آنتروپی، هافمن و WebP¶

این دفتر مستقل از سایت و فاقد دانلود اینترنتی است. بخش‌های اول به کتابخانه استاندارد Python نیاز دارند؛ آزمایش WebP به Pillow وابسته است.

In [1]:
from collections import Counter
from math import log2

def entropy(data):
    counts = Counter(data)
    n = sum(counts.values())
    return -sum((c/n)*log2(c/n) for c in counts.values())

a = [0]*32 + [85]*16 + [170]*8 + [255]*8
print("Entropy:", entropy(a), "bits/pixel")
print("Raw 8-bit bytes:",len(a))
Entropy: 1.75 bits/pixel
Raw 8-bit bytes: 64

تفاوت طول بیت خام و طول کد هافمن¶

کد هافمن برای نمادهای پرتکرار کوتاه‌تر است. شمارش بیت‌های بدنه، هزینه سرآیند فایل را شامل نمی‌شود.

In [2]:
from collections import Counter
import heapq, itertools

def huffman_codes(seq):
    counter=itertools.count()
    heap=[]
    for symbol,n in sorted(Counter(seq).items()):
        heapq.heappush(heap,(n,next(counter),symbol))
    if len(heap)==1: return {heap[0][2]:"0"}
    while len(heap)>1:
        fa,_,a=heapq.heappop(heap)
        fb,_,b=heapq.heappop(heap)
        heapq.heappush(heap,(fa+fb,next(counter),(a,b)))
    codes={}
    def visit(tree,bits):
        if isinstance(tree,int): codes[tree]=bits
        else: visit(tree[0],bits+"0");visit(tree[1],bits+"1")
    visit(heap[0][2],"")
    return codes

codes=huffman_codes(a)
stream="".join(codes[p] for p in a)
print("Codes:",codes)
print("Body bits:",len(stream),"instead of",len(a)*2,"for 4-symbol fixed-length")
reverse={code:sym for sym,code in codes.items()}
recovered=[];cur=""
for bit in stream:
    cur+=bit
    if cur in reverse:
        recovered.append(reverse[cur]);cur=""
assert recovered==a
print("Roundtrip: exact")
Codes: {0: '0', 85: '10', 170: '110', 255: '111'}
Body bits: 112 instead of 128 for 4-symbol fixed-length
Roundtrip: exact

آنتروپی یکسان؛ ساختار مکانی متفاوت¶

دو تصویر می‌توانند هیستوگرام و آنتروپی پیکسلی یکسان داشته باشند ولی اندازه فایل WebP متفاوتی پیدا کنند.

In [3]:
import random
w=h=128
blocks=bytes([0]*64+[255]*64)*h
noisy=list(blocks)
random.Random(42).shuffle(noisy)
print("H(blocks)=",entropy(blocks),"H(scrambled)=",entropy(noisy))
H(blocks)= 1.0 H(scrambled)= 1.0

WebP: اثر کیفیت در حجم و خطا¶

اگر Pillow با پشتیبانی WebP نصب باشد، آزمایش زیر یک تصویر ساده را تولید می‌کند و بدون دسترسی به اینترنت نتیجه را می‌سنجد.

In [4]:
try:
    from PIL import Image, ImageDraw, features
except ImportError:
    print("Pillow missing; skip WebP experiment")
else:
    if not features.check("webp"):
        print("This Pillow build has no WebP support")
    else:
        import io,math
        im=Image.new("RGB",(320,220),(178,211,244))
        d=ImageDraw.Draw(im)
        d.rectangle((0,145,320,220),fill=(63,129,75))
        d.rectangle((110,65,225,170),fill=(213,148,99))
        d.polygon([(105,67),(170,23),(232,67)],fill=(130,76,55))
        for q in [30,60,90]:
            buf=io.BytesIO();im.save(buf,format="WEBP",quality=q)
            buf.seek(0);r=Image.open(buf).convert("RGB")
            a=im.tobytes();b=r.tobytes()
            mse=sum((x-y)**2 for x,y in zip(a,b))/len(a)
            psnr=float("inf") if mse==0 else 10*math.log10(255*255/mse)
            print(f"quality={q}, bytes={len(buf.getvalue())}, PSNR={psnr:.2f}dB")
quality=30, bytes=720, PSNR=35.75dB
quality=60, bytes=868, PSNR=37.67dB
quality=90, bytes=1346, PSNR=38.87dB

تمرین¶

  1. تعداد نمادهای دارای احتمال مساوی را از ۴ به ۸ افزایش دهید؛ آنتروپی چه تغییری می‌کند؟

  2. در مثال هافمن، فراوانی نماد 0 را کم کنید و طول کدهای جدید را ببینید.

  3. چرا دو تصویر با آنتروپی یکسان لزوماً WebP هم‌اندازه ندارند؟