bmp转换为YUV420p指南

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

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值