YCbCr420(YUV420)和bmp24图像格式的转换 - Fla
YCbCr420(YUV420)和bmp24图像格式的转换
在视频编码的时候,原始序列一般为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; }
Apr 23, 2023 08:07:59 PM
crediblebh
Feb 22, 2024 04:44:28 PM
Hello. Great job. I did not anticipate this. This is a splendid articles