【图像压缩】QOI图像格式详解

2023-11-20

最近听说一种图像格式比较流行,想起我曾经是做图像压缩的emmmm,就来研究一下。
QOI(Quite OK Image Format),很好的图像格式(git链接),能快速地无损压缩图像。原理也非常简单,没有各种变换,直接空域处理,而无损压缩,自然也不能量化,好家伙,比JPEG不知简单到哪里去了。说明文档呢,只有一页,代码300余行,确实是图像压缩界的一股清流。

一、编码格式解析

首先,文件头的定义如下:

struct qoi_header_t {
	char     magic[4];   // magic bytes "qoif"
	uint32_t width;      // image width in pixels (BE)
	uint32_t height;     // image height in pixels (BE)
	uint8_t  channels;   // 3 = RGB, 4 = RGBA
	uint8_t  colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
};

文件头就是告诉电脑这是个什么文件,怎么解析。QOI的文件头14byte,4byte名字,4byte宽和高,2byte通道和颜色空间信息。这里面真正和编解码有关的只有宽和高(时间2021.12.22,后续可能有逻辑改动,下同)channels和colorspace虽然encode时写进去了,但decode时没用上。

定义完文件头,下面就是正式编码了。整体而言,QOI是按行从上往下,每行从左往右进行编码,不分通道,每个像素RGB(或RGBA)一起编码。对于每一个像素,按照不同的像素值,有四种编码策略:
1.前一像素的重复
2.一个出现过的像素的index
3.和前一个像素的残差
4.直接编码RGB(或RGBA)
注意这四种策略是有序的,优先尝试第一种,如果不行再尝试第二种,以此类推。显然最差的是第四种,相当于没有压缩,所以前面三种策略设计得要巧妙,尽量避免第四种情况。

【1】前一像素的重复
前面出现过的元素,自然就不用再编码了,这种情况一般出现在背景上,当前像素和前一像素的像素值完全相同(第一个像素的“前一像素”定义为R=B=G=0,A=255)。这时候就只需要编码标记位和对应的重复次数就行了。不过有一个限制,最多重复62次(后面解释原因)。如果一个像素3byte,重复n次,此时压缩率3n/1最大。

【2】一个出现过的像素的index
编码过程中,始终维护一个64个元素大小的数组,表示出现过的像素的哈希值,哈希的公式是:

index = (r * 3 + g * 5 + b * 7 + a * 11) % 64

每有一个像素的RGB值,就用这公式算一下,把它存到数组的index位置。如果这地方已经有值了,再看两个像素相不相同(可能出现RGB不一样但index相同的情况),如果相同,编码标记位和index。如果不相同,就覆盖,用后面3、4策略。此时压缩率3/1。

【3】和前一个像素的残差
残差就更好理解了,图像压缩领域的经典方法。QOI里的残差分为大小两种,给了不同的标记位。如果当前像素和前一像素的RGB差值均为-2到1(为啥不是-1到2?虽说差不多),就用小残差。小残差只有1byte,所以去掉标记位2bit后,RGB三个通道的残差各剩2bit,所以只能存4个值,而且最小值-2记为b00,最大值1记为b11。如果残差大一些,G的残差为-32到31,R和B的残差与G残差的差为-8到7,那么可以用大残差存储,只有2byte。如果残差更大,那不好意思,存不下了,只能用策略4。当然,两种残差都是在A不变的前提下,如果A不一样,只能用策略4。此时小残差压缩率3/1,大残差压缩率3/2。

【4】直接编码RGB(或RGBA)
以上策略都不行,干脆不压缩了,该是多少就是多少了。此时除了编码原有的RGB3byte,还需多编码1byte标记位,压缩率3/4(反向压缩)。

标记位说明
上面说了那么多标记位,到底是个啥?其实就是编解码器共同约定好的一套符号,有点像什么数据交换协议,都按照这个标准进行。QOI定义的标记位如下:

#define QOI_OP_INDEX  0x00 /* 00xxxxxx */
#define QOI_OP_DIFF   0x40 /* 01xxxxxx */
#define QOI_OP_LUMA   0x80 /* 10xxxxxx */
#define QOI_OP_RUN    0xc0 /* 11xxxxxx */
#define QOI_OP_RGB    0xfe /* 11111110 */
#define QOI_OP_RGBA   0xff /* 11111111 */

编码器是把一个个像素转成二进制码存起来,解码器是读这些0101的数据,恢复图像。所以按照约定,解码器先解出4byte,如果是“qoif”,不就说明这是QOI的码流嘛,再往后解码宽高啥的。除了文件头的14byte,后面也一样。先解出1byte标记位,如果前2bit是00,就说明是index类(对应上面策略2),后面的6bit就是index喽;要是01呢,说明是小残差,等等。但注意如果是10,说明是大残差,需要再读取1byte才能解码出当前像素的RGB值。所以如果前面是11,最后2bit就不能是10或11了,否则解码的时候不知道是这个像素重复多次呢(策略1)还是直接解码出RGB呢(策略4)。这也就解释了为什么重复最多62次,因为63和64已经被其他标记位占据了,这里只能空出来。
最后,QOI的结束码是0x01,不也和index类重复了吗。其实这里没关系了,因为只要宽×高的所有像素没有被填满,所有的0x01都被当成index类且index=1。所有像素解码完后,0x01当成啥都无所谓了,因为也没有位置放新的像素了。

二、性能和压缩率分析

性能首先不谈了,无空间转换无量化无熵编码,快得飞起。压缩率按作者做的实验确实比PNG高一些,但不知道压缩细节纹理比较丰富的图像如何。根据上面的分析,如果RGB都不压缩情况会非常糟糕,但平均而言还是可以得到一个不错的压缩率。
 
 
 
以上是本人对QOI的一些初步分析,如有错误请指出,如果codec有变化本文也会更新。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【图像压缩】QOI图像格式详解 的相关文章

随机推荐

  • 【FPGA多周期时序约束详解】- 解读FPGA多周期时序约束的全过程

    FPGA多周期时序约束详解 解读FPGA多周期时序约束的全过程 FPGA作为数字电路设计的常见工具 其设计中必然会遇到时序约束的问题 而多周期时序约束更是FPGA设计中不可避免的难点之一 本文将详细介绍FPGA多周期时序约束的全过程 并结合
  • PHP 使用 Kafka 安装拾遗

    最近项目开发中需要使用 Kafka 消息队列 经过检索 PHP下面有通用的两种方式来调用 Kafka php rdkafka 扩展 以 PHP 扩展的形式进行使用是非常高效的 另外 该项目也提供了非常完备的 文档 不过在 Mac 环境中安装
  • Android9 默认开启/关闭GPS

    gps默认打开 需要关闭的话 修改以下文件 frameworks base packages SettingsProvider res values defaults xml 将
  • xp系统蓝屏,xp系统蓝屏的详细解决过程

    xp系统蓝屏的详细解决过程 现在XP系统微软停止开发了 服务也升级不得了 刚刚使用时还是好好的 能够正常的运行 怎么一言不合就蓝屏了呢 那么xp蓝屏怎么办呢 跟你们分享一下小编解决xp蓝屏的经验吧 重新启动 快速按F8 用箭头上下选择 最后
  • 2022VLMo: Unified Vision-Language Pre-Training with Mixture-of-Modality-Experts

    摘要 我们提出了一个统一的视觉 语言预训练模型 VLMo 该模型与一个模块化的transformer网络共同学习一个双编码器和一个融合编码器 具体地 我们引入了模态混合专家 MoME Transformer 其中每个块包含一个特定于模态的专
  • tensorRT部署之 代码实现 onnx转engine/trt模型

    tensorRT部署之 代码实现 onnx转engine trt模型 前提已经装好显卡驱动 cuda cudnn 以及tensorRT 下面将给出Python C 两种转换方式 1 C 实现 项目属性配置好CUDA tensoeRT库 通常
  • 吴恩达机器学习笔记系列(五)——梯度下降

    一 gradient descent 梯度下降 1 概念 线性回归的目的就是找出使得误差 损失函数 最小的参数值 可以用梯度下降来确定 参数的大小 梯度下降是一种迭代方法 能够求解局部最小值 结果与初始点的选取有关 为了找到最小值 从某一点
  • ADFS 4.0 证书更新

    ADFS 4 0 证书更新 由于公网证书的过期 需要重新更新ADFS的服务通信证书 证书要求 带私钥 PFX格式 更换流程 证书安装到 证书 计算机 个人 安装后点开证书能看到 你有一个与证书对应的私钥 右击证书 gt 所有任务 gt 管理
  • 【uboot内核适配学习】uboot 修改默认ip

    1 修改默认ip作用 设备出场的时候都需要默认的ip 2 修改措施 找到uboot芯片配置文件 不同芯片厂家适配的文件必定是不一样的 位置也可能不一样 define CONFIG ETHADDR 00 40 5c 26 0a 5b MAC地
  • 面经——小米面经(2021春招)

    摘自 小米面经 2021春招 感谢小米 感谢雷总 感谢上官可编程 作者 阿波罗啦啦啦啦 发布时间 2021 05 01 11 08 41 网址 https blog csdn net weixin 44933419 article deta
  • 博客营销

    1 博客营销有什么价值 应注意什么 1 博客可以直接带来潜在用户 2 博客营销的价值体现在降低网站推广费用方面 3 博客文章内容为用户通过搜索引擎获取信息提供了机会 4 博客文章可以方便地增加企业网站的链接数量 5 可以实现更低的成本对读者
  • 指标体系建设

    1 背景 结合业务场景将多个不同指标和维度进行组合 从而针对某一真实业务场景进行数据分析和决策导向 并能在整体业务变化中发现和定位问题 2 概念理解与示例分析 2 1 指标体系 指标体系 名称 分类 解析 作用 示例 指标 结果型指标 时机
  • 汉诺塔问题 java

    汉诺塔问题 public class HanoiTower 编写一个main方法 public static void main String args Tower t1 new Tower t1 move 5 A B C 汉诺塔问题的解决
  • React事件处理及事件流

    React事件处理 React事件处理是通过将事件处理器绑定到组建上处理事件 事件触发的同时更新组建的内部状态 内部状态更新会触发组件的重绘 React 元素的事件处理和 DOM 元素的事件处理很相似 但语法上的略有区别 在React中事件
  • 如何删除gitee仓库下的文件

    有时我们可能在上传项目到github或者gitee时 忘记忽略了某个文件 就直接push上去了 最后发现上传多了 如何删除掉远程仓库中的文件呢 注 在github上我们只能删除仓库 无法删除文件夹或文件 所以只能通过命令 2 打开GitBa
  • Python 程序设计习题(4) —— 列表与元组

    目录 1 Python 习题部分 2 Python 习题讲解 列表 元组 其他 1 Python 习题部分 要想学习一门语言 便少不了练习 故附上部分 Python 习题 供大家学习参考 如有错误之处 还望指正 1 二年级一班举行了数学考试
  • springboot项目中application.properties无法变成小树叶问题解决

    1 检查我们的resources目录的状态 看看是不是处在普通文件夹的状态 如果是的话 我们需要重新mark一下 右键点击文件夹 选择mark directory as resources root 此时我们发现配置文件变成了小树叶 2 如
  • ScheduledThreadPoolExecutor周期定时任务异常处理踩坑的问题!!

    问题原因 在公司写项目的时候 有一个周期定时任务的需求 就想着阿里巴巴开发手册里不是说不能用Executors去创建线程池 因为存在如下问题 FixedThreadPool和SingleThreadPool 允许的请求队列长度为 Integ
  • NVME Format Command 个人笔记

    Format NVM command NVM Command Set Specific This command is used by the host to change the LBA data size and or metadata
  • 【图像压缩】QOI图像格式详解

    最近听说一种图像格式比较流行 想起我曾经是做图像压缩的emmmm 就来研究一下 QOI Quite OK Image Format 很好的图像格式 git链接 能快速地无损压缩图像 原理也非常简单 没有各种变换 直接空域处理 而无损压缩 自