基础知识
数据压缩是指减少表示给定信息量所需数据量的处理,其中,数据是信息传递的手段,包含不相关或重复信息的表示称之为冗余数据,即字面意思上的多余数据。用
b
b
b比特表示的相对数据冗余
R
R
R是
R
=
1
−
1
/
C
R=1-1/C
R=1−1/C,其中,
C
C
C称为压缩率,其定义为
C
=
b
/
b
′
C=b / b^{\prime}
C=b/b′,在该式中,
b
b
b通常是以二维灰度值阵列表示一幅图像所需的比特数。
二维灰度阵列受如下三种类型的数据冗余的影响,分别是编码冗余,空间和时间冗余和不相关的信息。下面来逐一介绍。
冗余编码
如果用于表示每个 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}_{\mathrm{{avg}}} = \mathop{\sum }\limits_{{k = 0}}^{{L - 1}}l\left( {r}_{k}\right) {p}_{r}\left( {r}_{k}\right)
Lavg=k=0∑L−1l(rk)pr(rk)
如果用自然的
m
m
m比特固定长度的码来表示灰度,那么上式的右侧将减少为
m
m
m比特。
当对事件集合分配码的时候,如果不取全部事件概率的优势,就会出现编码冗余。自然二进制编码对最大和最小可能值分配相同的比特数,无法使上式最小,从而产生了编码冗余。
空间冗余和时间冗余
在大部分图像中,像素是空间和时间相关的,单个像素携带的信息少,而且,一个像素可由其相邻像素推断出来,那么它的视觉贡献大多数是冗余的,为减少其冗余,须将二维灰度阵列变换为一种更为有效的方式,这种变换称之为映射,其中,如果原始二维灰度阵列的像素可以根据变换后的数据集合准确无误地重建,称之为可逆映射,反之为不可逆映射。
不相关的信息
压缩数据集最简单的方法之一就是消除多余数据。消除冗余是可能的,因为·这种信息对本身我们所期望的目标图像的用途并不起关键作用。去除冗余信息会导致定量信息的损失,这种去除方式称之为量化,它通常意味着将较宽范围的输入值映射为有限数量的输出值且量化操作不可逆。
图像信息的度量
一个具有概率
P
(
E
)
P(E)
P(E)的随机事件
E
E
E可被说成包含
I
(
E
)
=
log
1
P
(
E
)
=
−
log
P
(
E
)
I\left( E\right) = \log \frac{1}{P\left( E\right) } = - \log P\left( E\right)
I(E)=logP(E)1=−logP(E)单位的信息。如果以
m
m
m为底的对数,则这种度量称为
m
m
m元单位。如果底为2,则信息的单位是比特。
从一个可能事件的离散集合
{
a
1
,
a
2
,
.
.
.
a
J
}
\left \{ a_1,a_2,...a_J \right \}
{a1,a2,...aJ},给定一个独立统计随机事件的信源,与该集合相联系的概率为
{
P
(
a
1
)
,
P
(
a
2
)
,
.
.
.
P
(
a
J
)
}
\left \{ P(a_1),P(a_2),...P(a_J) \right \}
{P(a1),P(a2),...P(aJ)},则每个信源输出的平均信息称为该信源的熵,即
H
=
−
∑
j
=
1
J
P
(
a
j
)
log
P
(
a
j
)
H = - \mathop{\sum }\limits_{{j = 1}}^{J}P\left( {a}_{j}\right) \log P\left( {a}_{j}\right)
H=−j=1∑JP(aj)logP(aj),其中
a
j
a_j
aj称为信源符号,信源称为零记忆信源。
山农第一定理,也称为无噪声编码定理,山农使用一个单一码字考察了 n n n个连续信源符号表示的组合。当信息源的输出依赖于前面有限数量的输出时,那么该信源称为马尔可夫信源或有限记忆信源。
保真度准则
去除“与视觉不相关”信息会导致一定数量图像信息的丢失,当信息损失可以表示为压缩处理的输入和输出的数学函数时,则称其是以客观保真度准则为基础的。令
f
(
x
,
y
)
f(x,y)
f(x,y)是输入图像,
f
^
(
x
,
y
)
\widehat{f}\left( {x,y}\right)
f
(x,y)是它的近似,其误差
e
(
x
,
y
)
e(x,y)
e(x,y)表示为
e
(
x
,
y
)
=
f
^
(
x
,
y
)
−
f
(
x
,
y
)
e(x,y)=\widehat{f}\left( {x,y}\right)-f(x,y)
e(x,y)=f
(x,y)−f(x,y),两幅图像的总误差为
∑
x
=
0
M
−
1
∑
y
=
0
N
−
1
[
f
^
(
x
,
y
)
−
f
(
x
,
y
)
]
\mathop{\sum }\limits_{{x = 0}}^{{M - 1}}\mathop{\sum }\limits_{{y = 0}}^{{N - 1}}\left\lbrack {\widehat{f}\left( {x,y}\right) - f\left( {x,y}\right) }\right\rbrack
x=0∑M−1y=0∑N−1[f
(x,y)−f(x,y)]
使用人的主观评估图像更为恰当,下表是一种评估标准。
图像压缩模型
图像压缩系统由编码器和解码器组成,编码器执行压缩操作,解码器执行解压缩操作。例如,codec是一个具有编码和解码能力的装置或程序。下图是一个通用图像压缩系统的功能流程图。
如果输出图像是输入图像的精确复制品,则压缩系统被称为无误差的、无损的或者信息保持的压缩系统****。反之称为有损压缩系统。
一些基本的压缩方法
霍夫曼编码
当单独地对信源的符号进行编码时,霍夫曼编码对每个信源符号产生可能最小数量的编码符号,信源符号不是图像的灰度就是一个灰度映射操作的输出。下图针对二进制编码说明了霍夫曼处理。
首先对所有符号概率进行排序,从而创建一个简化信源序列。其次对每个化简后的信源进行编码,这个编码的平均长度为
L
avg
=
(
0.4
)
(
1
)
+
(
0.3
)
(
2
)
+
(
0.1
)
(
3
)
+
(
0.1
)
(
4
)
+
(
0.06
)
(
5
)
+
(
0.04
)
(
5
)
=
2.2
比特/像素
{L}_{\text{avg }} = \left( {0.4}\right) \left( 1\right) + \left( {0.3}\right) \left( 2\right) + \left( {0.1}\right) \left( 3\right) + \left( {0.1}\right) \left( 4\right) + \left( {0.06}\right) \left( 5\right) + \left( {0.04}\right) \left( 5\right) = {2.2}\text{比特/像素}
Lavg =(0.4)(1)+(0.3)(2)+(0.1)(3)+(0.1)(4)+(0.06)(5)+(0.04)(5)=2.2比特/像素
在编码建立之后,编码和或/无误差解码就简单地以查找表的方式完成。
对于有
J
J
J个信源符号情况,需要
J
J
J个符号概率,
J
−
2
J-2
J−2次信源·简化和
J
−
2
J-2
J−2次编码赋值,使用预计算的霍夫曼编码可以达到“接近最佳”编码。
Golomb编码
Golomb编码是一种无损数据压缩编码方法,由Solomon W. Golomb在1966年提出。它主要用于将非负整数序列编码成比特流,尤其适用于数据分布服从指数分布的情况。
原理
(1)Golomb编码基于两个基本参数:m(Golomb参数)和b(除数),其中m通常是2的整数幂,b是用来确定除法的参数。
(2)对于待编码的非负整数n,Golomb编码将n分成两部分:商部分q和余数部分r。商部分q由整数除法(n除以m)得到,余数r由模m运算得到。
解码过程
(1)计算商部分q:
q
=
n
/
/
m
q = n // m
q=n//m
(2)计算余数部分r:r = n % m
(3)如果r小于b,则使用Unary编码(一种编码方法,用来表示小于b的整数,即r个1后跟一个0),否则使用二进制表示r。
(4)最终编码由q和r的组合构成。
示例
假设m=4,b=2,对于待编码数值n=11:
(1)计算q:q = 11 // 4 = 2
(2)计算r:r = 11 % 4 = 3
(3)因为r = 3,大于b=2,所以用二进制编码r=3,即11。
(4)最终编码结果为:Golomb编码为"1011"。
算术编码
与其他变长编码不同,算数编码生成的是非块码。算术用于将数据序列转换为更短的比特流。它的原理是将整个数据流中的符号映射到一个单一的数值区间内,并根据符号的概率分布来对这个区间进行逐步的二进制分割。下图使用了一个具体案例来说明算术编码的基本过程。
其编码的基本过程可概括为如下步骤:
(1)初始化一个区间,通常为[0, 1)。
(2)根据输入符号序列的概率分布,将初始区间逐步缩小,使其逐步逼近目标符号对应的区间。
(3)最终编码结果为落入最终区间范围内的一个二进制数值。
解码主要指接收者利用相同的概率模型重建出相同区间范围,然后反向推导出原始符号序列。
LZW编码
LZW(Lempel-Ziv-Welch)编码是一种常用的无损数据压缩算法,它的主要思想是利用字典来存储已经出现过的字符串,并将长的字符串替换为较短的标记,从而实现数据的压缩。
示例
1.假设有一个输入序列为"ABABABAABABA":
初始字典包含所有单个字符和一些常见的字符串。
2.第一步:读取"A",在字典中找不到,输出"A"的索引(例如1)并将"A"加入字典。
3.第二步:读取"AB",在字典中找不到,输出"A"的索引(1)并将"AB"加入字典。
4.依此类推,直到整个输入序列被编码。
行程编码
行程编码是一种简单且有效的无损数据压缩技术,其基本原理是将连续重复出现的数据序列用一个计数值和对应的数据值来表示,从而实现数据压缩。当相同像素的行程较小时,行程编码会导致数据扩展。
尽管行程编码是一种压缩二值图像的方法,但对行程本身进行变长编码可以实现额外的压缩。对于白色行程的熵,表示为
H
1
H_1
H1,其近似行程熵为
H
R
L
=
H
0
+
H
1
L
0
+
L
1
{H}_{\mathrm{{RL}}} = \frac{{H}_{0} + {H}_{1}}{{L}_{0} + {L}_{1}}
HRL=L0+L1H0+H1
L
0
L_0
L0和
L
1
L_1
L1分别表示黑白色行程的平均值。该式提供了对二值图像的行程使用变长编码时所要求的每像素平均比特数的估计。
示例
假设有一个简单的文本序列:“AAAABBBCCDAA”:
(1)行程编码将其编码为:“4A3B2C1D2A”。
(2)连续的"A"有4个,所以编码为"4A"。
(3)连续的"B"有3个,编码为"3B"。
(4)连续的"C"有2个,编码为"2C"。
(5)“D"只出现一次,编码为"1D”。
(6)最后连续的"A"有2个,编码为"2A"。
比特平面编码
比特平面编码主要用于图像压缩和图像分析中。这种编码方法的核心思想是将图像的每个像素值分解为其二进制位,并将这些位按平面组织,从而简化图像的数据表示和处理。一幅 m m m比特单色图像的灰度可用下式表示
a
m
−
1
2
m
−
1
+
a
m
−
2
2
m
−
2
+
⋯
+
a
1
2
1
+
a
0
2
0
{a}_{m - 1}{2}^{m - 1} + {a}_{m - 2}{2}^{m - 2} + \cdots + {a}_{1}{2}^{1} + {a}_{0}{2}^{0}
am−12m−1+am−22m−2+⋯+a121+a020
基本概念
1.比特平面:在比特平面编码中,一个图像的像素值(通常为8位、16位或更高位深度)被分解为多个比特平面。例如,对于8位灰度图像,每个像素值可以表示为8个比特,这样就可以形成8个比特平面,其中每个平面只包含图像中所有像素在同一位上的值。
2.像素拆分: 例如,在一个8位灰度图像中,像素值的二进制形式为11010101,那么将这个像素值分成8个比特的比特平面后,第一平面(最低位)将是所有像素第1位的值,第二平面将是所有像素第2位的值,依此类推,直到第8平面。
步骤
1.转换为比特平面: 将整个图像转换为多个比特平面。每个平面表示的是所有像素在特定二进制位上的值。
2.平面处理: 对每个比特平面进行压缩,通常使用简单的编码技术,如游程编码(RLE)或霍夫曼编码,以减少数据量。
3.组合比特平面: 在解码时,将所有比特平面组合起来,重建原始图像。
块变换编码
块变换编码:通过对图像或视频帧进行分块处理,并对每个块应用数学变换来进行编码。这种技术广泛应用于图像压缩标准和视频压缩标准。下图显示了一个块变换编码系统。
编码器执行4种操作,分别是子图像分解、变换、量化和编码。然后在量化阶段,以一种预定义的方式选择性地消除或粗略性地量化携带最少信息的系数。任何或所有的变换编码步骤都可以根据局部图像内容进行适应性调整,称为自适应变换编码,若这些步骤对于所有子图像都是固定的,称为非自适应变换编码。
步骤
1.分块: 将图像或视频帧划分为多个固定大小的小块。例如,常见的块大小有8x8或16x16像素。
2.变换: 对每个块应用数学变换,将其从空间域转换到变换域(如频域)。常用的变换包括:
离散余弦变换(DCT): 最常见的变换,特别是在JPEG图像压缩中使用。
离散小波变换(DWT): 用于更高级的压缩算法,如JPEG 2000。
3.量化: 将变换后的系数进行量化,将其转换为离散的值。量化过程是有损的,即信息丢失是不可避免的,但它有助于减少数据的冗余。
4.编码: 对量化后的系数进行编码,通常使用无损编码技术(如霍夫曼编码)来进一步压缩数据。
5.压缩: 经过编码的数据被压缩以减少文件大小。
预测编码
预测编码通过利用图像或视频序列中相邻帧之间的相关性来减少数据冗余。其基本思想是通过预测当前帧的值,利用前一帧的信息来减少所需传输的数据量。
无损预测编码
下图显示了一个无损预测编码系统的基本组成。该系统由一个编码器和一个解码器组成,它们均包含有一个相同的预测器
运动补偿预测残差
它通过分析视频序列中相邻帧之间的运动来提高编码效率,利用运动估计和运动补偿技术来降低视频数据的冗余。下图说明了运动补偿预测编码的基本原理。每个视频帧都被分解成不重叠的矩形区域,这些区域称为宏块,每个宏块相对于它在前一视频帧中最合适的位置运动,以运动向量来编码。B帧要求对压缩的码流重新排序,以使这些帧以合适的编码序列提供给解码器。
有损预测编码
如下图所示,代替无误差编码器的取最接近整数功能的量化器被插入到了符号编码器和形成预测误差的那一点之间。
小波编码
小波编码是一种基于小波变换的图像和视频压缩技术。与传统的变换编码方法不同,小波编码使用小波变换将图像从空间域转换到小波域,以实现高效的压缩。小波变换的优点在于其能够同时在时间和频率域上提供多分辨率的信息,使得在图像压缩中更好地捕捉和表示图像的细节。下图显示了一个典型的小波编码系统。
步骤:
1.小波变换: 使用小波变换将图像分解成不同的子带,每个子带包含了不同尺度的频率信息。通常分解为四个子带:低频(LL)、水平高频(LH)、垂直高频(HL)、和对角线高频(HH)。
2.量化: 对小波变换后的系数进行量化。量化过程将小波系数映射到离散的值,以减少数据的位数。量化通常根据小波系数的重要性进行,不同子带的量化程度可能不同。
3.编码: 使用熵编码技术(如霍夫曼编码、算术编码)对量化后的系数进行压缩。熵编码能够有效地处理数据中重复出现的模式,进一步减少数据量。
4.重建: 在解码时,使用量化后的系数和逆小波变换来重建图像。逆小波变换将小波域的系数转换回空间域,恢复原始图像。
示例
假设我们有一个简单的8x8灰度图像块进行小波编码。我们将展示一个简单的小波变换过程。
原始图像块
[ 52, 55, 61, 66, 70, 61, 64, 73 ]
[ 63, 59, 55, 90, 109, 85, 69, 72 ]
[ 62, 59, 68, 113, 144, 104, 66, 73 ]
[ 64, 58, 71, 122, 154, 106, 70, 69 ]
[ 67, 61, 68, 104, 126, 88, 68, 70 ]
[ 79, 65, 60, 70, 77, 68, 58, 75 ]
[ 85, 71, 64, 59, 55, 61, 65, 83 ]
[ 87, 79, 69, 68, 65, 73, 78, 90 ]
小波变换
1.分解图像块: 使用离散小波变换(例如Haar小波),将图像块分解为低频(LL)和高频(LH、HL、HH)子带。假设经过变换得到的系数如下:
LL:
[ 57, 60, 85, 76 ]
[ 61, 67, 93, 83 ]
[ 69, 75, 99, 88 ]
[ 64, 69, 90, 77 ]
LH:
[ 14, -12, -8, 10 ]
[ 11, -6, -5, 12 ]
[ -8, 10, 9, -4 ]
[ -3, 12, -7, 15 ]
HL:
[ 3, -8, 12, -6 ]
[ -15, 10, -7, -1 ]
[ 5, -2, 7, -8 ]
[ 4, -3, -4, -9 ]
HH:
[ -5, -1, 7, -9 ]
[ 11, -4, -6, 0 ]
[ -8, 9, -3, 12 ]
[ 10, -2, -7, 3 ]
2.量化: 对每个子带的系数进行量化。例如,将小波系数除以一个量化因子,并四舍五入:
量化因子 = 5
量化后的LL:
[ 11, 12, 17, 15 ]
[ 12, 13, 19, 17 ]
[ 14, 15, 20, 18 ]
[ 13, 14, 18, 15 ]
量化后的LH:
[ 3, -2, -2, 2 ]
[ 2, -1, -1, 2 ]
[ -2, 2, 2, -1 ]
[ -1, 2, -1, 3 ]
量化后的HL:
[ 0, -2, 2, -1 ]
[ -3, 2, -1, -0 ]
[ 1, -0, 1, -2 ]
[ 1, -1, -1, -2 ]
量化后的HH:
[ -1, -0, 1, -2 ]
[ 2, -1, -1, 0 ]
[ -2, 2, -1, 2 ]
[ 2, -0, -1, 1 ]
3.编码: 对量化后的系数进行霍夫曼编码或算术编码。
重建图像
1.解码: 将编码后的数据解码为量化系数。
2.逆量化: 将量化系数乘以量化因子,恢复到小波系数。
3.逆小波变换: 使用逆小波变换将小波系数转换回空间域,重建图像块。
数字图像水印
水印图像主要有以下应用场景:
(1)版权识别;(2)用户识别或指纹;(3)著作权认定;(4)自动监视;(5)复制保护。
代码如下
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
img_t = Image.open('cat.jpg')
img_size_t = img_t.size
w = img_t.width
h = img_t.height
f = img_t.format
print(img_size_t)
print(w,h,f)
def watermark_Image3(img_path, output_path, text):
img = Image.open(img_path)
w = img.width
h = img.height
drawing = ImageDraw.Draw(img)
text_t = text
init_x = 100
init_y = 200
for x in range(init_x, w, 800):
for y in range(init_y, h, 200):
font = ImageFont.truetype(r'C:/Windows/Fonts/STHupo.ttf', size=100)
drawing.text((x,y), '{}!'.format(text_t), font=font, fill='red')
if init_y <= 200:
init_y += 100
else:
init_y -= 100
img.show()
img.save(output_path)
img_path_t = 'cat.jpg'
watermark_Image3(img_path_t, 'cat_watermarked3.jpg', 'cat喵喵')
效果如下图