RGB、YUV、NV21、BMP相互转换

概述

最近学习雷神的博客,深受启发。后面的代码或者借鉴原作的代码,或者修改自原作。一般的图片播放器无法显示rgb和yuv格式的图片,为了方便查看rgb和yuv图片,雷神修改了一个网上的YUV/RGB播放器,既支持YUV又支持RGB的播放器。不过要求图片名字类似于test_320x420.yuv,320x420是图片的宽和高。

rgb24转yuv420p

首先要了解rgb24和yuv420p的存储方式,yuv420p是先存y再存u最后是v分量。废话少说,直接上代码。

#include 
#include 
#include 
#include 

unsigned char clip_value(unsigned char x,unsigned char min_val,unsigned char  max_val){
	if(x>max_val){
		return max_val;
	}else if(x> 8) + 16  ;          
			u = (unsigned char)( ( -38 * r -  74 * g + 112 * b + 128) >> 8) + 128 ;          
			v = (unsigned char)( ( 112 * r -  94 * g -  18 * b + 128) >> 8) + 128 ;
			*(ptrY++) = clip_value(y,0,255);
			if (j%2==0&&i%2 ==0){
				*(ptrU++) =clip_value(u,0,255);
			}
			else{
				if (i%2==0){
				*(ptrV++) =clip_value(v,0,255);
				}
			}
		}
	}
	return true;
}

int simplest_rgb24_to_yuv420(const char *url_in, int w, int h,int num,const char *url_out){
	FILE *fp=fopen(url_in,"rb+");
	FILE *fp1=fopen(url_out,"wb+");
 
	unsigned char *pic_rgb24=(unsigned char *)malloc(w*h*3);
	unsigned char *pic_yuv420=(unsigned char *)malloc(w*h*3/2);
 
	for(int i=0;i

原rgb图与生成的yuv420p利用上面提到的播放器显示,如下:


可见,两幅图几乎看不出差别(若rgb图片宽高分别是w,h,则总像素个数是wh3,对应的yuv总像素个数是wh3/2,这么看图像信息还是有缺失的)。
另外,在用上述播放器显示rgb图片时,一定要选择正确的像素格式(播放器没那么智能,知道你的文件是什么格式,默认yuv420),否则就会出错,像下面这样。在播放器工具栏单击‘像素格式’选择‘rgb24格式’就能正常显示了。

rgb24转BMP

这里是雷神将rgb24封装为bmp格式的代码。开始直接用雷神的代码,发现生成的bmp图片无法显示,后来查阅bmp的结构才发现,雷神的结构体有点问题。bmp文件头应该是54字节,按照雷神的结构体,文件头占了106字节,因此修改了结构体和代码中的有关文件头的部分,然后输出的结果可以正常显示了。该转换需要注意两点:(1)BMP的存储方式是小端存储,低字节放低地址,高字节放高地址,而RGB的存储方式是大端存储;(2)结构体在内存中的对齐准则,(a)从第二个数据成员开始,数据成员存储的起始地址要从该成员大小的整数倍开始;(b)最后检查总存储单元是否为所有元素中最宽元素的整数倍,若不是,则需要补齐。若包含指针元素,指针占四个字节,与类型无关。

#include 
#include 
#include 
#include 

int simplest_rgb24_to_bmp(const char *rgb24path, int width, int height, const char *bmppath) {
	typedef struct
	{
		unsigned int imageSize;
		unsigned int blank;
		unsigned int startPosition;
	}BmpHead;

	typedef struct
	{
		unsigned int  Length;
		int  width;
		int  height;
		unsigned short  colorPlane;
		unsigned short  bitColor;
		unsigned int  zipFormat;
		unsigned int  realSize;
		int  xPels;
		int  yPels;
		unsigned int  colorUse;
		unsigned int  colorImportant;
	}InfoHead;

	int i = 0, j = 0;
	BmpHead m_BMPHeader = { 0 };
	InfoHead  m_BMPInfoHeader = { 0 };
	char bfType[2] = { 'B','M' };
	int header_size = 54;
	unsigned char *rgb24_buffer = NULL;
	FILE *fp_rgb24 = NULL, *fp_bmp = NULL;

	if ((fp_rgb24 = fopen(rgb24path, "rb")) == NULL) {
		printf("Error: Cannot open input RGB24 file.\n");
		return -1;
	}
	if ((fp_bmp = fopen(bmppath, "wb")) == NULL) {
		printf("Error: Cannot open output BMP file.\n");
		return -1;
	}

	rgb24_buffer = (unsigned char *)malloc(width*height * 3);
	fread(rgb24_buffer, 1, width*height * 3, fp_rgb24);

	m_BMPHeader.imageSize = 3 * width*height + header_size;
	m_BMPHeader.startPosition = header_size;

	m_BMPInfoHeader.Length = 40;
	m_BMPInfoHeader.width = width;
	//BMP storage pixel data in opposite direction of Y-axis (from bottom to top).
	m_BMPInfoHeader.height = -height;
	m_BMPInfoHeader.colorPlane = 1;
	m_BMPInfoHeader.bitColor = 24;
	m_BMPInfoHeader.realSize = 3 * width*height;

	fwrite(bfType, 1, sizeof(bfType), fp_bmp);
	fwrite(&m_BMPHeader, 1, sizeof(m_BMPHeader), fp_bmp);
	fwrite(&m_BMPInfoHeader, 1, sizeof(m_BMPInfoHeader), fp_bmp);

	//BMP save R1|G1|B1,R2|G2|B2 as B1|G1|R1,B2|G2|R2
	//It saves pixel data in Little Endian
	//So we change 'R' and 'B'
	for (j = 0;j < height;j++) {
		for (i = 0;i < width;i++) {
			char temp = rgb24_buffer[(j*width + i) * 3 + 2];
			rgb24_buffer[(j*width + i) * 3 + 2] = rgb24_buffer[(j*width + i) * 3 + 0];
			rgb24_buffer[(j*width + i) * 3 + 0] = temp;
		}
	}
	fwrite(rgb24_buffer, 3 * width*height, 1, fp_bmp);
	fclose(fp_rgb24);
	fclose(fp_bmp);
	free(rgb24_buffer);
	printf("Finish generate %s!\n", bmppath);
	return 0;
	return 0;
}

int main() {

	//Test
	simplest_rgb24_to_bmp("../process_100jpg/pic_rgb/15_1024x640.rgb",1024,640,1,"15_1024x640.bmp");
	return 0;
}

转成的bmp格式可以直接用普通的播放器显示。

BMP转rgb24

这个转换更简单,去掉bmp头就是rgb24。但是这里同样要注意两个问题:注意:(1)BMP的存储方式是小端存储,低字节放低地址,高字节放高地址,而RGB的存储方式是大端存储;(2)结构体在内存中的对齐准则。
解决方法:定义结构体解析BMP文件头,获取图片宽高信息;自下而上读取BMP文件的位图数据,调换R、B数据位置。代码如下:

#include 
#include 
#include 
#include 
#include 

#pragma pack(2)  
//下面两个结构是位图的结构  
typedef struct BITMAPFILEHEADER  
{   
    u_int16_t bfType;   
    u_int32_t bfSize;   
    u_int16_t bfReserved1;   
    u_int16_t bfReserved2;   
    u_int32_t bfOffBits;   
}BITMAPFILEHEADER;   
  
typedef struct BITMAPINFOHEADER  
{   
    u_int32_t biSize;   
    u_int32_t biWidth;   
    u_int32_t biHeight;   
      
}BITMAPINFODEADER; 

//bmp to rgb
int bmp_to_rgb(unsigned char *bmpBuf,int w,int h,unsigned char *rgbBuf)
{
	unsigned char*ptr_bmp, *ptr_rgb;
	char r,g,b;
	memset(rgbBuf,0,w*h*3);
	ptr_bmp= bmpBuf+54;
	ptr_rgb= rgbBuf;
	int j,i;
	for ( j = h-1; j>=0;j--){
		ptr_bmp = bmpBuf+54+ w*j*3 ;
		for (i = 0;i

rgb24转NV21

yuv420与nv21最大的区别就是uv分量的存储方式,nv21与nv12的区别在于nv分量的存储先后顺序。最初我以为rgb24转NV21相较于rgb24转yuv420p,只要把存储方式转换一下就行了,其实不然。nv21存储格式如下,四个y对应一对uv。只看y分量,可以发现,图像的宽或者高不可能为奇数。所以对于宽或者高是奇数的rgb图像,可以使用舍弃行、列,或者增加行、列的方式将生成的nv21图片的宽高成为偶数。这里使用舍弃行rgb行或列的方法,对于宽是奇数的rgb图像,读取rgb数据时,最后一列舍弃;高是奇数的话,最后一行舍弃。例如,对于图8_1024x683.rgb,它的高为683,在读取rgb图时舍弃最后一行。RGB、YUV、NV21、BMP相互转换_第1张图片
代码如下:

#include 
#include 
#include 
#include 

unsigned char clip_value(unsigned char x,unsigned char min_val,unsigned char  max_val){
	if(x>max_val){
		return max_val;
	}else if(x> 8) + 16  ;          
			u = (unsigned char)( ( -38 * r -  74 * g + 112 * b + 128) >> 8) + 128 ;          
			v = (unsigned char)( ( 112 * r -  94 * g -  18 * b + 128) >> 8) + 128 ;
			*(ptrY++) = clip_value(y,0,255);
			if (j%2==0&&i%2 ==0){
				*(ptrV++) =clip_value(v,0,255);ptrV++;
			}
			else{
				if (i%2==0){
				*(ptrU++) =clip_value(u,0,255);ptrU++;
				}
			}
		}
	}
	return 0;
}

int simplest_rgb24_to_yuv420(const char *url_in, int w, int h,int num,const char *url_out){
	FILE *fp=fopen(url_in,"rb+");
	FILE *fp1=fopen(url_out,"wb+");
 
	unsigned char *pic_rgb24=(unsigned char *)malloc(w*h*3);
	unsigned char *pic_yuv420=(unsigned char *)malloc(w*(h-1)*3/2);
 
	fread(pic_rgb24,1,w*h*3,fp);
	RGB24_TO_YUV420(pic_rgb24,w,h,pic_yuv420);
	fwrite(pic_yuv420,1,w*(h-1)*3/2,fp1);
	
	free(pic_rgb24);
	free(pic_yuv420);
	fclose(fp);
	fclose(fp1);
 
	return 0;
}

int main(){

	//Test
	simplest_rgb24_to_yuv420("./pic_rgb/8_1024x683.rgb",1024,683,1,"./8_1024x682.yuv");
	return 0;
}

开始在做图像格式转换时,只有雷神的一张lena的rgb图,网上搜了许久也没找到,就用libjpeg库将网上下载的jpg图像转为bmp,然后又将bmp利用上面的代码转为rgb。事实上,libjpeg库包含将jpg解压为rgb的方法,只不过转过来是错的!所以我只能先转bmp然后再转rgb。转换过程,下一篇再介绍,下面是我把100张jpg图片转成的rgb格式图片的链接。图片命名为类似1_500x352.rgb,序号_图片宽x图片高.jpg。上面代码中用到的rgb格式图片选自该文件夹。
rgb图片下载链接: link.
参考博客:
播放器链接: link.

雷神链接: link.

你可能感兴趣的:(图像格式转换)