小言_互联网的博客

数字图像处理——第八章 图像压缩

234人阅读  评论(0)

数字图像处理——第八章 图像压缩

1 图像压缩

1.1 为啥要图像压缩

图像压缩就是减少表示数字图像需要的数据量。也许你会问,就一张二维图像还减啥数据量,几个G容量能存一大堆。最近刚好在看Android,bitmap里面用的最多的数据格式是ARGB_8888,刚好算笔账:

所谓ARGB_8888:A:Alpha透明度。R:Red红色。G:Green绿色。B:Blue蓝色。ARGB8888:分别用8个bit来记录每个像素的A、R、G、B数据,就是常说的32bit位图,32位即是4个byte。假如你自拍一张,得到1920×1080的图像,用ARGB_8888存储,那么每个像素需要4byte存储,那你这张自拍共需要1920×1080×4=8294400byte,约等于8M,一张你的自拍就8M,1G只能放128张你的自拍。那如果是视频呢?1920×1080,25fps, 拍他1小时,……那就难以想象了。所以说,需要图像压缩。

1.2 为啥能图像压缩

那图像为何可以压缩呢?因为它有很多冗余信息。 常见图像、视频、音频数据中存在的冗余类型有:编码冗余、空间冗余、时间冗余。

1.2.1 编码冗余

对于图像来说,可以假设一个离散随机变量表示图像的灰度级,并且每个灰度级 r k r_{k} rk出现的概率为 p r p_{r} pr
p r ( r k ) = n k n k = 0 , 1 , 2 , ⋯   , L − 1 p_{r}\left(r_{k}\right)=\frac{n_{k}}{n} \quad k=0,1,2, \cdots, L-1 pr(rk)=nnkk=0,1,2,,L1
这里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_{a vg }=\sum_{k=0}^{l-1} l\left(r_{k}\right) p_{r}\left(r_{k}\right) Lavg=k=0l1l(rk)pr(rk)
就是说,将表示每个灰度级值所用的比特数和灰度级出现的概率相乘,将所得乘积相加后得到不同灰度级值的平均码字长度。如果某种编码的平均比特数越接近熵,則编码冗余越小。当这编码冗余中得到减少或消除时, 就实现了数据压缩。

1.2.2 空间冗余

同一景物表面上采样点的颜色之间通常存在着空间关联性,相邻各点的取值往往近似或者相同,这就是空间冗余。例如图片中有一片连续的区域,这个区域的像素都是相同的颜色,那么空间冗余就产生了。空间冗余主要发生在单张图片中。

如上图所示,相邻像素取值都是近似。 例如第一行的像素都是[176, 176, 176, 176, ……, 176, 176],如果共1920个像素,那需要4Byte×1920。那么如果空间压缩[76, 1920],表示接下来1920个像素的亮度都是176。

1.2.3 时间冗余

时间冗余主要发生在视频中。视频一般为位于一时间轴区间的一组连续画面,其中的相邻帧往往包含相同的背景和移动物体,只不过移动物体所在的空间位置略有不同,所以后一帧的数据与前一帧的数据有许多共同的地方,这种共同性是由于相邻帧记录了相邻时刻的同一场景画面,所以称为时间冗余。如下图所示,例如1秒25帧,每一帧之间都是间隔很短,前后帧的变话很少,也许物体只有细微的变化,主题背景不会发生改变。(前后两帧几乎没变化)

2 一些基本的压缩方法

2.1 霍夫曼编码

霍夫曼编码(Huffman Coding)是一种编码方法。学过数据结构这本书就一点都不陌生。霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。步骤如下:

  1. 将信源符号的概率按减小的顺序排队。
  2. 两个最小的概率相加,并继续这一步骤,始终将较高的概率分支放在右边,直到最后变成概率1。
  3. 画出由概率1处到每个信源符号的路径,顺序记下沿路径的0和1,所得就是该符号的霍夫曼码字。
  4. 将每对组合的左边一个指定为0,右边一个指定为1(或相反)。

接着试下对图片尽心霍夫曼编码,代码如下

首先将彩色图转为灰色图,此时图像的每个像素点可以用单独的像素值表示:

#定义函数,将彩色图转为灰色图,此时图像的每个像素点可以用单独的像素值表示
def picture_convert(filename,newfilename): 
    picture = Image.open(filename)
    picture = picture.convert('L')#将bmp 图片转换为灰值图
    picture. save(newfilename)#保存灰度图像
    return picture

统计每个像素出现的次数:

#定义函数,统计每个像素出现的次数
def pixel_number_caculate(list):
    pixel_number={
   }
    for i in list:
        if i not in pixel_number. keys():
            pixel_number[i]=1 #若此像素点不在字符频率字典里则直接添加
        else:
            pixel_number[i] += 1 #若存在在字符频串字典里则对应值加一 
    return pixel_number

构造节点,分別陚予其值和对应的权值:

#构造节点,分別陚予其值和对应的权值 
def node_construct(pixel_number): 
    node_list =[]
    for i in range(len(pixel_number)):
        node_list.append(node(weight=pixel_number[i][1],code=str(pixel_number[i][0])))
    return node_list

根据叶子结点列表,生成对应的霍夫曼编码树:

#根据叶子结点列表,生成对应的霍夫曼编码树
def tree_construct(listnode):
    listnode = sorted(listnode,key=lambda node:node.weight) 
    while len(listnode) != 1:
        #每次取权值的两个像素点进行合并
        low_node0,low_node1 = listnode[0], listnode[1]
        new_change_node = node()
        new_change_node.weight = low_node0.weight + low_node1.weight
        new_change_node.left = low_node0
        new_change_node.right = low_node1
        low_node0.parent = new_change_node
        low_node1.parent = new_change_node
        listnode.remove(low_node0)
        listnode.remove(low_node1)
        listnode.append(new_change_node)
        listnode = sorted(listnode, key=lambda node:node.weight) 
    return listnode

可得到结果:

2.2 行程编码

行程编码就是为了解决上述所提到的空间冗余,也称RLE。也就是在一幅图像中具有许多颜色相同的图块,在这些图块中,许多行上都具有相同的颜色,或者在一行上有许多连续的像素都具有相同的颜色值。在这种情况下就不需要存储每一个像素的颜色值,而仅仅存储一个像素的颜色值,以及具有相同颜色的像素数目就可以,或者存储像素的颜色值,以及具有相同颜色值的行数。具有相同颜色并且是连续的像素数目称为行程长度。例如,字符串AAABCDDDDDDDDBBBBB,利用RLE原理可以压缩为3ABC8D5B。RLE编码简单直观,编码/解码速度快,因此许多图形和视频文件,如.BMP .TIFF及AVI等格式文件的压缩均采用此方法。由于一幅图像中有许多颜色相同的图块,用一整数对存储一个像素的颜色值及相同颜色像素的数目。举个栗子:

上述图像分辨率为28×28,前几行基本都是0,则就可用(0, 28)来表示,其余像素同理可得。通过这样的编码方式足够直观,经济;是一种无损压缩;压缩比取决于图像本身特点,相同颜色图像块越大,图像块数目越少,压缩比越高。

2.3 算数编码

算术编码的基本原理是将编码的消息表示成实数0和1之间的一个间隔,消息越长,编码表示它的间隔就越小,表示这一间隔所需的二进制位就越多。具体步骤如下:

  1. 首先的准备工作——按照各信源信号好出现的频率,将[0, 1)这个区间分成若干段,那么每个信号源就会有自己对应的区间了;
  2. 将[0, 1)这个区间设置为初始间隔;
  3. 然后编码过程就是,按照待处理的信号,一个一个信号源读入,每读入一个信号,就将该信号源在[0, 1)上的范围等比例的缩小到最新得到的间隔中。
  4. 然后依次迭代,不断重复进行步骤3,直到最后信号中的信源信号全部读完为止;

举个例子:假设信源信号有{A, B, C, D}四个,他们的概率分别为{0.1, 0.4, 0.2, 0.3},接着对CADACDB这个信号进行编码,

做法如下:

  1. 首先读入信号C:因为C在最初始的间隔中是[0.5, 0.7),所以读入C之后我们的编码间隔就变成[0.5, 0.7)了;
  2. 然后读入的是A,A在初始区间内是占整个区间的前10%,因此对应这个上来也是需要占这个编码间隔的前10%,因此编码区间变为:[0.5, 0.52)了;
  3. 是再然后是D,因为D占整个区间的70% ~ 100%,所以也是占用这个编码区间的70% ~ 100%,操作后的编码区间为[0.514, 0.52)
  4. 直到所有的输入都全部读出,如下表所示:

2.4 LZW编码

考虑一段数据,abcabcabc,对于这样的一段数据,如果不做任何处理和压缩,假设每个字符用一个字节来表示,直接存储的空间应该是9字节。详细说明下:
下表是我们dictionary,也就是码表,字符与编码的对应。

观察上面的字符串,发现abc是重复的,如果abc能用一个字节来表示的话,那么,只需要3字节存储就足够了。意思就是以abc为一个整体,那么只要重复三遍,每遍一个字节,那么总共才需要三个字节。这就是LZW编码的motivation。

LZW算法:把一个第一次出现的字符串用一个数值来编码,在还原程序中再将这个数值还成原字符串如:用数值0x100代替字符串"abccddeee",每当出现这样的字符串时,都用0x100代替。LZW是可逆的,所有信息全部保留比多数压缩技术都复杂,压缩效率也较高。

3 数字图像水印

数字水印是将特定的数字信号嵌入数字产品中保护数字产品版权或完整性的技术。

3.1 简单的可见水印

简单可见水印由如下公式生成:
f w = ( 1 − α ) f + α w f_{w} = (1-\alpha)f+\alpha w fw=(1α)f+αw
其中α控制水印衬底的相对可见性,f为衬底,w为水印图片。通常α取值为0-1之间,效果如下所示,可以调节α的值来增强水印的效果

代码如下:

import cv2
import matplotlib.pyplot as plt
import numpy as np 
img = cv2.imread("  ")# 原图
img = img[:, :, [2, 1, 0]]
img_water = cv2.imread("  ")# 添加的水印
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_[x][y][0:3] = 0.3 * img_[x][y][0:3] + 0.7 * img_water[x][y][0:3]
        
plt.figure(dpi = 120)

plt.subplot(121)
plt.imshow(img)
plt.title("Origin picture")

plt.subplot(122)
plt.imshow(img_)
plt.title("Digital Watermark(α=0.7)")

plt.tight_layout()
plt.show()

3.2 LSB不可见水印

数字图像处理中已经知晓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 = img[:, :, [2, 1, 0]]
img_water = cv2.imread("  ")# 添加的水印
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
        
plt.figure(dpi = 120)

plt.subplot(121)
plt.imshow(im)
plt.title("Digital Watermark")

im = im % 4 / 4 * 255

plt.subplot(122)
plt.imshow(im)
plt.title("Watermark")

plt.tight_layout()
plt.show()

转载:https://blog.csdn.net/LLyj_/article/details/116724019
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场