tga转yuv,C语言实现
一、实验内容
将一个tga格式的文件转换成yuv格式的文件(选择了类型2、TGA24的TGA文件进行转换)
二、实验原理
1、关于tga文件格式的分析
关于tga文件格式的分析:tga文件分析
2、关于rgb转yuv
关于rgb转yuv:rgb与yuv互转
三、实验步骤
1、读取tga文件并得到rgb数据
读取tga文件并得到rgb数据,rgb数据即图像数据。对于类型2、TGA24的TGA文件来说,因为无调色版,所以从文件开头偏移一个文件头的大小后即可读取图像数据。
2、获得的图像数据转置
通过tga文件的分析可知,tga图像的x坐标和y坐标是从左下角开始的,即图像是从左下角开始,从左往右,从下往上存储的,因此要把获得的图像数据转置,使图像数据从上往下从左往右存储在buffer中。
3、将得到rgb数据转换成yuv数据并写入yuv文件中
将得到rgb数据转换成yuv数据并写入yuv文件中。
四、完整代码
命令参数行设置
head.h
#pragma pack(1)
#include<Windows.h>
typedef struct
{
unsigned char imageInfo;
unsigned char colorMapType;
unsigned char imageType;
unsigned short colorMapStart;
unsigned short colorMapLength;
unsigned char colorMapBits;
unsigned short xstart;
unsigned short ystart;
unsigned short imageWidth;
unsigned short imageHeight;
unsigned char pixelBits;
unsigned char imageDescribe;
}TGAHEADER;
#pragma pack()
void rgb2yuv(unsigned short picHeight, unsigned short picWidth, unsigned char *buffer, unsigned char *y, unsigned char *u, unsigned char *v, FILE *yuv);
void getRGB(unsigned short height, unsigned short width, FILE *tga, unsigned char *rgbData, FILE *rgb);
getRGB.cpp
#include<stdio.h>
#include<stdlib.h>
#include "head.h"
void getRGB(unsigned short height, unsigned short width, FILE *tga, unsigned char *rgbData,FILE *rgb)
{
fseek(tga, sizeof(TGAHEADER), SEEK_SET);
unsigned char *rgbData_Origin = NULL;
rgbData_Origin = (unsigned char *)malloc(height*width * 3);
fread(rgbData_Origin, 1, height*width * 3, tga);
//get rgbData(转置)
for (int i = 0;i < height;i++)
{
for (int j = 0;j < width;j++)
{
*(rgbData + i*width * 3 + 3 * j) = *(rgbData_Origin + (height - 1 - i)*width * 3 + 3 * j);
*(rgbData + i*width * 3 + 3 * j + 1) = *(rgbData_Origin + (height - 1 - i)*width * 3 + 3 * j + 1);
*(rgbData + i*width * 3 + 3 * j + 2) = *(rgbData_Origin + (height - 1 - i)*width * 3 + 3 * j + 2);
}
}
fwrite(rgbData, 1, height*width * 3, rgb);
}
rgb2yuv.cpp
#include<stdio.h>
#include<stdlib.h>
#include "head.h"
void rgb2yuv(unsigned short picHeight, unsigned short picWidth, unsigned char *rgbData, unsigned char *y, unsigned char *u, unsigned char *v, FILE *yuv)
{
//allocate buffer
unsigned char *r = NULL, *g = NULL, *b = NULL, *u420 = NULL, *v420 = NULL;
r = (unsigned char *)malloc(picHeight * picWidth);
g = (unsigned char *)malloc(picHeight * picWidth);
b = (unsigned char *)malloc(picHeight * picWidth);
u420 = (unsigned char *)malloc(picHeight * picWidth / 4);
v420 = (unsigned char *)malloc(picHeight * picWidth / 4);
//end allocate
//get r & g & b buffer
for (int i = 0;i<picHeight * picWidth;i++)
{
//rgb里存储顺序为b-g-r
*(b + i) = *(rgbData + 3 * i);
*(g + i) = *(rgbData + 3 * i + 1);
*(r + i) = *(rgbData + 3 * i + 2);
//错误:*(b + i) = *(rgbData + i);*(g + i) = *(rgbData + i + 1);*(r + i) = *(rgbData + i + 2);
}
//end
//rgb2yuv444
for (int i = 0;i<picHeight * picWidth;i++)
{
*(y + i) = (int)((*(r + i) * 66 + *(g + i) * 129 + *(b + i) * 25) / 255 + 16);
*(u + i) = (int)((*(r + i)*(-38) + *(g + i)*(-74) + *(b + i) * 112) / 255 + 128);
*(v + i) = (int)((*(r + i)*(112) + *(g + i)*(-94) + *(b + i)*(-18)) / 255 + 128);
}
//end
//upsample:yuv444 to yuv420
int k = 0;
for (int i = 0; i < picHeight / 2;i++)
{
for (int j = 0;j < picWidth / 2;j++)
{
*(u420 + k) = (*(u + picWidth * i * 2 + 2 * j) + *(u + picWidth * i * 2 + 2 * j + 1) + *(u + picWidth * (2 * i + 1) + 2 * j) + *(u + picWidth * (2 * i + 1) + 2 * j + 1)) / 4;
*(v420 + k) = (*(v + picWidth * i * 2 + 2 * j) + *(v + picWidth * i * 2 + 2 * j + 1) + *(v + picWidth * (2 * i + 1) + 2 * j) + *(v + picWidth * (2 * i + 1) + 2 * j + 1)) / 4;
k++;
//错误:*(u444 + picWidth * i + 2 * j)等
}
}
//end upsample
//write yuvFile
fwrite(y, 1, picHeight * picWidth, yuv);
fwrite(u420, 1, picHeight * picWidth / 4, yuv);
fwrite(v420, 1, picHeight * picWidth / 4, yuv);
//end write
//freebuffer
free(r);
free(g);
free(b);
free(u420);
free(v420);
//end free
}
main.cpp
#include<stdio.h>
#include<stdlib.h>
#include "head.h"
void main(int argc, char *argv[])
{
//open tga & yuv File
FILE *tga = NULL, *yuv = NULL, *rgb = NULL;
if ((tga = fopen(argv[1], "rb")) == NULL)
{
printf("tga file open failed!");
exit(0);
}
if ((yuv = fopen(argv[2], "wb")) == NULL)
{
printf("yuv file failed!");
exit(0);
}
if ((rgb = fopen(argv[3], "wb")) == NULL)
{
printf("rgb file failed!");
exit(0);
}
//get pic height & width
TGAHEADER File_header;
if (fread(&File_header, sizeof(TGAHEADER), 1, tga) != 1)
{
printf("read tga file failed!");
exit(0);
}
unsigned short height, width;
width = File_header.imageWidth;
height = File_header.imageHeight;
printf("image width:%d\n", width);
printf("image height:%d\n",height);
printf("image type:%d\n", File_header.imageType);
printf("pixel bits:%d\n", File_header.pixelBits);
//allocate buffer
unsigned char *y = NULL;
unsigned char *u = NULL;
unsigned char *v = NULL;
unsigned char *rgbData = NULL;
y = (unsigned char *)malloc(height*width);
u = (unsigned char *)malloc(height*width);
v = (unsigned char *)malloc(height*width);//注意这是444不是420 否则会搞出野指针
rgbData = (unsigned char *)malloc(height*width * 3);
//get tga data
getRGB(height, width, tga, rgbData,rgb);
//rgb2yuv
rgb2yuv(height, width, rgbData, y, u, v, yuv);
//free buffer & close file
free(y);
free(u);
free(v);
free(rgbData);
fclose(tga);
fclose(yuv);
fclose(rgb);
getchar();
}
五、结果
运行窗口:
原图与得到的图像:
tga | yuv |
---|---|
六、遇到的错误及分析
1、结构体字节无法正常读取,会少读取两个字节
经查找资料后发现是因为在写入结构体的 head.h 文件中缺少对界条件。
解决方法:
使用伪指令#pragma pack(n),C编译器将按照n个字节对齐
使用伪指令#pragma pack( ),取消自定义字节对齐方式
解决:在 head.h 文件中增添了这两条伪指令后结构体的字节便可正常读取
2、野指针导致内存泄露崩溃
如果使用了野指针导致内存泄漏的话,调试时会出现以下报错:
- is_block_type_valid(header->_block_use)
问题产生原因:
内存泄漏;所以当程序退出时,系统会收回分配的内存,于是调析构函数,由于内存已被错误地释放,于是就会出现“Debug Assertion Failed”的错误。
这个assert说明什么问题呢?说明有一块内存在被释放的时候,它的头部里面的信息已经被改掉了,和预期的不一样。内存分配的程序往往在被分配出的内存块头部放上一些校验信息。这个信息内存的用户是不知道也不应该修改的。这样,在内存被释放的时候,内存分配程序就可以验对这个头部信息是否被改过了。若被改过,就说明发生了内存corruption.
这种corruption有两种可能性:
1、在内存越界写东西;
2、这块内存已经被释放了,又被重复释放了一次。(在第一次被释放中,是内存分配程序改掉了头部信息);
3、header->_block_use就可能是空指针,或它指向的东西已经不存在了。 - 0xC0000005: 读取位置 xxx时发生访问冲突
这种错误的意思一般是指访问了不属于自己的内存空间,出现这种错误有几种原因:
给一个数组分配了比较小的内存空间,然后又给该数组赋了一个比较大的值
解决方法:
1、给数组分配更大一些的内存空间
2、句柄或指针在使用前被释放
3、解决方法:检查代码配合调试,揪出野指针
总结:C和C++中的指针尤其优势,尤其劣势,优势很明显,灵活,方便,使用起来简单;劣势当然也很明显,就是容易导致系统崩溃,指针操作不当,或没有及时释放,或没有判断指针是否越界,或没有及时置空,这些都很容易导致系统崩溃,所以以后使用指针是一定要养成好的习惯,才能准确的使用指针。
解决:最后检查代码发现自己main.cpp中u和v的buffer中分配的内存数值错误,导致内存越界从而崩溃。
3、使用的图像有问题
- 不知道为什么刚开始使用的图像出现了奇怪的问题,直接打开原图是很正常的,而用yuvViewer打开原图的时候呈现出了奇怪的状态,所以最后转出来的yuv文件显示出同样奇怪的样子,但是换图之后就没有这个问题了,所以我认为代码应该是没什么问题的,可能是最开始使用的图像中插入了什么奇怪的字节导致图像显示错误。
正常打开的图像 | 用yuvViewer显示出来的图像 | 转换出来的yuv图像 |
---|---|---|
解决:无解,不知道为什么,代码应该是没错的,毕竟用其他图像是可以正常显示的。