在記憶體內進行 JPEG 壓縮


一般要將圖片儲存成特定的檔案格式、進行壓縮的話,大部分都是直接寫到磁碟裡。不過,有的時候其實可能只是要進行壓縮,而不想寫到磁碟上(速度比較慢、而且還要再讀回來),這個時候,就必須要特別處理。

而 Independent JPEG Group 的 JPEG 函式庫(官網)目前就有提供 jpeg_mem_dest() 這個函式,可以再壓縮的時候,把資料寫到指定的記憶體空間,而不用寫到磁碟上。

如果要把相關功能包成一個函式的話,寫法大概就會像下面這樣:

std::pair<unsigned char*,unsigned long> CompressJPEG(
          unsigned int uWidth, unsigned int uHeight,
          unsigned char* pImg, int iQuality = 75 )
{
  std::pair<unsigned char*,unsigned long> mRes 
                     = std::make_pair( (unsigned char*)NULL, 0 );
  
  // setup JPEG compression structure data
  jpeg_compress_struct  jcInfo;
  jpeg_error_mgr      jErr;  // JPEG error handler
  jcInfo.err        = jpeg_std_error ( &jErr );
  
  // initialize JPEG compression object
  jpeg_create_compress( &jcInfo );
  
  // specify data destination is memory
  jpeg_mem_dest( &jcInfo, &mRes.first, &mRes.second );
  
  // image format
  jcInfo.image_width    = uWidth;
  jcInfo.image_height    = uHeight;
  jcInfo.input_components  = 3;
  jcInfo.in_color_space  = JCS_RGB;
  
  // set default compression parameters
  jpeg_set_defaults( &jcInfo );
   
  // set image quality
  jpeg_set_quality( &jcInfo, iQuality, TRUE );
  
  // start compressor
  jpeg_start_compress( &jcInfo, TRUE );
  int  iRowStride = jcInfo.image_width * jcInfo.input_components;
  while( jcInfo.next_scanline < jcInfo.image_height )  {
    JSAMPROW pData = &( pImg[ jcInfo.next_scanline * iRowStride ] );
    jpeg_write_scanlines( &jcInfo, &pData, 1 );
  }
  
  // finish compression
  jpeg_finish_compress( &jcInfo );
  
  // release JPEG compression object
  jpeg_destroy_compress( &jcInfo );
  
  return mRes;
}

這邊要輸入的參數基本上有四個,uWidthuHeight 是圖片的寬和高,pImg 則是圖片的內容(未壓縮的);最後的 iQuality 則是壓縮成 JPEG 時,所要設定的品質(0-100)。至於影像的像素格式,Heresy 這邊是寫死成三個 channel、也就是 RGB 的格式。

回傳的結果有兩種資料,這邊用 std::pair 來做簡單的包裝。裡面第一項的 unsigned char* 指到的記憶體空間,就是壓縮完的資料,而第二項的 unsigned long 數值,則是代表這個資料的長度。

而如果是要使用的話,假設有一個 unsigned char 的陣列 pImg,它的大小是 w x h 的話,那只要執行下方程式碼的第一行,就可以取得壓縮成 JPEG 的資料(pJpeg)了~

std::pair<unsigned char*,unsigned long> pJpeg = CompressJPEG( w, h, pImg, 100 );
ofstream of( "e:\\test.jpg", ios::binary );
of.write( (char*)pJpeg.first, pJpeg.second );
of.close();

而如果要驗證的話,最簡單的方法,還是把他寫出來、變成實際的檔案;這時候,只要透過 ofstrem,用 binary 的形式,整個寫出來就好了。

參考:《Compressing IplImage to JPEG using libjpeg in OpenCV

對「在記憶體內進行 JPEG 壓縮」的想法

  1. 想請問樓主對socket有研究嗎?
    目前想把這個壓縮後得到的jpeg.first傳到 android 顯示圖片

發表留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料