数据压缩_实验二_TGA2YUV

文件格式转换实验

一、实验目标

  • 读入TGA文件

  • 将TGA文件转换为YUV文件

二、实验原理

1、TGA文件格式

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

基本格式如下图:

image-20200404214536880

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

Untitled Diagram

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字段长度说明取值范围
CMapStart2 byte第一个元素索引值,相对于颜色表起始位置0~65535 ,UINT16
CMapLength2 byte颜色表包含的元素总个数0~65535 ,UINT16
CMapDepth1 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、真彩色图像

TGAYUV控制台输出
截屏2020-04-05上午11.42.06截屏2020-04-05上午11.38.52截屏2020-04-05上午11.37.45

2、伪彩色图像

TGAYUV控制台输出
1_bit截屏2020-04-05上午11.58.09截屏2020-04-05上午11.56.34
2_bit截屏2020-04-05下午12.10.26截屏2020-04-05下午12.09.40
4_bit截屏2020-04-05下午12.14.18截屏2020-04-05下午12.13.38
8_bit截屏2020-04-05下午12.15.40截屏2020-04-05下午12.15.17

五、错误总结

-结构体未考虑字节对齐
截屏2020-04-02下午2.43.16
- 图像信息数据读取顺序出错
截屏2020-04-02下午8.06.58

六、完整代码

// 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;
		}
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值