YUV420存儲爲BMP和JPG圖片

YUV420存儲爲BMP和JPG圖片

2016年08月11日 14:52:49 第2夢 閱讀數:14627

 版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/yixianfeng41/article/details/52181578

       網上大多數關於YUV420的資料都是關於YUV420P的,很少有YUV420SP的,因爲YUV420SP的UV是交錯存放的,處理起來相對麻煩點,但是YUV420SP也是一種常見格式,因此,在這裏,我將關於YUV420SP格式數據的處理總結下,方便有需要的同志。

 

一、YUV420格式數據介紹

       YUV,分爲三個分量,“Y”表示明亮度,也就是灰度值;“U"和”V"表示的則是色度,作用是描述影像色彩飽和度,用於指定像素的顏色。YUV主流的採樣方式有三種:YUV4:4:4,YUV4:2:2,YUV4:2:0,這裏主要介紹下YUV420。

 

        在YUV420中,一個像素點對應一個Y,一個2X2的小方塊對應一個U和V。對於所有YUV420圖像,它們的Y值排列是完全相同的,因爲只有Y的圖像就是灰度圖像。YUV420又分爲YUV420SP與YUV420P這兩種,這兩種格式的Y分佈是相同的,區別在於UV:YUV420p它是先把U存放完後,再存放V,也就是說UV它們是連續的;而YUV420sp它是UV、UV這樣交替存放的。(他們的內存分佈圖如下,左是YUV420sp,右是YUV420p)

               

 

二、從YUV內存中取數據組方法

       注意YUV是每四個Y對應一個UV,並且YUV420P和YUV420SP的UV的存放格式不同,取法也不同,總的來說,YUV420P的取法簡單,YUV420SP的取法相對複雜點。

1、YUV420SP

 


 
  1. for(int j=0;j<DataHeight;j++)

  2. {

  3. for(int i=0;i<DataWidth;i++)

  4. {

  5. y=ybase[i + j * DataWidth];// 每四個y對應一個uv

  6. u=ubase[j/2 * DataWidth+(i/2)*2];

  7. v=ubase[j/2 * DataWidth+(i/2)*2+1]; //一定要注意是u+1

  8. }

  9. }

 

 

2、YUV420P

 


 
  1. for(int j=0;j<DataHeight;j++)

  2. {

  3. for(int i=0;i<DataWidth;i++)

  4. {

  5. //yyyyyy ... uuuu ...vvv

  6. y=ybase[i + j * DataWidth];

  7. u=ubase[j/2 * DataWidth/2+(i/2)];

  8. v=vbase[j/2 * DataWidth/2+(i/2)];

  9. }

  10. }

 

上面代碼裏,ybase就是YUV中Y的起始地址,ubase就是u的起始地址,vbase就是v的起始地址。而YUV420SP格式中,V就是U的地址加一;YUV420P中U和V都是連續的。按照上面方法,我們就可以得到每一組YUV數據,然後自己可以將每一組數據保存下來,再進行處理。

 

三、YUV420轉換爲RGB數據

1、轉換公式

R=Y+1.4075*(V-128)
G=Y-0.3455*(U-128) – 0.7169*(V-128)
B=Y+1.779*(U-128)

2.轉換方法

YUV420SP

 


 
  1. for(int j=0;j<DataHeight;j++)

  2. {

  3. for(int i=0;i<DataWidth;i++)

  4. {

  5. unsigned char r,g,b;

  6. y=ybase[i + j * DataWidth];

  7. u=ubase[j/2 * DataWidth+(i/2)*2];

  8. v=ubase[j/2 * DataWidth+(i/2)*2+1];

  9.  
  10. b=(unsigned char)(y+1.779*(u- 128));

  11. g=(unsigned char)(y-0.7169*(v - 128)-0.3455*(u - 128));

  12. r=(unsigned char)(y+ 1.4075*(v - 128));*/

  13. }

  14. }


YUV420P

 

 


 
  1. for(int j=0;j<DataHeight;j++)

  2. {

  3. for(int i=0;i<DataWidth;i++)

  4. {

  5. unsigned char r,g,b;

  6.  
  7. y=ybase[i + j * DataWidth];

  8. u=ubase[j/2 * DataWidth/2+(i/2)];

  9. v=vbase[j/2 * DataWidth/2+(i/2)];

  10.  
  11. b=(unsigned char)(y+1.779*(u- 128));

  12. g=(unsigned char)(y-0.7169*(v - 128)-0.3455*(u - 128));

  13. r=(unsigned char)(y+ 1.4075*(v - 128));*/

  14. }

  15. }

 

 

四、RGB數據存儲爲圖片

注意rgb數據存儲爲bmp和jpg時的不同,將RBG數據存儲爲bmp時,數據是逆序存放,並且不是rgb,而是bgr;當將rgb數據存儲爲jpg時,則不用,不用逆序,數據也還是rgb。

 

1、存儲爲BMP圖片

請看另一篇博客,RGB TO BMP

2、存儲爲JPG圖片

存儲爲JPG圖片要用到一個開運庫,libjpeg,或者libjpeg-turbo,我用的是libjpeg,網上關於這兩個開源庫的資料很多,可以從這個下載編譯好的包,LIBJPEG包

封裝的存儲方法如下:

 


 
  1. int rgb2jpeg(const char * filename, unsigned char* rgbData,int image_width,int image_height,int quality)

  2. {

  3. struct jpeg_compress_struct jpeg; //identify a compress object

  4. struct jpeg_error_mgr jerr; //error information

  5.  
  6. jpeg.err = jpeg_std_error(&jerr);

  7. jpeg_create_compress(&jpeg); //init compress object

  8.  
  9. FILE* pFile;

  10. fopen_s(&pFile,filename,"wb" );

  11. if( !pFile ) return 0;

  12. jpeg_stdio_dest(&jpeg, pFile);

  13.  
  14. //compress param set,i just did a simple param set

  15. jpeg.client_data=(void*)&pFile;

  16. jpeg.image_width = image_width;

  17. jpeg.image_height = image_height;

  18. jpeg.input_components = 3;

  19. jpeg.in_color_space = JCS_RGB;

  20. jpeg_set_defaults(&jpeg);

  21. //// 指定亮度及色度質量

  22. jpeg.q_scale_factor[0] = jpeg_quality_scaling(100);

  23. jpeg.q_scale_factor[1] = jpeg_quality_scaling(100);

  24. //// 圖像採樣率,默認爲2 * 2

  25. jpeg.comp_info[0].v_samp_factor = 2;

  26. jpeg.comp_info[0].h_samp_factor = 2;

  27. //// set jpeg compress quality

  28. jpeg_set_quality(&jpeg, quality, TRUE); //100 is the highest

  29.  
  30. //start compress

  31. jpeg_start_compress(&jpeg, TRUE);

  32.  
  33. JSAMPROW row_pointer[1];

  34.  
  35. //from up to down ,set every pixel

  36. for( unsigned int i=0;i<jpeg.image_height;i++ )

  37. {

  38. row_pointer[0] = rgbData+i*jpeg.image_width*3;

  39. jpeg_write_scanlines( &jpeg,row_pointer,1 );

  40. }

  41. //stop compress

  42. jpeg_finish_compress(&jpeg);

  43.  
  44. fclose( pFile );

  45. pFile = NULL;

  46. jpeg_destroy_compress(&jpeg);

  47. return 0;

  48. }

 

 

五、YUV數據存儲爲JPG

 

網上有不少關於YUV420數據存儲爲JPG的代碼和博客,但是我用他們的代碼,老是不成功,不是運行不起來,就是效果不好,不過還是表示萬分感謝。

1、YUV420SP

 


 
  1. int yuv420p_to_jpeg(const char * filename, const char* pdata,int image_width,int image_height, int quality)

  2. {

  3. struct jpeg_compress_struct cinfo;

  4. struct jpeg_error_mgr jerr;

  5. cinfo.err = jpeg_std_error(&jerr);

  6. jpeg_create_compress(&cinfo);

  7.  
  8. FILE * outfile; // target file

  9. if ((outfile = fopen(filename, "wb")) == NULL) {

  10. fprintf(stderr, "can't open %s\n", filename);

  11. exit(1);

  12. }

  13. jpeg_stdio_dest(&cinfo, outfile);

  14.  
  15. cinfo.image_width = image_width; // image width and height, in pixels

  16. cinfo.image_height = image_height;

  17. cinfo.input_components = 3; // # of color components per pixel

  18. cinfo.in_color_space = JCS_YCbCr; //colorspace of input image

  19. jpeg_set_defaults(&cinfo);

  20. jpeg_set_quality(&cinfo, quality, TRUE );

  21.  
  22. //////////////////////////////

  23. // cinfo.raw_data_in = TRUE;

  24. cinfo.jpeg_color_space = JCS_YCbCr;

  25. cinfo.comp_info[0].h_samp_factor = 2;

  26. cinfo.comp_info[0].v_samp_factor = 2;

  27. /////////////////////////

  28.  
  29. jpeg_start_compress(&cinfo, TRUE);

  30.  
  31. JSAMPROW row_pointer[1];

  32.  
  33. unsigned char *yuvbuf;

  34. if((yuvbuf=(unsigned char *)malloc(image_width*3))!=NULL)

  35. memset(yuvbuf,0,image_width*3);

  36.  
  37. unsigned char *ybase,*ubase;

  38. ybase=pdata;

  39. ubase=pdata+image_width*image_height;

  40. int j=0;

  41. while (cinfo.next_scanline < cinfo.image_height)

  42. {

  43. int idx=0;

  44. for(int i=0;i<image_width;i++)

  45. {

  46. yuvbuf[idx++]=ybase[i + j * image_width];

  47. yuvbuf[idx++]=ubase[j/2 * image_width+(i/2)*2];

  48. yuvbuf[idx++]=ubase[j/2 * image_width+(i/2)*2+1];

  49. }

  50. row_pointer[0] = yuvbuf;

  51. jpeg_write_scanlines(&cinfo, row_pointer, 1);

  52. j++;

  53. }

  54. jpeg_finish_compress(&cinfo);

  55. jpeg_destroy_compress(&cinfo);

  56. fclose(outfile);

  57. return 0;

  58. }

 

 

2、YUV420P

其實YUV420P和YUV420SP主要區別就是取數據方式不同,前面對於YUV420P如何取數據已經講得很清楚了,YUV420P存儲爲JPG只需要在上面YUV420SP存儲爲JPG的基礎上改改取數據方法就好了。

 

效果:

這是一張1280X720的圖片,大小385kb,因爲是USB攝像頭,所以圖片質量感覺不是很高,總的來說效果不錯!

 

六、YUV420SP 與 YUV420P相互轉換

       知道了YUV420SP以及YUV420P的內存格式後,互相轉換就不是難事了。

1、YUV420SP TO YUV420P

 


 
  1. int yuv420sp_to_yuv420p(unsigned char * yuv420sp,unsigned char* yuv420p,int width,int height)

  2. {

  3. if(yuv420sp==NULL)

  4. return;

  5. int i=0,j=0;

  6. //Y

  7. for(i=0;i<width*height;i++)

  8. {

  9. yuv420p[i]=yuv420sp[i];

  10. }

  11. //U

  12. for(int j=0,i=0;j<width*height/2;j+=2,i++)

  13. {

  14. yuv420p[i + width*height] = yuv420sp[j+width*height];

  15. }

  16.  
  17. //V

  18. for(i=0,j=1,j<width*height/2;j+=2,i++)

  19. {

  20. yuv420p[i+width*height*5/4] = yuv420sp[j+width*height];

  21. }

  22. }

 

 

2、YUV420P TO YUV420SP

 


 
  1. int yuv420p_to_yuv420sp(unsigned char * yuv420p,unsigned char* yuv420sp,int width,int height)

  2. {

  3. if(yuv420p==NULL)

  4. return;

  5. int i=0,j=0;

  6. //Y

  7. for(i=0;i<width*height;i++)

  8. {

  9. yuv420sp[i]=yuv420p[i];

  10. }

  11.  
  12. int m=0,n=0;

  13. for(int j=0;j<width*height/2;j++)

  14. {

  15. if(j%2==0)

  16. yuv420sp[j+width*height]=yuv420p[m++];

  17. else

  18. yuv420sp[j+width*height]=yuv420p[n++];

  19. }

  20. }

 

 

七、參考博客

1、圖文詳解YUV420數據格式

2、使用libjpeg庫將yuv420數據轉換爲jpg圖片 

3、yuv420存儲爲JPG

 

 

 收藏 

 分享

  • brk1985

    brk1985: yuvbuf[idx++]=ubase[j/2 * image_width+(i/2)*2]; yuvbuf[idx++]=ubase[j/2 * image_width+(i/2)*2+1]; 應該是 yuvbuf[idx++]=ubase[j/2 * image_width/2+(i/2)*2]; yuvbuf[idx++]=ubase[j/2 * image_width/2+(i/2)*2+1];(3個月前#5樓)收起回覆

    0

    • qq_43707696

      當歸丶回覆 brk1985: 請問爲什麼要這樣改呢 我用的你和博主的都出現問題 說row_pointer傳的無效參數(3個月前)

      0

  • hoodlum1980

    hoodlum1980: 藉助你的代碼的啓發,我已經寫好了對內存中的 YUV420P / YV12 的視頻流數據,在內存中進行矩形摳圖,在內存編碼到 jpg 格式的字節流的方法。~所以還是感謝你的這篇文章啦。(10個月前#4樓)

    0

  • hoodlum1980

    hoodlum1980: 老哥,你這段找的 yuv-jpg 的代碼,裏面都內存泄露了(yuvbuf 都沒有 free 的)。寫這個代碼的人也太不負責任了吧。。細節的地方還開啓了 down-sample,那是縮放用的。還有具體格式(yuv在內存中分佈)的地方就不看啦。哎,不過還是比較感謝你提供的這段代碼給我的啓發。。。(10個月前#3樓)收起回覆

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章