Python |GIF 解析与构建(4):快速量化压缩256色算法
Python |GIF 解析与构建(4):快速量化压缩 256 色算法
目录
Python |GIF 解析与构建(4):快速量化压缩 256 色算法
一、引言
二、核心原理:色彩量化与动态优化
2.1 色彩空间压缩模型
2.2 动态优化策略
三、Python 实现:从图像加载到压缩全流程
3.1 关键代码解析
(1)RGB 量化函数
(2)动态压缩主逻辑
(3)质量评估与压缩率计算
3.2 执行结果示例
四、优化与扩展方向
4.1 性能优化
4.2 质量增强
4.3 GIF 适配
五、总结
一、引言
在 GIF 图像的处理中,颜色压缩是关键技术之一。本文聚焦于256 色快速量化压缩算法,结合 Python 实现,深入解析如何通过动态优化色彩空间,在保证图像质量的前提下实现高效压缩,适用于 GIF 动画、图标等对色彩数敏感的场景。
二、核心原理:色彩量化与动态优化
2.1 色彩空间压缩模型
通过RGB 分量位宽截断实现颜色量化,常见方案包括:
- 332 模型:R(3 位)、G(3 位)、B(2 位),共 28=256 种颜色
- 333 模型:R/G/B 各 3 位,共 29=512 种颜色(需进一步压缩至 256 色)
- 444 模型:R/G/B 各 4 位,共 212=4096 种颜色(需强压缩)
2.2 动态优化策略
-
自动选择压缩模型
遍历["332", "333", "444"]
模型,计算量化后颜色数:- 若颜色数≤256,直接使用该模型
- 若超过 256,回退至更低位宽模型(如 333→332)
-
哈希映射与颜色表构建
- 通过
set()
去重统计实际使用的颜色数 - 建立原始 RGB 值与量化后索引的映射表,确保像素值精准还原
- 通过
三、Python 实现:从图像加载到压缩全流程
3.1 关键代码解析
(1)RGB 量化函数
def rgb_deal(data, name="332"):if name == "332":r = data[0] >> 5 # R: 3位(0-7)g = data[1] >> 5 # G: 3位(0-7)b = data[2] >> 6 # B: 2位(0-3)return (r << 5) | (g << 2) | b # 8位组合值# 其他模型类似位运算处理
(2)动态压缩主逻辑
with Image.open("1.png").convert('RGB') as img:pixels = list(img.getdata())for k in range(3):# 计算当前模型下的颜色数hash_set = len(set([rgb_deal(bytes(i), name=list_name[k]) for i in pixels]))if hash_set > 256:k -= 1 # 回退模型break# 生成量化后的颜色索引列表image_hash = [rgb_deal(bytes(i), name=list_name[k]) for i in pixels]
(3)质量评估与压缩率计算
# PSNR(峰值信噪比)计算
def calculate_psnr(original_img, compressed_img):mse = np.mean((np.array(original_img)-np.array(compressed_img))**2)return 20 * np.log10(255.0 / np.sqrt(mse))# SSIM(结构相似性)计算
from skimage.metrics import structural_similarity as ssim
ssim_value = ssim(original_img, compressed_img, multichannel=True)
3.2 执行结果示例
尺寸 | 压缩模型 | 颜色数 | PSNR (dB) | SSIM | 压缩率 |
---|---|---|---|---|---|
1600×900 | 332 | 212 | 28.03 | 0.9138 | 46.80% |
300×160 | 444 | 30 | 35.39 | 0.8739 | 62.36% |
1260×40 | 333 | 236 | 28.16 | 0.9470 | 63.09% |
四、优化与扩展方向
4.1 性能优化
- 并行处理:使用
multiprocessing
加速像素遍历 - LUT 查找表:预先生成所有可能的 RGB 量化值映射,减少实时计算开销
4.2 质量增强
- 误差扩散算法(如 Floyd-Steinberg):通过扩散量化误差提升视觉效果
- 颜色聚类(如 K-means):替代固定位宽截断,实现更精准的色彩保留
4.3 GIF 适配
- 将量化后的 256 色转换为 GIF 专用的调色板(
ImagePalette
) - 支持动画帧批量处理,保持帧率与色彩一致性
五、总结
本文实现的256 色快速量化压缩算法具有以下特点:
- 动态适配:自动选择最优压缩模型,平衡压缩率与画质
- 高效性:基于位运算和哈希表,处理速度适用于实时场景
- 可扩展性:支持与其他图像优化技术(如 DCT 变换)结合
import os from PIL import Image""" 此算法压缩256色在gif使用的算法中属于 尺寸: 1600x900 压缩颜色种类: 212 压缩算法类别: 332 PSNR: 28.03 dB SSIM: 0.9138 原始: 350.89KB | 压缩后: 186.68KB | 压缩率: 46.80% 尺寸: 1600x900 压缩颜色种类: 214 压缩算法类别: 332 PSNR: 27.45 dB SSIM: 0.8755 原始: 664.39KB | 压缩后: 264.25KB | 压缩率: 60.23% 尺寸: 300x160 压缩颜色种类: 30 压缩算法类别: 444 PSNR: 35.39 dB SSIM: 0.8739 原始: 32.43KB | 压缩后: 12.21KB | 压缩率: 62.36% 尺寸: 485x450 压缩颜色种类: 59 压缩算法类别: 444 PSNR: 33.41 dB SSIM: 0.7623 原始: 100.68KB | 压缩后: 27.22KB | 压缩率: 72.97% 尺寸: 300x900 压缩颜色种类: 199 压缩算法类别: 332 PSNR: 26.80 dB SSIM: 0.9417 原始: 136.93KB | 压缩后: 91.08KB | 压缩率: 33.49% 尺寸: 1260x40 压缩颜色种类: 236 压缩算法类别: 333 PSNR: 28.16 dB SSIM: 0.9470 原始: 19.71KB | 压缩后: 7.28KB | 压缩率: 63.09% """# 动态优化法 def rgb_deal(data, name="332"):if name == "332":r = data[0] >> 5 # 3 bits (0-7)g = data[1] >> 5 # 3 bits (0-7)b = data[2] >> 6 # 2 bits (0-3)return (r << 5) | (g << 2) | belif name == "333":r = data[0] >> 5 # 3 bits (0-7)g = data[1] >> 5 # 3 bits (0-7)b = data[2] >> 5 # 3 bits (0-7)return (r << 6) | (g << 3) | belse:r = data[0] >> 4 # 4 bitsg = data[1] >> 4 # 4 bitsb = data[2] >> 4 # 4 bitsreturn (r << 8) | (g << 4) | b# rgb333方法 with Image.open("1.png").convert('RGB') as img:width, height = img.sizepixels = list(img.getdata())print(f"尺寸: {width}x{height}")# 优化压缩算子 list_name = ["332", "333", "444"] # 动态处理 for k in range(3):hash_set = len(set([rgb_deal(bytes(i), name=list_name[k]) for i in pixels]))if hash_set > 256:k -= 1breakimage_hash = [rgb_deal(bytes(i), name=list_name[k]) for i in pixels] image_set = set(image_hash) # 如果压缩数量大于256则压缩 print("压缩颜色种类:", len(image_set)) print("压缩算法类别:", list_name[k])hash_map = {key: (0, 0, 0) for key in image_set} # 全部哈希遍历映射字典 for s in image_set:hash_map[s] = pixels[image_hash.index(s)] # 字典赋值new_image = [hash_map.get(x, x) for x in image_hash] # RGB模式 new_img = Image.new('RGB', (width, height)) # 按行填充 new_img.putdata(new_image) # 保存为PNG文件 new_img.save('2.png', 'PNG')import numpy as np from skimage.metrics import structural_similarity as ssimdef calculate_psnr(original_img: Image.Image, compressed_img: Image.Image) -> float:"""计算两张图像之间的PSNR值"""# 将图像转换为NumPy数组original = np.array(original_img).astype(np.float64)compressed = np.array(compressed_img).astype(np.float64)# 确保图像尺寸相同if original.shape != compressed.shape:raise ValueError("两张图像的尺寸必须相同")# 计算MSE(均方误差)mse = np.mean((original - compressed) ** 2)# 避免除零错误if mse == 0:return float('inf')# 计算PSNR(假设像素值范围为0-255)max_pixel = 255.0psnr = 20 * np.log10(max_pixel / np.sqrt(mse))return psnrdef calculate_ssim(original_img: Image.Image, compressed_img: Image.Image) -> float:"""计算两张图像之间的SSIM值"""# 将图像转换为NumPy数组original = np.array(original_img).astype(np.float64)compressed = np.array(compressed_img).astype(np.float64)# 确保图像尺寸相同if original.shape != compressed.shape:raise ValueError("两张图像的尺寸必须相同")# 如果是彩色图像,分别计算每个通道的SSIM并取平均if len(original.shape) == 3 and original.shape[2] == 3: # RGB图像ssim_values = []for i in range(3): # 分别计算R、G、B通道ssim_val = ssim(original[:, :, i], compressed[:, :, i],data_range=compressed[:, :, i].max() - compressed[:, :, i].min())ssim_values.append(ssim_val)return np.mean(ssim_values)else: # 灰度图像return ssim(original, compressed,data_range=compressed.max() - compressed.min())# 加载图像 original = Image.open("1.png").convert("RGB") compressed = Image.open("2.png").convert("RGB")# 计算PSNR和SSIM psnr_value = calculate_psnr(original, compressed) ssim_value = calculate_ssim(original, compressed)print(f"PSNR: {psnr_value:.2f} dB") print(f"SSIM: {ssim_value:.4f}")# 替换为你的图片路径 original = "1.png" compressed = "2.png" # 获取文件大小(KB) o_size = os.path.getsize(original) / 1024 c_size = os.path.getsize(compressed) / 1024 # 计算并打印压缩率 ratio = (1 - c_size / o_size) * 100 print(f"原始: {o_size:.2f}KB | 压缩后: {c_size:.2f}KB | 压缩率: {ratio:.2f}%")
import os
from PIL import Image"""
此算法压缩256色在gif使用的算法中属于
尺寸: 1600x900
压缩颜色种类: 212
压缩算法类别: 332
PSNR: 28.03 dB
SSIM: 0.9138
原始: 350.89KB | 压缩后: 186.68KB | 压缩率: 46.80%
尺寸: 1600x900
压缩颜色种类: 214
压缩算法类别: 332
PSNR: 27.45 dB
SSIM: 0.8755
原始: 664.39KB | 压缩后: 264.25KB | 压缩率: 60.23%
尺寸: 300x160
压缩颜色种类: 30
压缩算法类别: 444
PSNR: 35.39 dB
SSIM: 0.8739
原始: 32.43KB | 压缩后: 12.21KB | 压缩率: 62.36%
尺寸: 485x450
压缩颜色种类: 59
压缩算法类别: 444
PSNR: 33.41 dB
SSIM: 0.7623
原始: 100.68KB | 压缩后: 27.22KB | 压缩率: 72.97%
尺寸: 300x900
压缩颜色种类: 199
压缩算法类别: 332
PSNR: 26.80 dB
SSIM: 0.9417
原始: 136.93KB | 压缩后: 91.08KB | 压缩率: 33.49%
尺寸: 1260x40
压缩颜色种类: 236
压缩算法类别: 333
PSNR: 28.16 dB
SSIM: 0.9470
原始: 19.71KB | 压缩后: 7.28KB | 压缩率: 63.09%
"""# 动态优化法
def rgb_deal(data, name="332"):if name == "332":r = data[0] >> 5 # 3 bits (0-7)g = data[1] >> 5 # 3 bits (0-7)b = data[2] >> 6 # 2 bits (0-3)return (r << 5) | (g << 2) | belif name == "333":r = data[0] >> 5 # 3 bits (0-7)g = data[1] >> 5 # 3 bits (0-7)b = data[2] >> 5 # 3 bits (0-7)return (r << 6) | (g << 3) | belse:r = data[0] >> 4 # 4 bitsg = data[1] >> 4 # 4 bitsb = data[2] >> 4 # 4 bitsreturn (r << 8) | (g << 4) | b# rgb333方法
with Image.open("1.png").convert('RGB') as img:width, height = img.sizepixels = list(img.getdata())print(f"尺寸: {width}x{height}")# 优化压缩算子
list_name = ["332", "333", "444"]
# 动态处理
for k in range(3):hash_set = len(set([rgb_deal(bytes(i), name=list_name[k]) for i in pixels]))if hash_set > 256:k -= 1breakimage_hash = [rgb_deal(bytes(i), name=list_name[k]) for i in pixels]
image_set = set(image_hash)
# 如果压缩数量大于256则压缩
print("压缩颜色种类:", len(image_set))
print("压缩算法类别:", list_name[k])hash_map = {key: (0, 0, 0) for key in image_set}
# 全部哈希遍历映射字典
for s in image_set:hash_map[s] = pixels[image_hash.index(s)] # 字典赋值new_image = [hash_map.get(x, x) for x in image_hash]
# RGB模式
new_img = Image.new('RGB', (width, height))
# 按行填充
new_img.putdata(new_image)
# 保存为PNG文件
new_img.save('2.png', 'PNG')import numpy as np
from skimage.metrics import structural_similarity as ssimdef calculate_psnr(original_img: Image.Image, compressed_img: Image.Image) -> float:"""计算两张图像之间的PSNR值"""# 将图像转换为NumPy数组original = np.array(original_img).astype(np.float64)compressed = np.array(compressed_img).astype(np.float64)# 确保图像尺寸相同if original.shape != compressed.shape:raise ValueError("两张图像的尺寸必须相同")# 计算MSE(均方误差)mse = np.mean((original - compressed) ** 2)# 避免除零错误if mse == 0:return float('inf')# 计算PSNR(假设像素值范围为0-255)max_pixel = 255.0psnr = 20 * np.log10(max_pixel / np.sqrt(mse))return psnrdef calculate_ssim(original_img: Image.Image, compressed_img: Image.Image) -> float:"""计算两张图像之间的SSIM值"""# 将图像转换为NumPy数组original = np.array(original_img).astype(np.float64)compressed = np.array(compressed_img).astype(np.float64)# 确保图像尺寸相同if original.shape != compressed.shape:raise ValueError("两张图像的尺寸必须相同")# 如果是彩色图像,分别计算每个通道的SSIM并取平均if len(original.shape) == 3 and original.shape[2] == 3: # RGB图像ssim_values = []for i in range(3): # 分别计算R、G、B通道ssim_val = ssim(original[:, :, i], compressed[:, :, i],data_range=compressed[:, :, i].max() - compressed[:, :, i].min())ssim_values.append(ssim_val)return np.mean(ssim_values)else: # 灰度图像return ssim(original, compressed,data_range=compressed.max() - compressed.min())# 加载图像
original = Image.open("1.png").convert("RGB")
compressed = Image.open("2.png").convert("RGB")# 计算PSNR和SSIM
psnr_value = calculate_psnr(original, compressed)
ssim_value = calculate_ssim(original, compressed)print(f"PSNR: {psnr_value:.2f} dB")
print(f"SSIM: {ssim_value:.4f}")# 替换为你的图片路径
original = "1.png"
compressed = "2.png"
# 获取文件大小(KB)
o_size = os.path.getsize(original) / 1024
c_size = os.path.getsize(compressed) / 1024
# 计算并打印压缩率
ratio = (1 - c_size / o_size) * 100
print(f"原始: {o_size:.2f}KB | 压缩后: {c_size:.2f}KB | 压缩率: {ratio:.2f}%")