閒來玩玩圖像處理,拿破崙說過:「不想本身實現濾鏡的美工不是好程序員~~#@!*^...#&!@......」 由於在學校作過不少美工的工做,並且從小就喜歡畫畫因此對圖像相關的東西都還比較感興趣,並且PS提供了強大的功能,那就是本身寫的濾鏡程序能夠以適當的形式嵌入做爲濾鏡庫裏的一種效果而存在,要是能本身能寫經常使用的濾鏡效果之後用起來就方便多了。從最簡單的bitmap開始,bitmap是Windows系統下的標準圖像格式。因爲位圖不採用任何壓縮方法,因此大小通常都比較大。圖像的結構能夠表示以下:程序員
1.首先是位圖文件頭算法
兩字節的位圖文件類型用於指示位圖,其值必須爲0x4d42,即"BM",接着是4個字節存儲的位圖大小,以後的4個字節保用不用,最後是記錄位圖數據距離位圖文件頭的偏移量。編程
2.位圖信息頭數據結構
內容太多就不一一介紹了。ide
若是是24位位圖,是指一個像素的顏色由24bit來決定,24bit中R、G、B三原色各佔8bit,固然也有32bit位圖,就是多了一個α份量,這個份量對於調節顏色的透明度很重要。24位的位圖在位圖信息頭後面緊接着的就是位圖數據,而對於8位、16位的位圖,位圖信息頭後還有一個顏色塊,用於說明各個顏色份量的亮度。當然咱們可使用MFC或者其餘一些類庫提供的編程接口來完成位圖解析與處理的工做,但不從頭實現一遍總會以爲失去了什麼,並且別人的實現方式你怎麼知道?現以C語言方式實現位圖的讀寫與幾個小濾鏡效果。google
首先在bitmap.h中定義須要的數據結構:spa
#pragma pack(1) typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; typedef long LONG; typedef struct bitmap_filehead{ WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffset; }BITMAPFILEHEADER; typedef struct bitmap_infohead{ DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biclrUsed; DWORD biclrImportant; }BITMAPINFOHEADER; typedef struct rgb_quad{ BYTE rgb_red; BYTE rgb_green; BYTE rgb_blue; BYTE rgb_Reserved; }RGBQUAD;
在位圖處理的時候須要注意一些問題,好比說每行的字節數必須是整數,因此下面用到的lineByte的計算方式應該是這樣:設計
int lineByte = (bmpWidth*biBitCount/8+3)/4*4; //每行的字節數必須爲4個倍數,bmpWidth爲寬度,biBitCount爲每一個像素所佔位數
下面來看第一個濾鏡:灰度效果。所謂灰度,無非是全部像素顏色介於黑白之間。由於純黑色的表示是RGB(0,0,0),而純白色則是RGB(255,255,255),因此要保證顏色從黑到白變化(0-255)就只能讓RGB三個份量一致,因此灰度濾鏡的公式爲X=(R+G+B)/3,而後只須要將原圖像的24位顏色數據所有賦值成X就好了。代碼以下,pBmpBuf是用於存放位圖數據的緩衝區。3d
//灰度處理 bool imageGray(unsigned char *pBmpBuf,int lineByte) { if(pBmpBuf==NULL) return false; for(int i=0;i<lineByte*bmpHeight/3;i++) { pBmpBuf[3*i] = (pBmpBuf[3*i]+pBmpBuf[3*i+1]+pBmpBuf[3*i+2])/3; pBmpBuf[3*i+1] = pBmpBuf[3*i]; pBmpBuf[3*i+2] = pBmpBuf[3*i]; } return true; }
貌似博客園不能上傳bmp格式的圖片,因此我把圖片轉成png格式的了,來看一下效果:code
第二個濾鏡:黑白效果。顧名思義也就是圖像只能有黑白兩種顏色,而這個濾鏡有點特殊,那就是根據什麼來判斷一個像素點到底應該是黑仍是白,感性的認識是原來較深的地方應該用黑色,較淺的地方應該用白色。那麼深和淺又怎麼區分呢?這個就要按照你須要達到的效果來定義了,也就是說這個閾值是自定義的,甚至均可以作成一個滑動條來改變。我在這裏是將原像素顏色計算獲得的灰度值與100進行比較,若是大於等於100則爲白色,反之則爲黑色,代碼以下:
bool imageBlackWhite(unsigned char *pBmpBuf,int lineByte) { if(pBmpBuf==NULL) return false; unsigned char temp; for(int i=0;i<lineByte*bmpHeight/3;i++) { temp = (pBmpBuf[3*i]+pBmpBuf[3*i+1]+pBmpBuf[3*i+2])/3; if(temp>=100) { pBmpBuf[3*i] = 255; pBmpBuf[3*i+1] = 255; pBmpBuf[3*i+2] = 255; } else { pBmpBuf[3*i] = 0; pBmpBuf[3*i+1] = 0; pBmpBuf[3*i+2] = 0; } } return true; }
效果以下:
邊緣查找實際上是一個很複雜的效果,往深裏作能夠涉及到濾波器設計等,在這裏從簡,就按照最簡單的想法,用像素的原始顏色數據減去柔化處理後的數據,獲得的就是邊緣,柔化的方法見底部。
bool imageEdge(unsigned char *pBmpBuf,int lineByte,unsigned long totalbytes) { if(pBmpBuf==NULL) return false; tempbuf = (unsigned char *)malloc(totalbytes); memcpy(tempbuf,pBmpBuf,totalbytes); for(int j=1;j<=bmpHeight-2;j++) { for(int i=1;i<=bmpWidth-2;i++) { pBmpBuf[j*lineByte+3*i] =( pBmpBuf[(j-1)*lineByte+3*(i-1)]/8 + pBmpBuf[(j-1)*lineByte+3*i]/8 + pBmpBuf[(j-1)*lineByte+3*(i+1)]/8+ \ pBmpBuf[j*lineByte+3*(i-1)]/8 + pBmpBuf[j*lineByte+3*(i+1)]/8 + pBmpBuf[(j+1)*lineByte+3*(i-1)]/8 + \ pBmpBuf[(j+1)*lineByte+3*i]/8 + pBmpBuf[(j+1)*lineByte+3*(i+1)]/8); pBmpBuf[j*lineByte+3*i+1] =( pBmpBuf[(j-1)*lineByte+3*(i-1)+1]/8 + pBmpBuf[(j-1)*lineByte+3*i+1]/8 + pBmpBuf[(j-1)*lineByte+3*(i+1)+1]/8+ \ pBmpBuf[j*lineByte+3*(i-1)+1]/8 + pBmpBuf[j*lineByte+3*(i+1)+1]/8 + pBmpBuf[(j+1)*lineByte+3*(i-1)+1]/8 + \ pBmpBuf[(j+1)*lineByte+3*i+1]/8 + pBmpBuf[(j+1)*lineByte+3*(i+1)+1]/8); pBmpBuf[j*lineByte+3*i+2] =( pBmpBuf[(j-1)*lineByte+3*(i-1)+2]/8 + pBmpBuf[(j-1)*lineByte+3*i+2]/8 + pBmpBuf[(j-1)*lineByte+3*(i+1)+2]/8+ \ pBmpBuf[j*lineByte+3*(i-1)+2]/8 + pBmpBuf[j*lineByte+3*(i+1)+2]/8 + pBmpBuf[(j+1)*lineByte+3*(i-1)+2]/8 + \ pBmpBuf[(j+1)*lineByte+3*i+2]/8 + pBmpBuf[(j+1)*lineByte+3*(i+1)+2]/8); } } for(int k=0;k<lineByte*bmpHeight/3;k++) { pBmpBuf[3*k] = tempbuf[3*k] - pBmpBuf[3*k]; pBmpBuf[3*k+1] = tempbuf[3*k+1] - pBmpBuf[3*k+1]; pBmpBuf[3*k+2] = tempbuf[3*k+2] - pBmpBuf[3*k+2]; } return true; }
效果以下,因爲只是很簡單的邊緣查找算法,因此效果不是太好,但人物輪廓仍是很明顯的:
反相效果就是底片效果,沒什麼好說的,對於像素的各個顏色份量對255求補,即R = 255-R,G = 255-G,B = 255-B。
bool imageReverse(unsigned char *pBmpBuf,int lineByte) { if(pBmpBuf==NULL) return false; for(int i=0;i<lineByte*bmpHeight/3;i++) { pBmpBuf[3*i] = 255-pBmpBuf[3*i]; pBmpBuf[3*i+1] = 255-pBmpBuf[3*i+1]; pBmpBuf[3*i+2] = 255-pBmpBuf[3*i+2]; } return true; }
效果以下:
柔化效果,這裏採用取自身和周圍8個像素點的顏色平均值來取代原像素的值,這樣可以比較粗略的消除一些噪點,使得圖像可以更加平滑,對於噪點比較多的圖像效果就是圖像更柔和了,而對於已經比較清晰的圖像,相應給人的感受就是清晰度變低了。
bool imageSoft(unsigned char *pBmpBuf,int lineByte) { if(pBmpBuf==NULL) return false; //不用正確的數據結構存儲,數據處理起來就是這麼費勁 for(int j=1;j<=bmpHeight-2;j++) { for(int i=1;i<=bmpWidth-2;i++) { pBmpBuf[j*lineByte+3*i] =( pBmpBuf[(j-1)*lineByte+3*(i-1)]/9 + pBmpBuf[(j-1)*lineByte+3*i]/9 + pBmpBuf[(j-1)*lineByte+3*(i+1)]/9+ \ pBmpBuf[j*lineByte+3*(i-1)]/9 + pBmpBuf[j*lineByte+3*i]/9 + pBmpBuf[j*lineByte+3*(i+1)]/9 + pBmpBuf[(j+1)*lineByte+3*(i-1)]/9 + \ pBmpBuf[(j+1)*lineByte+3*i]/9 + pBmpBuf[(j+1)*lineByte+3*(i+1)]/9); pBmpBuf[j*lineByte+3*i+1] =( pBmpBuf[(j-1)*lineByte+3*(i-1)+1]/9 + pBmpBuf[(j-1)*lineByte+3*i+1]/9 + pBmpBuf[(j-1)*lineByte+3*(i+1)+1]/9+ \ pBmpBuf[j*lineByte+3*(i-1)+1]/9 + pBmpBuf[j*lineByte+3*i+1]/9 + pBmpBuf[j*lineByte+3*(i+1)+1]/9 + pBmpBuf[(j+1)*lineByte+3*(i-1)+1]/9 + \ pBmpBuf[(j+1)*lineByte+3*i+1]/9 + pBmpBuf[(j+1)*lineByte+3*(i+1)+1]/9); pBmpBuf[j*lineByte+3*i+2] =( pBmpBuf[(j-1)*lineByte+3*(i-1)+2]/9 + pBmpBuf[(j-1)*lineByte+3*i+2]/9 + pBmpBuf[(j-1)*lineByte+3*(i+1)+2]/9+ \ pBmpBuf[j*lineByte+3*(i-1)+2]/9 + pBmpBuf[j*lineByte+3*i+2]/9 + pBmpBuf[j*lineByte+3*(i+1)+2]/9 + pBmpBuf[(j+1)*lineByte+3*(i-1)+2]/9 + \ pBmpBuf[(j+1)*lineByte+3*i+2]/9 + pBmpBuf[(j+1)*lineByte+3*(i+1)+2]/9); } } return true; }
效果以下,注意左邊是原圖、右邊是結果,仔細看仍是能看出有差異的,右邊模糊一些:
完整實例程序猛擊這裏。
(注:程序僅處理24位的位圖,因此沒有顏色塊,須要指明須要處理圖片的完整路徑,或者將所需處理圖片放在工程中。結果另存爲了copy-yourbitmap.bmp)