数据压缩_实验二_TGA2YUV
文章目录
文件格式转换实验
一、实验目标
-
读入TGA文件
-
将TGA文件转换为YUV文件
二、实验原理
1、TGA文件格式
TGA(Targa)格式是计算机上应用最广泛的图象格式。在兼顾了BMP的图象质量的同时又兼顾了JPEG的体积优势。并且还有自身的特点:通道效果、方向性。在CG领域常作为影视动画的序列输出格式,因为兼具体积小和效果清晰的特点。
基本格式如下图:
2、TGA颜色显示方式
TGA图像可分为真彩色图像和伪彩色图像。
==真彩色(true-color)==是指图像中的每个像素值都分成R、G、B三个基色分量,每个基色分量直接决定其基色的强度,这样产生的色彩称为真彩色。例如图像深度为24,用R:G:B=8:8:8来表示色彩,则R、G、B各占用8位来表示各自基色分量的强度,每个基色分量的强度等级为28=256种。
==伪彩色(pseudo-color)==图像的每个像素值实际上是一个索引值或代码,该代码值作为色彩查找表CLUT(Color Look-Up Table)中某一项的入口地址,根据该地址可查找出包含实际R、G、B的强度值。
三、实验步骤
1、基本流程图
2、关键步骤
- 创建与读入文件头结构体
将文件头结构体分为三部分:
-
_TgaHeader
-
_TgaHeader_CMap (颜色表信息字段)
-
_TgaHeader_Info (图像规格字段)
根据上文格式依次设置结构内各个变量大小。
注意:结构体成员按照定义时的顺序依次存储在连续的内存空间,但是结构体的大小并不是简单的把所有成员大小相加,而是遵循一定的规则,需要考虑到系统在存储结构体变量时的地址对齐问题。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。
在实际中,存储变量时地址要求对齐,编译器在编译程序时会遵循两条原则:
(1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
(2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。
对于偏移量不是自身大小整数倍的成员,编译器会在前一个成员后补充空字节。
对于大小不是所有成员变量整数倍的,编译器会自动在最后一个成员补充空字节。
由此可见,结构体类型需要考虑到字节对齐的情况,不同的顺序会影响结构体的大小及读入时对应数据的正确性。
打开文件后,直接从文件中读出三个结构体大小的数据内容。
相关代码如下:
// TGA文件结构体定义
typedef struct _TgaHeader {
BYTE IDLength; /* 00h Size of Image ID field */
BYTE ColorMapType; /* 01h Color map type */
BYTE ImageType; /* 02h Image type code */
} TGAHEAD;
typedef struct _TgaHeader_CMap {
WORD CMapStart; /* 03h Color map origin */
WORD CMapLength; /* 05h Color map length */
BYTE CMapDepth; /* 07h Depth of color map entries */
}TGAHEAD_CMap;
typedef struct _TgaHeader_Info {
WORD XOffset; /* 08h X origin of image */
WORD YOffset; /* 0Ah Y origin of image */
WORD Width; /* 0Ch Width of image */
WORD Height; /* 0Eh Height of image */
BYTE PixelDepth; /* 10h Image pixel size */
BYTE ImageDescriptor; /* 11h Image descriptor byte */
}TgaHeader_Info;
// 定义tga文件头
TGAHEAD FILE_header;
TGAHEAD_CMap FILE_header_CMap;
TgaHeader_Info FILE_header_Info;
// 读入tga文件头
tga_File.read((char*)(&FILE_header), 3);
tga_File.read((char*)(&FILE_header_CMap), 5);
tga_File.read((char*)(&FILE_header_Info), 10);
- 判断是否存在图像信息字段
若结构体_TgaHeader中IDLength值为0,则表示不存在图像描述信息字段,若值不为0,则IDLength字段大小则为图像描述信息字段大小。在读入后续数据前,需移动相应长度的文件指针。
ostream & seekp (int offset, int mode);
istream & seekg (int offset, int mode);
mode 代表文件读写指针的设置模式,有以下三种选项:
-
ios::beg:让文件读指针(或写指针)指向从文件开始向后的 offset 字节处。offset 等于 0 即代表文件开头。在此情况下,offset 只能是非负数。
-
ios::cur:在此情况下,offset 为负数则表示将读指针(或写指针)从当前位置朝文件开头方向移动 offset 字节,为正数则表示将读指针(或写指针)从当前位置朝文件尾部移动 offset字节,为 0 则不移动。
-
ios::end:让文件读指针(或写指针)指向从文件结尾往前的 |offset|(offset 的绝对值)字节处。在此情况下,offset 只能是 0 或者负数。
相关代码如下:
if (FILE_header.IDLength == 0) {
cout << "无图像信息字段" << endl;
}
else {
cout << "存在图像信息字段" << endl;
int offset = FILE_header.IDLength;
tga_File.seekg(offset, ios::cur);
}
- 读入颜色表数据
若结构体_TgaHeader中ImageType值为1,则图像为未压缩的颜色表图像;
若结构体_TgaHeader中ImageType值为2,则图像为未压缩的真彩色图像。
颜色表属性数据存放于结构体_TgaHeader_CMap中,具体内容如下:
_TgaHeader_CMap | 字段长度 | 说明 | 取值范围 |
---|---|---|---|
CMapStart | 2 byte | 第一个元素索引值,相对于颜色表起始位置 | 0~65535 ,UINT16 |
CMapLength | 2 byte | 颜色表包含的元素总个数 | 0~65535 ,UINT16 |
CMapDepth | 1 byte | 每一个元素的大小 | 15、16、24或32,UINT8 |
相关代码如下:
unsigned char* Table_Red = new unsigned char[CMap.CMapLength];
unsigned char* Table_Green = new unsigned char[CMap.CMapLength];
unsigned char* Table_Blue = new unsigned char[CMap.CMapLength];
// 读入颜色表
for (int i = 0; i < CMap.CMapLength; i++) {
file.read((char*)(Table_Blue + i), 1);
file.read((char*)(Table_Green + i), 1);
file.read((char*)(Table_Red + i), 1);
}
- 读入图像信息数据
- TGA图像左下角的像素点为实际图像信息数据中第一个像素点
- 真彩色图像和伪彩色图像图像信息数据读入由两个函数分别完成,其中伪彩色的函数在读入过程中同时完成由颜色索引到实际RGB值的映射
相关代码如下:
void Read_rgb(ifstream& file, unsigned char* r_buff, unsigned char* g_buff, unsigned char* b_buff, int height,int width) {
for (int i = height - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
file.read((char*)(b_buff + (i * width + j)), 1);
file.read((char*)(g_buff + (i * width + j)), 1);
file.read((char*)(r_buff + (i * width + j)), 1);
}
}
}
void Read_rgb_Cmap(ifstream& file, TGAHEAD_CMap &CMap,unsigned char* r_buff, unsigned char* g_buff, unsigned char* b_buff, int height, int width) {
unsigned char* Table_Red = new unsigned char[CMap.CMapLength];
unsigned char* Table_Green = new unsigned char[CMap.CMapLength];
unsigned char* Table_Blue = new unsigned char[CMap.CMapLength];
unsigned char* temp = new unsigned char[height * width];
// 读入颜色表
for (int i = 0; i < CMap.CMapLength; i++) {
file.read((char*)(Table_Blue + i), 1);
file.read((char*)(Table_Green + i), 1);
file.read((char*)(Table_Red + i), 1);
}
for (int i = height - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
file.read((char*)(temp + (i * width + j)), 1);
*(b_buff + (i * width + j)) = *(Table_Blue + (*(temp + (i * width + j))));
*(g_buff + (i * width + j)) = *(Table_Green + (*(temp + (i * width + j))));
*(r_buff + (i * width + j)) = *(Table_Red + (*(temp + (i * width + j))));
}
}
if (Table_Red != NULL) { delete Table_Red; }
if (Table_Green != NULL) { delete Table_Green; }
if (Table_Blue != NULL) { delete Table_Blue; }
if (temp != NULL) { delete temp; }
}
四、实验结果
1、真彩色图像
TGA | YUV | 控制台输出 |
---|---|---|
2、伪彩色图像
TGA | YUV | 控制台输出 |
---|---|---|
五、错误总结
-结构体未考虑字节对齐
- 图像信息数据读取顺序出错
六、完整代码
// Lab_2_TGA2YUV.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "RGB2YUV.h"
#include "tga_func.h"
// 初始化索引表
float RGB2YUV02990[256], RGB2YUV05870[256], RGB2YUV01140[256], RGB2YUV01684[256],
RGB2YUV03316[256], RGB2YUV05000[256], RGB2YUV04187[256], RGB2YUV00813[256];
int main(int argc, char* argv[])
{
unsigned char* ybuff = NULL, * ubuff = NULL, * vbuff = NULL;
unsigned char* U_yuv = NULL, * V_yuv = NULL;
unsigned char* rbuff = NULL, * gbuff = NULL, * bbuff = NULL;
Init_table();
ifstream tga_File;
tga_File.open(argv[1], ios::in | ios::binary);
if (!tga_File.is_open()) {
cout << "tga_File open failed." << endl;
}
// 定义tga文件头
TGAHEAD FILE_header;
TGAHEAD_CMap FILE_header_CMap;
TgaHeader_Info FILE_header_Info;
// 读入tga文件头
tga_File.read((char*)(&FILE_header), 3);
tga_File.read((char*)(&FILE_header_CMap), 5);
tga_File.read((char*)(&FILE_header_Info), 10);
int width = FILE_header_Info.Width;
int height = FILE_header_Info.Height;
// 输出元数据
cout << "图像分辨率:" << width << "X" << height << endl;
rbuff = new unsigned char[width * height];
gbuff = new unsigned char[width * height];
bbuff = new unsigned char[width * height];
ybuff = new unsigned char[width * height];
ubuff = new unsigned char[width * height];
vbuff = new unsigned char[width * height];
if (FILE_header.ImageType == 1) {
cout << "未压缩的颜色表图像" << endl;
cout << "元素表颜色总数:" << int(FILE_header_CMap.CMapLength) << endl;
cout << "每个元素大小:" << int(FILE_header_CMap.CMapDepth) << endl;
cout << "像素深度:" << int(FILE_header_Info.PixelDepth) << endl;
if (FILE_header.IDLength == 0) {
cout << "无图像信息字段" << endl;
}
else {
cout << "存在图像信息字段" << endl;
int offset = FILE_header.IDLength;
tga_File.seekg(offset, ios::cur);
}
Read_rgb_Cmap(tga_File, FILE_header_CMap,rbuff, gbuff, bbuff, height, width);
}
else if (FILE_header.ImageType == 2) {
cout << "未压缩的真彩色图像" << endl;
if (FILE_header.IDLength == 0) {
cout << "无图像信息字段" << endl;
Read_rgb(tga_File, rbuff, gbuff, bbuff, height,width);
}
else {
cout << "存在图像信息字段" << endl;
int offset = 0;
offset = FILE_header.IDLength;
tga_File.seekg(offset, ios::cur);
Read_rgb(tga_File, rbuff, gbuff, bbuff, height, width);
}
}else{
cout << "其他图像类型" << endl;
return 0;
}
Caculate_YUV(height, width, rbuff, gbuff, bbuff, ybuff, ubuff, vbuff);
U_yuv = new unsigned char[int(width * height / 4)];
V_yuv = new unsigned char[int(width * height / 4)];
DownSample_YUV(height,width,ubuff,vbuff,U_yuv,V_yuv);
WriteYUV(height, width, ybuff, U_yuv, V_yuv);
tga_File.close();
if (ybuff != NULL) { delete ybuff; }
if (ubuff != NULL) { delete ubuff; }
if (vbuff != NULL) { delete vbuff; }
if (U_yuv != NULL) { delete U_yuv; }
if (V_yuv != NULL) { delete V_yuv; }
if (rbuff != NULL) { delete rbuff; }
if (gbuff != NULL) { delete gbuff; }
if (bbuff != NULL) { delete bbuff; }
}
// tga_func.h
#pragma once
#ifndef tga_func_H_
#include <windows.h>
#include <iostream>
#include <fstream>
using namespace std;
// TGA文件结构体定义
typedef struct _TgaHeader {
BYTE IDLength; /* 00h Size of Image ID field */
BYTE ColorMapType; /* 01h Color map type */
BYTE ImageType; /* 02h Image type code */
} TGAHEAD;
typedef struct _TgaHeader_CMap {
WORD CMapStart; /* 03h Color map origin */
WORD CMapLength; /* 05h Color map length */
BYTE CMapDepth; /* 07h Depth of color map entries */
}TGAHEAD_CMap;
typedef struct _TgaHeader_Info {
WORD XOffset; /* 08h X origin of image */
WORD YOffset; /* 0Ah Y origin of image */
WORD Width; /* 0Ch Width of image */
WORD Height; /* 0Eh Height of image */
BYTE PixelDepth; /* 10h Image pixel size */
BYTE ImageDescriptor; /* 11h Image descriptor byte */
}TgaHeader_Info;
void Read_rgb(ifstream &file,unsigned char* r_buff,unsigned char *g_buff,unsigned char *b_buff, int height, int width);
void Read_rgb_Cmap(ifstream& file, TGAHEAD_CMap& CMap, unsigned char* r_buff, unsigned char* g_buff, unsigned char* b_buff, int height, int width);
#endif // !tga_func_H_
// tga_func.cpp
#include "tga_func.h"
void Read_rgb(ifstream& file, unsigned char* r_buff, unsigned char* g_buff, unsigned char* b_buff, int height,int width) {
for (int i = height - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
file.read((char*)(b_buff + (i * width + j)), 1);
file.read((char*)(g_buff + (i * width + j)), 1);
file.read((char*)(r_buff + (i * width + j)), 1);
}
}
}
void Read_rgb_Cmap(ifstream& file, TGAHEAD_CMap &CMap,unsigned char* r_buff, unsigned char* g_buff, unsigned char* b_buff, int height, int width) {
unsigned char* Table_Red = new unsigned char[CMap.CMapLength];
unsigned char* Table_Green = new unsigned char[CMap.CMapLength];
unsigned char* Table_Blue = new unsigned char[CMap.CMapLength];
unsigned char* temp = new unsigned char[height * width];
// 读入颜色表
for (int i = 0; i < CMap.CMapLength; i++) {
file.read((char*)(Table_Blue + i), 1);
file.read((char*)(Table_Green + i), 1);
file.read((char*)(Table_Red + i), 1);
}
for (int i = height - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
file.read((char*)(temp + (i * width + j)), 1);
*(b_buff + (i * width + j)) = *(Table_Blue + (*(temp + (i * width + j))));
*(g_buff + (i * width + j)) = *(Table_Green + (*(temp + (i * width + j))));
*(r_buff + (i * width + j)) = *(Table_Red + (*(temp + (i * width + j))));
}
}
if (Table_Red != NULL) { delete Table_Red; }
if (Table_Green != NULL) { delete Table_Green; }
if (Table_Blue != NULL) { delete Table_Blue; }
if (temp != NULL) { delete temp; }
}
// RGB2YUV.h
#pragma once
#ifndef RGB2YUV_H_
#define RGB2YUV_H_
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <malloc.h>
using namespace std;
// 声明全局变量
extern float RGB2YUV02990[256], RGB2YUV05870[256], RGB2YUV01140[256], RGB2YUV01684[256],
RGB2YUV03316[256], RGB2YUV05000[256], RGB2YUV04187[256], RGB2YUV00813[256];
void Init_table();
void Caculate_YUV(int height, int width, unsigned char* Red, unsigned char* Green, unsigned char* Blue, unsigned char* Y, unsigned char* U, unsigned char* V);
void WriteYUV(int Height, int Width, unsigned char* Y, unsigned char* U, unsigned char* V);
void DownSample_YUV(int Height, int Width, unsigned char* U, unsigned char* V, unsigned char* U_yuv, unsigned char* V_yuv);
#endif // !RGB2YUV_H_
// RGB2YUV.cpp
#include "RGB2YUV.h"
void Init_table() {
for (int i = 0; i < 256; i++) {
RGB2YUV02990[i] = (float)0.2990 * i;
RGB2YUV05870[i] = (float)0.5870 * i;
RGB2YUV01140[i] = (float)0.1140 * i;
RGB2YUV01684[i] = (float)0.1684 * i;
RGB2YUV03316[i] = (float)0.3316 * i;
RGB2YUV05000[i] = (float)0.5000 * i;
RGB2YUV04187[i] = (float)0.4187 * i;
RGB2YUV00813[i] = (float)0.0813 * i;
}
}
// 计算YUV
void Caculate_YUV(int height, int width, unsigned char* Red, unsigned char* Green, unsigned char* Blue, unsigned char* Y, unsigned char* U, unsigned char* V) {
float temp;
for (int i = 0; i < height * width; i++) {
// 处理Y分量
temp = RGB2YUV02990[*(Red + i)] + RGB2YUV05870[*(Green + i)] + RGB2YUV01140[*(Blue + i)];
temp = temp > 236 ? 236 : temp;
temp = temp < 16 ? 16 : temp;
*(Y + i) = (unsigned char)temp;
// 处理U分量
temp = -RGB2YUV01684[*(Red + i)] - RGB2YUV03316[*(Green + i)] + RGB2YUV05000[*(Blue + i)] + 128;
temp = temp > 236 ? 236 : temp;
temp = temp < 16 ? 16 : temp;
*(U + i) = (unsigned char)temp;
// 处理V分量
temp = RGB2YUV05000[*(Red + i)] - RGB2YUV04187[*(Green + i)] - RGB2YUV00813[*(Blue + i)] + 128;
temp = temp > 236 ? 236 : temp;
temp = temp < 16 ? 16 : temp;
*(V + i) = (unsigned char)temp;
}
}
// 计算RGB
// 写入YUV文件422格式
void WriteYUV(int Height, int Width,unsigned char* Y, unsigned char* U, unsigned char* V) {
ofstream File_out;
File_out.open("NewImage.yuv", ios::binary,ios::trunc);
if (!File_out) {
cout << "Failed to open NewImage.yuv" << endl;
}
else {
File_out.write((char*)Y, Height * Width);
File_out.write((char*)U, Height * Width / 4);
File_out.write((char*)V, Height * Width / 4);
File_out.close();
}
}
// 对UV进行下采样
void DownSample_YUV(int Height, int Width, unsigned char *U,unsigned char *V,unsigned char * U_yuv, unsigned char* V_yuv) {
// 处理UV分量
int k = 0;
float average_u, average_v;
for (int i = 0; i < Height * Width / 4; i++) {
average_u = (float)(*(U + k) + *(U + k + 1) + *(U + k + Width) + *(U + k + 1 + Width)) / 4.0;
average_v = (float)(*(V + k) + *(V + k + 1) + *(V + k + Width) + *(V + k + 1 + Width)) / 4.0;
*(U_yuv + i) = (unsigned char)average_u;
*(V_yuv + i) = (unsigned char)average_v;
if ((i+1) % (Width / 2) == 0) {
k = k + 2 + Width;
}
else {
k = k + 2;
}
}
}
推荐阅读