压缩算法之算术编码浅析与实现

article/2025/9/16 2:01:08

压缩算法之算术编码浅析与实现

    • 简介
    • 实现思路
    • 实现代码
    • 参考资料

简介


算术编码,属于熵编码的范畴,常用于各种信息压缩场合,如图像、视频、音频压缩领域。

基本原理

  • 核心原则:出现频率高的信息,分配少的比特,频率低的信息则分配多的比特
  • 简单来讲:将一串信息压缩到[0, 1]区间的一个浮点值

算法效果:
在这里插入图片描述
举个例子

在这里插入图片描述
在这里插入图片描述

解释

  • 假设输入为ARBER,每个符号对应概率为上图
  • 将之一字排开到0-1实数轴上
  • 对ARBER编码,最终输出一个具体的小数值[0, 1],解码是逆运算
    • 第一个A,得到0-0.2区间,对其按ABER符号的比例继续划分
    • 第二个R,得到0.12-2区间,继续按比例划分
    • 第三个B,得到0.136-0.152区间,重复以上过程
    • 最终输出:0.14432-0.1456区间,任意一个值都可以表达ARBER字符串

参考自:https://segmentfault.com/a/1190000011561822

实现思路


步骤如下:

  • 1)计算字符串的次数,除以总次数得到频率
  • 2)分区
  • 3)编码,将每个字符与码表对应,得到区间,再进行划分
  • 4)直到读取完字符串,然后输出编码结果
  • 5)逆过程解码,先判断该值在那个区,然后输出一个字符,再按比例划分区域,再判断,再输出
  • 6)直到划分至该值所对应的区间,区间除以2是其值结束

过程debug:

问题1:区间1迭代到第二个区间就对应不上了**
在这里插入图片描述

根源

  • 公式迭代时编码bug,顺序执行后,选取了更新后的low

改前:

low = low + (high - low) * pp[pos];
high = low + (high - low) * pp[pos + 1];

改后:

int low_tmp = low;
low = low_tmp + (high - low_tmp) * pp[pos];
high = low_tmp + (high - low_tmp) * pp[pos + 1];

问题2**:区间迭代到第二个区间仍有问题**

根因:

  • 单步迭代时,发现low_tmp是int类型,一直被截断成0了。

改正:将int 改为 double,即 double low_tmp = low;

实现代码


该算法用C++语言实现,代码如下:

#include <iostream>
#include <vector>
#include <string.h>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <queue>
#include <hash_map>using namespace std;// Algorithm  Coding Test
int main(int argc, const char *argv[])
{
// 算术编码实现// Test Data Only UpperCase Letter// string str1 = "ARBER";// string str1 = "ABCDEFJKJA";// string str1 = "ABACDAAB";// string str1 = "HELLOWORLDIAMCOMING";    // 只能解析到IAMCO// string str1 = "WEAREHAPPYTOSOLVEIT";    // 当输入字符的类型和长度多了后,码表变大,区间变小,精度要求更高,不断细分后编码值会越来越小string str1 = "WEAREARBERREBWAEWRRRRRRW";  // 当前算法最多只能解码长度为24的字符串// variablestring outStr;vector<int> cnt(26);vector<int> p1(26);// initint i = 0;while (str1[i]) {cnt[str1[i] - 'A']++;i++;}int sum = 0;int len = 0;for(i = 0; i < 26; i++) {if (cnt[i] != 0) {sum += cnt[i];len++;}// cout << cnt[i] << endl;}// processing,  calculation probability and partitionstd::cout << "CodeBook is displayed as below." << std::endl;double *pb = new double[len]();  //probabilitychar *strMap = new char[len]();double *pp = new double[len + 1]();  //probability partition 前后 0 1区间点数对应double *pp_tmp = new double[len + 1]();int j = 0;for (i = 0; i < len; i++) {while (cnt[j] == 0) {j++;}pb[i] = (double)cnt[j] / (double)sum;strMap[i] = 'A' + j;pp[i + 1] = pp[i] + pb[i];pp_tmp[i + 1] = pp[i + 1];j++;cout << pb[i] << " " << pp[i] << " " << strMap[i] << endl;}// codingdouble low = 0.0;double high = 1.0;i = 0;while (str1[i]) {int pos = -1;for (int j = 0; j < len; j++) {char ch = str1[i];if (strMap[j] == ch) {pos = j;break;}}if (pos >= 0) {double low_tmp = low;low = low_tmp + (high - low_tmp) * pp[pos];high = low_tmp + (high - low_tmp) * pp[pos + 1];// cout << low << " " << high << endl;}i++;}double output = (low + high) / 2;// cout << low << " " << high << " " << output << endl;// decodinglow = 0;high = 1;while (abs(output - (low + high) / 2) > 1e-15) { //double 精度是 15-16位有效数字// 解码一个字符i = 0;while (output > pp_tmp[i] && i < len + 1) { // 可用二分法查找来优化, 概率区间数组的长度比码表大1i++;}if (i == len + 1) { cout << "Decoding Fail!" << endl;return 0;} else {low = pp_tmp[i - 1];high = pp_tmp[i];outStr.push_back(strMap[i - 1]);}// update pp_tmppp_tmp[0] = low;pp_tmp[len] = high;for (i = 1; i < len; i++) {pp_tmp[i] = low + (high - low) * pp[i];// cout << pp_tmp[i] << " " << endl;}}// printfcout << endl;cout << "Source: " << str1 << endl;cout << "Coded : " << output << endl;cout << "Decode: " << outStr << endl;delete []pb;delete []strMap;delete []pp;delete []pp_tmp;system("pause");return 0;
}

上述代码可以利用二分查找法改进,优化后的代码段:

    // 上下文连接// decodinglow = 0;high = 1;while (abs(output - (low + high) / 2) > 1e-15) { //double 精度是 15-16位有效数字// 二分法的前提是序列有序, 由于pp_tmp是pp概率累加得来的,所以是递增的,可用二分法查找来优化。short pos0 = 0;short pos1 = len;short cur = (pos0 + pos1) / 2;while (pos1 - pos0 > 1) {if (output < pp_tmp[cur]) { // output是区间的中间值,一定不等于某区间端点pos1 = cur;cur = (pos0 + pos1) / 2;} else {pos0 = cur;cur = (pos0 + pos1) / 2;}}if (pos1 - pos0 == 1) {low = pp_tmp[pos0];high = pp_tmp[pos1];outStr.push_back(strMap[pos0]);}// 上下文连接// update pp_tmppp_tmp[0] = low;pp_tmp[len] = high;}

参考资料


  1. 思否:算数编码原理解析

http://chatgpt.dhexx.cn/article/wgdqi1mX.shtml

相关文章

算术编码(1)

序列a2a1的区间为&#xff08;0.2,0.22&#xff09; 算术解码步骤&#xff1a;

用分组编码解决算术编码的精度要求问题

这篇博客要介绍的是算术编码、译码。主要用分组编码的思路解决了当消息比较长时&#xff0c;小数位数太多&#xff0c;计算工具精度达不到的问题。 文末给出了matlab代码。题目的要求是&#xff1a;已知26个英文字母和空格的统计概率&#xff0c;对文本文档中的消息&#xff08…

算术编码 matlab程序,算术编码算法的matlab实现

算术编码算法的matlab实现 实验 1 算术编码算法的 Matlab 实现实验学时&#xff1a;2实验类型&#xff1a;(演示、验证、综合、√设计、研究)实验要求&#xff1a;(√必修、选修)一、实验目的掌握算数编码原理。二、实验内容利用 Matlab 编写程序实现算数编码&#xff0c;包括…

十六、算术编码_2、算术编码举例实现

基本原理 在一次算术编码的执行前,为简便起见,首先假设输入的信源为0/1的二进制信源,0和1的概率比为7:3。即二者的概率为: p(0) = 0.7; p(1) = 0.3;假设输入的待编码信息为[0, 0, 1],在编码每一个符号时,都需要对概率区间进行分割,并通过与编码区间进行比较,判断是否…

信息论基础:算术编码

1 引言 霍夫曼码是一种无损编码&#xff0c;而且是最优的符号码。但是&#xff0c;它有两个缺点&#xff1a;&#xff08;1&#xff09;每个符号至少需要一个比特&#xff1b;&#xff08;2&#xff09;当符号的概率分布变化时&#xff0c;使用不方便。 用一个例子来看看霍夫…

多媒体数据处理实验1:算术编码

1. 算法描述 功能&#xff1a; 给定概率字典以及待编码字符串&#xff0c;求出该字符串算术编码的结果&#xff08;最短二进制串&#xff09;&#xff0c;并能根据算数编码结果进行解码&#xff0c;得到原字符串。 2.算法流程&#xff1a; 算术编码流程&#xff1a; (1) 首先…

数字图像算术编码python_算术编码简介

上一篇讲了LZW编码&#xff0c;本篇讨论另一种不同的编码算法&#xff0c;算数编码。和哈夫曼编码一样&#xff0c;算数编码是熵编码的一种&#xff0c;是基于数据中字符出现的概率&#xff0c;给不同字符以不同的编码。本文也会对这两种编码方式的相似和不同点进行比较。 编码…

算术编码

文章首发于我的个人博客 前言 这篇博客主要总结大二下课程《信息论》实验的内容。主要包含固定模式的算数编码以及自适应模式的算术编码。我将首先介绍这两种算术编码的基本思想和实现思路&#xff0c;然后给出具体的python代码并对代码中的一些关键点进行解释说明。 固定模…

算术编码原理及其python实现

目录 1. 原理部分&#xff1a;2. 香农界理论分析&#xff1a;3. 代码实现&#xff1a;4.实验结果 1. 原理部分&#xff1a; 原理部分参考什么是算术编码 一个从信源序列到不可压缩二元序列的一个可逆映射&#xff0c;假设序列 { X 1 … X n } \{X_{1} \ldots X_{n}\} {X1​…X…

基本算术编码

1.基本思想 算术编码&#xff0c;就是用一个数编码一串字符串。它的思想是这样的:对一个需要编码的字符串&#xff0c;给出一个初始区间[0, 1),这个区间被分成n份&#xff0c;n是这串字符串中不同字符的个数&#xff0c;每一份占区间长度的比例与相应字符出现次数占整个字符串…

算术编码的基本概念

二、算术编码的基本概念 算术编码属于熵编码的一种重要的类型&#xff0c;其作用同变长编码等熵编码方法类似&#xff0c;用于压缩输入数据中的统计冗余&#xff0c;并且使用算术编码的压缩同样是无损压缩。 在本系列第1篇中讨论了典型的变长编码方法——哈夫曼编码。包括哈夫…

Otsu大津算法公式推导及python实现

目录 前言 一、类间平方差是什么&#xff1f; 二、公式推导及实现 1.求类间平方差 2.opencv-python编程实现 2.1 引入图像并灰度化 2.2 查看灰度值的分布情况 2.3 求全局平均阈值 2.4 求最大类间方差 3.算法的验证 总结 前言 OTSU&#xff08;大津算法&#xff09;是…

OTSU算法原理

OTSU算法原理及实现&#xff1a; 最大类间方差是由日本学者大津(Nobuyuki Otsu)于1979年提出&#xff0c;是一种自适应的阈值确定方法。算法假设图像像素能够根据阈值&#xff0c;被分成背景[background]和目标[objects]两部分。然后&#xff0c;计算该最佳阈值来区分这两类像素…

大津阈值分割算法(OTSU处理图像)

1.算法原理简述 对于图像I(x,y)&#xff0c;前景(即目标)和背景的分割阈值记作T&#xff0c;属于前景的像素点数占整幅图像的比例记为ω0&#xff0c;其平均灰度μ0&#xff1b;背景像素点数占整幅图像的比例为ω1&#xff0c;其平均灰度为μ1。图像的总平均灰度记为μ&#xf…

[图像处理]14.分割算法比较 OTSU算法+自适应阈值算法+分水岭

参考文献&#xff1a; OTSU阈值分割孔洞填充海陆分离_SwordKii的博客-CSDN博客 drawContours函数_普通网友的博客-CSDN博客_drawcontours R329-opencv阈值分割算法——自适应阈值_Third Impact的博客-CSDN博客_opencv自适应阈值分割 分水岭算法的python实现及解析_进不去的…

OTSU算法 (大津算法)理解代码

OTSU算法&#xff1a;对图像进行二值化的算法 介绍 OTSU算法是一种自适应的阈值确定的方法&#xff0c;又称大津阈值分割法&#xff0c;是最小二乘法意义下的最优分割。 它是按图像的灰度特性&#xff0c;将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景…

Pr-快速上手-基本操作-教程

视频链接: Pr教程 视频设计到的知识点: 视频的剪辑bgm的管理添加字幕及弹幕的添加鬼畜视频的制作发布教程

Pr常用操作技巧

操作技巧持续更新中 1.premiere如何裁剪视频尺寸 视频尺寸怎么修改 1.将premiere左下角的视频素材直接向右拖动到编辑区中 2.鼠标左键单击视频,然后选择上方的【效果】 3.这时,页面左侧将弹出效果的菜单栏,依次选择效果→视频效果→变换→裁剪 4.鼠标左键按住【裁剪】不放,直接…

Premiere: 基本操作

1、首先进入编辑模式&#xff0c;使得视频能加效果 2、这时候能找到视频的效果添加&#xff0c;这里有视频的水平翻转&#xff0c;高斯模糊等&#xff0c;需要什么效果直接&#xff0c;把该效果抓到视频上就可以。

PR的入门基础教程

提示&#xff1a;这里只记述作者学习PR入门基础教程视频后的总结 文章目录 常用视频概念第1天学习总结第2天学习总结第3天学习总结 常用视频概念 第1天学习总结 第2天学习总结 第3天学习总结