آزمایشگاه فصل ۸: آنتروپی، هافمن و 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
تمرین¶
تعداد نمادهای دارای احتمال مساوی را از ۴ به ۸ افزایش دهید؛ آنتروپی چه تغییری میکند؟
در مثال هافمن، فراوانی نماد
0را کم کنید و طول کدهای جدید را ببینید.چرا دو تصویر با آنتروپی یکسان لزوماً WebP هماندازه ندارند؟