YUV420P转BMP指南
一、YUV像素点转换为RGB公式
R = Y + 1.403 * (U - 128)
G = Y - 0.343 * (V - 128) - 0.714 * (U - 128)
B = Y + 1.770 * (V - 128)
同样值得注意的是:网上的公式依然是反过来的(网上对应V的地方是U,U的地方是V)
二、从YUV420P格式到BMP转换算法
由上图可得,yuv420格式是每四个Y公用一对UV,所以U和V的数量都为Y的四分之一
而:下图则是YUV420在图像文件中的存储形式,首先存储所有的Y像素点,接着是U,最后是V
转换过程可以理解为首先从yuv文件中取得所有的y,u,v分别放在对应的数组yuv_y,yuv_u , yuv_v中。由于Y像素点的总数与实际像素是对应的,但是需要确定这些Y使用的是哪组的u,v。根据转换公式获得的rgb像素点按顺序存储到bmp文件中即可。
转换图示:
由于y的个数跟像素点个数相同,所以这里不必管y。
用yuv_y的行下标/2 * (yuv宽 / 2) + yuv_y的列下标/2,即可获得某一个y使用的是那一组的u和v
由于bmp图片是倒序存放,所以算法还应该变为:
用(yuv高 - yuv_y的行下标 - 1)/ 2 * (yuv宽 / 2) + yuv_y的列下标 / 2
三、代码
(1)main函数:注意主函数传参需要传入与bmp相同的宽和高
#include "YUV_TO_BMP.h"
int main(int argc , char **argv)
{
if(argc < 3)
{
printf("请输入图片路径\n");
return 0;
}
fn_Yuv_To_Bmp(argv[1] , atoi(argv[2]) , atoi(argv[3]));
return 0;
}
(2)YUV_TO_BMP.c
#include "YUV_TO_BMP.h"
int fn_Yuv_To_Bmp(const char * yuv_path , int width , int hight)
{
yuv_width = width;
yuv_hight = hight;
//1.打开yuv文件,并获取Y,U,V分别放在不同的数组中
int ret = fn_Open_Yuv(yuv_path);
if(ret < 0)
{
printf("获取yuv像素点失败\n");
return -1;
}
else
{
printf("打开yuv成功\n");
}
//2.转yuv为rgb
ret = fn_Convert_Yuv();
if(ret < 0)
{
printf("申请rgb空间失败\n");
return -1;
}
else
{
printf("rgb像素点转换成功\n");
}
//3.创建bmp文件,处理文件头,并把rgb放进bmp里面
ret = fn_Make_Bmp();
if(ret < 0)
{
printf("创建bmp文件失败\n");
return -1;
}
else
{
printf("YUV转BMP成功\n");
}
return 0;
}
int fn_Make_Bmp()
{
int bmp_fd = open("./test.bmp" , O_RDWR | O_TRUNC | O_CREAT , 0777);
if(bmp_fd <= 0)
{
perror("");
return -1;
}
//处理bmp数据头
struct bitmap_header head_info;
memset(&head_info , 0 , 14);
head_info.type = BM;
head_info.size = yuv_hight * yuv_width * 3 + 54;
head_info.offbits = 54;
struct bitmap_info bmp_info;
memset(&bmp_info , 0 , 40);
bmp_info.size = 40;
bmp_info.width = yuv_width;
bmp_info.height = yuv_hight;
bmp_info.planes = 1;
bmp_info.bit_count = 24;
bmp_info.size_img = yuv_hight * yuv_width * 3;
bmp_info.X_pel = yuv_width;
bmp_info.Y_pel = yuv_hight;
//把数据头,像素点写入bmp文件中
write(bmp_fd , &head_info , 14);
write(bmp_fd , &bmp_info , 40);
int ret = write(bmp_fd , rgb_buf , yuv_hight * yuv_width * 3);
printf("ret = %d\n" , ret);
return 0;
}
int fn_Convert_Yuv()
{
rgb_buf = malloc(yuv_width * yuv_hight * 3);
if(rgb_buf == NULL)
{
return -1;
}
int i = 0;
int j = 0;
for(i = 0 ; i < yuv_hight ; i++)
{
for(j = 0 ; j < yuv_width ; j++)
{
//r
rgb_buf[i * yuv_width * 3 + j * 3] = (unsigned char)
((float)yuv_y[(yuv_hight - i - 1) * yuv_width + j] +
1.403 * ((float)yuv_u[(((yuv_hight - i - 1) / 2) * (yuv_width / 2) + (j / 2))] - 128));
if(rgb_buf[i * yuv_width * 3 + j * 3] < 0)
{
rgb_buf[i * yuv_width * 3 + j * 3] = 0;
}
else if(rgb_buf[i * yuv_width * 3 + j * 3] > 255)
{
rgb_buf[i * yuv_width * 3 + j * 3] = 255;
}
//g
rgb_buf[i * yuv_width * 3 + j * 3 + 1] = (unsigned char)
((float)yuv_y[(yuv_hight - i - 1) * yuv_width + j] -
0.3455 * ((float)yuv_v[(((yuv_hight - i - 1) / 2) * (yuv_width / 2) + (j / 2))] - 128) -
0.7169 * ((float)yuv_u[(((yuv_hight - i - 1) / 2) * (yuv_width / 2) + (j / 2))] - 128));
if(rgb_buf[i * yuv_width * 3 + j * 3 + 1] < 0)
{
rgb_buf[i * yuv_width * 3 + j * 3 + 1] = 0;
}
else if(rgb_buf[i * yuv_width * 3 + j * 3 + 1] > 255)
{
rgb_buf[i * yuv_width * 3 + j * 3 + 1] = 255;
}
//b
rgb_buf[i * yuv_width * 3 + j * 3 + 2] = (unsigned char)
((float)yuv_y[(yuv_hight - i - 1) * yuv_width + j] +
1.779 * ((float)yuv_v[(((yuv_hight - i - 1) / 2) * (yuv_width / 2) + (j / 2))] - 128));
if(rgb_buf[i * yuv_width * 3 + j * 3 + 2] < 0)
{
rgb_buf[i * yuv_width * 3 + j * 3 + 2] = 0;
}
else if(rgb_buf[i * yuv_width * 3 + j * 3 + 2] > 255)
{
rgb_buf[i * yuv_width * 3 + j * 3 + 2] = 255;
}
}
}
return 0;
}
int fn_Open_Yuv(const char * yuv_path)
{
int yuv_fd = open(yuv_path , O_RDWR);
if(yuv_fd <= 0)
{
perror("");
return -1;
}
yuv_y = malloc(yuv_width * yuv_hight);
yuv_u = malloc(yuv_width / 2 * yuv_hight / 2);
yuv_v = malloc(yuv_width / 2 * yuv_hight / 2);
if(yuv_y == NULL || yuv_u == NULL || yuv_v == NULL)
{
printf("申请yuv空间失败\n");
return -1;
}
//读取yuv数据
int ret = 0;
ret = read(yuv_fd , yuv_y , yuv_width * yuv_hight);
if(ret != yuv_width * yuv_hight)
{
printf("读取y数据失败\n");
return -1;
}
ret = read(yuv_fd , yuv_u , yuv_width / 2 * yuv_hight / 2);
if(ret != yuv_width / 2 * yuv_hight / 2)
{
printf("读取u数据失败\n");
return -1;
}
ret = read(yuv_fd , yuv_v , yuv_width / 2 * yuv_hight / 2);
if(ret != yuv_width / 2 * yuv_hight / 2)
{
printf("读取v数据失败\n");
return -1;
}
close(yuv_fd);
return 0;
}
(3)YUV_TO_BMP.h(头文件中包含了bmp文件头结构体)
#ifndef YUV_TO_BMP_H
#define YUV_TO_BMP_H
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define BM 19778
unsigned char * yuv_y;
unsigned char * yuv_u;
unsigned char * yuv_v;
unsigned char * rgb_buf;
int yuv_width;
int yuv_hight;
int fn_Yuv_To_Bmp(const char * yuv_path , int width , int hight);
int fn_Open_Yuv(const char * yuv_path);
int fn_Convert_Yuv();
int fn_Make_Bmp();
//bmp头文件信息结构体
struct bitmap_header//文件头 -->14个字节
{
unsigned short type; //文件类型,必须为BM
unsigned int size; // 位图文件大小
unsigned short reserved1; //预留位
unsigned short reserved2; //预留位
unsigned int offbits; // bmp图像文件头数据偏移量(填54)
}__attribute__((packed));//--》忽略该结构体地址对齐
struct bitmap_info//像素头 --》40个字节
{
unsigned int size; // 本结构大小
unsigned int width; //像素点宽度
unsigned int height; //像素点高度
unsigned short planes;//目标设备的级别,必须为1
unsigned short bit_count; // 色深每个像素点所占的位数24bit
unsigned int compression; //是否压缩,0表示不压缩
unsigned int size_img; // bmp数据大小,必须是4的整数倍
unsigned int X_pel;//位图水平分辨率
unsigned int Y_pel;//位图垂直分辨率
unsigned int clrused;//位图实际使用的颜色表中的颜色数(24位位图 = 0)
unsigned int clrImportant;//位图显示过程中重要的颜色数(24位位图 = 0)
}__attribute__((packed));
#endif