闲来玩玩图像处理,拿破仑说过:“不想本身实现滤镜的美工不是好程序员~~#@!*^...#&!@......” 由于在学校作过不少美工的工做,并且从小就喜欢画画因此对图像相关的东西都还比较感兴趣,并且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)