代码主要功能描述:
获取彩色图片rgb信息 -> 转化为灰度信息 -> 对灰度信息进行压缩 -> 对压缩文件进行解压恢复灰度信息
核心代码讲解:
如何读取图片rgb信息:
在这里我们用到<windows.h>库中的一系列函数和类,首先定义结构体 pixColor 来存储每个像素点的rgb值
另外需要注意使用的图片的格式需要为bmp格式,因为该格式的图像是以矩阵的格式点存储每个像素点的信息的。 还需要注意对于不同行进行读取时,奇数行和偶数行要从不同的方向开始录入数组中。(因为图片压缩的像素点是需要每行首位相接的,呈“S”形的。)
typedef struct pixColor//用来存储每个像素点的rgb值
{
unsigned char b;
unsigned char g;
unsigned char r;
}PIXCOLOR, * PPIXCOLOR;
int main()
{
BITMAPFILEHEADER bmf;
BITMAPINFOHEADER bif;
FILE* pf;
//返回值为0表示打开成功,非0则为失败
errno_t err = fopen_s(&pf, FILENAME, "rb");
if (0 == err)
{
fread(&bmf, sizeof(bmf), 1, pf);
fread(&bif, sizeof(bif), 1, pf);
if (0x4d42 != bmf.bfType)
{
cout << "文件不是位图!" << endl;
fclose(pf);
}
else if (24 != bif.biBitCount)
{
cout << "位图不是24位!" << endl;
fclose(pf);
}
else
{
cout << "图片的宽度:" << bif.biWidth << endl;
cout << "图片的高度:" << bif.biHeight << endl;
//sum = bif.biWidth * bif.biHeight;
PPIXCOLOR* ColorData = new PPIXCOLOR[bif.biHeight];
//一行的字节数
int lineCount = bif.biSizeImage / bif.biHeight;
//存的时候从倒数第一行开始存,所以读的时候先往倒数第一行放
//读颜色数据
for (int i = bif.biHeight - 1; i >= 0; i--)
{
ColorData[i] = new PIXCOLOR[bif.biWidth];
fread(ColorData[i], sizeof(PIXCOLOR), bif.biWidth, pf);
fseek(pf, lineCount - bif.biWidth * sizeof(PIXCOLOR), SEEK_CUR);
}
fclose(pf);
for (int i = 0; i < bif.biHeight; i++)
{
if (i % 2 == 0)//奇数行正序读取
{
for (int j = 0; j < bif.biWidth; j++)
{
p[++sum] = RGBtoGRAY((int)ColorData[i][j].r, (int)ColorData[i][j].g, (int)ColorData[i][j].b);
}
}
else//偶数行倒序读取
{
for (int j = bif.biWidth - 1; j >= 0; j--)
{
p[++sum] = RGBtoGRAY((int)ColorData[i][j].r, (int)ColorData[i][j].g, (int)ColorData[i][j].b);
}
}
delete[] ColorData[i];
}
cout << "像素点总数:" << sum << " 总位数:" << sum * 8 << endl;
delete[] ColorData;
ColorData = nullptr;
}
}
system("pause");
return 0;
}
如何获取灰度值数据:
有由RGB值获取灰度值的公式如下:
int RGBtoGRAY(int a, int b, int c)//彩色图片转为灰色图片,运用公式
{
return 0.299 * a + 0.587 * b + 0.114 * c;
}
如何对数据进行压缩:
在这里直接用的书上的图片压缩算法,关于该算法的具体讲解看上一篇博客
int length(int i)
{
int k = 1;
i = i / 2;
while (i > 0)
{
k++;
i /= 2;
}
return k;
}
void compress(int n)
{
int lmax = 256, header = 11;
s[0] = 0;
for (int i = 1; i <= n; i++)
{
b[i] = length(p[i]);
int bmax = b[i];
s[i] = s[i - 1] + bmax;
l[i] = 1;
for (int j = 2; j <= i && j <= lmax; j++)
{
if (bmax < b[i - j + 1])bmax = b[i - j + 1];
if (s[i] > s[i - j] + j * bmax)
{
s[i] = s[i - j] + j * bmax;
l[i] = j;
}
}
s[i] += header;
}
int ans = 0;
int a = s[n];
int c = n;
while (c >= 1)
{
para[++ans] = c;
c -= l[c];
}
cout << "总组数:" << ans << endl;
reverse(para + 1, para + 1 + ans);
//for (int i = 1; i <= 100; i++)cout << para[i] << endl;
cout <<"计算得压缩后总位数:"<< s[n] << endl;
}
如何将压缩后的数据转为二进制信息存入文件中:
经过以上的压缩算法所得到的分段的信息依旧是int型的数值,而存入文件中要求转变为二进制流。
在这里我申请了一个bool型数组bitstream,将二进制流存进该数组中来代替存到文件中。
如何获取分段信息:
在图片压缩算法中有一个l[]数组(在动态规划的过程中,倘若以第i个数据结尾,l[i]表示第i个数据所在分段的长度);
在这里我从我最开始的一个错误的想法讲起,在最开始我天真的认为只要l[i]=1,那么i便是该分段的开头,但是后来做到后面我发现最后压缩所得的总位数和运用图片压缩算法得到的最优总位数s[n]要大,做到这里显然觉得显然离大谱,之后又写了很多代码去检测错误和输出l[]数组的信息,到这里发现l[]数组中的值并不是单纯的每段从1加到段尾。此时再重新回去看图片压缩算法。
在这里当s[i] > s[i - j] + j * bmax时,l[i] = j; 值此时第i个数要与前面的j-1个字符划分为一段。
但是当循环到其后面的数时之前的分组情况会被打破,因为此时新的数的位数可能会很小,前面的数放弃与更前面的数合为一段,而是与最新的数合为一段情况会更好。此时前面的数对应的l[i]已经不对了而且并没有得到修改,且难以修改。
此时我们就需要另外寻找方法获取分段信息。想到每次循环都有可能会打破之前的分段格局,那不如从末尾入手;当循环结束时,末尾l[n]的数值肯定是正确的(以为后面没有循环来打破此时的分段格局了),那么l[n-l[n]]的数值也肯定是正确的(因为已经确定第n-l[n]后面的数值的分段是正确的,后面的循环并不会影响第n-l[n]前面的所有数值的分段格局),依次类推l[b-l[b]],(b=n-l[n])的数值也是正确的。
如何存储二进制信息:
前三位二进制存储该段位数,之后八位存储该段的数值的总数,之后存取所有的数值。
此时存储每个数值的二进制可以按照对应的位数倒着存储。
//在这里,para数组存储每个分段的长度,ans存储总段数。
int ans = 0;
//int a = s[n];
int c = n;
//逆向获得所有分段的长度
while (c >= 1)
{
para[++ans] = c;
c -= l[c];
}
cout << "总组数:" << ans << endl;
//由逆向倒置为正向
reverse(para + 1, para + 1 + ans);
//for (int i = 1; i <= 100; i++)cout << para[i] << endl;
cout <<"计算得压缩后总位数:"<< s[n] << endl;
//以下代码将所有的灰度值按照分组格式,以二进制存入bitstream
for (int i = 1; i <= ans;i++)
{
int left = para[i - 1] + 1;//记录每组的左端在p中的位置
int right = para[i];//右端
int bm = 1;//记录该段的最大位数
for (int j = left; j <= right; j++)
{
bm = max(bm, b[j]);
}
int bm2 = bm;
//先存该组最大位数 ,倒着存储三位
bm--;
for (int j = sum2 + 3; j > sum2; j--)
{
bitstream[j] = bm % 2;
bm /= 2;
}
sum2 += 3;
//存储该组的总数,倒着存储八位
int shu = right - left + 1 - 1;
for (int j = sum2 + 8; j > sum2; j--)
{
bitstream[j] = shu % 2;
shu /= 2;
}
sum2 += 8;
//存储该组所有的灰度
for (int j = left; j <= right; j++)
{
int pp = p[j];
//对于每个灰度值,倒着存储bm2位
for (int k = sum2 + bm2; k > sum2; k--)
{
bitstream[k] = pp % 2;
pp /= 2;
}
sum2 += bm2;
}
}
cout << "真实压缩后总位数:"<<sum2 << endl;
如何解压二进制信息:
这里比较简单,先读取前三位二进制,获取该段数值的位数,然后读八位,获取该段的数值总数,之后根据前面读出的总位数和总数,读取出该段所有的灰度值,依次循环直到末尾。
void decompress()//解压函数 只能根据比特流bitstream 和 比特流总位数sun2 两个条件
{
int p1 = 0;//记录扫到了第几个二进制
int ps = 0;//记录已经获取了几个灰度值
while (p1 < sum2)
{
//获取该组最大位数
int a1=0;
int mul = 1;
for (int i = p1 + 3; i > p1; i--)
{
a1 += bitstream[i] * mul;
mul *= 2;
}
p1 += 3;
a1++;
//获取该组灰度值的总数
int a2 = 0;
mul = 1;
for (int i = p1 + 8; i > p1; i--)
{
a2 += bitstream[i] * mul;
mul *= 2;
}
p1 += 8;
a2++;
//获取该组所有的灰度值
for (int i = 1; i <= a2; i++)
{
int a3 = 0;
mul = 1;
for (int j = p1 + a1; j > p1; j--)
{
a3 += bitstream[j] * mul;
mul *= 2;
}
p1 += a1;
last[++ps] = a3;
}
}
cout << "在文件中读取到的总位数:" << p1 << endl;
cout << "解压获取到的灰度值总数:" << ps << endl;
}
其他
在这里由于太麻烦我并没有再用还原的灰度值数据去绘图。
只写了一个函数来判断,压缩前和解压后的灰度值信息是否一致
void judge()//利用该函数判断压缩前的灰度数组,与解压后的灰度数组是否一致
{
for (int i = 1; i <= sum; i++)
{
if (p[i] != last[i])
{
cout << "不一致" << endl;
cout << i << " " << p[i] << " " << last[i] << endl;
return;
}
}
cout << "与压缩前完全一致" << endl;
}
总代码
#include <iostream>
#include <windows.h>
#include<algorithm>
const int NN = 360000;
int p[NN], l[NN], b[NN], s[NN];
int para[NN];//用该数组记录所有段落的结尾
int last[NN];//存储解压后的灰度数据
bool bitstream[2000000];
int sum;//用来记录像素点的总数
int sum2;//记录压缩后的总位数
using namespace std;
char FileName[] = "4.bmp";//图片位置,应为bmp格式的位图
#define FILENAME FileName
typedef struct pixColor//用来存储每个像素点的rgb值
{
unsigned char b;
unsigned char g;
unsigned char r;
}PIXCOLOR, * PPIXCOLOR;
int length(int i);
void compress(int n)
{
int lmax = 256, header = 11;
s[0] = 0;
for (int i = 1; i <= n; i++)
{
b[i] = length(p[i]);
int bmax = b[i];
s[i] = s[i - 1] + bmax;
l[i] = 1;
for (int j = 2; j <= i && j <= lmax; j++)
{
if (bmax < b[i - j + 1])bmax = b[i - j + 1];
if (s[i] > s[i - j] + j * bmax)
{
s[i] = s[i - j] + j * bmax;
l[i] = j;
}
}
s[i] += header;
}
int ans = 0;
//int a = s[n];
int c = n;
while (c >= 1)
{
para[++ans] = c;
c -= l[c];
}
cout << "总组数:" << ans << endl;
reverse(para + 1, para + 1 + ans);
//for (int i = 1; i <= 100; i++)cout << para[i] << endl;
cout <<"计算得压缩后总位数:"<< s[n] << endl;
//以下代码将所有的灰度值按照分组格式,以二进制存入bitstream
for (int i = 1; i <= ans;i++)
{
int left = para[i - 1] + 1;//记录每组的左端在p中的位置
int right = para[i];//右端
int bm = 1;//记录该段的最大位数
for (int j = left; j <= right; j++)
{
bm = max(bm, b[j]);
}
int bm2 = bm;
//先存改组最大位数
bm--;
for (int j = sum2 + 3; j > sum2; j--)
{
bitstream[j] = bm % 2;
bm /= 2;
}
sum2 += 3;
//存储该组的总数
int shu = right - left + 1 - 1;
for (int j = sum2 + 8; j > sum2; j--)
{
bitstream[j] = shu % 2;
shu /= 2;
}
sum2 += 8;
//存储该组所有的灰度
for (int j = left; j <= right; j++)
{
int pp = p[j];
for (int k = sum2 + bm2; k > sum2; k--)
{
bitstream[k] = pp % 2;
pp /= 2;
}
sum2 += bm2;
}
}
cout << "真实压缩后总位数:"<<sum2 << endl;
}
void decompress()//解压函数 只能根据比特流bitstream 和 比特流总位数sun2 两个条件
{
int p1 = 0;//记录扫到了第几个二进制
int ps = 0;//记录已经获取了几个灰度值
while (p1 < sum2)
{
//获取该组最大位数
int a1=0;
int mul = 1;
for (int i = p1 + 3; i > p1; i--)
{
a1 += bitstream[i] * mul;
mul *= 2;
}
p1 += 3;
a1++;
//获取该组灰度值的总数
int a2 = 0;
mul = 1;
for (int i = p1 + 8; i > p1; i--)
{
a2 += bitstream[i] * mul;
mul *= 2;
}
p1 += 8;
a2++;
//获取该组所有的灰度值
for (int i = 1; i <= a2; i++)
{
int a3 = 0;
mul = 1;
for (int j = p1 + a1; j > p1; j--)
{
a3 += bitstream[j] * mul;
mul *= 2;
}
p1 += a1;
last[++ps] = a3;
}
}
cout << "在文件中读取到的总位数:" << p1 << endl;
cout << "解压获取到的灰度值总数:" << ps << endl;
}
void judge()//利用该函数判断压缩前的灰度数组,与解压后的灰度数组是否一致
{
for (int i = 1; i <= sum; i++)
{
if (p[i] != last[i])
{
cout << "不一致" << endl;
cout << i << " " << p[i] << " " << last[i] << endl;
return;
}
}
cout << "与压缩前完全一致" << endl;
}
int length(int i)
{
int k = 1;
i = i / 2;
while (i > 0)
{
k++;
i /= 2;
}
return k;
}
int RGBtoGRAY(int a, int b, int c)//彩色图片转为灰色图片,运用公式
{
return 0.299 * a + 0.587 * b + 0.114 * c;
}
int main()
{
BITMAPFILEHEADER bmf;
BITMAPINFOHEADER bif;
FILE* pf;
//返回值为0表示打开成功,非0则为失败
errno_t err = fopen_s(&pf, FILENAME, "rb");
if (0 == err)
{
fread(&bmf, sizeof(bmf), 1, pf);
fread(&bif, sizeof(bif), 1, pf);
if (0x4d42 != bmf.bfType)
{
cout << "文件不是位图!" << endl;
fclose(pf);
}
else if (24 != bif.biBitCount)
{
cout << "位图不是24位!" << endl;
fclose(pf);
}
else
{
cout << "图片的宽度:" << bif.biWidth << endl;
cout << "图片的高度:" << bif.biHeight << endl;
//sum = bif.biWidth * bif.biHeight;
PPIXCOLOR* ColorData = new PPIXCOLOR[bif.biHeight];
//一行的字节数
int lineCount = bif.biSizeImage / bif.biHeight;
//存的时候从倒数第一行开始存,所以读的时候先往倒数第一行放
//读颜色数据
for (int i = bif.biHeight - 1; i >= 0; i--)
{
ColorData[i] = new PIXCOLOR[bif.biWidth];
fread(ColorData[i], sizeof(PIXCOLOR), bif.biWidth, pf);
fseek(pf, lineCount - bif.biWidth * sizeof(PIXCOLOR), SEEK_CUR);
}
fclose(pf);
for (int i = 0; i < bif.biHeight; i++)
{
if (i % 2 == 0)//奇数行正序读取
{
for (int j = 0; j < bif.biWidth; j++)
{
p[++sum] = RGBtoGRAY((int)ColorData[i][j].r, (int)ColorData[i][j].g, (int)ColorData[i][j].b);
}
}
else//偶数行倒序读取
{
for (int j = bif.biWidth - 1; j >= 0; j--)
{
p[++sum] = RGBtoGRAY((int)ColorData[i][j].r, (int)ColorData[i][j].g, (int)ColorData[i][j].b);
}
}
delete[] ColorData[i];
}
cout << "像素点总数:" << sum << " 总位数:" << sum * 8 << endl;
compress(sum);//调用压缩函数
decompress();
judge();
delete[] ColorData;
ColorData = nullptr;
}
}
system("pause");
return 0;
}