欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据压缩任务三:读入 RGB 文件并输出三个分量的概率分布示意图和熵

程序员文章站 2022-07-14 22:16:00
...

思路分析

在听到这个“练手”题目的时候其实心里的思路是很明确的,总共就以下几步

  1. 读入老师提供的 .rgb 文件中的所有数据
  2. 将数据按照颜色通道进行分类存储
  3. 统计各通道颜色值出现次数并计算概率
  4. 通过概率计算得信源熵
  5. 输出 .txt 文件并在 Excel 中输出图片

前期尝试的问题

在真正操作时因为许久没用用 C++ 编写程序(只剩下模糊印象了)还是遇到了一些问题。

  • 比如在数据读写部分最开始尝试用 ifstream / ofstream
    完成,但发现自己似乎并没有学过所以使用中一直不顺利。在与同学的交流中决定使用 fopen、fread、fwrite
    进行读写。虽然最开始的部分非常顺利,但在最后文本输出时 fwrite 函数使用上出现问题,最终决定通过 fprinte 实现文本输出。

  • 同时在最开始完成数据读入分类后通过单个输出数组信息的方式验证存取的信息是否正确时,遇到了输出混乱的问题。尽管用于存取信息的数组定义为 unsigned char 形式,但输出的内容不仅有 ASCII 码对应后的字符,还有大段大段的空白(怀疑是 ASCII 对应为空格或回行)。
    但事实证明忽略这个输出测试的结果继续往后写完得到的数据是对的。

  • 在计算各数值出现概率是,直接用总数据量作为除数计算,使得第一次出的曲线形状基本OK但用 SUM 函数加和计算后发现概率仅为 1/3 而不是1,进而快速定位到了问题所在。

  • 在计算各通道分量的熵时,忘记在循环语句中判断概率是否为零,使得结果一度输出为 nan

仍需改进的问题

在最终的代码实现中,为了小心起见设定了过多的计数器,甚至在按通道分配数据时给每一个通道设置了计数器后用 if 判断语句进行分配,是的代码明显复杂。可以将数据分配部分的代码改为步长为 3 的 for 循环语句实现,不但只需要两个计数器(一个用于循环获取数据值一个用于分配给不同数据组)还大幅缩减了代码体量

最终代码实现

//
//  main.cpp
//  Data Compress Homework
//
//  Created by Zachary Chia on 2020/3/9.
//  Copyright © 2020 Zachary Chia. All rights reserved.
//

#include <iostream>
#include <fstream>
#include <cmath>

using namespace std;

int main() {

    //打开、创建所需文件
    FILE*file;
    file = fopen("/Users/zachary/Downloads/down.rgb","rb");
    FILE*setRed = fopen("/Users/zachary/Documents/Data Compress Homework/setRed.txt","w");
    FILE*setGreen = fopen("/Users/zachary/Documents/Data Compress Homework/setGreen.txt","w");
    FILE*setBlue = fopen("/Users/zachary/Documents/Data Compress Homework/setBlue.txt","w");
    
    //确定文件长度
    fseek(file,0,SEEK_END);
    long fileLength = ftell(file);
    cout<<"file length = "<<fileLength<< "\n";
    fseek(file,0,SEEK_SET);
    
    //定义文件读取、存储数组
    unsigned char *dataArray;
    dataArray = (unsigned char *)malloc(fileLength*sizeof(unsigned char));
    long actualCount = fread(dataArray,sizeof(unsigned char),fileLength,file);
    unsigned char *r;
    r = (unsigned char *)malloc(fileLength*sizeof(unsigned char)/3);
    unsigned char *g;
    g = (unsigned char *)malloc(fileLength*sizeof(unsigned char)/3);
    unsigned char *b;
    b = (unsigned char *)malloc(fileLength*sizeof(unsigned char)/3);
    
    //定义不同颜色通道计数
    int m = 0;  //b
    int n = 0;  //g
    int p = 0;  //r
    int reference = 0;  //reference
    
    //定义参照数组跟三通道计数数组
    unsigned char item[256];
    double countRed[256];
    double countGreen[256];
    double countBlue[256];
    for(int i = 0; i<256 ; i++)
    {
        item[i] = (unsigned char) i;
        countRed[i] = 0;
        countGreen[i] = 0;
        countBlue[i] = 0;
    }
    
    
    //向不同通道分配文件值,并进行计数
    for(int i = 0;i < fileLength;i++)
    {
        if( i % 3 == 0)
        {
            b[m] = dataArray[i];
            while(b[m] != item[reference])
            {
                reference++;
            }
            countBlue[reference]++;
            reference = 0;
            m++;
        }
        else if( i % 3 == 1)
        {
            g[n] = dataArray[i];
            while(g[n] != item[reference])
            {
                reference++;
            }
            countGreen[reference]++;
            reference = 0;
            n++;
        }
        else
        {
            r[p] = dataArray[i];
            while(r[p] != item[reference])
            {
                reference++;
            }
            countRed[reference]++;
            reference = 0;
            p++;
        }
    }
    

    //计算三通道数值的出现概率
    for(int i = 0; i<256 ; i++)
    {
        countRed[i] = 3 * countRed[i] / actualCount;
        countGreen[i] = 3 * countGreen[i] / actualCount;
        countBlue[i] = 3 * countBlue[i] / actualCount;
    }
    
    //定义并求解三通道信源熵
    double redEntropy = 0;
    double greenEntropy = 0;
    double blueEntropy = 0;
    
    for(int i = 0; i<256 ; i++)
    {
        if(countRed[i] != 0)
        {redEntropy = redEntropy + countRed[i] * (log(1/countRed[i])/log(2));}
        if(countGreen[i] != 0)
        {greenEntropy = greenEntropy + countGreen[i] * (log(1/countGreen[i])/log(2));}
        if(countBlue[i] != 0)
        {blueEntropy = blueEntropy + countBlue[i] * (log(1/countBlue[i])/log(2));}
    }
    
    //输出x三通道各自的信源熵及概率
    fprintf(setRed, "Color\tEntropy\n");
    fprintf(setRed, "Red\t%-8f\n",redEntropy);
    fprintf(setRed, "Symbol\tFrequency\n");
    fprintf(setGreen, "Color\tEntropy\n");
    fprintf(setGreen, "Green\t%-8f\n",greenEntropy);
    fprintf(setGreen, "Symbol\tFrequency\n");
    fprintf(setBlue, "Color\tEntropy\n");
    fprintf(setBlue, "Blue\t%-8f\n",blueEntropy);
    fprintf(setBlue, "Symbol\tFrequency\n");
    for (int i = 0; i < 256; i++)
       {
           fprintf(setRed, "%-3d\t%-8f\n", i, countRed[i]); // 将数据输出到文件中
           fprintf(setGreen, "%-3d\t%-8f\n", i, countGreen[i]); // 将数据输出到文件中
           fprintf(setBlue, "%-3d\t%-8f\n", i, countBlue[i]); // 将数据输出到文件中
       }
    //验证是否输出
    cout<<"total numer of data is "<< actualCount<<"\n";
    
    cout<<"Red Source Entropy is "<<redEntropy<<"\n";
    cout<<"Green Source Entropy is "<<greenEntropy<<"\n";
    cout<<"Blue Source Entropy is "<<blueEntropy<<"\n";
    
    //清空地址跟数据,关闭文件
    free(r);
    free(g);
    free(b);
    free(dataArray);
    fclose(setRed);
    fclose(setGreen);
    fclose(setBlue);
    fclose(file);
    
    
    cout<<"Process Done\n";
    
    return 0;
}

最终结果呈现

数据压缩任务三:读入 RGB 文件并输出三个分量的概率分布示意图和熵
数据压缩任务三:读入 RGB 文件并输出三个分量的概率分布示意图和熵