首先需要明确此处“图像压缩”的含义,本处提到的图像压缩是指在保证图像质量的前提下实现图像压缩。而压缩的对象是图像的存储大小。那么对图像压缩可以减少图像在传输时的带宽需求,提高网站等客户端等相应速度、提升用户体验,以及节省存储空间。
图像之所以可以被压缩时因为它有很多冗余信息。 常见图像、视频、音频数据中存在的冗余类型有:编码冗余、空间冗余、时间冗余。
假设图像的灰度级可以用离散的随机变量表示,且每个灰度级 r k r_k rk出现的概率为 P r P_r Pr,
P r ( r k ) = n k n k = 1 , 2 , 3 , … , L − 1 P_r(r_k)=\frac{n_k}{n}\quad k=1,2,3,…,L-1 Pr(rk)=nnkk=1,2,3,…,L−1
L L L指灰度级数, n k n_k nk是第k个灰度级在图像中出现的次数,n是图像中的像素总数。如果用于表示每个 r k r_k rk值的比特数为 l ( r k ) l(r_k) l(rk),则用于表示每个像素的平均比特数为:
L a v g = ∑ k = 0 l − 1 l ( r k ) P r ( r k ) L_{avg}=\sum_{k=0}^{l-1}l(r_k)P_r(r_k) Lavg=k=0∑l−1l(rk)Pr(rk)
即,不同灰度级值的平均字码长度等于每个灰度级值所用的比特数和灰度级出现的概率相乘后求和。如果某种编码的平均比特数越接近熵,则编码冗余越小,当编码冗余得到减少或消除时,就实现了数据压缩。
同异物体表面上采样点点颜色之间通常存在着空间关联性,相邻区域通常具有相似的颜色和亮度,这种情况就属于空间冗余。通过编码相似的像素值或像素块可以实现对图像的压缩。
时间冗余主要发生在视频中,视频通常由一系列连续的图像组成,相邻帧之间往往包含重复或类似的内容。通过分析视频帧之间的变化以及运动情况,选择性的保留重要信息,丢弃冗余部分可以实现对视频的高效压缩。
霍夫曼编码(Huffman Coding)是一种编码方法。霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。步骤如下:
构建灰度值频率字典
def build_freq_dict(gray_image):
freq_dict = defaultdict(int)
for row in gray_image:
for pixel in row:
freq_dict[pixel] += 1
return freq_dict
构建霍夫曼树
def build_huffman_tree(freq_dict):
heap = [[weight, [symbol, ""]] for symbol, weight in freq_dict.items()]
heapq.heapify(heap)
while len(heap) > 1:
lo = heapq.heappop(heap)
hi = heapq.heappop(heap)
for pair in lo[1:]:
pair[1] = '0' + pair[1]
for pair in hi[1:]:
pair[1] = '1' + pair[1]
heapq.heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:])
return sorted(heapq.heappop(heap)[1:], key=lambda p: (len(p[-1]), p))
对灰度值数据进行霍夫曼编码:
def huffman_encode_gray_image(gray_image):
freq_dict = build_freq_dict(gray_image)
huffman_tree = build_huffman_tree(freq_dict)
encoding_dict = {symbol: code for symbol, code in huffman_tree}
encoded_data = ''.join(encoding_dict[pixel] for row in gray_image for pixel in row)
return encoded_data, encoding_dict
对霍夫曼编码的数据进行解码
def huffman_decode_gray_image(encoded_data, encoding_dict, image_shape):
decoding_dict = {code: symbol for symbol, code in encoding_dict.items()}
decoded_data = []
code = ""
for bit in encoded_data:
code += bit
if code in decoding_dict:
symbol = decoding_dict[code]
decoded_data.append(symbol)
code = ""
decoded_image = np.array(decoded_data, dtype=np.uint8).reshape(image_shape)
return decoded_image
行程编码就是为了解决前文所提到的空间冗余,也称RLE。RLE可以用于压缩一些具有大片相同颜色的图像,如黑白二值图像或颜色较为简单的图像。RLE算法的基本思想是将连续重复出现的数据序列用一个符号表示,同时记录重复的次数。RLE编码简单直观,编码/解码速度快,因此许多图形和视频文件,如.BMP .TIFF及AVI等格式文件的压缩均采用此方法。
行程编码是一种无损压缩方法,压缩比取决于图像本身特点,相同颜色图像块越大,图像块数目越少,压缩比越高。
算术编码的基本原理是将编码的消息表示成实数0和1之间的一个间隔,消息越长,编码表示它的间隔就越小,表示这一间隔所需的二进制位就越多。具体步骤如下:
算术编码通常能够达到比霍夫曼编码和行程编码更高的压缩率,尤其在对于长序列的数据和频率分布不均匀的数据时。并且在编码过程中可以根据实际的数据分布进行动态调整,因此对于不同类型的数据能够产生更好的压缩效果。但是算术编码涉及到浮点数运算和频率累积,这些操作可能需要大量的计算资源和时间。
LZW编码用于将输入数据中的重复序列编码为短的标记,从而实现数据的压缩。LZW编码在图像、文本、音频等领域都有广泛应用,特别是在文本压缩和图像压缩中表现出色。LZW编码的主要思想是根据输入数据的重复序列构建一个字典,然后将重复序列用较短的标记来表示。编码过程中,算法从输入数据中不断读取字符,并将其与已有字典中的序列进行比较。如果找到匹配的序列,就继续读取字符,直到找不到匹配为止,然后将匹配的序列对应的标记输出。然后将当前字符添加到字典中,以便后续的匹配。
数字水印是将特定的数字信号嵌入数字产品中保护数字产品版权或完整性的技术。
简单可见水印由如下公式生成:
f w = ( 1 − α ) f + α w f_w=(1-\alpha)f+\alpha w fw=(1−α)f+αw
α \alpha α用于控制水印的可见性,f为原图,w为水印图片,通常 α \alpha α取值为0-1之间。
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("img/image.jpg")# 原图
img = img[:, :, [2, 1, 0]]
img_water = cv2.imread("img/logo.png")# 添加的水印
img_water = img_water[:, :, [2, 1, 0]]
img_ = img.copy()
for x in range(img_water.shape[0]):
for y in range(img_water.shape[1]):
img_[500+x][700+y][0:3] = 0.3 * img_[x][y][0:3] + 0.7 * img_water[x][y][0:3]
数字图像处理中已经知晓8比特位图像的最低比特对人眼感知几乎没有影响,因此,可以将水印图像的高阶比特位插入在衬底的低阶比特位中。
f w = 4 f 4 + w 64 f_w=4\frac{f}{4}+\frac{w}{64} fw=44f+64w
将原图使用无符号整数除以4并乘以4,来置最低两个比特位为0,并用64除w,将w的两个最高比特位移到衬底的最低比特位上。
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("img/image.jpg")# 原图
img = img[:, :, [2, 1, 0]]
img_water = cv2.imread("img/logo.png")# 添加的水印
img_water = img_water[:, :, [2, 1, 0]]
im = img.copy()
im = im // 4 * 4
for x in range(img_water.shape[0]):
for y in range(img_water.shape[1]):
im[x][y] += img_water[x][y] // 64