YCbCr420(YUV420)和bmp24图像格式的转换 - Fla

YCbCr420(YUV420)和bmp24图像格式的转换

Fla posted @ 2013年4月17日 14:37 in Linux/Ubuntu with tags rgb HEVC YUV YCbCr , 6464 阅读

在视频编码的时候,原始序列一般为YUV序列,可惜目前好用的工具不支持单帧图像的导出,于是我写了这样一个命令行程序。

用来进行两者的转换。

YUV420图像的格式可以参考这篇文章,http://shellman.blog.sohu.com/139831864.html

YUV到RGB之间的相互转换,我遵从的是这篇文章:http://www.douban.com/note/76361504/

// yuv<-->rgb

Y''= 0.299*R'' + 0.587*G'' + 0.114*B''
U''= -0.147*R'' - 0.289*G'' + 0.436*B'' = 0.492*(B''- Y'')
V''= 0.615*R'' - 0.515*G'' - 0.100*B'' = 0.877*(R''- Y'')

R'' = Y'' + 1.140*V''
G'' = Y'' - 0.394*U'' - 0.581*V''
B'' = Y'' + 2.032*U''

// yCbCr<-->rgb

Y’ = 0.257*R' + 0.504*G' + 0.098*B' + 16
Cb' = -0.148*R' - 0.291*G' + 0.439*B' + 128
Cr' = 0.439*R' - 0.368*G' - 0.071*B' + 128

R' = 1.164*(Y’-16) + 1.596*(Cr'-128)
G' = 1.164*(Y’-16) - 0.813*(Cr'-128) - 0.392*(Cb'-128)
B' = 1.164*(Y’-16) + 2.017*(Cb'-128)

// Note: 上面各个符号都带了一撇,表示该符号在原值基础上进行了gamma correction

下面是我的源代码,可以在windows上用VC编译运行即可。

// ConvRGB24YCbCr420.cpp : 定义控制台应用程序的入口点。
//

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <iostream>
#include <fstream>
using namespace std;

typedef unsigned char byte;
typedef signed int Int;
typedef unsigned int UInt;
typedef signed short Pel;

// RGB 转 YCbCr 的系数
#define RGB2YUV_SHIFT 16
#define BY ((int)( 0.098*(1<<RGB2YUV_SHIFT)+0.5))
#define BV ((int)(-0.071*(1<<RGB2YUV_SHIFT)+0.5))
#define BU ((int)( 0.439*(1<<RGB2YUV_SHIFT)+0.5))
#define GY ((int)( 0.504*(1<<RGB2YUV_SHIFT)+0.5))
#define GV ((int)(-0.368*(1<<RGB2YUV_SHIFT)+0.5))
#define GU ((int)(-0.291*(1<<RGB2YUV_SHIFT)+0.5))
#define RY ((int)( 0.257*(1<<RGB2YUV_SHIFT)+0.5))
#define RV ((int)( 0.439*(1<<RGB2YUV_SHIFT)+0.5))
#define RU ((int)(-0.148*(1<<RGB2YUV_SHIFT)+0.5))

inline byte clip(const Int x)
{
  return x > 255 ? 255 : ( x < 0 ? 0 : x );
}

Int GetY(const byte *p)
{
  return Int(1.164 * (*p-16) + 0.5);
}
// pcPicYuv := pcPic->getPicYuvRec()
// pRGB := RGB颜色的起始地址
static void YCbCr420toRGB(const byte *y, const byte *u, const byte *v, FILE *fp, int width, int height, int stride)
{
  UInt dRGB = width * 3;		// bmp 一行的某个像素的下一行同样位置的像素值偏移
  byte *rgbBuf = (byte *)malloc(dRGB * 2);
  UInt dY = stride;	// YUV 一行的一个y值对应下一行的同样位置的位置增量
  const Int dY2 = dY + 1, dRGB2 = dRGB + 3;
  UInt d2Y = 2 * dY - width;	// YUV Y像素偏移值的修正
  UInt dUV = (stride - width) / 2;	// YUV UV像素偏移值的修正
  Int dB = 0, dG = 0, dR = 0, i = 0, j = 0;
  height = height >> 1 ;	width = width >> 1 ;	// 行数和列数均减半
  for ( j = 0; j < height; j++ )
  {
    byte *pRGB = rgbBuf;
    for ( i = 0; i < width; i++ )
    {
      // RGB_888: 三个像素值依次为  RGB
      // YCbCr 转 RGB 的系数
      Int Cb = *u- 128,  Cr = *v - 128;
      dR = 1.596 * Cr;
      dG = -0.813 * Cr - 0.392 * Cb;
      dB = 2.017 * Cb;
      Int y0 = GetY(y), y1 = GetY(y+1);
      Int y2 = GetY(y+dY), y3 = GetY(y+dY2);
      *(  pRGB) = clip(y0 + dB);		// B
      *(pRGB+ 3 ) = clip(y1 + dB);
      *(pRGB+dRGB) = clip(y2 + dB);
      *(pRGB+dRGB2) = clip(y3 + dB);
      *(++pRGB) = clip(y0 + dG);		// G
      *(pRGB+ 3 ) = clip(y1 + dG);
      *(pRGB+dRGB) = clip(y2 + dG);
      *(pRGB+dRGB2) = clip(y3 + dG);
      *(++pRGB) = clip(y0 + dR);		// R
      *(pRGB+ 3 ) = clip(y1 + dR);
      *(pRGB+dRGB) = clip(y2 + dR);
      *(pRGB+dRGB2) = clip(y3 + dR);

      pRGB += 4;	// 总计两个RGBA像素,指针需要移动 6 个字节,补齐
      // 初始化中已经对 A 赋值,故此处不再处理 2013-3-24
      y+=2;
      u++;	v++;	// 横向两个点才变化一次,因为其表示了 2 * 2个点的像素值
    }	// for loop (i < width)
    fwrite( rgbBuf, 2, dRGB, fp );
    fflush(fp);
    y += d2Y;		// y 像素值的偏移位置修正(两行)
    u += dUV;		// u 像素值偏移的修正
    v += dUV;		// v 像素值偏移的修正
  }	// for loop (j < height)
}

void rgb24toyuv420(const unsigned char *src, unsigned char *ydst, unsigned char *udst,
  unsigned char *vdst,unsigned int width, unsigned int height,unsigned int lumStride,
  unsigned int chromStride, unsigned int srcStride)
{
  unsigned int y;
  const unsigned int chromWidth = width>>1;

  y=0;
  for(; y<height; y+=2)
  {
    unsigned int i;
    for(i=0; i<chromWidth; i++)
    {
      unsigned int b= src[6*i+0];
      unsigned int g= src[6*i+1];
      unsigned int r= src[6*i+2];

      unsigned int Y  =  ((RY*r + GY*g + BY*b)>>RGB2YUV_SHIFT) + 16;
      unsigned int U  =  ((RU*r + GU*g + BU*b)>>RGB2YUV_SHIFT) + 128;

      udst[i] 	= (U);
      ydst[2*i] 	= (Y);    // 一行的两个点

      b= src[6*i+3];
      g= src[6*i+4];
      r= src[6*i+5];

      Y  =  ((RY*r + GY*g + BY*b)>>RGB2YUV_SHIFT) + 16;
      ydst[2*i+1] 	= (Y);  // 一行的第二个点
    }
    ydst += lumStride;    // 下一行
    src  += srcStride;    // 到下一行

    for(i=0; i<chromWidth; i++)
    {
      unsigned int b= src[6*i+0];
      unsigned int g= src[6*i+1];
      unsigned int r= src[6*i+2];

      unsigned int Y  =  ((RY*r + GY*g + BY*b)>>RGB2YUV_SHIFT) + 16;
      unsigned int V  =  ((RV*r + GV*g + BV*b)>>RGB2YUV_SHIFT) + 128;

      vdst[i] 	= (V);
      ydst[2*i] 	= (Y);  // 下一行的第一个亮度点

      b= src[6*i+3];
      g= src[6*i+4];
      r= src[6*i+5];

      Y  =  ((RY*r + GY*g + BY*b)>>RGB2YUV_SHIFT) + 16;
      ydst[2*i+1] 	= (Y);  // 下一行的第二个亮度点
    }
    udst += chromStride;
    vdst += chromStride;
    ydst += lumStride;
    src  += srcStride;
  }
}

int HeadStand(unsigned char *pBuf,int pWidth,int pHeight)
{
  unsigned char *tBuf;
  int i;

  if ((tBuf = (unsigned char *)malloc(pWidth*pHeight*3)) == NULL)
    return -1;
  for (i=0;i<pHeight/2;i++)
  {
    memcpy(tBuf,pBuf+i*pWidth*3,pWidth*3);
    memcpy(pBuf+i*pWidth*3,pBuf+(pHeight-i-1)*pWidth*3,pWidth*3);
    memcpy(pBuf+(pHeight-i-1)*pWidth*3,tBuf,pWidth*3);
  }
  free(tBuf);
  return 0;
}

void ConvRGB242YCbCr(const char *input, const char *output)
{
  char    tDesPathName[100];
  FILE *fp;
  unsigned char *rgbBuf,*ty,*tu,*tv;
  int width=0,height=0,iSizeImage = 0;
  BITMAPFILEHEADER bitmap;
  BITMAPINFOHEADER bitmapHeader;
  int i,StrLen,SelYUVFormat;
  int ySize,uvSize;

  //检测输入是不是合法

  fp = fopen(input,"rb");
  if(fp == NULL)
  {
    cerr << "\n错误的输入文件路径(无法打开文件):" << input << endl;
    return;
  }

  //得到bitmap头部信息,包括文件头和bitmap头
  memset(&(bitmap),0,sizeof(bitmap));
  memset(&(bitmapHeader),0,sizeof(bitmapHeader));
  fread(&bitmap,sizeof(char),sizeof(bitmap),fp);
  fread(&bitmapHeader,sizeof(char),sizeof(bitmapHeader),fp);

  //分配空间
  width = bitmapHeader.biWidth;
  height = bitmapHeader.biHeight;
  if((width%2 != 0) || (height%2 != 0))
  {
    cerr << "\n图像的分辨率必须是偶数! width = " << width << " height = " << height << endl;
    return;
  }
  //	iSizeImage = bitmapHeader.biSizeImage;
  iSizeImage = width*height*3;
  rgbBuf = (unsigned char *)malloc(iSizeImage);
  ySize = iSizeImage/3;
  ty = (unsigned char *)malloc(ySize);
  uvSize = ySize >> 2;
  tu = (unsigned char *)malloc(uvSize);
  tv = (unsigned char *)malloc(uvSize);
  fseek(fp,bitmap.bfOffBits,SEEK_SET);
  if(rgbBuf == NULL || ty == NULL || tu == NULL || tv == NULL )
  {
    cerr << "\n内存分配失败。iSizeImage = " << iSizeImage << " ySize = " << ySize << " uvSize = " << uvSize << endl;
    return;
  }
  fread(rgbBuf,sizeof(char),iSizeImage,fp);
  HeadStand(rgbBuf,width,height);
  rgb24toyuv420(rgbBuf,ty,tu,tv,width,height,width, width/2,width*3);   // 这里应该 是 width / 2,不是 width/4
  fclose(fp);

  fp = fopen(output,"wb");
  if(fp == NULL)
  {
    cerr << "\n错误的输出路径,无法创建指定文件:" << output << endl;
    return;
  }
  fwrite(ty,sizeof(char),ySize,fp);
  fwrite(tu,sizeof(char),uvSize,fp);
  fwrite(tv,sizeof(char),uvSize,fp);
  fclose(fp);
  free(rgbBuf);
  free(ty);
  free(tu);
  free(tv);

  system("echo bmp24 to YCbCr420 转换完成在 %time%");
}

void ConvYCbCr420RGB24(const char *input, const char *output, const int width, const int height)
{
  char    tDesPathName[100];
  FILE *fp;
  unsigned char *rgbBuf,*ty,*tu,*tv;
  int iSizeImage = 0;
  BITMAPFILEHEADER bitmap;
  BITMAPINFOHEADER bitmapHeader;
  char tStr1[100],tStr2[100];
  int i,StrLen,SelYUVFormat;
  int ySize,uSize,vSize;

  fp = fopen( input, "rb");
  if(fp == NULL) {
    cerr << "\n错误的输入文件路径(无法打开文件):" << input << endl;
    return;
  }

  //得到要转化的yuv格式
  //得到bitmap头部信息,包括文件头和bitmap头
  memset(&(bitmap),0,sizeof(bitmap));
  memset(&(bitmapHeader),0,sizeof(bitmapHeader));
  
  // 写位图信息头内容
  bitmapHeader.biBitCount = 24;
  bitmapHeader.biClrImportant = 0;
  bitmapHeader.biClrUsed = 0;
  bitmapHeader.biCompression = 0;
  bitmapHeader.biPlanes = 1;
  bitmapHeader.biSize = 40;
  bitmapHeader.biSizeImage = 0;
  bitmapHeader.biXPelsPerMeter = 0;
  bitmapHeader.biYPelsPerMeter = 0;
  bitmapHeader.biHeight = -height;
  bitmapHeader.biWidth = width;
  bitmapHeader.biSizeImage = height * width * (bitmapHeader.biBitCount / 8);

  bitmap.bfType = 0x4D42;
  bitmap.bfOffBits = 54;
  bitmap.bfSize = bitmapHeader.biSizeImage + 54;
  //分配空间
  if((width%4 != 0) || (height%4 != 0))
  {
    cerr << "\n图像的宽度和高度必须是4的倍数!" << endl;
    return;
  }
  //	iSizeImage = bitmapHeader.biSizeImage;
  iSizeImage = width*height*3;
  rgbBuf = (unsigned char *)malloc(iSizeImage);
  memset( rgbBuf, 0, iSizeImage );
  if(rgbBuf == NULL)
    return;
  ySize = iSizeImage/3;
  ty = (unsigned char *)malloc(ySize);
  if(ty == NULL)
    return;
  uSize = iSizeImage/12;
  tu = (unsigned char *)malloc(uSize);
  if(tu == NULL)
    return;
  vSize = iSizeImage/12;
  tv = (unsigned char *)malloc(vSize);
  if(tv == NULL)
    return;
  fread(ty,sizeof(char),ySize,fp);
  fread(tu,sizeof(char),uSize,fp);
  fread(tv,sizeof(char),vSize,fp);
  fclose(fp);

  fp = fopen(output,"wb");
  if(fp == NULL) {
    cerr << "\n输出文件路径错误,无法创建路径:" << output << endl;
    return;
  }
  fwrite( &bitmap, 1, sizeof(bitmap), fp );
  fwrite( &bitmapHeader, 1, sizeof(bitmapHeader), fp );
  fflush(fp);
  YCbCr420toRGB( ty, tu, tv, fp, width, height, width );
  fclose(fp);

  free(rgbBuf);
  free(ty);
  free(tu);
  free(tv);
  system("echo YCbCr to bmp24 转换完成在 %time%");
}

int main(int argc, char **argv)
{
  // -y -i "D:\image_1.yuv" -o "D:\image_1.bmp" -w 4320 -h 3240
  if ( argc < 6 || argc > 10 || (argc == 6 && argv[1][1] != 'b' && argv[1][1] != 'B' )
    || (argc > 6 && argc < 10) || (argc == 10 && argv[1][1] != 'y' && argv[1][1] != 'Y' ))
  {
    cerr<< " ConvRGB24YCbCr420 help infomation.          \n"
        << " -y\t convert from YCbCr420 to RGB24         \n"
        << " -b\t convert from RGB24 to YCbCr            \n"
        << " -i\t input file path                        \n"
        << " -o\t output file path                       \n"
        << " -w\t image width when input is YUV          \n"
        << " -h\t image height when input is YUV         \n"
        << " e.g.                                        \n"
        << " -y -i input.yuv -o output.bmp -w 480 -h 320 \n"
        << " -b -i input.bmp -o output.yuv               \n"
        << endl;
    return 0;
  }
  cout << " input : " << argv[3] << endl;
  cout << " output: " << argv[5] << endl;
  if ( argc == 6 )
  {
    ConvRGB242YCbCr(argv[3], argv[5]);
  }
  else
  {
    int width = atoi(argv[7]);
    int height = atoi(argv[9]);
    cout << " width = " << width << " height = " << height << endl;
    ConvYCbCr420RGB24(argv[3], argv[5], width, height );
  }
	return 0;
}
PS:欢迎访问本人的CSDN博客:http://blog.csdn.net/luofl1992
seo service london 说:
Feb 22, 2024 04:44:28 PM

Hello. Great job. I did not anticipate this. This is a splendid articles


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter
Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee