图像的基本有损压缩和无损压缩及解压

关键词:5-5-5,5-6-5,游长编码优化,图像压缩、解压

背景

有损量化这里介绍从8-8-8到5-5-5和5-6-5的量化压缩原理及其编程实现。无损压缩这里我基于游长编码算法(利用像素的重复)提出一种简单改进算法,即在图像的各通道上进行游长编码,利用各通道像素值得重复性分别进行压缩,一定程度上提高了压缩性,因为两个相邻像素虽然不同,但他们的某个通道可能会相同。

Giuthub源码:https://github.com/jiangxh1992/QuantisationAndCompression

English Version:http://jiangxh.top/articles/2016-10/compressionEN


有损量化5-5-5和5-6-5

压缩对象使图像的RGB通道值,每个值都是0~255之间的数字,分别使用8位保存,因此原始图像每个像素要使用3*8=24位,即‘8-8-8’。这里要将其量化压缩,使用16位来保存24位的信息,因此要损失部分精度,压缩率固定为1.50。

5-5-5指的是只使用低15位,剩下的一位弃用,这样每个通道一致的都压缩为5位;

5-6-5则是充分使用了16位,其中G通道占6位,另外两通道各占5位。

算法原理很简单:

压缩时5-5-5是将每个通道的二进制值都右移3位(除以8),保留剩下的5位,然后依次放入16位数的低15位;解压时分别将各通道的5位二进制数取出并左移3位,低位补0还原成8位,因此低三位的数据丢失掉了。

5-6-6和5-5-5同理,只是G通道的二进制数右移2两位(除以4),将剩下的6位和其他两通道的10位一同放入16位二进制数中。解压时同样是低位补0还原为8位。

算法代码:

程序背景说明:widthheight指的是导入的图片的尺寸(像素个数),Input是保存三个通道的像素值的数组,这里windows工程存储的三通道顺序为B,G,R,不是R,G,B。

5-5-5:

unsigned char *CAppQuantize::Quantize555(int &qDataSize) {

    int i, j ;
    unsigned int r, g, b ;
    unsigned short rgb16 ;

    qDataSize = width * height * 2 ;

    unsigned char *quantizedImageData = new unsigned char[width * height * 2] ;

    for(j = 0; j < height; j++) {
        for(i = 0; i < width; i++) {
            b = pInput[(i + j * width) * 3 + 0] ;   // Blue Color Component
            g = pInput[(i + j * width) * 3 + 1] ;   // Red Color Component
            r = pInput[(i + j * width) * 3 + 2] ;   // Green COlor Component
            rgb16 = ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3) ;
            quantizedImageData[(i + j * width) * 2 + 0] = rgb16 & 0xFF ;
            quantizedImageData[(i + j * width) * 2 + 1] = (rgb16 >> 8) & 0xFF ;
        }
    }

    return quantizedImageData ;
}
void CAppQuantize::Dequantize555(unsigned char *quantizedImageData, unsigned char *unquantizedImageData) {

    int i, j ;
    unsigned int r, g, b ;
    unsigned short rgb16 ;

    for(j = 0; j < height; j++) {
        for(i = 0; i < width; i++) {
            rgb16 = quantizedImageData[(i + j * width) * 2 + 0] | (((unsigned short) quantizedImageData[(i + j * width) * 2 + 1]) << 8) ;
            b = rgb16 & 0x1F;
            g = (rgb16 >> 5) & 0x1F ;
            r = (rgb16 >> 10) & 0x1F ;
            unquantizedImageData[(i + j * width) * 3 + 0] = (b << 3) ;
            unquantizedImageData[(i + j * width) * 3 + 1] = (g << 3) ;
            unquantizedImageData[(i + j * width) * 3 + 2] = (r << 3) ;
        }
    }
}

5-6-5:

unsigned char *CAppQuantize::Quantize565(int &qDataSize) {

    int i, j;
    unsigned int r, g, b;
    unsigned short rgb16;

    qDataSize = width * height * 2 ;
    unsigned char *quantizedImageData = new unsigned char[width * height * 2] ;

    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            b = pInput[(i + j * width) * 3 + 0];    // Blue Color Component
            g = pInput[(i + j * width) * 3 + 1];    // Green Color Component
            r = pInput[(i + j * width) * 3 + 2];    // Red Color Component
            rgb16 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); // r分量和b分量右移3位,g分量右移2位

            quantizedImageData[(i + j * width) * 2 + 0] = rgb16 & 0xFF; // 高8位
            quantizedImageData[(i + j * width) * 2 + 1] = (rgb16 >> 8) & 0xFF;// 低8位
        }
    }

    return quantizedImageData ;
}
void CAppQuantize::Dequantize565(unsigned char *quantizedImageData, unsigned char *unquantizedImageData) {

    int i, j;
    unsigned int r, g, b;
    unsigned short rgb16;

    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            rgb16 = quantizedImageData[(i + j * width) * 2 + 0] | (((unsigned short)quantizedImageData[(i + j * width) * 2 + 1]) << 8);
            b = rgb16 & 0x1F;   // 保留高5位
            g = (rgb16 >> 5) & 0x3F;// 右移5位后保留高6位
            r = (rgb16 >> 11) & 0x1F;// 右移11位后保留高5位
            unquantizedImageData[(i + j * width) * 3 + 0] = (b << 3); // 左移3位,高位补0
            unquantizedImageData[(i + j * width) * 3 + 1] = (g << 2); // 左移2位,高位补0
            unquantizedImageData[(i + j * width) * 3 + 2] = (r << 3); // 左移3位,高位补0
        }
    }
}

通道游长编码无损压缩

压缩过程:

压缩后的数据形式是:两个无符号8位二进制数为一组,第一个存储重复的个数,第二个存储通道值。

分B,G,R三个通道依次进行,对于每个通道从第一个值开始,计算后面相同的值的个数,碰到新的不同值或者重复个数超出了8位数的表示上限,则将之前的重复值和通道值保存到一组压缩后的数据中,并开始下一组同样的计算压缩,直到所有数据全部压缩完。

解压过程:

解压也是分三个通道依次解压,由于三个通道的压缩数据都放在了同一个数组,因此先要找到G通道和R通道的开始位置offset_g和offset_r,寻找方法是循环同时累加计算前面通道各像素的重复个数,每当重复个数达到图片像素个数,下一个即时另一个通道的开始了。之后开始解压,每次从各通道取一个值组成一个像素,直到各通道同时取完,解压后的数据就是压缩前的原数据了,实现了图像的无损压缩。

算法代码:

无损压缩:

unsigned char *CAppCompress::Compress(int &cDataSize) {

    unsigned char *compressedData ;
    cDataSize = width * height * 3 ;    

    // 存储压缩后的数据,最差的情况尺寸也不会到大于cDataSize * 2
    compressedData = new unsigned char[cDataSize * 2];
    // 实际压缩字符长度
    int compressedSize = 0;

    // 采用分通道游离的方法,按照每个通道相邻像素的重复性进行压缩
    // 1.b通道
    unsigned short curB = pInput[0];// 第一个像素的b
    unsigned short repeat = 1;// 重复次数
    for (int i = 1; i < cDataSize / 3; i++)
    {
        unsigned short nextB = pInput[i * 3 + 0];// 下一个像素的b
        if (nextB == curB && repeat < 127)
        {
            ++repeat;
            // 如果是最后一个则存储
            if (i == cDataSize / 3 - 1)
            {
                // 存储最后一个b值组
                compressedData[compressedSize] = repeat;
                compressedData[compressedSize + 1] = curB;
                // 增加编码数据长度
                compressedSize += 2;
            }
        }
        else
        {
            // 存储上一个b值组
            compressedData[compressedSize] = repeat;
            compressedData[compressedSize + 1] = curB;
            // 增加编码数据长度
            compressedSize += 2;
            // 换下一种b值
            curB = nextB;
            repeat = 1;
            // 如果是最后一个
            if (i == cDataSize / 3 - 1)
            {
                // 存储最后一个b值
                compressedData[compressedSize] = 1;
                compressedData[compressedSize + 1] = curB;
                // 增加编码数据长度
                compressedSize += 2;
            }
        }
    }

    // 2.g通道
    unsigned short curG = pInput[1];// 第一个像素的g
    repeat = 1;// 重复次数
    for (int i = 1; i < cDataSize / 3; i++)
    {
        unsigned short nextG = pInput[i * 3 + 1];// 下一个像素的g
        if (nextG == curG && repeat <= 127)
        {
            ++repeat;
            // 如果是最后一个则存储
            if (i == cDataSize / 3 - 1)
            {
                // 存储最后一个g值组
                compressedData[compressedSize] = repeat;
                compressedData[compressedSize + 1] = curG;
                // 增加编码数据长度
                compressedSize += 2;
            }
        }
        else
        {
            // 存储上一个g值组
            compressedData[compressedSize] = repeat;
            compressedData[compressedSize + 1] = curG;
            // 增加编码数据长度
            compressedSize += 2;
            // 换下一种g值
            curG = nextG;
            repeat = 1;
            // 如果是最后一个
            if (i == cDataSize / 3 - 1)
            {
                // 存储最后一个g值
                compressedData[compressedSize] = 1;
                compressedData[compressedSize + 1] = curB;
                // 增加编码数据长度
                compressedSize += 2;
            }
        }
    }

    // 3.r通道
    unsigned short curR = pInput[2];// 第一个像素的r
    repeat = 1;// 重复次数
    for (int i = 1; i < cDataSize / 3; i++)
    {
        unsigned short nextR = pInput[i * 3 + 2];// 下一个像素的r
        if (nextR == curR && repeat <= 127)
        {
            ++repeat;
            // 如果是最后一个则存储
            if (i == cDataSize / 3 - 1)
            {
                // 存储最后一个g值组
                compressedData[compressedSize] = repeat;
                compressedData[compressedSize + 1] = curR;
                // 增加编码数据长度
                compressedSize += 2;
            }
        }
        else
        {
            // 存储上一个g值组
            compressedData[compressedSize] = repeat;
            compressedData[compressedSize + 1] = curR;
            // 增加编码数据长度
            compressedSize += 2;
            // 换下一种r值
            curR = nextR;
            repeat = 1;
            // 如果是最后一个
            if (i == cDataSize / 3 - 1)
            {
                // 存储最后一个r值
                compressedData[compressedSize] = 1;
                compressedData[compressedSize + 1] = curR;
                // 增加编码数据长度
                compressedSize += 2;
            }
        }
    }

    // 取出压缩后的纯数据
    cDataSize = compressedSize;
    unsigned char *finalData = new unsigned char[cDataSize];
    for (int i = 0; i < cDataSize; i++)
    {
        unsigned char temp = compressedData[i];
        finalData[i] = temp;
    }
    delete compressedData;
    compressedData = finalData;

    return compressedData;
}

无损解压缩:

void CAppCompress::Decompress(unsigned char *compressedData, int cDataSize, unsigned char *uncompressedData) {

    // 寻找g通道和r通道在压缩数据数组中的偏移坐标
    int offset_r = 0, offset_g = 0;
    int pixelCount = 0;
    for (int i = 0; i < cDataSize;)
    {
        int curRpeat = compressedData[i];
        pixelCount += curRpeat;
        i += 2;
        if (pixelCount == width*height)
        {
            offset_g = i;// g通道的开始坐标
        }
        if (pixelCount == width*height * 2)
        {
            offset_r = i;// r通道的开始坐标
        }
    }

    unsigned int b, g, r;
    int repeat;
    // 1.还原b通道
    for (int i = 0, j = 0; i < width*height, j < offset_g; j += 2)
    {
        // 恢复一组重复的b值
        repeat = compressedData[j];
        for (int p = 0; p < repeat; p++)
        {
            int d = compressedData[j + 1];
            uncompressedData[i * 3 + p*3 + 0] = compressedData[j + 1];
        }
        i += repeat;
    }

    // 2.还原g通道
    for (int i = 0, j = offset_g; i < width*height, j < offset_r; j += 2)
    {
        repeat = compressedData[j];
        for (int p = 0; p < repeat; p++)
        {
            int d = compressedData[j + 1];
            uncompressedData[i * 3 + p * 3 + 1] = compressedData[j + 1];
        }
        i += repeat;
    }

    // 3.还原r通道
    for (int i = 0, j = offset_r; i < width*height, j < cDataSize; j += 2)
    {
        repeat = compressedData[j];
        for (int p = 0; p < repeat; p++)
        {
            int d = compressedData[j + 1];
            uncompressedData[i * 3 + p * 3 + 2] = compressedData[j + 1];
        }
        i += repeat;
    }
}

效果分析:

最好情况: 算法基于通道像素重复,最好的情况自然是纯色推图像。算法对于颜色比较单调的图像压缩效果较好;

最差情况: 最差情况是三个通道相邻的两个像素的值都不同,这时候压缩后的数据刚好是原数据的两倍大小,每一个像素各通道值都额外用了一个8位存储重复个数,且重复个数都是1。

压缩到六十四分之一:

压缩到三分之一:

压缩失败:


量化压缩与无损压缩组合

直接使用该算法对图像压缩,面对色彩变化丰富的图像总是压缩失败的,但如果先对图像进行有损量化,再对量化后的图像进行无损压缩往往可以取得不错的效果。量化实际上是为无损压缩提高了容错性,本来两个通道值相差可能很小,如果能包容这微小的差异那么将大大提高压缩率。下图中打印的三个压缩率依次是:直接压缩的压缩率、有损量化的压缩率、对量化后的图像再进行无损压缩的压缩率。

时间: 2024-10-06 02:57:01

图像的基本有损压缩和无损压缩及解压的相关文章

python tar.gz格式压缩、解压

压缩 代码 import tarfile import os def tar(fname): t = tarfile.open(fname + ".tar.gz", "w:gz") for root, dir, files in os.walk(fname): print root, dir, files for file in files: fullpath = os.path.join(root, file) t.add(fullpath) t.close()

ZIP压缩算法详细分析及解压实例解释

最近自己实现了一个ZIP压缩数据的解压程序,觉得有必要把ZIP压缩格式进行一下详细总结,数据压缩是一门通信原理和计算机科学都会涉及到的学科,在通信原理中,一般称为信源编码,在计算机科学里,一般称为数据压缩,两者本质上没啥区别,在数学家看来,都是映射.一方面在进行通信的时候,有必要将待传输的数据进行压缩,以减少带宽需求:另一方面,计算机存储数据的时候,为了减少磁盘容量需求,也会将文件进行压缩,尽管现在的网络带宽越来越高,压缩已经不像90年代初那个时候那么迫切,但在很多场合下仍然需要,其中一个原因是

基于哈夫曼编码的压缩解压程序

这个程序是研一上学期的课程大作业.当时,跨专业的我只有一点 C 语言和数据结构基础,为此,我查阅了不少资料,再加上自己的思考和分析,实现后不断调试.测试和完善,耗时一周左右,在 2012/11/19 完成.虽然这是一个很小的程序,但却是我完成的第一个程序. 源码托管在 Github:点此打开链接 一.问题描述: 名称:基于哈夫曼编码的文件压缩解压 目的:利用哈夫曼编码压缩存储文件,节省空间 输入:任何格式的文件(压缩)或压缩文件(解压) 输出:压缩文件或解压后的原文件 功能:利用哈夫曼编码压缩解

Linux解压有思路,所以任行

接触Linux系统后,发现有些特殊的文件,有些看似和windows下的文件有些渊源,例如filename.zip,是否真是“大明湖畔夏雨荷”?研习了相关Linux指导资料后,犹如思路打开,可以在“任行”一回. 1.filename.tar 此类文件适用于tar命令,tar是Linux中常用的打包命令,常称为tar包. tar -c 压缩归档 tar -x 解压 tar -t 查看内容 tar -r 向压缩归档文件末尾追加文件 tar -u更新原压缩包中的文件 tar -v显示过程 tar -o将

.net GZipStream 压缩与解压

简介: GzipStream表示GZip 数据格式,它使用无损压缩和解压缩文件的行业标准算法. 这种格式包括一个检测数据损坏的循环冗余校验值. GZip 数据格式使用的算法与 DeflateStream 类的算法相同,但它可以扩展以使用其他压缩格式. 这种格式可以通过不涉及专利使用权的方式轻松实现. 实际使用中因为涉及到网络传输大量数据,直接传送简直不能忍,用GzipStream压缩了一下后再传输流量立即下降了80%,,主要是因为ASCII文本格式有比较高的压缩率所以会比较高. GzipStre

hadoop压缩与解压

1 压缩 一般来说,计算机处理的数据都存在一些冗余度,同时数据中间,尤其是相邻数据间存在着相关性,所以可以通过一些有别于原始编码的特殊编码方式来保存数据, 使数据占用的存储空间比较小,这个过程一般叫压缩.和压缩对应的概念是解压缩,就是将被压缩的数据从特殊编码方式还原为原始数据的过程. 压缩广泛应用于海量数据处理中,对数据文件进行压缩,可以有效减少存储文件所需的空间,并加快数据在网络上或者到磁盘上的传输速度.在Hadoop中,压缩应用于文件存储.Map阶段到Reduce阶段的数据交换(需要打开相关

SDL2播放FFmpeg解压的视频

SDL2简化了播放过程,这里引入播放视频. 1. 以我的<FFmpeg入门测试>为工程. 2. 到http://www.libsdl.org/index.php下载SDL2-devel-2.0.9-VC.zip (Visual C++ 32/64-bit)最新版.解压将SDL2-2.0.9\lib\x86目录内的SDL2.dll考入解决方案的Debug目录中.在解决方案目录中新建SDL目录,拷入SDL2-2.0.9内的include和lib两个目录. 3. 创建开发环境: 3.1 包含编译文件

使用JAVA解压加密的中文ZIP压缩包

近来项目中需要对ZIP压缩包解压,然后将解压后的内容存放到指定的目录下. 该压缩包的特性: 使用标准的zip压缩格式(压缩算法没有深入探究) 压缩包中带有目录并且目录名称是中文 压缩时加了密码 因为jre中自带的java.util.zip.*包不支持中文及加密压缩,所以选择使用zip4j包. 下面是解压的实现代码: 1 public class UnZip { 2 private final int BUFF_SIZE = 4096; 3 4 /* 5 获取ZIP文件中的文件名和目录名 6 */

C#解压多个文件夹下的多个zip到一个目录下

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.IO; 6 using ICSharpCode.SharpZipLib; 7 using ICSharpCode.SharpZipLib.Zip; 8 using ICSharpCode.SharpZipLib.Checksums; 9 10 namespace Unzip 11