爱美容
当前位置: 首页 美容百科

基于深度学习压缩算法(深入解析数据压缩算法)

时间:2023-05-23 作者: 小编 阅读量: 1 栏目名: 美容百科

数据压缩的主要目的还是减少数据传输或者转移过程中的数据量。适用于数据中存在大量重固子串的情况。压缩完成后将串表丢弃。输入为17个7位ASC字符,总共119位,输出为13个8位编码,总共104位,压缩比为87%。GIF规范规定的是12位,超过12位的表达范围就推倒重来,并且GIF为了提高压缩率,采用的是变长的字长。另外GIF还规定了一个结束标志END,它的值是清除标志CLEAR再加1。其它两种情况初始的字长就为5位和9位。

1、为什么要做数据压缩?

数据压缩的主要目的还是减少数据传输或者转移过程中的数据量。

2、什么是数据压缩?

是指在不丢失信息的前提下,缩减数据量以减少存储空间,提高传输、存储和处理效率的一种技术方法。或者是按照一定的算法对数据进行重新组织,减少数据的冗余和存储的空间。

3、常见的数据压缩算法

(1).LZW压缩

LZW压缩是一种无损压缩,应用于gif图片。适用于数据中存在大量重固子串的情况。

原理:

LZW算法中,首先建立一个字符串表,把每一个第一次出现的字符串放入串表中,并用一个数字来表示,这个数字与此字符串在串表中的位置有关,并将这个数字存入压缩文件中,如果这个字符串再次出现时,即可用表示它的数字来代替,并将这个数字存入文件中。压缩完成后将串表丢弃。如"print" 字符串,如果在压缩时用266表示,只要再次出现,均用266表示,并将"print"字符串存入串表中,在图象解码时遇到数字266,即可从串表中查出266所代表的字符串"print",在解压缩时,串表可以根据压缩数据重新生成。

编码过程:

编码后输出:41 42 52 41 43 41 44 81 83 82 88 41 80。输入为17个7位ASC字符,总共119位,输出为13个8位编码,总共104位,压缩比为87%。

解码过程:

对输出的41 42 52 41 43 41 44 81 83 82 88 41 80进行解码,如下表所示:

解码后输出:

ABRACADABRABRABRA

特殊标记:

随着新的串(string)不断被发现,标号也会不断地增长,如果原数据过大,生成的标号集(string table)会越来越大,这时候操作这个集合就会产生效率问题。如何避免这个问题呢?Gif在采用lzw算法的做法是当标号集足够大的时候,就不能增大了,干脆从头开始再来,在这个位置要插入一个标号,就是清除标志CLEAR,表示从这里我重新开始构造字典,以前的所有标记作废,开始使用新的标记。

这时候又有一个问题出现,足够大是多大?这个标号集的大小为比较合适呢?理论上是标号集大小越大,则压缩比率就越高,但开销也越高。 一般根据处理速度和内存空间连个因素来选定。GIF规范规定的是12位,超过12位的表达范围就推倒重来,并且GIF为了提高压缩率,采用的是变长的字长。比如说原始数据是8位,那么一开始,先加上一位再说,开始的字长就成了9位,然后开始加标号,当标号加到512时,也就是超过9为所能表达的最大数据时,也就意味着后面的标号要用10位字长才能表示了,那么从这里开始,后面的字长就是10位了。依此类推,到了2^12也就是4096时,在这里插一个清除标志,从后面开始,从9位再来。

GIF规定的清除标志CLEAR的数值是原始数据字长表示的最大值加1,如果原始数据字长是8,那么清除标志就是256,如果原始数据字长为4那么就是16。另外GIF还规定了一个结束标志END,它的值是清除标志CLEAR再加1。由于GIF规定的位数有1位(单色图),4位(16色)和8位(256色),而1位的情况下如果只扩展1位,只能表示4种状态,那么加上一个清除标志和结束标志就用完了,所以1位的情况下就必须扩充到3位。其它两种情况初始的字长就为5位和9位。

代码示例:

[cpp] view plain copy

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <map>
  5. #include <algorithm>
  6. #include <vector>
  7. using namespace std;
  8. long len=0;//原字符串的长度
  9. long loc=0;//去重之后字符串的长度
  10. map<string,long> dictionary;
  11. vector <long> result;
  12. #define MAX 100;
  13. void LZWcode(string a,string s)
  14. {
  15. //memset(&result,0,sizeof(int));
  16. string W,K;
  17. for(long i=0;i<loc;i)
  18. {
  19. string s1;
  20. s1=s[i];//将单个字符转换为字符串
  21. dictionary[s1]=i 1;
  22. }
  23. W=a[0];
  24. loc =1;
  25. for(int i=0;i<len-1;i)
  26. {
  27. K=a[i 1];
  28. string firstT=W;
  29. string secontT=W;
  30. if(dictionary.count(firstT.append(K))!=0)//map的函数count(n),返回的是map容器中出现n的次数
  31. W=firstT;
  32. else
  33. {
  34. result.push_back(dictionary[W]);
  35. dictionary[secontT.append(K)]=loc;
  36. W=K;
  37. }
  38. }
  39. if(!W.empty())
  40. result.push_back(dictionary[W]);
  41. for(int i=0;i<result.size();i)
  42. cout<<result[i];
  43. }
  44. void LZWdecode(int *s,int n)
  45. {
  46. string nS;
  47. for(int i=0;i<n;i)
  48. for(map<string,long>::iterator it=dictionary.begin(); it!=dictionary.end();it)
  49. if(it->second==s[i])
  50. {
  51. cout<<it->first<<" ";
  52. }
  53. for(map<string,long>::iterator it=dictionary.begin(); it!=dictionary.end();it)//输出压缩编码的字典表
  54. cout<<it->first<<" "<<it->second<<endl;
  55. }
  56. int main(int argc, char const *argv[])
  57. {
  58. cout<<"本程序的解码是根据输入的编码字符进行的解码,并不是全256 的字符"<<endl;
  59. cout<<"选择序号:"<<endl;
  60. cout<<"1.压缩编码 2.解码"<<endl;
  61. int n;
  62. while(scanf("%d",&n)!=EOF)
  63. {
  64. switch(n)
  65. {
  66. case 1:
  67. {
  68. char s[100],a[100];
  69. cout<<"输入一串字符:"<<endl;
  70. cin>>s;
  71. len=strlen(s);
  72. for(int i=0;i<len;i)
  73. a[i]=s[i];
  74. sort(s,s len);//排序
  75. loc=unique(s,s len)-s;//去重
  76. LZWcode(a,s);
  77. break;
  78. }
  79. case 2:
  80. {
  81. cout<<"输入解码数组的长度:"<<endl;
  82. int changdu;
  83. cin>>changdu;
  84. cout<<"输入解码数串(每个数串以空格隔开):"<<endl;
  85. int s[changdu];
  86. for(int i=0;i<changdu;i)
  87. cin>>s[i];
  88. LZWdecode(s, changdu);
  89. break;
  90. }
  91. default:
  92. cout<<"你的输入不正确,请从重新开始"<<endl;
  93. }
  94. if(n==2)
  95. {
  96. auto iter=result.begin(); // 每次正确输入结束后对结果进行清零
  97. while(iter!=result.end())
  98. result.erase(iter);
  99. }
  100. }
  101. return 0;
  102. }

(2).霍夫曼压缩

哈夫曼编码是无损压缩当中最好的方法。它使用预先二进制描述来替换每个符号,长度由特殊符号出现的频率决定。常见的符号需要很少的位来表示,而不常见的符号需要很多位来表示。哈夫曼算法在改变任何符号二进制编码引起少量密集表现方面是最佳的。然而,它并不处理符号的顺序和重复或序号的序列。

原理:

利用数据出现的次数构造Huffman二叉树,并且出现次数较多的数据在树的上层,出现次数较少的数据在树的下层。于是,我们就可以从根节点到每个数据的路径来进行编码并实现压缩。

编码过程:

假设有一个包含100000个字符的数据文件要压缩存储。各字符在该文件中的出现频度如下所示:

在此,我会给出常规编码的方法和Huffman编码两种方法,这便于我们比较。

常规编码方法:我们为每个字符赋予一个三位的编码,于是有:

此时,100000个字符进行编码需要100000 * 3 = 300000位。

Huffman编码:利用字符出现的频度构造二叉树,构造二叉树的过程也就是编码的过程。

这种情况下,对100000个字符编码需要:(45 * 1(1613129)*3(95)*4) * 1000 = 224000

孰好孰坏,例子说明了一切!好了,老规矩,下面我还是用上面的例子详细说明一下Huffman编码的过程。

首先,我们需要统计出各个字符出现的次数,如下:

接下来,我根据各个字符出现的次数对它们进行排序,如下:

好了,一切准备工作就绪。

在上文我提到,huffman编码的过程其实就是构造一颗二叉树的过程,那么我将各个字符看成树中将要构造的各个节点,将字符出现的频度看成权值。Ok,有了这个思想,here we go!

构造huffman编码二叉树规则:

从小到大,

从底向上,

依次排开,

逐步构造。

首先,根据构造规则,我将各个字符看成构造树的节点,即有节点a、b、c、d、e、f。那么,我先将节点f和节点e合并,如下图:

于是就有:

经过排序处理得:

接下来,将节点b和节点c也合并,则有:

于是有:

经过排序处理得:

第三步,将节点d和节点fe合并,得:

于是有:

继续,这次将节点fed和节点bc合并,得:

于是有:

最后,将节点a和节点bcfed合并,有:

以上步骤就是huffman二叉树的构造过程,完整的树如下:

二叉树成了,最后就剩下编码了,编码的规则为:左0右1

于是根据编码规则得到我们最终想要的结果:

从上图中我们得到各个字符编码后的编码位:

代码示例:

哈夫曼树结构:

[cpp] view plain copy

  1. struct element
  2. {
  3. int weight; // 权值域
  4. int lchild, rchild, parent; // 该结点的左、右、双亲结点在数组中的下标
  5. };

weight保存结点权值;lchild保存该节点的左孩子在数组中的下标;rchild保存该节点的右孩子在数组中的下标;parent保存该节点的双亲孩子在数组中的下标。

哈夫曼算法的C实现:

[cpp] view plain copy

  1. #include<iostream>
  2. #include <iomanip>
  3. using namespace std;
  4. // 哈夫曼树的结点结构
  5. struct element
  6. {
  7. int weight; // 权值域
  8. int lchild, rchild, parent; // 该结点的左、右、双亲结点在数组中的下标
  9. };
  10. // 选取权值最小的两个结点
  11. void selectMin(element a[],int n, int &s1, int &s2)
  12. {
  13. for (int i = 0; i < n; i)
  14. {
  15. if (a[i].parent == -1)// 初始化s1,s1的双亲为-1
  16. {
  17. s1 = i;
  18. break;
  19. }
  20. }
  21. for (int i = 0; i < n; i)// s1为权值最小的下标
  22. {
  23. if (a[i].parent == -1 && a[s1].weight > a[i].weight)
  24. s1 = i;
  25. }
  26. for (int j = 0; j < n; j)
  27. {
  28. if (a[j].parent == -1&&j!=s1)// 初始化s2,s2的双亲为-1
  29. {
  30. s2 = j;
  31. break;
  32. }
  33. }
  34. for (int j = 0; j < n; j)// s2为另一个权值最小的结点
  35. {
  36. if (a[j].parent == -1 && a[s2].weight > a[j].weight&&j != s1)
  37. s2 = j;
  38. }
  39. }
  40. // 哈夫曼算法
  41. // n个叶子结点的权值保存在数组w中
  42. void HuffmanTree(element huftree[], int w[], int n)
  43. {
  44. for (int i = 0; i < 2*n-1; i) // 初始化,所有结点均没有双亲和孩子
  45. {
  46. huftree[i].parent = -1;
  47. huftree[i].lchild = -1;
  48. huftree[i].rchild = -1;
  49. }
  50. for (int i = 0; i < n; i) // 构造只有根节点的n棵二叉树
  51. {
  52. huftree[i].weight = w[i];
  53. }
  54. for (int k = n; k < 2 * n - 1; k) // n-1次合并
  55. {
  56. int i1, i2;
  57. selectMin(huftree, k, i1, i2); // 查找权值最小的俩个根节点,下标为i1,i2
  58. // 将i1,i2合并,且i1和i2的双亲为k
  59. huftree[i1].parent = k;
  60. huftree[i2].parent = k;
  61. huftree[k].lchild = i1;
  62. huftree[k].rchild = i2;
  63. huftree[k].weight = huftree[i1].weighthuftree[i2].weight;
  64. }
  65. }
  66. // 打印哈夫曼树
  67. void print(element hT[],int n)
  68. {
  69. cout << "index weight parent lChild rChild" << endl;
  70. cout << left; // 左对齐输出
  71. for (int i = 0; i < n;i)
  72. {
  73. cout << setw(5) << i << " ";
  74. cout << setw(6) << hT[i].weight << " ";
  75. cout << setw(6) << hT[i].parent << " ";
  76. cout << setw(6) << hT[i].lchild << " ";
  77. cout << setw(6) << hT[i].rchild << endl;
  78. }
  79. }
  80. int main()
  81. {
  82. int x[] = { 5,29,7,8,14,23,3,11 }; // 权值集合
  83. element *hufftree=new element[2*8-1]; // 动态创建数组
  84. HuffmanTree(hufftree, x, 8);
  85. print(hufftree,15);
  86. system("pause");
  87. return 0;
  88. }

说明:

parent域值是判断结点是否写入哈夫曼树的唯一条件,parent的初始值为-1,当某结点加入时,parent域的值就设置为双亲结点在数组的下标。构造哈夫曼树时,首先将n个权值的叶子结点存放到数组haftree的前n个分量中,然后不断将两棵子树合并为一棵子树,并将新子树的根节点顺序存放到数组haftree的前n个分量的后面。

(3).游程编码(RLC)

游程编码又称“运行长度编码”或“行程编码”,是一种无损压缩编码,JPEG图片压缩就用此方法,很多栅格数据压缩也是采用这种方法。

栅格数据如图3-1所示:

3-1 栅格数据

原理:

用一个符号值或串长代替具有相同值的连续符号(连续符号构成了一段连续的“行程”。行程编码因此而得名),使符号长度少于原始数据的长度。只在各行或者各列数据的代码发生变化时,一次记录该代码及相同代码重复的个数,从而实现数据的压缩。

常见的游程编码格式包括TGA,Packbits,PCX以及ILBM。

例如:5555557777733322221111111

行程编码为:(5,6)(7,5)(3,3)(2,4)(1,7)。可见,行程编码的位数远远少于原始字符串的位数。

并不是所有的行程编码都远远少于原始字符串的位数,但行程编码也成为了一种压缩工具。

例如:555555 是6个字符 而(5,6)是5个字符,这也存在压缩量的问题,自然也会出现其他方式的压缩工具。

在对图像数据进行编码时,沿一定方向排列的具有相同灰度值的像素可看成是连续符号,用字串代替这些连续符号,可大幅度减少数据量。

游程编码记录方式有两种:①逐行记录每个游程的终点列号:②逐行记录每个游程的长度(像元数)

第一种方式:

上面的栅格图形可以记为:A,3 B,5 A,1 C,4 A,5

第二种就记作:A,3 B,2 A,1 C,3 A,1

行程编码是连续精确的编码,在传输过程中,如果其中一位符号发生错误,即可影响整个编码序列,使行程编码无法还原回原始数据。

代码示例:

根据输入的字符串,得到大小写不敏感压缩后的结果(即所有小写字母均视为相应的大写字母)。输入一个字符串,长度大于0,且不超过1000,全部由大写或小写字母组成。输出输出为一行,表示压缩结果,形式为:

(A,3)(B,4)(C,1)(B,2)

即每对括号内部分别为字符(都为大写)及重复出现的次数,不含任何空格。

样例输入:aAABBbBCCCaaaaa

样例输出:(A,3)(B,4)(C,3)(A,5)

[cpp] view plain copy

  1. #include<stdio.h>
  2. #include<string.h>
  3. char a[1001];
  4. int main()
  5. {
  6. char t;
  7. int i;
  8. gets(a);
  9. int g=1;
  10. int k=strlen(a);
  11. if(a[0]>='a'&&a[0]<='z')
  12. a[0]-=32;
  13. t=a[0];
  14. for(i=1;i<=k;i)
  15. {
  16. if(a[i]>='a'&&a[i]<='z')
  17. a[i]-=32;
  18. if(a[i]==t)
  19. g;
  20. if(a[i]!=t)
  21. {
  22. printf("(%c,%d)",t,g);
  23. g=1;
  24. t=a[i];
  25. }
  26. }return 0;
  27. }

应用场景:

(1).区域单色影像图

(2).红外识别图形

(3).同色区块的彩色图形

    推荐阅读
  • 张铎妈妈令人窒息(张铎妈妈想要无片酬演戏)

    近日,家庭伦理类综艺节目《婆婆和妈妈》正在持续热播中,该节目从播出到现在也有一段时间了,马上就要到结尾阶段了。程莉莎选择了花束,没想到陈松伶的婆婆和杨子也是花束,这个新的家庭也是很有趣了。程莉莎直接将杨子当作自己的老公一样使唤,杨子本来想用对待黄圣依的方式来对待程莉莎,但是发现压根就不管用,直接就被程莉莎给碾压了。

  • 入门眼线教程(画眼线的方法)

    入门眼线教程棉签按住上眼皮,会比用手指的接触感更干爽,接触眼皮面积也会很小。在同一个点,左右反复描绘几次,直至完成整条内眼线,具体可看图中示范。外眼线的部分不需要太夸张,用棉签按住眼尾轻轻向外拉,用眼线笔贴近睫毛根部,画一条细细的眼线即可。眼尾的部分稍稍向外平拉,眼尾钝一些,可以瞎按的整个眼更自然。划过眼线之后,用棉棒在沿线位置按压一下,用来吸走多余油分,也可以防止眼线脱妆。

  • 梅艳芳帅气角色(几千人中被选中饰演梅艳芳)

    距离传奇巨星梅艳芳逝世十八年时,这部关于她的传记电影《梅艳芳》终于来了,该片将于11月12日上映。备受瞩目的梅艳芳饰演者,是模特出道、从几千人中拿到角色的新人演员王丹妮。遗憾的是,今年3月廖启智因病去世,没能赶上“徒弟”王丹妮的出师。《梅艳芳》不仅是传记片,从头到尾还穿插了大量的歌舞,这给王丹妮带来了又一挑战。经过半年的特训,王丹妮已经分不清哪部分是自己,哪部分是梅艳芳了。

  • 虾线是在背上还是肚子下(虾线到底是在背上还是肚子下)

    以下内容希望对你有帮助!虾线是在背上还是肚子下虾线是虾的消化道,在虾的背部。虾的腹部也有一条线,但是这条线并不是虾的肠道,也不是我们日常生活中所说的虾线。如果想要除掉虾线,这个时候可以准备一个牙签并将它插入虾体内,然后把牙签往上挑直到将虾线挑出来,最后把虾线完整地拽出来。还可以将虾头掰断,将虾头中的内脏挤出来,接着也能将虾线给拉出来。

  • 福特野马哪款车型最好看(福特全新SUV售价5款车型任你选)

    MachE是福特旗下将推出的一款纯电SUV,新车或将定位为紧凑级车型。近日海外媒体曝光了福特全新MachE的车型售价,新车预计将推出5款车型,海外起售价为43,895美元兑,并将于下周即将开幕的2019年洛杉矶车展上正式发布。动力方面,新车将采用纯电形式驱动,并配备前后双电机,最大续航里程可达600km。外观方面,新车前脸采用了封闭式格栅设计,车身侧面呈溜背式造型,车尾与野马跑车车型保持一致。

  • 理睬的意思是什么一个一个字解释(理睬可以怎么解释)

    我们一起去了解并探讨一下这个问题吧!理睬的意思是什么一个一个字解释理睬的意思为对别人言行给以注意并表示态度。理的释义有四个,分别为:物质本身的纹路、层次,客观事物本身的次序,如心理、肌理、条理、事理。自然科学,有时特指“物理学”,如理科、数理化、理疗。按事物本身的规律或依据一定的标准对事物进行加工、处置,如理财、理事、管理、自理、修理、总理。睬的释义为理会、搭理。

  • 九江庐山市春节期间黄码人员核酸检测点

    健康码黄码人员需接受必要的核酸检测、健康监测等措施。请提前报告社区,并在做好防护后再前往以下黄码人员核酸采样点。提交成功后,县区解码专员会在24小时内在线审核,实现网上转码全闭环操作。如还有其他需要解答的问题,请与属地指挥部或社区工作人员联系。

  • 初唐四杰是谁 盛唐四杰是谁

    与四杰同时代的张说在《赠太尉裴公神道碑》中称:“在选曹,见骆宾王、卢照邻、王勃、杨炯”。

  • 牛种类 牛有几种

    牛是我们常见的一种动物,起源于中新世,是由原古鹿类分化的一支混杂而进步的支系,牛的用途有很多,那么牛种类有哪些?乳用品种是生长牛奶和奶制品为主,如荷兰牛。肉用品种是生产牛肉和牛肉制品,如短角牛,海福特牛,安格斯牛等。役用品种主要是用来耕作,如中国的水牛,黄牛等。兼用品种是乳肉兼用、肉役,如西门塔尔牛,丹麦红牛,瑞士褐牛等。