admin管理员组文章数量:1794759
[数据压缩]
文章目录
- 一. 实验名称
- 二. 实验目的
- 三. 实验要求
- 四. 实验内容
- 1.典型的BMP图像文件由四部分组成:
- ① 位图文件头
- ② 位图信息头
- ③ 调色板
- ④ 图像数据字节阵列
- 2.字节序
- 五. 实验过程
- 1.实验代码逻辑
- 2.main函数逻辑
- 3.代码实现
- 4.debug过程
- 5.实验结果
- 六. 实验总结
- 结构体
- 文件操作
- 动态内存分配
一. 实验名称
图像文件的读写和转换(设计性实验)
二. 实验目的
1.理解图像文件的基本组成。
2.掌握结构体作为复杂数据对象的用法。进一步熟悉由问题到程序的解决方案,并掌握编程细节:如内存分配、倒序读写、字节序、文件读写过程等。
三. 实验要求
选择BMP转YUV文件实验
-
素材为5个含不同场景的bmp文件,要求附上本人logo。(基本要求为24bit的BMP,进阶要求为支持小于24bit的BMP。)
-
编写将第一步所生成的多个BMP文件转化为YUV文件,要求可在命令行中设置每个画面出现的帧数。最后形成的YUV文件应至少包含200帧。重点掌握函数定义、缓冲区分配、倒序读写、结构体的操作。
-
对整个程序进行调试,并将生成的YUV文件用播放软件观看,验证是否正确。
四. 实验内容
实验指导、基础补充:
BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB)。它采用位映射存储格式,除了图像深度可选以外,在绝大多数应用中不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选lbit、4bit、8bit、16bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。
1.典型的BMP图像文件由四部分组成:
- 位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
- 位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
- 调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
- 位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。
① 位图文件头
typedef struct tagBITMAPFILEHEADER {WORD bfType; //文件类型DWORD bfSize; //文件大小,单位:字节WORD bfReserved1;//保留,设为0WORD bfReserved2;//保留,设为0DWORD bfOffBits; //说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量
} BITMAPFILEHEADER;
bfType=0x4d42才是BMP文件
打开实验bmp素材,查看bmp二进制信息。数据是小端存储的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xtu3NV22-1649517681093)(E:\【else】\素材库\typora_pic\image-20220409121144655.png)]
② 位图信息头
typedef struct tagBITMAPINFOHEADER {DWORD biSize; //说明结构体所需字节数LONG biWidth; //以像素为单位说明图像的宽度LONG biHeight; //以像素为单位说明图像的高度WORD biPlanes; //说明位面数,必须为1WORD biBitCount; //说明位数像素,1、2、4、8、24DWORD biCompression; //说明图像是否压缩及压缩类型BI_RGB、BI_RLE8、BI_RLE4、BI_BITFIELDSDWORD biSizeImage; //以字节为单位说明图像大小,必须是4的整数倍LONG biXPelsPerMeter; //目标设备的水平分辨率,像素/米LONG biYPelsPerMeter; //目标设备的垂直分辨率,像素/米DWORD biClrUsed; //说明图像实际用到的颜色数,如果为0则颜色数为2的 biBitCount次方cDWORD biClrImportant; //说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要
} BITMAPINFOHEADER;
③ 调色板
调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。
typedef struct tagRGBQUAD {BYTE rgbBlue; //蓝色分量BYTE rgbGreen; //绿色分量BYTE rgbRed; //红色分量BYTE rgbReserved;//保留,指定为0
} RGBQUAD;
④ 图像数据字节阵列
紧跟在调色板之后的是图像数据字节阵列。对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值(逻辑色)。对于真彩色图,图像数据就是实际的R、G、B值。图像的每一扫描行由表示图像像素的连续的字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。规定每一扫描行的字节数必须是4的整倍数,也就是DWORD对齐的。扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下角的像素,而最后一个字节表示位图右上角的像素。
对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值逻辑色,真彩色位图如24bit BMP直接使用实际的 R 、G、B值。
2.字节序
不同的计算机系统采用不同的字节序存储数据,同样一个4字节的32位整数,在内存中存储的方式不同。字节序分为小尾字节序(Little Endian)和大尾字节序(Big Endian)。Intel处理器大多数使用小尾字节序。小尾就是“低位在前,高位在后”。大尾就是“高位在前,低位在后”。TCP/IP各层协议将字节序定义为大尾,因此TCP/IP协议中使用的字节序通常称之为网络字节序。在实现BMP文件头信息的写入和读出时,需要注意整数保存时的字节序。例如:文件大小是以Intel序保存的。在编程前先用二进制打开方式观察BMP文件各个部分的数据存储格式。
五. 实验过程
1.实验代码逻辑
2.main函数逻辑
3.代码实现
- 头文件
#pragma once
#include "headinfo.h"int RGB2YUV(int x_dim, int y_dim, void* bmp, void* y_out, void* u_out, void* v_out);
int READRGB(int width, int height, FILE* bmpFile, unsigned char* rgb);
void InitLookupTable();
- rgb2yuv.cpp
#include<iostream>
#include "bmp2yuv.h"
#define uchar unsigned char
using namespace std;static float RGBYUV02990[256], RGBYUV05870[256], RGBYUV01140[256];
static float RGBYUV01684[256], RGBYUV03316[256];
static float RGBYUV04187[256], RGBYUV00813[256];int RGB2YUV(int width,int height,void* bmp,void* y_out,void* u_out,void* v_out )
{//变量定义static int init_Done = 0;uchar* r, * g, * b;uchar* y, * u, * v;uchar* y_buf, * u_buf, * v_buf;uchar* sub_u_buf,* sub_v_buf;uchar* s_u, * s_v;uchar* cu1, * cu2, * cv1, * cv2;long size;//把查找表算好if (init_Done == 0){InitLookupTable();init_Done = 1;}//操作指针分配内存空间size = width * height;y_buf = (uchar*)y_out;sub_u_buf = (uchar*)u_out;sub_v_buf = (uchar*)v_out;u_buf = (uchar*)malloc(size * sizeof(uchar));v_buf = (uchar*)malloc(size * sizeof(uchar));//rgb2yuvb = (uchar*)bmp;y = y_buf;u = u_buf;v = v_buf;for (long i = 0; i < size; i++){g = b + 1;r = b + 2;*y = (uchar)(RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);*u = (uchar)(-RGBYUV01684[*r] - RGBYUV03316[*g] + (*b) / 2 + 128);*v = (uchar)((*r) / 2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);b += 3;y++;u++;v++;}for (long j = 0; j < height / 2; j++){//采样后的数组s_u = sub_u_buf + j * width / 2;s_v = sub_v_buf + j * width / 2;//在没采样的数据中操作位置的指针cu1 = u_buf + 2 * j * width;cv1 = v_buf + 2 * j * width;cu2 = u_buf + (2 * j + 1) * width;cv2 = v_buf + (2 * j + 1) * width;//开始赋值for (long i = 0; i < width / 2; i++){*s_u = (*cu1 + *(cu1 + 1) + *cu2 + *(cu2 + 1)) / 4;*s_v = (*cv1 + *(cv1 + 1) + *cv2 + *(cv2 + 1)) / 4;s_u++;s_v++;cu1 += 1;cv1 += 2;cu2 += 2;cv2 += 2;}}free(u_buf);free(v_buf);return 0;
}
int READRGB(int width, int height, FILE* bmpFile, uchar* rgb)
{uchar* bmp = (uchar*)malloc(sizeof(uchar) * width * height * 3);uchar* tmp_rgb = (uchar*)malloc(sizeof(uchar) * width * height * 3);//读取bmp文件fread(bmp, width * height * 3, 1, bmpFile);for (int i = 0; i < width * height*3; i++) {*(tmp_rgb + i) = *bmp;bmp++;}//bmp文件的存储顺序从左到右、从上到下//24bit的bmp提取rgb数据时 倒序转正序写入缓存区for (int i = 0; i < height; i++){for (int j = 0; j < width * 3; j++){*(rgb + (height - 1 - i) * width * 3 + j) = *tmp_rgb;tmp_rgb++;}}return 0;
}
void InitLookupTable()
{int i;for (i = 0; i < 256; i++) RGBYUV02990[i] = (float)0.2990 * i;for (i = 0; i < 256; i++) RGBYUV05870[i] = (float)0.5870 * i;for (i = 0; i < 256; i++) RGBYUV01140[i] = (float)0.1140 * i;for (i = 0; i < 256; i++) RGBYUV01684[i] = (float)0.1684 * i;for (i = 0; i < 256; i++) RGBYUV03316[i] = (float)0.3316 * i;for (i = 0; i < 256; i++) RGBYUV04187[i] = (float)0.4187 * i;for (i = 0; i < 256; i++) RGBYUV00813[i] = (float)0.0813 * i;
}
- main.cpp
#include<iostream>
#include "headinfo.h"
#include "bmp2yuv.h"
#include<windows.h>
#define _CRT_SECURE_NO_DEPRECATE
#define uchar unsigned char
using namespace std;
//本实验素材是24bit真彩图,无调色板,代码省去调色板的判断int main()
{//定义变量BITMAPFILEHEADER FILE_header;BITMAPINFOHEADER INFO_header;FILE* bmpFile;FILE* yuvFile;uchar* rgbBuf;uchar* yBuf;uchar* uBuf;uchar* vBuf;int FWidth;int FHeight;const int picNum = 6;//创建指向图片的指针const char* bmpFileName[picNum] = { "1.bmp","2.bmp","3.bmp","4.bmp","5.bmp","6.bmp" };const char* yuvFileName = "video.yuv";//创建新的输出文件fopen_s(&yuvFile, yuvFileName,"wb");//逐个图片转换处理for (int i = 0; i < picNum; i++){//open每个bmp文件fopen_s(&bmpFile,bmpFileName[i], "rb");//读取bmp文件头if (fread(&FILE_header, sizeof(BITMAPFILEHEADER), 1, bmpFile)!=1){cout << "read file header error!" << endl;exit(0);}else {cout << "read file header success!" << endl;}//判断是否是bmp文件类型if (FILE_header.bfType != 0x4D42) //此处是bmp的小端模式的数据{cout << "not the bmpFile!" << endl;exit(0);}else {cout << "this is a bmp file!" << endl;}//读取bmp信息头if (fread(&INFO_header, sizeof(BITMAPINFOHEADER), 1, bmpFile)!=1){cout << "read info header error!" << endl;exit(0);}else {cout << "read info header success!" << endl;}//通过结构体实例的成员函数 来调用参数FWidth = INFO_header.biWidth;FHeight = INFO_header.biHeight;//给操作指针分配内存空间rgbBuf = (uchar*)malloc(FWidth * FHeight * 3);yBuf = (uchar*)malloc(FWidth * FHeight);uBuf = (uchar*)malloc(FWidth * FHeight / 4);vBuf = (uchar*)malloc(FWidth * FHeight / 4);//读取bmp的rgb数据READRGB(FWidth, FHeight, bmpFile, rgbBuf);//fwrite(rgbBuf, 1, FHeight * FWidth * 3, yuvFile);//rgb2yuvif (RGB2YUV(FWidth, FHeight,rgbBuf, yBuf, uBuf, vBuf)){cout << "rgb2yuv failed!" << endl;}for (int i = 0; i < FWidth * FHeight; i++){if (yBuf[i] < 16) yBuf[i] = 16;if (yBuf[i] > 235) yBuf[i] = 235;}for (int i = 0; i < FWidth * FHeight / 4; i++){if (uBuf[i] < 16) uBuf[i] = 16;if (uBuf[i] > 240) uBuf[i] = 240;if (vBuf[i] < 16) vBuf[i] = 16;if (vBuf[i] > 240) vBuf[i] = 240;}for (int i = 0; i < 40; i++){fwrite(yBuf, 1,FWidth * FHeight, yuvFile);fwrite(uBuf, 1,(FWidth * FHeight)/4, yuvFile);fwrite(vBuf, 1,(FWidth * FHeight)/4, yuvFile);}//释放空间,关闭文件free(rgbBuf);free(yBuf);free(uBuf);free(vBuf);fclose(bmpFile);}fclose(yuvFile);//结束
}
4.debug过程
ps: 我以为bmp结构体要自定义,自己声明一个头文件写结构体。报重定义的错,经过讨论才知道,BITMAPFILEHEADER 结构体在
<windows.h> 头文件中已经定义。
ps: 排除rgb2yuv函数的错以后,才知道READRGB函数中循环的i忘记乘3.
5.实验结果
六. 实验总结
知识点:
结构体
数据元素类型不同一逻辑数据体—结构体。用函数处理结构体中的数据,把结构成员的值个别地传给函数处理。
文件操作
FILE* fopen(char *filename,char *mode)//filename 打开文件路径 mode 打卡模式//若成功,返回指向被打开的文件的指针,若不成功返回空指针null
size_t fwrite(void* buffer,size_t size,size_t count,FILE* fp)//buffer 要读写的数据块地址,size 读写数据项的字节数,count 读写数据项数量,fp文件指针//若成功,返回世纪读写的数据项数量,若不成功,返回0
size_t fread(void* buffer,size_t size,size_t count,FILE* fp)//buffer 要读写的数据块地址,size 读写数据项的字节数,count 读写数据项数量,fp文件指针//若成功,返回世纪读写的数据项数量,若不成功,返回0
动态内存分配
void *malloc(size_t size)//返回一个指向它的指针。
还有重新捡起指针的用法。
本文标签: 数据压缩
版权声明:本文标题:[数据压缩] 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1707228755a545358.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论