1. 程式人生 > >CImg:外掛(plugin)使用說明塈實現JPEG影象記憶體編碼/解碼

CImg:外掛(plugin)使用說明塈實現JPEG影象記憶體編碼/解碼

殺雞用牛刀?

如果你想對影象進行簡單處理,你一般會想到用什麼?可能多數人想到的是OpenCV。
對,OpenCV是個非常強大的影象視覺工具庫,用途非常廣泛。簡單的影象處理用它肯定是可以的。
但OpenCV實在太龐大了,用起來有時反而不方便,就好比你現在肚子餓了只想簡單吃個午飯,你是選擇街邊的飯館買一份快餐15分鐘解決問題,還是打電話給高階西餐廳訂個位子要排隊等到下週一才能吃上?
用OpenCV完成一些簡單的影象處理就好比用一把牛刀殺雞,能用但不好用,比如要寫一個簡單的測試程式,需要載入顯示影象檔案,並對影象做一個簡單的處理(縮放,旋轉,繪圖),用OpenCV,就要多一個依賴庫,可能還要為此編譯OpenCV.而庫中90%的功能都用不到,想想就好麻煩。

CImg是一個小型的C++語言跨平臺的影象處理開源庫,有多小型?核心程式碼只有一個CImg.h檔案。但是這個工具就像一把瑞士軍刀,擁有的功能卻非常多,常用的影象顯示,影象格式解碼,矩陣運算,色彩空間轉換,簡單繪圖。。。等等該有的功能都有了。
所以我在寫一些沒有效能有要求的測試程式的時候,會選擇用CImg來完成,沒有依賴庫,編譯出的程式碼到哪裡都能執行,方便啊,不然呢,你寫個測試程式給客戶,客戶的電腦上不了,為啥沒裝OpenCV,客戶問啥是OpenCV?..你再解釋一遍,再教客戶安裝OpenCV,想想都頭大。

CImg外掛

幾年前就用過CImg,當時是用它在測試程式中做簡單的影象顯示,非常方便。在libjpeg的支援下也用它載入JPEG影象檔案,當時還在困擾CImg沒有提供對JPEG格式影象記憶體編碼/解碼的功能。所以為此花了挺大精力自己實現了jpeg影象的記憶體編碼/解碼功能,參見我之前的博文:

最近的工作中又要對JPEG影象進行記憶體解碼了,原打算用之前寫的程式碼,但我重新看了CImg的程式碼。才發現CImg在核心程式碼CImg.h之外還提供了很多外掛(plugins),如下:

    │  CImg.h
	│  README.md
	│
	├─examples
	│  └─img
	│
	├─plugins
	│      add_fileformat.h
	│      bayer.h
	│      chlpca.h
	│      cvMat.h
	│      draw_gradient.h
	│      inpaint.h
	│      ipl.h
	│      ipl_alt.h
	│      jpeg_buffer.h
	│      loop_macros.h
	│      matlab.h
	│      nlmeans.h
	│      patchmatch.h
	│      skeleton.h
	│      tiff_stream.h
	│      tinymatwriter.h
	│      vrml.h
	│      vtk.h
	│
	└─resources
	        compile_win_icl.bat
	        compile_win_visualcpp.bat

可以看到有個plugins\jpeg_buffer.h,就是實現jpeg記憶體壓縮和解壓縮的。有了這個外掛的支援,CImg類就多了load_jpeg_buffersave_jpeg_buffer兩個成員函式,分別用於jpeg檔案的壓縮和解壓縮。具體怎麼用呢?examples資料夾下use_jpeg_buffer.cpp就是示例程式碼。以下程式碼來自use_jpeg_buffer.cpp,本文作者只是添加了中文註釋

#include <cstdio>
// JPEG檔案的讀寫需要libjpeg的支援,所以這裡必須要include jpeglib.h jerror.h
#include <jpeglib.h>
#include <jerror.h>

// 這一行放在#include "CImg.h"前面,用於將jpeg_buffer.h外掛加入CImg類的定義
// CImg.h中有多個下面這樣的程式碼,將你定義的外掛標頭檔案 include到CImg類定義中
//             #ifdef cimg_plugin
//             #include cimg_plugin
//             #endif
//             #ifdef cimg_plugin1
//             #include cimg_plugin1
//             #endif
// 以此類推,如果你同時也想讓CImg物件能轉換成OpenCV的矩陣物件cv::Mat
// 就可以定義 cimg_plugin1 為  "plugins/cvMat.h"
//            #define cimg_plugin1 "plugins/cvMat.h"
#define cimg_plugin "plugins/jpeg_buffer.h"
#include "CImg.h"
using namespace cimg_library;
int main() {

  // 將一個JPEG檔案的資料讀取到記憶體緩衝區 'buffer_input'中
  const char *filename_input = "foo.jpg";
  std::fprintf(stderr," - Reading file '%s'\n",filename_input);
  std::FILE *file_input = std::fopen(filename_input,"rb");
  if (!file_input) { std::fprintf(stderr,"Input JPEG file not found !"); std::exit(0); }

  std::fprintf(stderr," - Construct input JPEG-coded buffer\n");
  unsigned buf_size = 500000; // 這裡定義檔案長度
  JOCTET *buffer_input = new JOCTET[buf_size];
  if (std::fread(buffer_input,sizeof(JOCTET),buf_size,file_input)) std::fclose(file_input);
  // -> 'buffer_input' is now a valid jpeg-coded memory buffer.

  std::fprintf(stderr," - Create CImg instance from JPEG-coded buffer\n");
  CImg<unsigned char> img;
  // 將記憶體緩衝區 'buffer_input'中的影象資料呼叫load_jpeg_buffer函式實現記憶體解壓縮,buffer_input用完就可以刪除了。
  img.load_jpeg_buffer(buffer_input, buf_size);
  delete[] buffer_input;

  // 然後你可以在CImg物件上做你想要的影象處理,比如下面的程式碼在影象上寫文字 ‘ Hello!’,並顯示出來
  std::fprintf(stderr," - Do simple processing\n");
  const unsigned char purple[] = { 255, 0, 0 };
  const unsigned char black[] = { 0, 0, 0 };
  img.mirror('y').draw_text(0,0,"   Hello!   ",purple,black,1,57);

  // Display image to see if everything's fine.
  img.display("Using 'jpeg_buffer.h' plugin");

  // 定義一個JPEG壓縮輸出緩衝區,因為無法預測JPEG壓縮輸出的資料尺寸,所以這裡定義了原檔案尺寸2倍。
  // 實際應用中為保險起見,應該以影象解析度來決定緩衝區的大小, 
  std::fprintf(stderr," - Construct output JPEG-coded buffer\n");
  JOCTET *buffer_output = new JOCTET[2*buf_size];

  // 呼叫save_jpeg_buffer函式將處理過的CImg物件的影象資料壓縮成JPEG格式寫入輸出緩衝區‘buffer_output ’
  // 呼叫結束時'buf_size'中會輸出實際輸出的資料長度
  img.save_jpeg_buffer(buffer_output,buf_size,60);


  // 將輸出緩衝區‘buffer_output ’中的JPEG影象資料寫入一個新檔案
  const char *filename_output = "foo_output.jpg";
  std::fprintf(stderr," - Save output file '%s'\n",filename_output);
  std::FILE* file_output = std::fopen(filename_output,"wb");
  std::fwrite(buffer_output, sizeof(JOCTET), buf_size, file_output);
  std::fclose(file_output);
  delete[] buffer_output;

  std::fprintf(stderr," - All done !\n");
  return 0;
}

使用很簡單吧?
示例程式碼雖然囉裡囉嗦一大堆,關鍵程式碼其實就只有兩行。唉,幾年我要是多仔細看CImg一眼,知道plugins下還有寶可挖,我又何必費力自己實現JPEG記憶體解碼呢,重複發明輪子,真的好無奈。

執行DEMO

如果你想知道CImg可以都能幹哪些工作,執行一下它的demo就知道了。
windows下編譯DEMO很簡單,在執行resources資料夾下的批處理程式\compile_win_visualcpp.bat就會自動編譯所有的DEMO,因為CImg.h檔案很大,所以編譯的時間有點久。
編譯完成之後,執行CImg_demo.exe就會出現下面的介面,你可以選擇你要執行的DEMO程式

在這裡插入圖片描述

在這裡插入圖片描述

NOTE

另外作為一個簡單小型的影象處理工具庫,它有啥缺點呢?我覺得就最大的缺點就是編譯時間偏長,CImg.h一個頭檔案就有2.8MB,編譯這麼大的原始檔,編譯器的負載很重,所以編譯時間比較長,建議在儘量集中在一個cpp原始碼中使用CImg.h時不要到處隨意#include <CImg.h>,否則會讓整專案的程式碼編譯耗時大大增加。