前言
在畢業(yè)設(shè)計里面绪钥,下位機通過OV7725和屏幕截屏灿里,獲得了一張bmp格式的圖像。我希望可以將這個圖片發(fā)送到上位機進行圖像處理程腹,但是由于bmp格式的圖片比較大匣吊,在傳輸過程當中需要的時間比較長。所以,我希望可以將這個bmp圖像轉(zhuǎn)換成jpg格式色鸳,然后再發(fā)送社痛。經(jīng)過試驗,220多k得bmp圖片命雀,轉(zhuǎn)換成jpg格式后只有20多k(注意蒜哀,這不是壓縮比,要根據(jù)具體的圖片來確定咏雌,圖片越單一壓縮比越高)凡怎。
獲得源碼
通過資料搜集,發(fā)現(xiàn)一個開源庫libjpeg可以用來實現(xiàn)這個功能赊抖,這個庫的主要作者是LJG统倒,通過上面的兩個連接你都可以獲得源碼。我采用的后面的LJG的連接的源碼氛雪,下載對應(yīng)機器的版本房匆,然后編譯之。
編譯源碼
編譯源碼也是一個技術(shù)活报亩,我的環(huán)境是Mac OS X EI 10.11(后文的C集成開發(fā)環(huán)境是Xcode 6)浴鸿。
在Mac下編譯源碼的話比較簡單,將下載的壓縮包解壓弦追,打開命令行岳链,cd進入目錄,執(zhí)行命令:
./configure --prefix=“你希望編譯后文件的絕對路徑”(不要帶引號)
然后就執(zhí)行make和make install命令劲件,你就會在你規(guī)定的路徑下看到編譯出來的文件掸哑。
BMP格式
bmp格式,是window的標準位圖格式零远。詳細介紹苗分,百度百科請點擊在這里。下面是簡要介紹牵辣,典型的BMP圖像文件由四部分組成:
- 位圖頭文件數(shù)據(jù)結(jié)構(gòu)摔癣,它包含BMP圖像文件的類型、顯示內(nèi)容等信息纬向;
- 位圖信息數(shù)據(jù)結(jié)構(gòu)择浊,它包含有BMP圖像的寬、高逾条、壓縮方法琢岩,以及定義顏色等信息;
- 調(diào)色板膳帕,這個部分是可選的,有些位圖需要調(diào)色板,有些位圖危彩,比如真彩色圖(24位的BMP)就不需要調(diào)色板 (注意攒磨,本文僅僅處理24位的bmp,非24位的用后面的算法是行不通的汤徽!)娩缰;
- 位圖數(shù)據(jù),這部分的內(nèi)容根據(jù)BMP位圖使用的位數(shù)不同而不同谒府,在24位圖中直接使用RGB拼坎,而其他的小于24位的使用調(diào)色板中顏色索引值。
讀取BMP圖片算法
既然完疫,我們要講bmp圖片轉(zhuǎn)換泰鸡,那第一步首先是要將bmp圖片讀到內(nèi)存當中才可以進行處理。下面壳鹤,敘述bmp的讀取方法盛龄。
讀取BMP頭文件數(shù)據(jù)
根據(jù)上面的知識,我們先嘗試以下讀取BMP的頭文件數(shù)據(jù)芳誓。由資料可知余舶,BMP頭文件具體包括以下內(nèi)容:
- 位圖文件類型 【1,2】字節(jié)
- 位圖文件大星绿省(單位:字節(jié))【3匿值,6】字節(jié)
- 保留字1 【7,8】字節(jié)
- 保留字2 【9赂摆,10】字節(jié)
- 位圖數(shù)據(jù)偏移量(單位:字節(jié))【11挟憔,14】字節(jié)
根據(jù)以上的內(nèi)容,這些信息都是按照順序存放的库正。不難理解出曲楚,我們的處理方法,應(yīng)該是定義一個結(jié)構(gòu)體來存放它們褥符。先看一下目錄結(jié)構(gòu)龙誊,在一個文件夾(我命名為c_test)下新建三個文件:bmp2jpg.h,bmp2jpg.c和main.c喷楣。
下面是結(jié)構(gòu)體定義的代碼:
//以下代碼放在bmp2jpg.h文件里面
//關(guān)于其中DWORD WORD這些變量的說明如下:
//請自己根據(jù)自己使用的系統(tǒng)來確定 WORD是兩個字節(jié)趟大,DWORD是4個字節(jié)
typedef struct tagBITMAPFILEHEADER {
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER,tagBITMAPFILEHEADER;
細心的讀者肯定會發(fā)現(xiàn),上面定義的結(jié)構(gòu)體怎么少了位圖文件類型一項铣焊。這里作出解釋:一般來說逊朽,會在程序開始先讀取并判斷前面2個字節(jié)(即位圖文件類型)是否為BM(若是,說明這兒文件是bmp格式曲伊。否則叽讳,就不再執(zhí)行后面的處理了)追他,所以結(jié)構(gòu)體里面沒有定義。另外岛蚤,編譯器默認是4字節(jié)對齊的(一次讀取4個字節(jié))邑狸,直接添加這個會導(dǎo)致后面的數(shù)據(jù)讀取錯誤。
//bmp2jpg.c
#include "bmp2jpg.h"
// 頭文件結(jié)構(gòu)體
BITMAPFILEHEADER bmpFileHeader;
// 位圖類型
char fileType[2];
// bmp文件指針
FILE *bmp;
void showBmpHead(BITMAPFILEHEADER *pBmpHead) {
printf("bmp文件大小:%u\r\n", (*pBmpHead).bfSize);
printf("保留字1:%d\r\n", (*pBmpHead).bfReserved1);
printf("保留字2:%d\r\n", (*pBmpHead).bfReserved2);
printf("實際位圖數(shù)據(jù)的偏移字節(jié)數(shù):%u\r\n", (*pBmpHead).bfOffBits);
printf("\r\n");
}
// 讀取bmp圖片涤妒,成功返回 0
int readBmpFileHeader(const char *bmp_file) {
bmp = fopen(bmp_file, "rb");
if (bmp == NULL) {
printf("%s\n", "fail to open");
return -1;
}
// 讀取文件前兩個字節(jié)判斷是否bmp格式
fread(fileType, sizeof(char), 2, bmp);
if (strcmp(fileType, "BM")==0) {
fread(&bmpFileHeader, sizeof(tagBITMAPFILEHEADER), 1, bmp);
showBmpHead(&bmpFileHeader);
}else {
printf("%s\n", "this file is not a bmp file");
return -1;
}
return 0;
}
上面是讀取bmp的代碼单雾,用fopen以二進制的形式打開圖片文件,然后讀取一定的字節(jié)數(shù)([2,14]字節(jié)她紫,共12字節(jié))硅堆,放到結(jié)構(gòu)體變量當中,再通過showBmpHead將它們顯示出來贿讹。主函數(shù)那里調(diào)用一下readBmpFileHeader()并把圖片的路徑以字符串的形式傳進去即可渐逃。主函數(shù)如下:
//main.c
#include <stdio.h>
#include "bmp2jpg.h"
int main() {
readBmpFileHeader("/Users/ERIC_LAI/Desktop/bmp2jpg/bmp2jpg/image2.bmp");
return 0;
}
結(jié)果如下所示:
bmp文件大小:230454
保留字1:0
保留字2:0
實際位圖數(shù)據(jù)的偏移字節(jié)數(shù):54
應(yīng)用同樣的道理,我們可以把第二部分的信息也獲取出來并保存到一個結(jié)構(gòu)體上围详,這里不一一贅述朴乖。
讀取BMP圖片數(shù)據(jù)
讀取的方法很簡單,定義一個容器助赞,接著上面的操作把剩下的數(shù)據(jù)讀取進容器里面就可以了买羞。這里敘述的是在pc上的讀取,在stm32等內(nèi)存有限的設(shè)備上不能使用這種方法(后文會敘述)雹食。
// 這里將函數(shù)寫成內(nèi)部方法畜普,可以根據(jù)需要自行調(diào)整
// show?的方法是調(diào)試用的群叶,這里注釋掉了
// 讀取bmp圖片吃挑,成功返回 0
static int readBmpFile(const char *bmp_file) {
FILE *bmp;
static char fileType[2];
bmp = fopen(bmp_file, "rb");
if (bmp == NULL) {
printf("%s\n", "fail to open");
return -1;
}
// 讀取文件前兩個字節(jié)判斷是否bmp格式
fread(fileType, sizeof(char), 2, bmp);
// 讀取頭文件
if (strcmp(fileType, "BM")==0) {
readFlag = fread(&bmpFileHeader, sizeof(tagBITMAPFILEHEADER), 1, bmp);
// showBmpHead(&bmpFileHeader);
// 讀取位圖信息
if (readFlag == 1) {
readFlag = fread(&bmpInfoHeader, sizeof(tagBITMAPINFOHEADER), 1, bmp);
// showBmpInfo(&bmpInfoHeader);
} else {
printf("%s\n", "fail to read the bmp information");
return -1;
}
// 讀取位圖數(shù)據(jù)
data_size = bmpInfoHeader.biWidth * bmpInfoHeader.biHeight * depth;
//動態(tài)分配內(nèi)存,記得需要釋放(這里在調(diào)用這個函數(shù)的函數(shù)里面釋放街立,下文可以看到)
bmp_data = malloc(data_size);
fread(bmp_data, sizeof(BYTE), data_size, bmp);
}else {
printf("%s\n", "this file is not a bmp file");
return -1;
}
fclose(bmp);
return 0;
由于本文只研究真彩色的圖(畢業(yè)設(shè)計時間有限舶衬,以后可能會研究對其他類型圖片的操作),所以對于這種bmp圖片赎离,剩下的部分就是具體的圖片數(shù)據(jù)編碼了逛犹。在真彩色的bmp格式的圖片當中,每一個像素用三個數(shù)據(jù)來表示梁剔,分別是BGR(blue green red)虽画。因為在第二部分,我們獲取了圖片的寬度和高度荣病。通過這兩個參數(shù)码撰,我們便可以知道整個圖片的排列規(guī)則,以及應(yīng)該怎么顯示它們了个盆。
下面脖岛,就是正菜了朵栖!演示如何使用jpeglib庫將bmp圖片轉(zhuǎn)換成jpg格式圖片,使用之前需要注意柴梆,將編譯的時候生成的lib和include文件夾添加到當前工程的目錄下混槐,否則會報錯找不到某些函數(shù),轉(zhuǎn)換函數(shù)代碼如下:
// 這個函數(shù)對外暴露轩性,輸入兩個參數(shù),分別是待轉(zhuǎn)換的bmp圖片路徑和希望生成jpg圖片的路徑
int mp2jpg(const char *bmp_file, const char *jeg_file) {
FILE *outfile;
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1]; /* 行指針 */
int row_stride; /* 行跨度(圖像中一行需要多少個字節(jié)來表示) */
char tmp = '0';
int index = 0;
// 讀取bmp圖像
if (readBmpFile(bmp_file) == -1) return -1;
// 將BGR編碼方式轉(zhuǎn)換成RGB編碼方式
for (index = 0; index < data_size; index = index+3) {
tmp = bmp_data[index];
bmp_data[index] = bmp_data[index+2];
bmp_data[index+2] = tmp;
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
if ((outfile = fopen(jeg_file, "wb")) == NULL) {
fprintf(stderr, "can't open %s\n", jeg_file);
return -1;
}
// 設(shè)置jpeg參數(shù)
jpeg_stdio_dest(&cinfo, outfile);
cinfo.image_width = bmpInfoHeader.biWidth;
cinfo.image_height = bmpInfoHeader.biHeight;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, JPEG_QUALITY, TRUE);
jpeg_start_compress(&cinfo, TRUE);
/* 這里使用庫提供的cinfo.next_scanline作為循環(huán)計數(shù)器狠鸳,這樣我們不需要自己來追蹤揣苏。
* 如果下一行小于圖片的高度則繼續(xù)循環(huán)
*/
row_stride = bmpInfoHeader.biWidth * depth;
while (cinfo.next_scanline < bmpInfoHeader.biHeight) {
// 取當前行最后一位數(shù)據(jù)的地址
row_pointer[0] = & bmp_data[cinfo.next_scanline * row_stride];
// 寫入jpg格式數(shù)據(jù)
(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
// 完成轉(zhuǎn)換,釋放對象空間件舵,關(guān)閉文件
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
free(bmp_data);
fclose(outfile);
return 0;
}
總結(jié)之卸察,目前整個工程下有3個文件,分別是main.c铅祸,bmp2jpg.c和bmp2jpg.h坑质。bmp2jpg.c有上文的兩個函數(shù)readBmp和bmp2jpg,main.c也很簡單只是調(diào)用了一下bmp2jpg這個函數(shù)临梗。比較復(fù)雜的是bmp2jpg.h涡扼,這里定義了一些結(jié)構(gòu)體,暴露了一個函數(shù)盟庞。下面給出代碼:
//main.c
#include <stdio.h>
#include "bmp2jpg.h"
int main() {
bmp2jpg("/Users/ERIC_LAI/Desktop/bmp2jpg/bmp2jpg/image2.bmp",
"/Users/ERIC_LAI/Desktop/bmp2jpg/bmp2jpg/image.jpg");
return 0;
}
//bmp2jpg.h
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "jpeglib.h"
#include <setjmp.h>
// #define WIDTHunsigned charS(bits) (((bits)+31)/32*4)
typedef unsigned char BYTE; //1
typedef unsigned short WORD; //2
typedef unsigned int DWORD; //4
typedef long LONG; //8
typedef struct tagBITMAPFILEHEADER {
DWORD bfSize; //4
WORD bfReserved1; //2
WORD bfReserved2; //2
DWORD bfOffBits; //4
} BITMAPFILEHEADER,tagBITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER {
DWORD biSize; //4
DWORD biWidth; //4
DWORD biHeight; //4
WORD biPlanes; //2
WORD biBitCount; //2
DWORD biCompression; //4
DWORD biSizeImage; //4
DWORD biXPelsPerMeter; //4
DWORD biYPelsPerMeter; //4
DWORD biClrUsed; //4
DWORD biClrImportant; //4
} BITMAPINFOHEADER,tagBITMAPINFOHEADER;
typedef struct tagBGRA {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} BGRA,tagBGRA;
int bmp2jpg(const char *bmp_file, const char *jeg_file);