
图像压缩是减少图像数据量的技术,主要分为无损压缩 (不丢失信息)和有损压缩(允许一定信息损失以换取更高压缩率)。以下是几种经典算法及其Python实现:
一、经典图像压缩算法简介
-
行程长度编码(RLE)
无损压缩,适用于存在连续重复像素的图像(如线条图、图标)。原理是记录"像素值+连续出现次数"替代重复像素。
-
哈夫曼编码(Huffman Coding)
无损压缩,基于像素值出现频率构建变长编码(高频值用短码),广泛用于JPEG、PNG等格式。
-
离散余弦变换(DCT)
有损压缩,JPEG核心算法。将图像分块后转换到频率域,保留低频分量(视觉重要),丢弃高频分量(细节)。
-
小波变换(Wavelet Transform)
有损/无损均可,JPEG 2000采用。相比DCT能更好保留边缘信息,压缩率更高。
二、Python实现示例
1. 行程长度编码(RLE)
适用于简单图像(如二值图),实现简单且压缩效果直观。
python
import numpy as np
from PIL import Image
def rle_compress(image):
"""将灰度图像压缩为RLE编码(列表形式:[(值, 长度), ...])"""
# 将二维图像转为一维数组
flat = image.flatten()
if len(flat) == 0:
return []
compressed = []
current_val = flat[0]
count = 1
for val in flat[1:]:
if val == current_val:
count += 1
else:
compressed.append((current_val, count))
current_val = val
count = 1
# 处理最后一组
compressed.append((current_val, count))
return compressed
def rle_decompress(compressed, shape):
"""将RLE编码解压为图像"""
flat = []
for val, count in compressed:
flat.extend([val] * count)
# 恢复为原始形状
return np.array(flat).reshape(shape)
# 测试
if __name__ == "__main__":
# 读取图像并转为灰度图
img = Image.open("test_image.png").convert("L") # 替换为你的图像路径
img_array = np.array(img)
print("原始图像形状:", img_array.shape)
print("原始数据量:", img_array.size, "像素")
# 压缩
compressed = rle_compress(img_array)
print("RLE压缩后数据量:", len(compressed), "组")
# 解压
decompressed = rle_decompress(compressed, img_array.shape)
# 验证是否完全恢复(无损压缩)
print("解压后与原始是否一致:", np.array_equal(img_array, decompressed))
# 保存解压后的图像
Image.fromarray(decompressed).save("rle_decompressed.png")
2. 哈夫曼编码(Huffman Coding)
基于频率的变长编码,适合像素值分布不均匀的图像。
python
import numpy as np
from PIL import Image
import heapq
from collections import defaultdict
# 构建哈夫曼树节点
class HuffmanNode:
def __init__(self, value, freq):
self.value = value # 像素值
self.freq = freq # 频率
self.left = None
self.right = None
# 用于优先队列排序
def __lt__(self, other):
return self.freq < other.freq
# 构建哈夫曼树
def build_huffman_tree(freq_dict):
heap = [HuffmanNode(val, freq) for val, freq in freq_dict.items()]
heapq.heapify(heap)
while len(heap) > 1:
left = heapq.heappop(heap)
right = heapq.heappop(heap)
# 合并节点(值为None表示内部节点)
merged = HuffmanNode(None, left.freq + right.freq)
merged.left = left
merged.right = right
heapq.heappush(heap, merged)
return heap[0] if heap else None
# 生成哈夫曼编码表
def generate_code_table(root):
code_table = {}
def traverse(node, current_code=""):
if node is None:
return
# 叶子节点(有像素值)
if node.value is not None:
code_table[node.value] = current_code if current_code else "0"
return
# 左子树加"0",右子树加"1"
traverse(node.left, current_code + "0")
traverse(node.right, current_code + "1")
traverse(root)
return code_table
# 哈夫曼压缩
def huffman_compress(image):
flat = image.flatten()
# 统计频率
freq_dict = defaultdict(int)
for val in flat:
freq_dict[val] += 1
# 构建树和编码表
root = build_huffman_tree(freq_dict)
code_table = generate_code_table(root)
# 编码数据(转为二进制字符串)
encoded_bits = "".join([code_table[val] for val in flat])
return encoded_bits, code_table, image.shape
# 哈夫曼解压
def huffman_decompress(encoded_bits, code_table, shape):
# 反转编码表(编码->值)
reverse_table = {code: val for val, code in code_table.items()}
current_code = ""
flat = []
for bit in encoded_bits:
current_code += bit
if current_code in reverse_table:
flat.append(reverse_table[current_code])
current_code = ""
return np.array(flat).reshape(shape)
# 测试
if __name__ == "__main__":
img = Image.open("test_image.png").convert("L")
img_array = np.array(img)
print("原始图像形状:", img_array.shape)
print("原始数据量:", img_array.size * 8, "比特(假设8位灰度)")
# 压缩
encoded_bits, code_table, shape = huffman_compress(img_array)
print("哈夫曼压缩后数据量:", len(encoded_bits), "比特")
print("压缩率:", len(encoded_bits)/(img_array.size * 8))
# 解压
decompressed = huffman_decompress(encoded_bits, code_table, shape)
# 验证无损性
print("解压后与原始是否一致:", np.array_equal(img_array, decompressed))
Image.fromarray(decompressed).save("huffman_decompressed.png")
3. 离散余弦变换(DCT)
JPEG核心算法,有损压缩,通过丢弃高频分量减少数据量。
python
import numpy as np
from PIL import Image
from scipy.fftpack import dct, idct
# JPEG标准亮度量化矩阵(用于有损压缩)
QUANTIZATION_MATRIX = np.array([
[16, 11, 10, 16, 24, 40, 51, 61],
[12, 12, 14, 19, 26, 58, 60, 55],
[14, 13, 16, 24, 40, 57, 69, 56],
[14, 17, 22, 29, 51, 87, 80, 62],
[18, 22, 37, 56, 68, 109, 103, 77],
[24, 35, 55, 64, 81, 104, 113, 92],
[49, 64, 78, 87, 103, 121, 120, 101],
[72, 92, 95, 98, 112, 100, 103, 99]
], dtype=np.float32)
def dct_compress(image, quant_matrix=QUANTIZATION_MATRIX):
"""DCT压缩:分块->DCT->量化"""
h, w = image.shape
# 确保尺寸是8的倍数(JPEG分块大小为8x8)
h_padded = ((h + 7) // 8) * 8
w_padded = ((w + 7) // 8) * 8
padded = np.pad(image, ((0, h_padded - h), (0, w_padded - w)), mode='constant')
compressed = np.zeros_like(padded, dtype=np.int32)
# 对每个8x8块进行处理
for i in range(0, h_padded, 8):
for j in range(0, w_padded, 8):
block = padded[i:i+8, j:j+8]
# 转换为[-128, 127](DCT通常基于零均值)
block_shifted = block - 128
# 2D DCT变换
dct_block = dct(dct(block_shifted, axis=0, norm='ortho'), axis=1, norm='ortho')
# 量化(除以量化矩阵并取整,核心压缩步骤)
quant_block = np.round(dct_block / quant_matrix).astype(np.int32)
compressed[i:i+8, j:j+8] = quant_block
return compressed, (h, w) # 保留原始尺寸用于解压
def dct_decompress(compressed, original_shape, quant_matrix=QUANTIZATION_MATRIX):
"""DCT解压:反量化->IDCT->拼接"""
h_padded, w_padded = compressed.shape
h, w = original_shape
decompressed = np.zeros_like(compressed, dtype=np.float32)
# 对每个8x8块处理
for i in range(0, h_padded, 8):
for j in range(0, w_padded, 8):
quant_block = compressed[i:i+8, j:j+8]
# 反量化
dct_block = quant_block * quant_matrix
# 逆DCT变换
block_shifted = idct(idct(dct_block, axis=0, norm='ortho'), axis=1, norm='ortho')
# 恢复到[0, 255]
block = np.round(block_shifted + 128).clip(0, 255).astype(np.uint8)
decompressed[i:i+8, j:j+8] = block
# 裁剪回原始尺寸
return decompressed[:h, :w].astype(np.uint8)
# 测试
if __name__ == "__main__":
img = Image.open("test_image.png").convert("L")
img_array = np.array(img)
print("原始图像形状:", img_array.shape)
# 压缩
compressed, original_shape = dct_compress(img_array)
# 解压(会有损失)
decompressed = dct_decompress(compressed, original_shape)
# 保存结果
Image.fromarray(decompressed).save("dct_decompressed.png")
print("DCT压缩解压完成(有损压缩,可能有画质损失)")
三、算法对比与应用场景
算法 | 类型 | 压缩率 | 适用场景 | 典型应用 |
---|---|---|---|---|
RLE | 无损 | 低 | 连续重复像素多的图像(图标) | BMP、Fax格式 |
哈夫曼编码 | 无损 | 中 | 像素分布不均匀的图像 | JPEG、PNG、ZIP |
DCT | 有损 | 高 | 自然图像(人眼对细节不敏感) | JPEG |
小波变换 | 有损/无损 | 高 | 需要高压缩率+保留细节的场景 | JPEG 2000、医学图像 |
以上实现均为简化版本,实际工业级压缩(如JPEG)会结合多种算法(如DCT+哈夫曼编码)并优化细节(如色彩空间转换、熵编码)。