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