精华内容
下载资源
问答
  • JPEG压缩编码过程

    千次阅读 2011-06-02 15:27:00
        JPEG压缩编码过程   1.1JPEG简介    JPEG(Joint Photographic Experts Group)由ISO与IEC于1986年联合成立一个专家委员会(WG1)。该委员会制定了一系列静态连续...

     

     

    JPEG压缩编码过程

     

    1.1 JPEG简介

     

             JPEGJoint Photographic Experts Group)是由ISOIEC1986年联合成立的一个专家委员会(WG1)该委员会制定了一系列的静态连续色调图像压缩编码标准(如:有损、无损及接近无损等编码标准,并于1996年开始制定JPEG 2000标准。

    JPEG文件使用的颜色空间为YCbCr空间。因此要先将RGB转化为YCbCr空间:

                       Y = 0.299 R + 0.587 G + 0.114 B

         Cb = - 0.1687R - 0.3313G + 0.5 B + 128

         Cr = 0.5 R - 0.4187G - 0.0813 B + 128

     

    1.2     JPEG压缩编码及解压缩算法

    1.1 压缩编码算法                1.2解压缩算法

     

    JPEG压缩编码算法的主要计算步骤如下(解压缩步骤与其相反)

    (1)正向离散余弦变换(FDCT)

    (2)量化;

    (3)Z形扫描;

    (4)使用差分脉冲编码调制(DPCM)对直流系数(DC)进行编码;使用行程长度编码(RLE)对交流系数(AC)进行编码;熵编码。

     

    1.2.1          FDCT

     

             对每个单独的彩色图像分量,把整个分量图像分成8x8的图像块,并作为二维离散变换DCT的输入。通过DCT变换,把能量集中在少数几个系数上。它所作的变换可以简单地理解成(x,y)->(x’,y’),即实数对应到实数。

    DCT变换使用下面的计算公式:

    它的逆变换使用下式计算:

    在上面的式子中:

     

    f(ij)经变换之后,F(0,0)是直流系数(DC,64个空域图像采样值的平均值),其他为交流系数(AC)

     

    1.2.2  量化

     

        量化是对经过FDCT变换后的频率系数进行量化,其目的是减小非“0”系数的幅度以及增加“0”值系数的数目。对于有损压缩算法,使用均匀量化器进行量化。用FDCT生成的数据除以对应的量化步距。因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。

    1.3亮度量化表         1.4 色差量化表

     

    1.2.3          z形扫描

     

     

    1.5 Z形扫描

     

             量化后的系数要重新编排,目的是为了增加连续的“0”系数的个数,就是“0”的游程长度,方法是按照Z字形的式样编排,其结果是把一个8x8的矩阵变成一个1x64的矢量,频率较低的系数放在矢量的顶部。

     

    1.2.4          DC编码

     

             8x8图像块经过DCT变换之后得到的DC直流系数有两个特点,一是系数的数值比较大,二是相邻8x8图像块的DC系数值变化不大。JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值(Delta)进行编码。

     

    1.2.5         AC编码

             AC系数的特点:1x64矢量中包含有许多“0”系数,并且许多“0”是连续的。JPEG使用非常简单和直观的游程编码(RLE)对它们进行编码。

    例如:5555557777733322221111111

    行程编码为:(56)(75)(33)(24)(17)

     

    1.2.6         熵编码

             采用huffman编码。对DPCM编码后的直流DC系数和RLE编码后的交流AC系数作进一步的压缩。压缩数据符号时,霍夫曼编码器对出现频度比较高的符号分配比较短的代码,而对出现频度较低的符号分配比较长的代码。这种可变长度的霍夫曼码表可以事先进行定义。

        参考资料

    http://read.chaoxing.com/ebook/read_11769804.html DCT

    http://read.chaoxing.com/ebook/read_12304091.html 数字图像处理

    http://nes.ustc.edu.cn/jy/05/050001/050001005/m3_2.ppt JPEG图像编码标准

    展开全文
  • 本博客主要自己看的,看了下面这些文章后就会很容易理解JPEG压缩编码的过程。 书籍: 多媒体技术基础(第二版)林福宗————5.6 JPEG压缩编码 论文: 基于DCT的JPEG图像压缩的研究_马媛媛.pdf基于...

    本博客主要是自己看的,看了下面这些文章后就会很容易理解JPEG压缩编码的过程。

    书籍:

    1. 多媒体技术基础(第二版)林福宗————5.6  JPEG压缩编码

    论文:

    1. 基于DCT的JPEG图像压缩的研究_马媛媛.pdf
    2. 基于MATLAB的DCT变换在JPEG图像压缩中的应用_李秀敏.pdf
    3. 基于DCT的JPEG图像压缩编码算法的MATLAB实现_钱裕禄.pdf
    4. 基于Matlab的JPEG图像压缩方法研究_吴亚榕.pdf
    5. 基于DCT的JPEG图像压缩及实现_吴术路.pdf
    6. 基于DCT变换的JPEG图像压缩及其MATLAB实现_余秋菊.pdf
    展开全文
  • 空间光调制过程是空间编码压缩光谱成像方法中影响光谱成像数据保真度重要环节。为拓展现有压缩光谱成像空间光调制的编码种类,揭示其与成像数据保真度关联规律,针对压缩光谱成像中的编码调制效应展开研究。基于...
  • Data-Compression-:该项目包括减少存储或传输给定信息所需的数据量的过程。 使用霍夫曼编码算法实现无损数据压缩。 在压缩过程中,有一个文件包含要压缩的输入文本,输出将创建另外两个文件。 一个文件包含压缩文本...
  • 音视频在开发中,最重要也最复杂的就是编解码的过程,在上一篇的《Android音视频开发:踩一踩“门槛”》中,我们说音频的编码根据大小划分有两种:压缩编码和非压缩编码,那到底怎么实现的这两中编码的呢?...

    音视频在开发中,最重要也是最复杂的就是编解码的过程,在上一篇的《Android音视频开发:踩一踩“门槛”》中,我们说音频的编码根据大小划分有两种:压缩编码和非压缩编码,那到底是怎么实现的这两中编码的呢?这一次就详细了解Android中如何使用这两种方式进行音频编码

    前景提要
    这里先回顾一下音频的压缩编码和非压缩编码:

    非压缩编码:音频裸数据,也即是我们所说的PCM
    压缩编码:对数据进行压缩,压缩不能被人耳感知到的冗余信号
    因为非压缩编码实在是太大了,所以我们生活中所接触的音频编码格式都是压缩编码,而且是有损压缩,比如 MP3或AAC。
    那如何操作PCM数据呢?Android SDK中提供了一套对PCM操作的API:AudioRecord 和 AudioTrack;

    由于AudioRecord(录音) 和 AudioTrack(播放)操作过于底层而且过于复杂,所以Android SDK 还提供了一套与之对应更加高级的API:MediaRecorder(录音)和MediaPlayer(播放),用于音视频的操作,当然其更加简单方便。我们这里只介绍前者,通过它来实现对PCM数据的操作。

    对于压缩编码,我们则通过MediaCodec和Lame来分别实现AAC音频和Mp3音频压缩编码。话不多说,请往下看!

    AudioRecord
    由于AudioRecord更加底层,能够更好的并且直接的管理通过音频录制硬件设备录制后的PCM数据,所以对数据处理更加灵活,但是同时也需要我们自己处理编码的过程。

    AudioRecord的使用流程大致如下:

    根据音频参数创建AudioRecord
    调用startRecording开始录制
    开启录制线程,通过AudioRecord将录制的音频数据从缓存中读取并写入文件
    释放资源
    在使用AudioRecord前需要先注意添加RECORD_AUDIO录音权限。

    创建AudioRecord
    我们先看看AudioRecord构造方法

    public AudioRecord (int audioSource,
    int sampleRateInHz,
    int channelConfig,
    int audioFormat,
    int bufferSizeInBytes)
    复制代码
    audioSource,从字面意思可知音频来源,由MediaRecorder.AudioSource提供,主要有以下内容

    · CAMCORDER 与照相机方向相同的麦克风音频源

    · DEFAULT 默认

    · MIC 麦克风音频源

    · VOICE_CALL 语音通话

    这里采用MIC麦克风音频源

    sampleRateInHz,采样率,即录制的音频每秒钟会有多少次采样,可选用的采样频率列表为:8000、16000、22050、24000、32000、44100、48000等,一般采用人能听到最大音频的2倍,也就是44100Hz。

    channelConfig,声道数的配置,可选值以常量的形式配置在类AudioFormat中,常用的是CHANNEL_IN_MONO(单声道)、CHANNEL_IN_STEREO(双声道)

    audioFormat,采样格式,可选值以常量的形式定义在类AudioFormat中,分别为ENCODING_PCM_16BIT(16bit)、ENCODING_PCM_8BIT(8bit),一般采用16bit。

    bufferSizeInBytes,其配置的是AudioRecord内部的音频缓冲区的大小,可能会因为生产厂家的不同而有所不同,为了方便AudioRecord提供了一个获取该值最小缓冲区大小的方法getMinBufferSize。

    public static int getMinBufferSize (int sampleRateInHz,
    int channelConfig,
    int audioFormat)
    复制代码
    在开发过程中需使用getMinBufferSize此方法计算出最小缓存大小。

    切换录制状态
    首先通过调用getState判断AudioRecord是否初始化成功,然后通过startRecording切换成录制状态

    if (null!=audioRecord && audioRecord?.state!=AudioRecord.STATE_UNINITIALIZED){
    audioRecord?.startRecording()
    }
    复制代码
    开启录制线程
    thread = Thread(Runnable {
    writeData2File()
    })
    thread?.start()
    复制代码
    开启录音线程将录音数据通过AudioRecord写入文件

    private fun writeData2File() {
    var ret = 0
    val byteArray = ByteArray(bufferSizeInBytes)
    val file = File(externalCacheDir?.absolutePath + File.separator + filename)

    if (file.exists()) {
        file.delete()
    } else {
        file.createNewFile()
    }
    val fos = FileOutputStream(file)
    while (status == Status.STARTING) {
        ret = audioRecord?.read(byteArray, 0, bufferSizeInBytes)!!
        if (ret!=AudioRecord.ERROR_BAD_VALUE || ret!=AudioRecord.ERROR_INVALID_OPERATION|| ret!=AudioRecord.ERROR_DEAD_OBJECT){
            fos.write(byteArray)
        }
    }
    fos.close()
    

    }
    复制代码
    释放资源
    首先停止录制

    if (null!=audioRecord && audioRecord?.state!=AudioRecord.STATE_UNINITIALIZED){
    audioRecord?.stop()
    }
    复制代码
    然后停止线程

    if (thread!=null){
    thread?.join()
    thread =null
    }
    复制代码
    最后释放AudioRecord

    if (audioRecord != null) {
    audioRecord?.release()
    audioRecord = null
    }
    复制代码
    通过以上一个流程之后,就可以得到一个非压缩编码的PCM数据了。

    但是这个数据在音乐播放器上一般是播放不了的,那么怎么验证我是否录制成功呢?当然是使用我们的AudioTrack进行播放看看是不是刚刚我们录制的声音了。

    【完整代码-AudioRecord】

    AudioTrack
    由于AudioTrack是由Android SDK提供比较底层的播放API,也只能操作PCM裸数据,通过直接渲染PCM数据进行播放。当然如果想要使用AudioTrack进行播放,那就需要自行先将压缩编码格式文件解码。

    AudioTrack的使用流程大致如下:

    根据音频参数创建AudioTrack
    调用play开始播放
    开启播放线程,循环想AudioTrack缓存区写入音频数据
    释放资源
    创建AudioTrack
    我们来看看AudioTrack的构造方法

    public AudioTrack (int streamType,
    int sampleRateInHz,
    int channelConfig,
    int audioFormat,
    int bufferSizeInBytes,
    int mode,
    int sessionId)
    复制代码
    streamType,Android手机上提供音频管理策略,按下音量键我们会发现由媒体声音管理,闹铃声音管理,通话声音管理等等,当系统有多个进程需要播放音频的时候,管理策略会决定最终的呈现效果,该参数的可选值将以常量的形式定义在类AudioManager中,主要包括以下内容:

    · STREAM_VOCIE_CALL:电话声音

    · STREAM_SYSTEM:系统声音

    · STREAM_RING:铃声

    · STREAM_MUSCI:音乐声

    · STREAM_ALARM:警告声

    · STREAM_NOTIFICATION:通知声

    因为这里是播放音频,所以我们选择STREAM_MUSCI。

    sampleRateInHz,采样率,即播放的音频每秒钟会有多少次采样,可选用的采样频率列表为:8000、16000、22050、24000、32000、44100、48000等,一般采用人能听到最大音频的2倍,也就是44100Hz。

    channelConfig,声道数的配置,可选值以常量的形式配置在类AudioFormat中,常用的是CHANNEL_IN_MONO(单声道)、CHANNEL_IN_STEREO(立体双声道)

    audioFormat,采样格式,可选值以常量的形式定义在类AudioFormat中,分别为ENCODING_PCM_16BIT(16bit)、ENCODING_PCM_8BIT(8bit),一般采用16bit。

    bufferSizeInBytes,其配置的是AudioTrack内部的音频缓冲区的大小,可能会因为生产厂家的不同而有所不同,为了方便AudioTrack提供了一个获取该值最小缓冲区大小的方法getMinBufferSize。

    mode,播放模式,AudioTrack提供了两种播放模式,可选的值以常量的形式定义在类AudioTrack中,一个是MODE_STATIC,需要一次性将所有的数据都写入播放缓冲区中,简单高效,通常用于播放铃声、系统提醒的音频片段;另一个是MODE_STREAM,需要按照一定的时间间隔不间断地写入音频数据,理论上它可以应用于任何音频播放的场景。

    sessionId,AudioTrack都需要关联一个会话Id,在创建AudioTrack时可直接使用AudioManager.AUDIO_SESSION_ID_GENERATE,或者在构造之前通过AudioManager.generateAudioSessionId获取。

    上面这种构造方法已经被弃用了,现在基本使用如下构造(最小skd 版本需要>=21),参数内容与上基本一致:

    public AudioTrack (AudioAttributes attributes,
    AudioFormat format,
    int bufferSizeInBytes,
    int mode,
    int sessionId)
    复制代码
    通过AudioAttributes.Builder设置参数streamType

    var audioAttributes = AudioAttributes.Builder()
    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
    .build()
    复制代码
    通过AudioFormat.Builder设置channelConfig,sampleRateInHz,audioFormat参数

    var mAudioFormat = AudioFormat.Builder()
    .setChannelMask(channel)
    .setEncoding(audioFormat)
    .setSampleRate(sampleRate)
    .build()
    复制代码
    切换播放状态
    首先通过调用getState判断AudioRecord是否初始化成功,然后通过play切换成录播放状态

    if (null!=audioTrack && audioTrack?.state != AudioTrack.STATE_UNINITIALIZED){
    audioTrack?.play()
    }
    复制代码
    开启播放线程
    开启播放线程

    thread= Thread(Runnable {
    readDataFromFile()
    })
    thread?.start()
    复制代码
    将数据不断的送入缓存区并通过AudioTrack播放

    private fun readDataFromFile() {
    val byteArray = ByteArray(bufferSizeInBytes)

    val file = File(externalCacheDir?.absolutePath + File.separator + filename)
    if (!file.exists()) {
        Toast.makeText(this, "请先进行录制PCM音频", Toast.LENGTH_SHORT).show()
        return
    }
    val fis = FileInputStream(file)
    var read: Int
    status = Status.STARTING
    
    while ({ read = fis.read(byteArray);read }() > 0) {
        var ret = audioTrack?.write(byteArray, 0, bufferSizeInBytes)!!
        if (ret == AudioTrack.ERROR_BAD_VALUE || ret == AudioTrack.ERROR_INVALID_OPERATION || ret == AudioManager.ERROR_DEAD_OBJECT) {
            break
        }
    }
    fis.close()
    

    }
    复制代码
    释放资源
    首先停止播放

    if (audioTrack != null && audioTrack?.state != AudioTrack.STATE_UNINITIALIZED) {
    audioTrack?.stop()
    }
    复制代码
    然后停止线程

    if (thread!=null){
    thread?.join()
    thread =null
    }
    复制代码
    最后释放AudioTrack

    if (audioTrack != null) {
    audioTrack?.release()
    audioTrack = null
    }
    复制代码
    经过这样几个步骤,我们就可以听到刚刚我们录制的PCM数据声音啦!这就是使用Android提供的AudioRecord和AudioTrack对PCM数据进行操作。

    但是仅仅这样是不够的,因为我们生活中肯定不是使用PCM进行音乐播放,那么怎么才能让音频在主流播放器上播放呢?这就需要我们进行压缩编码了,比如mp3或aac压缩编码格式。

    【完整代码-AudioTrack】

    MediaCodec编码AAC
    AAC压缩编码是一种高压缩比的音频压缩算法,AAC压缩比通常为18:1;采样率范围通常是8KHz~96KHz,这个范围比MP3更广一些(MP3的范围一般是:16KHz~48KHz),所以在16bit的采样格式上比MP3更精细。

    方便我们处理AAC编码,Android SDK中提供了MediaCodecAPI,可以将PCM数据编码成AAC数据。大概需要以下几个步骤:

    创建MediaCodec
    为MediaCodec配置音频参数
    启动线程,循环往缓冲区送入数据
    通过MediaCodec将缓冲区的数据进行编码并写入文件
    释放资源
    创建MediaCodec
    通过MediaCodec.createEncoderByType创建编码MediaCodec

    mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
    复制代码
    配置音频参数
    // 配置采样率和声道数
    mediaFormat = MediaFormat.createAudioFormat(MINE_TYPE,sampleRate,channel)
    // 配置比特率
    mediaFormat?.setInteger(MediaFormat.KEY_BIT_RATE,bitRate)
    // 配置PROFILE,其中属AAC-LC兼容性最好
    mediaFormat?.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
    // 最大输入大小
    mediaFormat?.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 10 * 1024)

    mediaCodec!!.configure(mediaFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE)
    mediaCodec?.start()

    inputBuffers = mediaCodec?.inputBuffers
    outputBuffers = mediaCodec?.outputBuffers
    复制代码
    启动线程
    启动线程,循环读取PCM数据送入缓冲区

    thread = Thread(Runnable {
    val fis = FileInputStream(pcmFile)
    fos = FileOutputStream(aacFile)
    var read: Int
    while ({ read = fis.read(byteArray);read }() > 0) {
    encode(byteArray)
    }
    })
    thread?.start()
    复制代码
    AAC编码
    将送入的PCM数据通过MediaCodec进行编码,大致流程如下:

    通过可用缓存去索引,获取可用输入缓冲区
    将pcm数据放入输入缓冲区并提交
    根据输出缓冲区索引,获取输出缓冲区
    创建输出数据data,并添加ADTS头部信息(有7byte)
    将outputBuffer编码后数据写入data(data有7byte偏移)
    将编码数据data写入文件
    重复以上过程
    private fun encode(byteArray: ByteArray){
    mediaCodec?.run {
    //返回要用有效数据填充的输入缓冲区的索引, -1 无限期地等待输入缓冲区的可用性
    val inputIndex = dequeueInputBuffer(-1)
    if (inputIndex > 0){
    // 根据索引获取可用输入缓存区
    val inputBuffer = this@AACEncoder.inputBuffers!![inputIndex]
    // 清空缓冲区
    inputBuffer.clear()
    // 将pcm数据放入缓冲区
    inputBuffer.put(byteArray)
    // 提交放入数据缓冲区索引以及大小
    queueInputBuffer(inputIndex,0,byteArray.size,System.nanoTime(),0)
    }
    // 指定编码器缓冲区中有效数据范围
    val bufferInfo = MediaCodec.BufferInfo()
    // 获取输出缓冲区索引
    var outputIndex = dequeueOutputBuffer(bufferInfo,0)

        while (outputIndex>0){
            // 根据索引获取可用输出缓存区
            val outputBuffer =this@AACEncoder.outputBuffers!![outputIndex]
            // 测量输出缓冲区大小
            val bufferSize = bufferInfo.size
            // 输出缓冲区实际大小,ADTS头部长度为7
            val bufferOutSize = bufferSize+7
            
            // 指定输出缓存区偏移位置以及限制大小
            outputBuffer.position(bufferInfo.offset)
            outputBuffer.limit(bufferInfo.offset+bufferSize)
            // 创建输出空数据
            val data = ByteArray(bufferOutSize)
            // 向空数据先增加ADTS头部
            addADTStoPacket(data, bufferOutSize)
            // 将编码输出数据写入已加入ADTS头部的数据中
            outputBuffer.get(data,7,bufferInfo.size)
            // 重新指定输出缓存区偏移
            outputBuffer.position(bufferInfo.offset)
            // 将获取的数据写入文件
            fos?.write(data)
            // 释放输出缓冲区
            releaseOutputBuffer(outputIndex,false)
            // 重新获取输出缓冲区索引
            outputIndex=dequeueOutputBuffer(bufferInfo,0)
        }
    }
    

    }
    复制代码
    释放资源
    编码完成后,一定要释放所有资源,首先关闭输入输出流

    fos?.close()
    fis.close()
    复制代码
    停止编码

    if (mediaCodec!=null){
    mediaCodec?.stop()
    }
    复制代码
    然后就是关闭线程

    if (thread!=null){
    thread?.join()
    thread =null
    }
    复制代码
    最后释放MediaCodec

    if (mediaCodec!=null){
    mediaCodec?.release()
    mediaCodec = null

    mediaFormat = null
    inputBuffers = null
    outputBuffers = null
    

    }
    复制代码
    通过以上一个流程,我们就可以得到一个AAC压缩编码的音频文件,可以听一听是不是自己刚刚录制的。我听了一下我自己唱的一首歌,觉得我的还是可以的嘛,也不是那么五音不全~~

    展开全文
  • 哈夫曼编码(Huffman Coding),又称霍夫曼编码一种...本文主要利用霍夫曼编码压缩字节数组(可以任何文件),流程主要 1.源字节数组—>根据各个字节出现频次,创建有权重(即该字节出现次数)二叉

    哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。

    哈夫曼编码,主要目的是根据使用频率来最大化节省字符(编码)的存储空间。
    本文主要利用霍夫曼编码来压缩字节数组(可以是任何文件),同时修复了尚硅谷韩顺平老师的bug,对于字节数组最后一位的补位问题
    流程主要是
    1.源字节数组—>根据各个字节出现的频次,创建有权重(即该字节出现的次数)的二叉树结点,存储到列表中
    2.根据上一步的列表list,创建霍夫曼树(创建过程原理可参照百度,原理较为简单),获得根节点
    3.获得霍夫曼树后,重新编码,根据路径左0右1的原则,对每个字符的霍夫曼编码存储到map中,便于后续压缩
    4.对照编码map,对源字节数组重新编码,获得一串二进制数组
    5.编码后的二进制数组,每8位存储为一个字节,得到的结果就是编码压缩后的新字节数组。
    6.解码:先将压缩后的字节数组依次读取转成二进制字符串
    7.反转map,依次读取二进制字符串,还原源字节数组

    附上源码

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.OutputStream;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class HuffmanCode {
    	public static void main(String[] args) {
    		
    		zipFile("G:/src.bmp", "G:/dest.zip");
    		System.out.println("压缩成功");
    		
    		unZipFile("G:/dest.zip", "G:/src2.bmp");
    		System.out.println("解压成功");
    		
    		/*
    		String content = "i like like like java do you like a javae";
    //		String content = "你好";
    		
    		byte[] contentBytes = content.getBytes();
    		System.out.println("原文件字节数组:"+Arrays.toString(contentBytes));
    //		System.out.println("原文件字节数组长度="+contentBytes.length);//40
    		
    		byte[] huffmanCodeBytes = huffmanZip(contentBytes);
    		System.out.println("压缩后的数组为:"+Arrays.toString(huffmanCodeBytes));
    		
    		byte[] decodeBytes = decode(huffmanCodes, huffmanCodeBytes);
    		System.out.println("解码后的结果:"+new String(decodeBytes));
    		*/
    		
    		/*
    		List<Node> nodes = getNodes(contentBytes);
    		System.out.println(nodes);
    		
    		Node huffmanTreeRoot = createHuffmanTree(nodes);
    		huffmanTreeRoot.preList();
    		
    		Map<Byte, String> huffmanCodes = createHuffmanCode(huffmanTreeRoot);
    		System.out.println(huffmanCodes);
    		
    		byte[] huffmanCodeBytes = zip(contentBytes, huffmanCodes);
    		System.out.println(Arrays.toString(huffmanCodeBytes)); //只有17长度*/
    	}
    	
    	//存放哈夫曼编码表
    	static Map<Byte, String> huffmanCodes = new HashMap<>();
    	//二进制字符串8位存放一个字节,存放编码后的最后一个数字位数
    	static int lastNumCap;
    	
    	//解压文件
    	public static void unZipFile(String src,String dest) {
    		InputStream is = null;
    		ObjectInputStream ois = null;
    		OutputStream os = null;
    		
    		try {
    			is = new FileInputStream(src);
    			ois = new ObjectInputStream(is);
    			byte[] srcBytes = (byte[]) ois.readObject();//读取的编码后的字节数组
    			Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();//读取编码表
    			byte[] destBytes = decode(huffmanCodes, srcBytes);
    			
    			os = new FileOutputStream(dest);
    			os.write(destBytes);
    			os.flush();
    		} catch (Exception e) {
    			System.out.println(e.getMessage());
    		}finally {
    			try {
    				os.close();
    				ois.close();
    				is.close();
    			} catch (IOException e) {
    				// TODO 自动生成的 catch 块
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	//利用霍夫曼编码压缩普通文件
    	public static void zipFile(String src,String dest) {
    		InputStream is = null;
    		OutputStream os = null;
    		ObjectOutputStream oos = null;
    		try {
    			is = new FileInputStream(src);
    			byte[] srcBytes = new byte[is.available()];
    			is.read(srcBytes);
    			
    			byte[] zipBytes = huffmanZip(srcBytes);
    			
    			os = new FileOutputStream(dest);
    			oos = new ObjectOutputStream(os);//为了便于依次写入文件和编码表,便于后续解压
    			oos.writeObject(zipBytes);
    			oos.writeObject(huffmanCodes);
    			
    		} catch (Exception e) {
    			System.out.println(e.getMessage());
    		}finally {
    			try {
    				oos.close();
    				os.close();
    				is.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	//完成数据解压
    	//1.压缩后的数组[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
    	//byte转成二进制字符串,并且拼接
    	//2.逐个读取二进制字符串,根据哈夫曼编码表解码,得到解码后的字节数组
    	
    	/**
    	 * 解压方法
    	 * @param huffmanCodes 霍夫曼编码表(压缩时创建)
    	 * @param huffmanBytes 需要解压字节数组
    	 * @return 压缩前的源文件,即解压后的字节数组
    	 */
    	private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanBytes) {
    		StringBuilder stringBuilder = new StringBuilder();
    
    		//1.字节数组转成二进制字符串[-89, -34, -121, -34, -121, -->
    		//101010001011111111001000101111111100100。。。
    		for(int i = 0;i<huffmanBytes.length;i++) {
    			byte b = huffmanBytes[i];
    			boolean flag = (i==huffmanBytes.length-1);//判断是否是最后一个,根据实际需要是否要补位
    			stringBuilder.append(byteToBitString(!flag, b));
    		}
    //		System.out.println(stringBuilder);
    		
    		//2.读取二进制字符串,根据编码表获得原本的字节数组
    		//反转map,便于查询101010001011111111001000
    		Map<String, Byte> map = new HashMap<>();
    		for(Map.Entry<Byte, String> entry:huffmanCodes.entrySet()) {
    			map.put(entry.getValue(), entry.getKey()); 
    		}
    		
    		//打印解码用的map,测试用
    //		System.out.println(map);
    		List<Byte> list = new ArrayList<>();
    		int count;//每次读取的长度,读取不到递增变化
    		boolean flag = true;
    		String str;
    		
    		for(int i = 0;i<stringBuilder.length();) {
    //			if(i==stringBuilder.length()-4) {
    //				System.out.println("???");
    //			}
    			count = 1;
    			flag = true;
    			while(flag) {
    				str = stringBuilder.substring(i,i+count);
    				if(map.containsKey(str)) {//包含key
    					list.add(map.get(str));
    					//退出循环
    					flag = false;
    				}else {
    					//需要继续往下读取
    					count++;
    				}
    			}
    			
    			//读取完一个字节后,i+count这里还没读取
    			i +=count;
    		}
    //		System.out.println(list.size());
    		//将list中的byte放入数组
    		byte[] decodeBytes = new byte[list.size()];
    		
    		for(int i = 0;i<decodeBytes.length;i++) {
    			decodeBytes[i] = list.get(i);
    		}
    		
    		return decodeBytes;
    	}
    	
    	/**
    	 * 字节转二进制字符串
    	 * @param flag 表示是否是字节数组最后一个,true表示不是。则需要补位
    	 * @param b
    	 * @return
    	 */
    	private static String byteToBitString(boolean flag,byte b) {
    		int temp = b;//转成int类型
    		if(flag) {
    			temp |=256;//1 0000 0000按位取或  处理正数	
    		}else {
    			int a;
    			//测试使用
    //			System.out.println("temp="+temp);
    			if(lastNumCap!=0) {//例如最后剩下001,lastNumCap=3, a = 1000,所以补充3个0
    				a = (int) (Math.pow(2, lastNumCap));		
    			}else {//说明二进制恰好被整除,最后剩一个8位,仍然按照取8位处理
    				a = 256;
    			}	
    			temp |= (int) a;
    			//测试用代码
    //			System.out.println("a="+a);
    //			System.out.println("temp="+temp);
    		}
    		
    		String str = Integer.toBinaryString(temp);
    		if(flag) {			
    			return str.substring(str.length()-8);
    		}else {
    			lastNumCap = lastNumCap==0?8:lastNumCap;//整除正常取8位,否则取lastNumCap位1001,取001
    			return str.substring(str.length()-lastNumCap);
    		}
    	}
    	
    	/**
    	 * 封装压缩方法
    	 * @param contentBytes 需要压缩的原字节数组
    	 * @return 压缩后的数组
    	 */
    	private static byte[] huffmanZip(byte[] contentBytes) {
    		//根据传入的字符串数组,获得各个字符出现的权重
    		List<Node> nodes = getNodes(contentBytes);
    		//创建赫夫曼树
    		Node huffmanTreeRoot = createHuffmanTree(nodes);
    		//根据赫夫曼树创建赫夫曼编码表
    		Map<Byte, String> huffmanCodes = createHuffmanCode(huffmanTreeRoot);
    		//根据赫夫曼编码表,获取编码后的压缩数组
    		byte[] huffmanCodeBytes = zip(contentBytes, huffmanCodes);
    		return huffmanCodeBytes;
    	}
    	
    	//赫夫曼编码字节数组方法
    	/**
    	 * 
    	 * @param bytes 原字符串的字节数组
    	 * @param huffmanCodes 霍夫曼编码表
    	 * @return 编码后的压缩数组
    	 */
    	private static byte[] zip(byte[] bytes,Map<Byte, String> huffmanCodes) {
    		StringBuilder stringBuilder = new StringBuilder();
    		for(byte key:bytes) {
    			stringBuilder.append(huffmanCodes.get(key));
    		}
    		//这是编码后的二进制字节串
    //		System.out.println(stringBuilder.length());
    //		System.out.println(stringBuilder);
    		
    		int len;//压缩数组的长度
    		lastNumCap = stringBuilder.length() % 8; //记录最后剩下的一位的长度
    		//确定返回压缩数组的长度
    		//也可以一句搞定 len = (stringBuilder.length()+7)/8
    		if(stringBuilder.length()%8==0) {
    			len = stringBuilder.length() / 8;
    		}else {
    			len = stringBuilder.length() / 8 + 1;
    		}
    		
    		byte[] huffmanCodeBytes = new byte[len];
    		int index = 0;
    		
    		for(int i = 0;i<stringBuilder.length();i +=8) {
    			String strByte;
    			if(i+8>stringBuilder.length()) {
    				strByte = stringBuilder.substring(i);
    			}else {
    				strByte = stringBuilder.substring(i,i+8);
    			}
    			huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte, 2);
    			index++;
    		}
    		
    		return huffmanCodeBytes;
    	}
    	
    	
    	
    	//提供方法createHuffmanCode的重载
    	private static Map<Byte, String> createHuffmanCode(Node root){
    		if(root==null) {
    			return null;
    		}
    		createHuffmanCode(root.left, "0", new StringBuilder());
    		createHuffmanCode(root.right,"1",new StringBuilder());
    		
    		return huffmanCodes;
    	}
    	
    	
    	/**
    	 * 生成相应的赫夫曼编码
    	 * @param node 赫夫曼树根节点
    	 * @param code 区分左右路径,左为0,右为1
    	 * @param stringBuilder 到达当前结点的路径,不包括上一个0(1),需要附加code
    	 */
    	public static void createHuffmanCode(Node node,String code,StringBuilder stringBuilder){
    		//这里新建一个stringBuilder,避免后面的修改覆盖
    		StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
    		//这里采用额外参数添加的方式,与上面的理由相同
    		stringBuilder2.append(code);
    		if(node!=null) {
    			if(node.data!=null) {
    				huffmanCodes.put(node.data, stringBuilder2.toString());
    			}else {
    				createHuffmanCode(node.left,"0", stringBuilder2);
    				createHuffmanCode(node.right, "1",stringBuilder2);
    			}
    		}
    	}
    	
    	/**
    	 * 根据原字节数组,获得每个字节权重,创建结点的列表
    	 * @param bytes 需要压缩的字节数组
    	 * @return
    	 */
    	public static List<Node> getNodes(byte[] bytes){
    		List<Node> nodes = new ArrayList<Node>();
    		
    		Map<Byte,Integer> counts = new HashMap<Byte, Integer>();
    		for(byte data:bytes) {
    			Integer count = counts.get(data);
    			if(count==null) {//说明map中还未出现此data
    				counts.put(data, 1);
    			}else {
    				counts.put(data, count+1);
    			}
    		}
    		
    		for(Map.Entry<Byte, Integer> entry:counts.entrySet()) {
    			nodes.add(new Node(entry.getKey(), entry.getValue()));
    		}
    		
    		return nodes;
    	}
    	
    	/**
    	 * 创建霍夫曼树
    	 * @param nodes  例如 [Node [data=-70, weight=1], Node [data=-60, weight=1]
    	 * @return 创建好的霍夫曼树根节点
    	 */
    	public static Node createHuffmanTree(List<Node> nodes) {
    		while(nodes.size()>1) {
    			Collections.sort(nodes);
    			Node leftNode = nodes.remove(0);
    			Node rightNode = nodes.remove(0);
    			Node parent = new Node(null, leftNode.weight+rightNode.weight);
    			parent.left = leftNode;
    			parent.right = rightNode;
    			nodes.add(parent);
    		}
    		
    		return nodes.get(0);
    	}
    }
    
    class Node implements Comparable<Node>{
    	Byte data;//字符
    	int weight;//权重
    	Node left;
    	Node right;
    	
    	public Node(Byte data, int weight) {
    		this.data = data;
    		this.weight = weight;
    	}
    	
    	@Override
    	public String toString() {
    		return "Node [data=" + data + ", weight=" + weight + "]";
    	}
    	
    	//前序遍历
    	public void preList() {
    		System.out.println(this);
    		if(this.left!=null) {
    			this.left.preList();
    		}
    		
    		if(this.right!=null) {
    			this.right.preList();
    		}
    	}
    
    	@Override
    	public int compareTo(Node o) {
    		// TODO 自动生成的方法存根
    		return this.weight - o.weight;
    	}
    }
    
    展开全文
  • 基于Matlab图像压缩编码

    热门讨论 2010-03-23 15:56:44
    尽管我们希望能够无损压缩,但是通常有损压缩的压缩比(即原图象占的字节数与压缩后图象占的字节数之比,压缩比越大,说明压缩效率越高)比无损压缩的高。JPEG编码先把图象色彩RBG变成亮度Y和色度Cr、Cb,它利用人的...
  • “ 本节为opencv数字图像处理(15):图像压缩的第二小节,图像压缩中的编码方法:霍夫曼编码、Golomb编码、Rice编码、算术编码及其实现,主要包括:霍夫曼编码、Golomb编码、Rice编码、算术编码的原理与实现代码。...
  • 据百度百科, 霍夫曼编码, 又称为哈夫曼编码(Huffman), 一种可变字长编码, 在1952年提出...霍夫曼编码, 是一种基于最小冗余编码的压缩算法, 采用数据中符号和频率构建二叉树(前缀树). 该二叉树构建过程是将每个符号.
  • 于是研究利用GPU来加速处理图像编解码以及图像处理, 为此很有必要先了解JPEG的的编解码过程。文章参考了大量外部资料,引用了相关图片以及数据,所涉及到内容或者原理都有相应链接跳转以供查询。...
  • 自己本人独立完成,简化了压缩过程,获取哈夫曼编码后直接写进压缩文件,免去了复杂位运算。其实位运算压缩这个过程,只不过要满8位才会写入,我这个代码省去了判断满8位麻烦,直接写入,可供参考。欢迎大家...
  • 算术编码是一种高效视频编码方法,有着广阔应用前景。在视频编码基本思想基础上,概述算术编码的原理和发展过程,重点分析H.264中基于上下文自适应二进制算术编码的特点,最后对算术编码的最新发展作介绍。
  • 本课题实现信源编解码:PCM编码+ 数据压缩+信道(加性噪声)+数据解压缩+PCM译码。利用C语言使编码器实现输入信号完成PCM技术三个过程:采样、量化与编码,解码器实现还原原信号过程。 二、设计目的 脉冲编码...
  • 原理介绍什么Huffman压缩Huffman( 哈夫曼 ) 算法在上世纪五十年代初提出来了,它一种无损压缩方法,在压缩过程中不会丢失信息熵。并且能够证明 Huffman 算法在无损压缩算法中最优。Huffman 原理简单,实现...
  • 前言:在音视频通信中,音视频数据压缩是有效降低带宽主要方法;其中,视频占用了更高比例带宽,视频压缩更为重要。如果不压缩,一副 RGB 图像,按照 800 x 600 分辨率, 每秒 25 帧帧率, 那么:每秒...
  • 本人小辣鸡一枚,学校一个数据结构作业。在此记录。 编译器:codeblocks 功能: 1.选择文件建立哈夫曼树 2.建立密码本,对文件进行编码 3.选择需要进行解码文件解码 ...下面运行过程,运行完之后,22.tx
  • 文件压缩与解压缩(哈夫曼编码

    千次阅读 2017-09-07 15:41:51
    本文采用哈夫曼编码的方式进行文件(文本文件)压缩和解压缩,首先介绍项目的整体思路:哈夫曼编码压缩文件实际就是统计出文件中各个字符出现...下面将具体介绍文件的压缩和解压缩步骤: 文件的压缩的核心产生哈夫曼
  • Huffman编码实现压缩压缩

    千次阅读 2016-04-04 23:22:05
    Huffman( 哈夫曼 ) 算法在上世纪五十年代初提出来了,它一种无损压缩方法,在压缩过程中不会丢失信息熵,而且可以证明 Huffman 算法在无损压缩算法中最优。 Huffman 原理简单,实现起来也不困难,在现在...
  • jpeg图像的压缩编码与解码

    千次阅读 2012-11-20 16:42:42
    下面我们开始了解jpeg文件的压缩编码过程。 首先我们得了解DCT离散余弦变换和霍夫曼编码以及其他一些常用的编码方式。 JPEG的压缩编码过程如下: 1.颜色模型转换 这一步将常见RGB颜色模型转变为YCrCb模型, ...
  • 本节为opencv数字图像处理(15):图像压缩的第二小节,图像压缩中的编码方法:LZW编码,主要包括:LZW编解码实例与实现代码。 1. LZW编码   这种编码目的在于消除图像中的空间冗余,一种无误差的压缩方法,其...
  • 压缩编码与格式

    千次阅读 2017-07-05 11:15:41
    1、视频编码是指视频文件压缩过程运算方法,同一种格式视频文件其视频编码和音频编码有可能不同。比如同样AVI格式视频文件,其视频编码可以DIVX、XVID、AVC、H.263、H.264、Windows Madia Video等等,...
  • 在图像处理过程中,压缩算法编制最为重要部分,详细论述了利用小波变换来 压缩彩色连续图像技术细节,同时也对在MATLAB环境中实现该算法时遇到问题做了分析, 并给出了解答。
  • HTTP协议压缩格式和URL编码介绍

    千次阅读 2020-12-17 08:05:00
    HTTP压缩是指web服务器和浏览器之间压缩传输请求响应结果方法,通过采用通用的压缩算法,将数据包压缩后进行传输,从而提升页面加载速度,给用户一个更好体验。1 HTTP压缩过程数据...
  • 小波变换压缩编码

    2013-03-20 18:51:44
    图像压缩是用最少数据量来表示尽可能多原图像信息一个过程。小波变换当前数学中一个迅速发展新领域,在MATLAB中,图像压缩是其应用领域中一个方面。 论文首先介绍了图像压缩编码的研究背景和论文研究...
  • 摘 要:哈夫曼编码是一种数据编码方式,以哈夫曼树——...夫曼编码图像压缩技术的原理、算法、过程,并利用VB6.0作为编程开发工具,开发了一个对256色BMP图像进行压缩/解压缩的软件系统, 验证了算法的合理性和可行性。
  • 在本文中,我们将研究编码,编解码器和压缩技术的过程。这包括推荐编解码器的用途,尽管取决于具体情况。它还说明了为什么某些与压缩相关的工件可能会出现在您的视频中。因此,您将更好地了解此过程以及它与自适应...
  • 视频压缩原理之 预测编码

    千次阅读 2015-12-25 09:59:32
    预测法最简单和实用视频压缩方法,压缩编码后传输并不是像素本身取样幅 值,而是该取样预测值和实际值之差(就是传输差分值)。   预测法流程图如下: (在量化过程中,会产生量化误差)   ...
  • 哈夫曼编码压缩

    2020-04-12 15:38:43
    哈夫曼编码是可变字长编码的一种,其根据数据建立哈夫曼树得来。通过哈夫曼编码可以对数据进行压缩。 图示 1. 以下面这句话为例:“oh my god!oh my god! hey”,首先得出字符串中每个字符出现次数,并以...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,536
精华内容 614
关键字:

压缩的过程是编码