彩色空间转换:yuv(4:2:0)格式文件转换rgb文件格式转换器

本次实验的目的为将一个yuv(4:2:0)格式的文件转换成一个rgb文件格式的文件,那么首先我们就要先了解一下这两个文件格式都是如何组成的。接下来是我的一些个人理解。

**一 介绍文件格式
**
1 rgb图像文件
rgb图像文件每一个像素点灰度值都是用8比特的B分量,8比特G分量,8比特的R分量存放保存,每个像素点依次保存,也就是在读取或写入文件时顺序为BGRBGRBGR…
2 yuv文件格式
yuv格式分为4种:4:4:4,4:2:2,4:2:0,4:1:1,目前最常用的是 4:2:0格式,本次实验也采用的该格式。第一位数指的是Y分量,也就是亮度信号,每一个像素点都有一个亮度值,后两位代表了UV分量,也就是说,在这里UV分量数量分别为亮度信号的四分之一,具体分布如下图所示
在这里插入图片描述
二 yuv转换成rgb的转换公式

通过y,u,v分量计算相应的RGB分量,计算公式如图所示
在这里插入图片描述

值得一提的是,这里面的UV分量是YUV文件实际分量值减去128得来的

三 转换器实现流程关键代码分析

首先是本实验需要用到的函数名,储存在头文件yuv2rgb.h中

int YUV2RGB(int x_dim, int y_dim, void* bmp, void* r_out, void* g_out, void* b_out, int flip);

void InitLookupTable();

本实验中运用到了大量的指针,而并没有用到数组,我之前其实对数组形式更加的了解,在这里也是想挑战一下自己,接下来让我们看看这两个函数的实现
首先定义了一个查找表,用来简化公式计算的书写复杂度,因为8比特存储所以一共有256级

// An highlighted block
static float RGBYUV14075[256], RGBYUV03455[256], RGBYUV07169[256], RGBYUV17790[256];

接下来这个函数是给这个查找表赋值

// An highlighted block
void InitLookupTable()//给查找表赋值
{
	int i;

	for (i = 0; i < 256; i++) RGBYUV14075[i] = (float)1.4075 * (i-128);
	for (i = 0; i < 256; i++) RGBYUV03455[i] = (float)0.3455 * (i - 128);
	for (i = 0; i < 256; i++) RGBYUV07169[i] = (float)0.7169 * (i - 128);
	for (i = 0; i < 256; i++) RGBYUV17790[i] = (float)1.7790 * (i - 128);

}

接下来我们就进入到了代码的最核心部分,也就是转换的部分,思路简单来说就是,首先先将yuv数据分开,提取出Y分量U分量V分量,而后由于UV分量数量为Y分量数量的四分之一,所以要进行扩大,将UV分量扩充成四倍,而后对应于Y分量计算出相应的RGB分量,写入文件,即完成了转换。
提取YUV分量的函数如下所示

for (i = 0; i<size; i ++) {
		*(y) = *(yuv_buffer + i);
		y++;
	}
	for (i = 0; i<size/4; i++) {
		*(u) = *(yuv_buffer + i+size);
		u++;
	}
	for (i = 0; i < size / 4; i ++) {
		*(v) = *(yuv_buffer + i+size*5/4);
		v++;
	}

在这里的yuv_buffer指针指向了yuv文件数据的头。
如下代码完成了UV的扩充

for (i = 0;  i < y_dim / 2; i ++) {
		pu1 = u1 + i * 2 * x_dim;
		pu2 = u1 + (i * 2 + 1) * x_dim;
		pv1 = v1 + i * 2 * x_dim;
		pv2 = v1 + (i * 2 + 1) * x_dim;
		for (j = 0; j<x_dim/2;j++) {
			*(pu1) = *(u_buffer + j + i * x_dim / 2);
			*(pu2) = *(pu1);
			pu1++;
			pu2++;
			*(pu1) = *(u_buffer + j + i * x_dim / 2);
			*(pu2) = *(pu1);
			pu1++;
			pu2++;
			*(pv1) = *(v_buffer + j + i * x_dim / 2);
			*(pv2) = *(pv1);
			pv1++;
			pv2++;
			*(pv1) = *(v_buffer + j + i * x_dim / 2);
			*(pv2) = *(pv1);
			pv1++;
			pv2++;

		}
	}

u1指针分配了一块扩充好的空间,现在要往该空间里面填数值,pu1,pu2这两个指针刚开始分别指向了u_buffer的第一行和第二行的开始,u_buffer指针指向了扩充前U分量的头部,先填充一二行,而后跳转到三四行继续填充。
而后就可以进行计算了,在这里设置了两种模式,正向和反向,由于最后采用yuvviewer查看,解析文件时会反向解析,可以使图像倒转。函数如下所示

if (flip)
	{
		for (i = 0; i < size; i++)
		{
			float R = *(y_buffer + i) + RGBYUV14075[*(v1 + i)];
			float G = *(y_buffer + i) - RGBYUV03455[*(u1 + i)] - RGBYUV07169[*(v1 + i)];
			float B = *(y_buffer + i) + RGBYUV17790[*(u1 + i)];
			if(R<0)
			{
				R= 0;
			}
			if (G < 0)
			{
				G = 0;
			}
			if (B < 0)
			{
				B= 0;
			}
			if (R >255)
			{
				R = 255;
			}
			if (G> 255)
			{
				G = 255;
			}
			if (B > 255)
			{
				B = 255;
			}
			
			
			*(r) =(unsigned char) (R);
			*(g) = (unsigned char)(G);
			*(b) = (unsigned char)(B);
			r++;
			g++;
			b++;
		}
	}
	else {
		for (j = 0; j < y_dim; j++) {
			y = y_buffer + (y_dim - 1 - j) * x_dim;
			pu1 = u1 + (y_dim - 1 - j) * x_dim;
			pv1=v1+ (y_dim - 1 - j) * x_dim;
			for (i = 0; i < x_dim; i++) {
				float R = *(y+ i) + RGBYUV14075[*(pv1 + i)];
			float G = *(y + i) - RGBYUV03455[*(pu1 + i)] - RGBYUV07169[*(pv1 + i)];
			float B = *(y + i) + RGBYUV17790[*(pu1 + i)];
			if(R<0)
			{
				R= 0;
			}
			if (G < 0)
			{
				G = 0;
			}
			if (B < 0)
			{
				B= 0;
			}
			if (R >255)
			{
				R = 255;
			}
			if (G> 255)
			{
				G = 255;
			}
			if (B > 255)
			{
				B = 255;
			}
				*(r) = (unsigned char)(R);
				*(g) = (unsigned char)(G);
				*(b) = (unsigned char)(B);
				r++;
				g++;
				b++;

			}
		}
	
	}

在这里可以看到进行了值的判断,原因为yuv自己的保护机制,不允许UV分量小于16或大于240,所以在计算时可能会出现RGB分量值为负或大于255的情况,在这里加以更正。

四 完整代码展示

接下来先附上主函数

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "yuv2rgb.h"
#pragma warning(disable:4996)
#define u_int8_t	unsigned __int8
#define u_int		unsigned __int32
#define u_int32_t	unsigned __int32
#define FALSE		false
#define TRUE		true
int main(int argc, char** argv)
{
	u_int W = 521;			/* --width=<uint> */
	u_int H = 521;
	bool flip = FALSE;				/* --flip */
	unsigned int i;
	char* rgbFileName = NULL;
	char* yuvFileName = NULL;
	FILE* rgbFile = NULL;
	FILE* yuvFile = NULL;
	u_int8_t* yuvBuf = NULL;
	u_int8_t* rBuf = NULL;
	u_int8_t* gBuf = NULL;
	u_int8_t* bBuf = NULL;
	u_int32_t videoFramesWritten = 0;
	yuvFileName = argv[1];
	rgbFileName = argv[2];
	W = atoi(argv[3]);
	H = atoi(argv[4]);
	yuvFile = fopen(yuvFileName, "rb");
	if (yuvFile == NULL)
	{
		printf("对不起,没有找到该文件\n");
		exit(1);
	}
	else
	{
		printf("打开的文件为%s\n", yuvFileName);
	}
	rgbFile = fopen(rgbFileName, "wb");
	if (rgbFile == NULL)
	{
		printf("没有找到该文件\n");
		exit(1);
	}
	else
	{
		printf("输出的文件是 %s\n", rgbFileName);
	}
	yuvBuf = (u_int8_t*)malloc(W * H * 3 / 2);
	rBuf = (u_int8_t*)malloc(W * H);
	gBuf = (u_int8_t*)malloc(W * H);
	bBuf = (u_int8_t*)malloc(W * H);
	if (yuvBuf == NULL || rBuf == NULL || gBuf == NULL || bBuf == NULL)
	{
		printf("no enought memory\n");
		exit(1);
	}
	while (fread(yuvBuf, 1, W*H* 3/2, yuvFile))
	{
		if (YUV2RGB(W, H, yuvBuf, rBuf, gBuf, bBuf, flip))
		{
			printf("error");
			return 0;
		}


		
		for (i = 0; i < W * H; i++) {
			fwrite(bBuf+i, 1, 1, rgbFile);
			fwrite(gBuf+i, 1, 1, rgbFile);
			fwrite(rBuf+i, 1, 1, rgbFile);
		}

		printf("\r...%d", ++videoFramesWritten);
	}

	printf("\n%u %ux%u video frames written\n",
		videoFramesWritten, W, H);

	/* cleanup */

	fclose(rgbFile);
	fclose(yuvFile);

	return(0);

}

接下来就是转换函数了

#include "stdlib.h"
#include "yuv2rgb.h"
static float RGBYUV14075[256], RGBYUV03455[256], RGBYUV07169[256], RGBYUV17790[256]; //定义查找表
void InitLookupTable()//给查找表赋值
{
	int i;

	for (i = 0; i < 256; i++) RGBYUV14075[i] = (float)1.4075 * (i-128);
	for (i = 0; i < 256; i++) RGBYUV03455[i] = (float)0.3455 * (i - 128);
	for (i = 0; i < 256; i++) RGBYUV07169[i] = (float)0.7169 * (i - 128);
	for (i = 0; i < 256; i++) RGBYUV17790[i] = (float)1.7790 * (i - 128);

}
int YUV2RGB(int x_dim, int y_dim, void* bmp, void* r_out, void* g_out, void* b_out, int flip)
{
	static int init_done = 0;
    long i, j, size;
	unsigned char* y_buffer, *u_buffer, *v_buffer, *yuv_buffer;
	unsigned char* r, * g, * b;
	unsigned char* y, * u, * v;
	unsigned char* u1, * v1;
	unsigned char* pu1, * pu2;
	unsigned char* pv1, *pv2;
	r = (unsigned char*)r_out;
	g = (unsigned char*)g_out;
	b = (unsigned char*)b_out;
	
	if (init_done == 0)//初始化查找表
	{
		InitLookupTable();
		init_done = 1;
	}
	if ((x_dim % 2) || (y_dim % 2)) return 1;//如果行和列不为2的倍数,则无法转换
	size = x_dim * y_dim;//定义大小
	yuv_buffer = (unsigned char*)bmp;
	y_buffer = (unsigned char*)malloc(size* sizeof(unsigned char));
	u_buffer = (unsigned char*)malloc(size* sizeof(unsigned char) /4);
	v_buffer = (unsigned char*)malloc(size * sizeof(unsigned char) / 4);
	u1 = (unsigned char*)malloc(size* sizeof(unsigned char));
	v1= (unsigned char*)malloc(size* sizeof(unsigned char));
	y = y_buffer;
	u = u_buffer;
	v = v_buffer;
	for (i = 0; i<size; i ++) {
		*(y) = *(yuv_buffer + i);
		y++;
	}
	for (i = 0; i<size/4; i++) {
		*(u) = *(yuv_buffer + i+size);
		u++;
	}
	for (i = 0; i < size / 4; i ++) {
		*(v) = *(yuv_buffer + i+size*5/4);
		v++;
	}
	for (i = 0;  i < y_dim / 2; i ++) {
		pu1 = u1 + i * 2 * x_dim;
		pu2 = u1 + (i * 2 + 1) * x_dim;
		pv1 = v1 + i * 2 * x_dim;
		pv2 = v1 + (i * 2 + 1) * x_dim;
		for (j = 0; j<x_dim/2;j++) {
			*(pu1) = *(u_buffer + j + i * x_dim / 2);
			*(pu2) = *(pu1);
			pu1++;
			pu2++;
			*(pu1) = *(u_buffer + j + i * x_dim / 2);
			*(pu2) = *(pu1);
			pu1++;
			pu2++;
			*(pv1) = *(v_buffer + j + i * x_dim / 2);
			*(pv2) = *(pv1);
			pv1++;
			pv2++;
			*(pv1) = *(v_buffer + j + i * x_dim / 2);
			*(pv2) = *(pv1);
			pv1++;
			pv2++;

		}
	}
	if (flip)
	{
		for (i = 0; i < size; i++)
		{
			float R = *(y_buffer + i) + RGBYUV14075[*(v1 + i)];
			float G = *(y_buffer + i) - RGBYUV03455[*(u1 + i)] - RGBYUV07169[*(v1 + i)];
			float B = *(y_buffer + i) + RGBYUV17790[*(u1 + i)];
			if(R<0)
			{
				R= 0;
			}
			if (G < 0)
			{
				G = 0;
			}
			if (B < 0)
			{
				B= 0;
			}
			if (R >255)
			{
				R = 255;
			}
			if (G> 255)
			{
				G = 255;
			}
			if (B > 255)
			{
				B = 255;
			}
			
			
			*(r) =(unsigned char) (R);
			*(g) = (unsigned char)(G);
			*(b) = (unsigned char)(B);
			r++;
			g++;
			b++;
		}
	}
	else {
		for (j = 0; j < y_dim; j++) {
			y = y_buffer + (y_dim - 1 - j) * x_dim;
			pu1 = u1 + (y_dim - 1 - j) * x_dim;
			pv1=v1+ (y_dim - 1 - j) * x_dim;
			for (i = 0; i < x_dim; i++) {
				float R = *(y+ i) + RGBYUV14075[*(pv1 + i)];
			float G = *(y + i) - RGBYUV03455[*(pu1 + i)] - RGBYUV07169[*(pv1 + i)];
			float B = *(y + i) + RGBYUV17790[*(pu1 + i)];
			if(R<0)
			{
				R= 0;
			}
			if (G < 0)
			{
				G = 0;
			}
			if (B < 0)
			{
				B= 0;
			}
			if (R >255)
			{
				R = 255;
			}
			if (G> 255)
			{
				G = 255;
			}
			if (B > 255)
			{
				B = 255;
			}
				*(r) = (unsigned char)(R);
				*(g) = (unsigned char)(G);
				*(b) = (unsigned char)(B);
				r++;
				g++;
				b++;

			}
		}
	
	}
	free(y_buffer);
	free(u_buffer);
	free(v_buffer);
	free(u1);
	free(v1);
	return 0;
}

**五 原图像与输出图像对比
**
原图像
在这里插入图片描述
转换完成的图像

在这里插入图片描述
无明显误差,总的来说效果还是不错的!

六 小tips

可以看到在主函数中采用了argv参数调用的形式,这样只需要在参数目录中输入文件名以及格式就可以完成转换,大大提升了通用性。
接下来附上使用过程吧
我采用的是VS2019版本,点击项目中属性,出现如下

在这里插入图片描述
而后点击调试

在这里插入图片描述
而后在命令参数中,依次填入初始YUV文件名 要生成的RGB文件名,宽 高,而后在工作目录中找到文件所在位置就可以了。

七 实验总结与误差分析

本次实验存在的误差应该主要集中于YUV的保护机制导致计算的RGB分量的值不准确,还有UV的扩充不够准确的问题,本次实验我觉得使我对指针的理解更为透彻,可以看出,我用了大量的指针,所以最后能够成功我还是比较开心的,在编写代码期间也是备受煎熬啊。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值