bmp转换为YUV420p指南(Linux):
一、如何获得一张YUV图片
(1)在网络上下载一张随便的jpg图片或者bmp图片
使用画图软件打开,并且设置成640*480大小的
(2)在Linux下安装ffmpeg
apt install ffmpeg
(3)使用ffmpeg转换jpg为YUV格式
ffmpeg -i 1.jpg -s 640x480 -pix_fmt yuv420p test.yuv
二、如何查看YUV图片
利用ffmpeg自带的图片查看器ffplay:
ffplay -video_size 640x480 test.yuv
三、YUV420格式
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分量的字节数
而U和V所占的字节数相同:640x480/4
YUV420P在屏幕像素上的分布表示:
每四个Y使用同一组U和V。共用UV的方式如上图
四个Y公用的 U = 四个Y对应的U之和/4
四个Y公用的 V = 四个Y对应的V之和/4
上面两条公式就是YUV420P的采样公式
四、RGB与YUV互相转化公式
一个像素的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)
对于420p格式的YUV图片:RGB转换YUV以后,得到的U和V还需要进行一次采样转化,转化方式为每四个点公用一个U和V,公用的方式如上面的YUV420示意图。
五、RGB转换为YUV420:
在这里我们以bmp(24位位图)图片格式为例:
1.首先获得全部的RGB像素点(包含处理bmp数据头)
有关bmp数据头部感兴趣的自己上百度找
不感兴趣的直接用C库函数 leesk函数跳过头部54个字节
由于是24位bmp,RGB像素点不需要任何修改,直接读取即可,存放在rgb_buf中
![bmp示意图](C:\Users\A\Desktop\工作\md图片\bmp示意图.png)int fn_Open_Bmp(const char * bmp_path)
{
int bmp_fd = open(bmp_path , O_RDWR);
if(bmp_fd <= 0)
{
perror("");
return -1;
}
//为头信息申请空间
struct bitmap_header * head_info = malloc(14);
struct bitmap_info * bmp_info = malloc(40);
if(head_info == NULL || bmp_info == NULL)
{
printf("申请bmp头信息空间失败\n");
return -1;
}
//读取bmp的头信息
read(bmp_fd , head_info , 14);
read(bmp_fd , bmp_info , 40);
bmp_w = bmp_info->width;
bmp_h = bmp_info->height;
printf("bmp_x = %d bmp_y = %d\n" , bmp_w , bmp_h);
//读取RGB信息
rgb_buf = malloc(bmp_w * bmp_h * 3);
if(rgb_buf == NULL)
{
printf("申请bmp_rgb空间失败\n");
return -1;
}
unsigned int ret = read(bmp_fd , rgb_buf , bmp_w * bmp_h * 3);
if(ret != bmp_w * bmp_h * 3)
{
printf("读取bmp_rgb失败\n");
return -1;
}
free(bmp_info);
free(head_info);
close(bmp_fd);
return 0;
}
2.根据RGB转换所有像素点的YUV
bmp的存储形式:
int fn_Convert_Yuv()
{
unsigned char * tmp_y = NULL;
unsigned char * tmp_u = NULL;
unsigned char * tmp_v = NULL;
//申请空间
yuv_y = malloc(bmp_w * bmp_h);
//yuv_tmp_u和yuv_tmp_v保存所有的U,V数据,已备后面进行采样
yuv_tmp_u = malloc(bmp_w * bmp_h);
yuv_tmp_v = malloc(bmp_w * bmp_h);
//由于yuv_y等参数需要进行地址加,所以这里使用tmp_y等保存最开始的地址
tmp_y = yuv_y;
tmp_u = yuv_tmp_u;
tmp_v = yuv_tmp_v;
if(yuv_y == NULL || yuv_tmp_u == NULL || yuv_tmp_v == NULL)
{
printf("申请yuv空间失败\n");
return -1;
}
//由于bmp图片存放的格式是倒叙,所以int i = bmp_h - 1是在bmp图片从下往上,从左往右转换YUV
//遍历所有的像素点进行转换
for(int i = bmp_h - 1 ; i >= 0 ; i--)
{
for(int j = 0 ; j < bmp_w ; j++)
{
*yuv_y = (unsigned char)
((float)(rgb_buf[bmp_w * i * 3 + j * 3]) * 0.3f +
(float)(rgb_buf[bmp_w * i * 3 + j * 3 +1]) * 0.59f +
(float)(rgb_buf[bmp_w * i + j * 3 + 2]) * 0.11f);
*yuv_tmp_u = (unsigned char)
((float)(rgb_buf[bmp_w * i * 3 + j * 3]) / 2 -
(float)(rgb_buf[bmp_w * i * 3 + j * 3 + 1]) * 0.4187f -
(float)(rgb_buf[bmp_w * i * 3 + j * 3 + 2]) * 0.0813f + 128);
*yuv_tmp_v = (unsigned char)
(-(float)(rgb_buf[bmp_w * i * 3 + j * 3]) * 0.1684f -
(float)(rgb_buf[bmp_w * i * 3 + j * 3 + 1]) * 0.3316f +
(float)(rgb_buf[bmp_w * i * 3 + j * 3 + 2]) / 2 + 128);
yuv_y++;
yuv_tmp_v++;
yuv_tmp_u++;
}
}
//指针偏移为开始位置
yuv_y = tmp_y;
yuv_tmp_u = tmp_u;
yuv_tmp_v = tmp_v;
printf("转yuv成功\n");
return 0;
}
3.根据YUV420格式再使用上面的采样公式对所有的UV进行YUV420格式的采样
采样公式:
四个Y公用的 U = 四个Y对应的U之和/4
四个Y公用的 V = 四个Y对应的V之和/4
void fn_Convert_Uv()
{
unsigned char * tmp_u = NULL;
unsigned char * tmp_v = NULL;
yuv_u = malloc(bmp_w/2 * bmp_h/2);
yuv_v = malloc(bmp_w/2 * bmp_h/2);
tmp_u = yuv_u;
tmp_v = yuv_v;
if(yuv_u == NULL || yuv_v == NULL)
{
printf("申请uv空间失败\n");
return;
}
//对根据yuv_tmp_u和yuv_tmp_v,对UV进行采样,转换的数据保存在yuv_u和yuv_v中
for(int i = 0 ; i < bmp_h ; i+=2)
{
for(int j = 0 ; j < bmp_w ; j+=2)
{
*yuv_u = ((yuv_tmp_u[i * bmp_w + j] +
yuv_tmp_u[i * bmp_w + j + 1] +
yuv_tmp_u[(i) * bmp_w + j]+
yuv_tmp_u[(i) * bmp_w + j + 1])) / 4;
*yuv_v = ((yuv_tmp_v[i * bmp_w + j] +
yuv_tmp_v[i * bmp_w + j + 1] +
yuv_tmp_v[(i) * bmp_w + j]+
yuv_tmp_v[(i) * bmp_w + j + 1])) / 4;
yuv_v++;
yuv_u++;
}
}
yuv_u = tmp_u;
yuv_v = tmp_v;
//对y、u、v 信号进行抗噪处理(可以不要)
for (int i=0;i<bmp_w * bmp_h ;i++)
{
if(yuv_y[i]<16)
yuv_y[i] = 16;
if(yuv_y[i]>235)
yuv_y[i] = 235;
}
for(int i=0;i<bmp_w * bmp_h/4;i++)
{
if(yuv_u[i]<16)
yuv_u[i] = 16;
if(yuv_v[i]<16)
yuv_v[i] = 16;
if(yuv_u[i]>240)
yuv_u[i] = 240;
if(yuv_v[i]>240)
yuv_v[i] = 240;
}
printf("转uv成功\n");
return;
}
4.创建.yuv文件,并且把YUV写进文件中
void fn_Make_Yuv(const char * bmp_path)
{
char path_buf[1024] = {0};
int yuv_fd = open("./test.yuv" , O_RDWR | O_TRUNC | O_CREAT);
if(yuv_fd <= 0)
{
perror("");
return;
}
write(yuv_fd , yuv_y , bmp_w * bmp_h);
write(yuv_fd , yuv_u , bmp_w/2 * bmp_h/2);
write(yuv_fd , yuv_v , bmp_w/2 * bmp_h/2);
}
六、完整代码
把这些代码都放在同一个文件夹里面,然后编译
把需要转换的bmp图片作为主函数传参就行(别的格式不行)
main.c:
#include "BMP_TO_YUV.h"
//test main
int main(int argc , char ** argv)
{
if(argc < 1)
{
printf("请输入图片路径\n");
}
fn_Bmp_To_Yuv(argv[1]);
}
BMP_TO_YUV.c:
#include "BMP_TO_YUV.h"
int fn_Bmp_To_Yuv(const char * bmp_path)
{
//1.打开bmp
if(fn_Open_Bmp(bmp_path) != 0)
{
return -1;
}
else
{
printf("打开bmp成功\n");
}
//2.转换yuv
fn_Convert_Yuv();
//3.对uv进行采样
fn_Convert_Uv();
//4.创建yuv文件并写入像素点
fn_Make_Yuv(bmp_path);
printf("yuv_y len = %ld\n" , strlen(yuv_y));
printf("yuv_u len = %ld\n" , strlen(yuv_u));
printf("yuv_v len = %ld\n" , strlen(yuv_v));
system("ffplay -video_size 640x480 -pixel_format yuv420p test.yuv");
free(yuv_y);
free(yuv_u);
free(yuv_v);
free(yuv_tmp_u);
free(yuv_tmp_v);
return 0;
}
/*
打开bmp
处理bmp的头信息
并跳过bmp的头54个字节
*/
int fn_Open_Bmp(const char * bmp_path)
{
int bmp_fd = open(bmp_path , O_RDWR);
if(bmp_fd <= 0)
{
perror("");
return -1;
}
struct bitmap_header * head_info = malloc(14);
struct bitmap_info * bmp_info = malloc(40);
if(head_info == NULL || bmp_info == NULL)
{
printf("申请bmp头信息空间失败\n");
return -1;
}
//读取bmp的头信息
read(bmp_fd , head_info , 14);
read(bmp_fd , bmp_info , 40);
bmp_w = bmp_info->width;
bmp_h = bmp_info->height;
printf("bmp_x = %d bmp_y = %d\n" , bmp_w , bmp_h);
//读取RGB信息
rgb_buf = malloc(bmp_w * bmp_h * 3);
if(rgb_buf == NULL)
{
printf("申请bmp_rgb空间失败\n");
return -1;
}
unsigned int ret = read(bmp_fd , rgb_buf , bmp_w * bmp_h * 3);
if(ret != bmp_w * bmp_h * 3)
{
printf("读取bmp_rgb失败\n");
return -1;
}
free(bmp_info);
free(head_info);
close(bmp_fd);
return 0;
}
int fn_Convert_Yuv()
{
unsigned char * tmp_y = NULL;
unsigned char * tmp_u = NULL;
unsigned char * tmp_v = NULL;
yuv_y = malloc(bmp_w * bmp_h);
yuv_tmp_u = malloc(bmp_w * bmp_h);
yuv_tmp_v = malloc(bmp_w * bmp_h);
//由于yuv_y等参数需要进行地址加,所以这里使用tmp_y等保存最开始的地址
tmp_y = yuv_y;
tmp_u = yuv_tmp_u;
tmp_v = yuv_tmp_v;
if(yuv_y == NULL || yuv_tmp_u == NULL || yuv_tmp_v == NULL)
{
printf("申请yuv空间失败\n");
return -1;
}
for(int i = bmp_h - 1 ; i >= 0 ; i--)
{
for(int j = 0 ; j < bmp_w ; j++)
{
*yuv_y = (unsigned char)
((float)(rgb_buf[bmp_w * i * 3 + j * 3]) * 0.3f +
(float)(rgb_buf[bmp_w * i * 3 + j * 3 +1]) * 0.59f +
(float)(rgb_buf[bmp_w * i + j * 3 + 2]) * 0.11f);
*yuv_tmp_u = (unsigned char)
((float)(rgb_buf[bmp_w * i * 3 + j * 3]) / 2 -
(float)(rgb_buf[bmp_w * i * 3 + j * 3 + 1]) * 0.4187f -
(float)(rgb_buf[bmp_w * i * 3 + j * 3 + 2]) * 0.0813f + 128);
*yuv_tmp_v = (unsigned char)
(-(float)(rgb_buf[bmp_w * i * 3 + j * 3]) * 0.1684f -
(float)(rgb_buf[bmp_w * i * 3 + j * 3 + 1]) * 0.3316f +
(float)(rgb_buf[bmp_w * i * 3 + j * 3 + 2]) / 2 + 128);
yuv_y++;
yuv_tmp_v++;
yuv_tmp_u++;
}
}
yuv_y = tmp_y;
yuv_tmp_u = tmp_u;
yuv_tmp_v = tmp_v;
printf("RGB转yuv成功\n");
}
void fn_Convert_Uv()
{
unsigned char * tmp_u = NULL;
unsigned char * tmp_v = NULL;
yuv_u = malloc(bmp_w/2 * bmp_h/2);
yuv_v = malloc(bmp_w/2 * bmp_h/2);
tmp_u = yuv_u;
tmp_v = yuv_v;
if(yuv_u == NULL || yuv_v == NULL)
{
printf("申请uv空间失败\n");
return;
}
//对UV进行采样
for(int i = 0 ; i < bmp_h ; i+=2)
{
for(int j = 0 ; j < bmp_w ; j+=2)
{
*yuv_u = ((yuv_tmp_u[i * bmp_w + j] +
yuv_tmp_u[i * bmp_w + j + 1] +
yuv_tmp_u[(i) * bmp_w + j]+
yuv_tmp_u[(i) * bmp_w + j + 1])) / 4;
*yuv_v = ((yuv_tmp_v[i * bmp_w + j] +
yuv_tmp_v[i * bmp_w + j + 1] +
yuv_tmp_v[(i) * bmp_w + j]+
yuv_tmp_v[(i) * bmp_w + j + 1])) / 4;
yuv_v++;
yuv_u++;
}
}
yuv_u = tmp_u;
yuv_v = tmp_v;
//对y、u、v 信号进行抗噪处理
for (int i=0;i<bmp_w * bmp_h ;i++)
{
if(yuv_y[i]<16)
yuv_y[i] = 16;
if(yuv_y[i]>235)
yuv_y[i] = 235;
}
for(int i=0;i<bmp_w * bmp_h/4;i++)
{
if(yuv_u[i]<16)
yuv_u[i] = 16;
if(yuv_v[i]<16)
yuv_v[i] = 16;
if(yuv_u[i]>240)
yuv_u[i] = 240;
if(yuv_v[i]>240)
yuv_v[i] = 240;
}
printf("转uv成功\n");
}
void fn_Make_Yuv(const char * bmp_path)
{
char path_buf[1024] = {0};
int yuv_fd = open("./test.yuv" , O_RDWR | O_TRUNC | O_CREAT);
if(yuv_fd <= 0)
{
perror("");
return;
}
write(yuv_fd , yuv_y , bmp_w * bmp_h);
write(yuv_fd , yuv_u , bmp_w/2 * bmp_h/2);
write(yuv_fd , yuv_v , bmp_w/2 * bmp_h/2);
}
char * get_name(const char * path)
{
char tmp_path[1024] = {0};
char * tmp_p = malloc(1024);
char * p = NULL;
strcpy(tmp_path , path);
p = strtok(tmp_path , "/");
strcpy(tmp_p , p);
while(1)
{
p = strtok(NULL , "/");
if(p == NULL)
{
break;
}
else
{
strcpy(tmp_p , p);
}
}
return tmp_p;
}
BMP_TO_YUV.h:
#ifndef BMP_TO_YUV_H
#define BMP_TO_YUV_H
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int fn_Bmp_To_Yuv(const char * bmp_path);
int fn_Open_Bmp(const char * bmp_path);
int fn_Convert_Yuv();
void fn_Convert_Uv();
void fn_Make_Yuv();
char * get_name(const char * path);
int bmp_w;
int bmp_h;
unsigned char * rgb_buf;
unsigned char * yuv_y;
unsigned char * yuv_tmp_u;
unsigned char * yuv_tmp_v;
unsigned char * yuv_u;
unsigned char * yuv_v;
unsigned char * y_out;
unsigned char * u_out;
unsigned char * v_out;
//bmp头文件信息结构体
struct bitmap_header//文件头 -->14个字节
{
unsigned short type; //文件类型,必须为BM
unsigned int size; // 位图文件大小
unsigned short reserved1; //预留位
unsigned short reserved2; //预留位
unsigned int offbits; // bmp图像文件头数据偏移量
}__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;//位图实际使用的颜色表中的颜色数
unsigned int clrImportant;//位图显示过程中重要的颜色数
}__attribute__((packed));
#endif