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

数据压缩_实验二_TGA2YUV

程序员文章站 2022-03-23 12:56:01
...

文件格式转换实验

一、实验目标

  • 读入TGA文件

  • 将TGA文件转换为YUV文件

二、实验原理

1、TGA文件格式

TGA(Targa)格式是计算机上应用最广泛的图象格式。在兼顾了BMP的图象质量的同时又兼顾了JPEG的体积优势。并且还有自身的特点:通道效果、方向性。在CG领域常作为影视动画的序列输出格式,因为兼具体积小和效果清晰的特点。

基本格式如下图:

数据压缩_实验二_TGA2YUV

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、基本流程图

数据压缩_实验二_TGA2YUV

2、关键步骤

- 创建与读入文件头结构体

将文件头结构体分为三部分:

  1. _TgaHeader

  2. _TgaHeader_CMap (颜色表信息字段)

  3. _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 控制台输出
数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV

2、伪彩色图像

TGA YUV 控制台输出
数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV
数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV
数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV
数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV 数据压缩_实验二_TGA2YUV

五、错误总结

-结构体未考虑字节对齐

数据压缩_实验二_TGA2YUV

- 图像信息数据读取顺序出错

数据压缩_实验二_TGA2YUV

六、完整代码

// 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;
		}
	}
}
相关标签: 数据压缩