BMP转换为YUV(Linux):
1.YUV格式
- YUV两大类格式:
(1)平面格式(planar formats)
将Y、U、V的三个分量分别存放在不同的矩阵中。先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
如:YUV420P在数组中的存储格式
(2)紧缩格式(packed formats)
YUV数据是交替存储在数组中的,类似与BMP的RGB存储
如:YUV422 在数组中的存储格式
-
YUV420示意图
每四个像素点共用一对UV值,所以,在640x480的图像中,需要:
640x480 + 640x480/4 + 640x480/4字节来存储。
640x480 是Y分量的字节数
640x480/4 而U和V所占的字节数相同:YUV420P在屏幕像素上的分布表示:
每四个Y使用同一组U和V。共用UV的方式如上图
四个Y公用的 U = 四个Y对应的U之和/4
四个Y公用的 V = 四个Y对应的V之和/4
上面两条公式就是YUV420P的采样公式
-
YUV422示意图
每两个像素点共用一对UV值,所以,在512x512的图像中,需要:
512x512 + 512x512/2 + 512x512/2字节来存储。
512x512 是Y分量的字节数
512x512/2 而U和V所占的字节数相同
每两个Y使用同一组U和V。两个Y公用的 U = 两个Y对应的U之和/2
两个Y公用的 V = 两个Y对应的V之和/2
上面两条公式就是YUV422P的采样公式
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法[^1] 功能;
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
2.RGB与YUV互相转化公式
Y=0.2990R + 0.5870G + 0.1140B
U=0.500R - 0.419G - 0.081B + 128
V=-0.1684R - 0.3316G + 0.500B + 128
(注意:网上找的大部分公式可能来源于同一个地方,所以U和V的公式反过来了,这导致我在转换的时候出现色彩不正常的情况,并且研究很久才知道U和V的公式反过来了))
一个YUV像素点对应的RGB:
R = Y + 1.403 * (V - 128)
G = Y - 0.343 * (U - 128) - 0.714 * (V - 128)
B = Y + 1.770 * (V - 128)
3.RGB转换为YUV420:
在这里我们以bmp(24位位图)图片格式为例:
- 首先获得全部的RGB像素点(包含处理bmp数据头)
由于是24位bmp,RGB像素点不需要任何修改,直接读取即可,存放在g_RgbBuf中
/**************************************************************************
- 函数名称: FnOpenBmp()
- 功能描述: 1.打开bmp
2.处理bmp的头信息,并跳过bmp的头54个字节
- 输入参数: * BmpPath:输入bmp图像地址
- 输出参数: 无
- 返 回 值: 成功返回0
- 其它说明: 本函数里面有图像读取和图像信息内存分配功能
- 修改日期 版本号 修改人 修改内容
- -----------------------------------------------
-
**************************************************************************/
int FnOpenBmp(const char * BmpPath)
{
int nRet;
int BmpFd = open(BmpPath , O_RDWR);
if(BmpFd <= 0)
{
perror("");
printf("错误代码:%d\n",BmpEer);
return BmpEer;
}
//开辟信息存放空间
struct BitmapHeader * HeadInfo = malloc(HeadInfoRAM);
struct BitmapInfo * BmpInfo = malloc(BmpInfoRAM );
if(NULL == HeadInfo || NULL == BmpInfo )
{
printf("错误代码:%d\n",ApplyBmpHeadEer);
return ApplyBmpHeadEer;
}
//读取bmp的头信息
nRet = read(BmpFd , HeadInfo , HeadInfoRAM);
if(nRet != HeadInfoRAM)
{
printf("错误代码:%d\n",ReadHeadInfoRAMEer);
return ReadHeadInfoRAMEer;
}
nRet = read(BmpFd , BmpInfo , BmpInfoRAM);
if(nRet != BmpInfoRAM)
{
printf("错误代码:%d\n",ReadBmpInfoRAMEer);
return ReadBmpInfoRAMEer;
}
//计算有没有满足长宽4倍的条件
if(0 == BmpInfo->width % 4)
{
g_BmpWidth = BmpInfo->width;
}
else
{
g_BmpWidth = ((BmpInfo->width * BmpInfo->bit_count+31)/32)*4;
}
if(0==BmpInfo->height % 2)
{
g_BmpHeight = BmpInfo->height;
}
else
{
g_BmpHeight = BmpInfo->height + 1;
}
// g_BmpWidth = BmpInfo->width ;
// g_BmpHeight = BmpInfo->height ;
printf("bmp_x = %d bmp_y = %d\n" , g_BmpWidth , g_BmpHeight);
//读取RGB信息
g_RgbBuf = malloc(g_BmpWidth * g_BmpHeight * 3);
if(NULL == g_RgbBuf)
{
printf("错误代码:%d\n",RgbBufEer);
return RgbBufEer;
}
unsigned int ret = read(BmpFd , g_RgbBuf , g_BmpWidth * g_BmpHeight * 3);
// if(ret != bmp_w * bmp_h * 3)
// {
// printf("读取bmp_rgb失败\n");
// return -1;
// }
free (BmpInfo);
free (HeadInfo);
close(BmpFd);
return 0;
}
- 根据RGB转换所有像素点的YUV
bmp的存储形式:
/**************************************************************************
* 函数名称: FnConvertYuv()
* 功能描述: 将RGB数据进行运算成YUV数据
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0
* 其它说明: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
*
**************************************************************************/
int FnConvertYuv()
{
unsigned char * TmpY = NULL;
unsigned char * TmpU = NULL;
unsigned char * TmpV = NULL;
g_YuvY = malloc(g_BmpWidth * g_BmpHeight);
g_YuvTmpU = malloc(g_BmpWidth * g_BmpHeight);
g_YuvTmpV = malloc(g_BmpWidth * g_BmpHeight);
//由于yuv_y等参数需要进行地址加,所以这里使用tmp_y等保存最开始的地址
TmpY = g_YuvY;
TmpU = g_YuvTmpU;
TmpV = g_YuvTmpV;
if(NULL == g_YuvY || NULL == g_YuvTmpU || NULL == g_YuvTmpV)
{
printf("错误代码:%d\n",ApplyYuv);
return ApplyYuv;
}
for(int i = g_BmpHeight - 1 ; i >= 0 ; i--)
{
for(int j = 0 ; j < g_BmpWidth ; j++)
{
//转换原理还得研究?
*g_YuvY = (unsigned char)
((float)(g_RgbBuf[g_BmpWidth * i * 3 + j * 3]) * 0.3f + //r
(float)(g_RgbBuf[g_BmpWidth * i * 3 + j * 3 +1]) * 0.59f + //g
(float)(g_RgbBuf[g_BmpWidth * i + j * 3 + 2]) * 0.11f); //b
*g_YuvTmpU = (unsigned char)
((float)(g_RgbBuf[g_BmpWidth * i * 3 + j * 3]) / 2 -
(float)(g_RgbBuf[g_BmpWidth * i * 3 + j * 3 + 1]) * 0.4187f -
(float)(g_RgbBuf[g_BmpWidth * i * 3 + j * 3 + 2]) * 0.0813f + 128);
*g_YuvTmpV = (unsigned char)
(-(float)(g_RgbBuf[g_BmpWidth * i * 3 + j * 3]) * 0.1684f -
(float)(g_RgbBuf[g_BmpWidth * i * 3 + j * 3 + 1]) * 0.3316f +
(float)(g_RgbBuf[g_BmpWidth * i * 3 + j * 3 + 2]) / 2 + 128);
g_YuvY++;
g_YuvTmpV++;
g_YuvTmpU++;
}
}
g_YuvY = TmpY;
g_YuvTmpU = TmpU;
g_YuvTmpV = TmpV;
printf("BMP转YUV成功\n");
return 0;
}
- 根据YUV格式再使用上面的采样公式对所有的UV进行对应格式的采样
/**************************************************************************
* 函数名称: FnConvertUv()
* 功能描述: 转换成功的YUV数据进行UV采样
* 输入参数: g_Mode: 0-420格式 2-422格式
* 输出参数: 无
* 返 回 值: 0
* 其它说明: 1.420格式:四个Y公用的 U = 四个Y对应的U之和/4,四个Y公用的 V = 四个Y对应的V之和/4
2.422格式:两个Y公用的 U = 两个Y对应的U之和/2,两个Y公用的 V = 两个Y对应的V之和/2
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
*
**************************************************************************/
int FnConvertUv()
{
unsigned char * TmpU = NULL;
unsigned char * TmpV = NULL;
if( YUV420 == g_Mode )
{
g_YuvU = malloc(g_BmpWidth/2 * g_BmpHeight/2);
g_YuvV = malloc(g_BmpWidth/2 * g_BmpHeight/2);
TmpU = g_YuvU;
TmpV = g_YuvV;
if( NULL == g_YuvU || NULL == g_YuvV)
{
printf("错误代码:%d\n",ApplyUv);
return ApplyUv;
}
//对420UV进行采样
for(int i = 0 ; i < g_BmpHeight ; i+=2)
{
for(int j = 0 ; j < g_BmpWidth ; j+=2)
{
*g_YuvU = (g_YuvTmpU[i * g_BmpWidth + j] +
g_YuvTmpU[i * g_BmpWidth + j + 1] +
g_YuvTmpU[i * g_BmpWidth + j]+
g_YuvTmpU[i * g_BmpWidth + j + 1]) / 4;
*g_YuvV = (g_YuvTmpV[i * g_BmpWidth + j] +
g_YuvTmpV[i * g_BmpWidth + j + 1] +
g_YuvTmpV[i * g_BmpWidth + j]+
g_YuvTmpV[i * g_BmpWidth + j + 1]) / 4;
g_YuvU++;
g_YuvV++;
}
}
}
if( YUV422 == g_Mode )
{
g_YuvU = malloc(g_BmpWidth/2 * g_BmpHeight);
g_YuvV = malloc(g_BmpWidth/2 * g_BmpHeight);
TmpU = g_YuvU;
TmpV = g_YuvV;
if( NULL == g_YuvU || NULL == g_YuvV)
{
printf("错误代码:%d\n",ApplyUv);
return ApplyUv;
}
//对422UV进行采样
for(int i = 0 ; i < g_BmpHeight ; i+=1)
{
for(int j = 0 ; j < g_BmpWidth ; j+=2)
{
*g_YuvU = (g_YuvTmpU[i * g_BmpWidth + j] +
g_YuvTmpU[i * g_BmpWidth + j + 1] ) /2;
*g_YuvV = (g_YuvTmpV[i * g_BmpWidth + j] +
g_YuvTmpV[i * g_BmpWidth + j + 1] ) /2;
g_YuvU++;
g_YuvV++;
}
}
}
g_YuvU = TmpU;
g_YuvV = TmpV;
return 0;
printf("YUV数据转换完成\n");
}
- 创建.yuv文件,并且把YUV写进文件中
/**************************************************************************
* 函数名称: FnMakeYuv()
* 功能描述: 生成YUV格式文件,写入yuv数据
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0
* 其它说明: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
*
**************************************************************************/
int FnMakeYuv(const char * BmpPath)
{
int nRet ;
char PathBuf[1024] = {0};
// printf("bmp_x4 = %d bmp_y4 = %d\n" , bmp_w , bmp_h);
if( YUV420 == g_Mode )
{
int YuvFd = open("./test.yuv" , O_RDWR | O_TRUNC | O_CREAT);
if(YuvFd <= 0)
{
perror("");
printf("错误代码:%d\n",YUVEer);
return YUVEer;
}
nRet = write(YuvFd , g_YuvY , (g_BmpWidth * g_BmpHeight));
if(nRet != (g_BmpWidth * g_BmpHeight))
{
printf("错误代码:%d\n",WiteYuvYEer);
return WiteYuvYEer;
}
nRet = write(YuvFd , g_YuvU , (g_BmpWidth/2 * g_BmpHeight/2));
if(nRet != (g_BmpWidth/2 * g_BmpHeight/2))
{
printf("错误代码:%d\n",WiteYuvUEer);
return WiteYuvUEer;
}
nRet = write(YuvFd , g_YuvV , (g_BmpWidth/2 * g_BmpHeight/2));
if(nRet != (g_BmpWidth/2 * g_BmpHeight/2))
{
printf("错误代码:%d\n",WiteYuvVEer);
return WiteYuvVEer;
}
close(YuvFd); //关闭文件
}
if( YUV422 == g_Mode )
{
int YuvFd = open("./test.yuv" , O_RDWR | O_TRUNC | O_CREAT);
if(YuvFd <= 0)
{
perror("");
printf("错误代码:%d\n",YUVEer);
return YUVEer;
}
nRet = write(YuvFd , g_YuvY , (g_BmpWidth * g_BmpHeight));
if(nRet != (g_BmpWidth * g_BmpHeight))
{
printf("错误代码:%d\n",WiteYuvYEer);
return WiteYuvYEer;
}
nRet = write(YuvFd , g_YuvU , (g_BmpWidth/2 * g_BmpHeight));
if(nRet != (g_BmpWidth/2 * g_BmpHeight))
{
printf("错误代码:%d\n",WiteYuvUEer);
return WiteYuvUEer;
}
nRet = write(YuvFd , g_YuvV , (g_BmpWidth/2 * g_BmpHeight));
if(nRet != (g_BmpWidth/2 * g_BmpHeight))
{
printf("错误代码:%d\n",WiteYuvVEer);
return WiteYuvVEer;
}
close(YuvFd); //关闭文件
}
printf("YUV文件生成完成\n");
return 0;
}
4.成果展示
以420格式为例: