本章所涉及的方法有:灰度化彩色图像,将图像转换为ASNII码文件,直方图均衡化,伽马校正,哈尔小波变换。
0.知识储备
这里我们处理的是bmp格式的图像,bmp格式的文件有3个文件头,第一个文件头大小为14个字节,主要是对整个文件的信息的存储。
typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
第二个文件头大小为50个字节,主要存储了图像的信息,比如,图像有多少行,多少列,大小等等。
typedef struct tagBITMAPINFOHEADER{ DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
第三个文件头,是图像的调色板,这里我们使用的文件是24位真彩,所以没有调色板,这个文件头不存在,所以我就不列在下面了。
注意:bmp格式图像数据是倒着存贮的,具体可以参考百度百科http://baike.baidu.com/view/7671.htm#2。
1.将彩色图像转换为灰度图像
我们都知道,图像是一像素为单位的,一个像素有三个色彩分量组成,即RGB(红绿蓝),每一个分量由8位(一个字节)组成,范围是0-255 。灰度图像可由彩色图像转化,可以使用公式gray = 0.3*red+0.59*green+0.11*blue,并且三个分量的值相等,即red = gray,green=gray,blue=gray。那么,我们的图像就变成了灰度图像。
/******************************************** * 将文件转换成灰度图像 * ********************************************/ int CBitMapFile::turn2Gray() { if (m_imageArray == NULL) { return -2; } for (int i =0,j= 0,k=0;i<m_sizeImage;) { //转换为灰度图像,注意填充的字节 BYTE blue,green,red,gray; blue = m_imageArray[i]; green = m_imageArray[i+1]; red = m_imageArray[i+2]; m_realColorImageArray[k] = blue; m_realColorImageArray[k+1] = green; m_realColorImageArray[k+2] = red; gray = (BYTE)(0.3*red+0.59*green+0.11*blue); m_imageArray[i] = gray; m_imageArray[i+1] = gray; m_imageArray[i+2] = gray; m_realImageArray[j] = m_imageArray[i]; i += 3; k += 3; j++; ////跳过填充字节 if (j % m_width == 0) { i += m_addByte; } } return 1; }
2.图像转换为ASNII码文件
三个步骤:(1)提取图像数据(2)建立映射表(3)处理灰度并映射
映射表我建立的是8个字符'@','$','#','%','!','~','^','`'。
把灰度值除上32将其范围映射到0-7这个范围内。
将灰度值按照映射表映射。
/****************************************** *对图像的每个像素做文本映射,最终写入文件* ******************************************/ int CBitMapFile::turn2Txt(CFile& txtFile) { char* _txtBuf = new char[m_width*m_height+2*m_height]; memset(_txtBuf,0,m_width*m_height+2*m_height); //文本映射 char txtMap[8] = {'@','$','#','%','!','~','^','`'}; char* _buf = new char[m_width+2]; memset(_buf,0,m_width+2); //TRACE(_T("\'\\r\'=%x,\'\\n\'=%x"),'\r','\n'); for (int i = m_height-1;i>=0;i--) { for (int j = 0;j<m_width;j++) { _buf[j] = txtMap[m_realImageArray[i*m_width+j]>>5]; } _buf[m_width] = '\r'; _buf[m_width+1] = '\n'; for (int k=0;k<m_width+2;k++) { _txtBuf[(m_height-1-i)*m_width+k+(m_height-1-i)*2] = _buf[k]; } } txtFile.Write(_txtBuf,sizeof(char)*(m_width*m_height+2*m_height)); delete _txtBuf; delete _buf; return 1; }
3.直方图均衡化
每一幅图像都有自己的直方图
这幅图像的灰度大部分集中在较高的灰度,均衡化就使图像在概率率较大的地方稀疏一点,概率较小的地方稠密一点。
方法是:(1)统计各个灰度的概率(2)归一化(3)画直方图(4)累计概率(5)取整得到映射(6)将原灰度值映射到新灰度值
/******************************************** * 对bmp文件的每个像素的灰度值进行统计 * ********************************************/ int CBitMapFile::addupLevel() { for (int i = 0;i<m_width*m_height;i++) { m_grayStatistic[m_realImageArray[i]] += 1; } for (int i = 0;i<256;i++) { if (m_grayStatistic[i]>m_max) { m_max = m_grayStatistic[i]; } } //TRACE(_T("m_max = %d\n"),m_max); return 1; } /******************************************** * 对bmp文件的每个像素的灰度值进行归一化 * * 并计算每个像素灰度值的概率 * ********************************************/ int CBitMapFile::turn2One() { for (int i =0;i<256;i++) { m_grayTurn2OneData[i] = (double)m_grayStatistic[i]/m_max; m_grayFrequencyPerLevel[i] = (double)m_grayStatistic[i]/(m_width*m_height); } m_maxFrequency = m_max/(m_width*m_height); return 1; } /******************************************** * 清除统计数组、归一化数组、频率数组 * ********************************************/ void CBitMapFile::clearup() { memset(m_grayStatistic,0,sizeof(LONG)*256); memset(m_grayTurn2OneData,0,sizeof(double)*256); memset(m_grayFrequencyPerLevel,0,sizeof(double)*256); m_max = 0; m_maxFrequency = 0.0f; } /******************************************** * 灰度均衡化 * ********************************************/ void CBitMapFile::equation(CFile& file) { double _temp =0.0f; double* _array = new double[256]; memset(_array,0,sizeof(double)*256); int* _intArray = new int[256]; memset(_intArray,0,sizeof(int)*256); BYTE* _writeArray = new BYTE[m_sizeImage]; memset(_writeArray,0,sizeof(BYTE)*m_sizeImage); for(int i = 0;i<256;i++) { _array[i] = ((m_grayFrequencyPerLevel[i])*255)+_temp; _temp = _array[i]; } for (int i = 0;i<256;i++) { _intArray[i] = (int)(_array[i]); } for (int i = 0,j = 0;i<m_sizeImage;) { _writeArray[i] = _intArray[m_realImageArray[j]]; _writeArray[i+1] = _intArray[m_realImageArray[j]]; _writeArray[i+2] = _intArray[m_realImageArray[j]]; j++; i += 3; if (j%m_width == 0) { for (int k = 0;k<m_addByte;k++) { _writeArray[i+k] = 0; } i += m_addByte; } } file.Write(&m_file_header,sizeof(BITMAPFILEHEADER)); file.Write(&m_info_header,sizeof(BITMAPINFOHEADER)); file.Write(_writeArray,sizeof(BYTE)*m_sizeImage); delete _array; delete _intArray; delete _writeArray; }
4.伽马校正
伽马校正其实很简单,就是将旧的灰度值通过一个由灰度值为底,伽马次幂的映射得到新的灰度值,它的做用是,对于不同的伽马系数的选取,会使得某些较亮的图片,变得对比度适当。
/******************************************** * 伽马校正 * ********************************************/ void CBitMapFile::gammaCorrection(double gamma,CFile& file) { //数据准备 double* _correctData = new double[m_width*m_height]; BYTE* _writeArray = new BYTE[m_sizeImage]; memset(_writeArray,0,sizeof(BYTE)*m_sizeImage); memset(_correctData,0,sizeof(double)*m_width*m_height); //算法 for (int i = 0;i<m_width*m_height;i++) { _correctData[i] = (double)m_realImageArray[i]/255; _correctData[i] = pow(_correctData[i],gamma)*255; } //写入文件准备 for (int i = 0,j = 0;i<m_sizeImage;) { _writeArray[i] = (BYTE)_correctData[j]; _writeArray[i+1] = (BYTE)_correctData[j]; _writeArray[i+2] = (BYTE)_correctData[j]; j++; i += 3; if (j%m_width == 0) { for (int k = 0;k<m_addByte;k++) { _writeArray[i+k] = 0; } i += m_addByte; } } //写入文件 file.Write(&m_file_header,sizeof(BITMAPFILEHEADER)); file.Write(&m_info_header,sizeof(BITMAPINFOHEADER)); file.Write(_writeArray,sizeof(BYTE)*m_sizeImage); delete _writeArray; delete _correctData; }
这个是伽马选择7.5的效果
4.哈尔小波变换(HWT,haar wavelet tansform)
这个是小波变换里最简单的一个,由于涉及较多的数学,这里就不做赘述了,只是附上程序。(程序使用的矩阵乘法是我自己写的,效率较低,经过测试在128*128的大小的图像效果可以,再大就会出现未响应的现象。)
注意,哈尔小波变换的矩阵是偶数*偶数的,所以图片的行列也必须是偶数。
Matrix.h
#pragma once #define ZERO 0 //零阵 #define IDENTITY 1//单位阵 #define HAAR 2//HAAR小波矩阵 #define UNSQUARE -1//非方阵 #define UNSAME -2//非同型矩阵 #define UNSAME -2//非同型矩阵 #define UNEVEN -3//行列非偶数 class CMatrix { public: CMatrix(int r,int c); CMatrix(CMatrix& m); CMatrix(double* d,int r,int c); CMatrix(BYTE* d,int r,int c); ~CMatrix(); int inital(int type);//初始化为单位阵,或特殊矩阵(以后扩展) #ifdef _DEBUG void display(); #endif ////////////////////////////////////////////////////////////////////////// public: CMatrix& transpose();//矩阵的转置 void nonZero(); ////////////////////////////////////////////////////////////////////////// public: CMatrix& operator=(CMatrix& m1); friend CMatrix operator+(CMatrix& m1,CMatrix& m2); friend CMatrix operator-(CMatrix& m1,CMatrix& m2); friend CMatrix operator*(CMatrix& m1,double& a); friend CMatrix operator*(CMatrix& m1,CMatrix& m2); ////////////////////////////////////////////////////////////////////////// public: inline int getRow(){return m_row;} inline int getCol(){return m_col;} //inline CString getName(){return m_name;} inline double getElem(int r,int c){return m_data[r*m_row+c];} inline double* getData(){return m_data;} ////////////////////////////////////////////////////////////////////////// protected: int m_row; int m_col; double* m_data; // CString m_name; };
Matrix.cpp
#include "StdAfx.h" #include "MatrixBase.h" #ifdef _DEBUG #include <iostream> #endif CMatrix::CMatrix(int r,int c) { m_row = r; m_col = c; m_data = new double[r*c]; memset(m_data,0,sizeof(double)*r*c); } CMatrix::CMatrix(double* d,int r,int c) { m_row = r; m_col = c; m_data = new double[r*c]; for (int i = 0;i<r*c;i++) { m_data[i] = d[i]; } } CMatrix::CMatrix(BYTE* d,int r,int c) { m_row = r; m_col = c; m_data = new double[r*c]; for (int i = 0;i<r*c;i++) { m_data[i] = (double)d[i]; } } CMatrix::CMatrix(CMatrix& m) { this->m_row = m.m_row; this->m_col = m.m_col; m_data = new double[m_row*m_col]; for (int i = 0;i<m_row*m_col;i++) { this->m_data[i] = m.m_data[i]; TRACE(_T("data[%d]=%f\n"),i,this->m_data[i]); } } CMatrix::~CMatrix() { delete[] m_data; } int CMatrix::inital(int type) { switch(type) { case IDENTITY: if (m_row != m_col) { return UNSQUARE; } for (int i=0;i<m_col;i++) { m_data[i*m_row+i] = 1; } break; case HAAR: if (m_row != m_col) { return UNSQUARE; } if (m_row%2 != 0) { return UNEVEN; } for(int i = 0;i<m_row/2;i++) { m_data[i*m_row+i*2] = 1; m_data[i*m_row+i*2+1] = 1; } for (int i = m_row/2,j = 0;i<m_row;i++,j++) { m_data[i*m_row+j*2] = -1; m_data[i*m_row+j*2+1] = 1; } break; default: break; } return type; } CMatrix& CMatrix::operator=(CMatrix& m1) { ASSERT(m_row == m1.getRow() && m_col == m1.getCol()); for (int i = 0;i<m_row*m_col;i++) { m_data[i] = m1.getElem(i/m_row,i%m_row); TRACE(_T("\'=\'data[%d]=%f\n"),i,m_data[i]); } return *this; } CMatrix operator+(CMatrix& m1,CMatrix& m2) { ASSERT(m1.m_row == m2.m_row && m1.m_col==m2.m_col); CMatrix ret(m1.m_row,m1.m_col); for (int i = 0;i<m1.m_row*m1.m_col;i++) { ret.m_data[i] = m1.m_data[i]+m2.m_data[i]; TRACE(_T("ret[%d]=%f\n"),i,ret.m_data[i]); } return ret; } CMatrix operator-(CMatrix& m1,CMatrix& m2) { ASSERT(m1.m_row == m2.m_row && m1.m_col==m2.m_col); CMatrix ret(m1.m_row,m1.m_col); for (int i = 0;i<m1.m_row*m1.m_col;i++) { ret.m_data[i] = m1.m_data[i]-m2.m_data[i]; TRACE(_T("ret[%d]=%f\n"),i,ret.m_data[i]); } return ret; } CMatrix operator*(CMatrix& m1,double& a) { CMatrix ret(m1); for (int i = 0;i<m1.m_row*m1.m_col;i++) { ret.m_data[i] *=a; TRACE(_T("ret[%d]=%f\n"),i,ret.m_data[i]); } return ret; } CMatrix operator*(CMatrix& m1,CMatrix& m2) { ASSERT(m1.m_col == m2.m_row); CMatrix ret(m1.m_row,m2.m_col); for (int i= 0;i<m1.m_row;i++) { for (int j = 0;j<m2.m_col;j++) { double _temp = 0; for (int k = 0;k<m1.m_col;k++) { _temp += m1.m_data[i*m1.m_row+k]*m2.m_data[k*m2.m_row+j]; } ret.m_data[i*m1.m_row+j] = _temp; } } return ret; } CMatrix& CMatrix::transpose() { double* _newData = new double[m_row*m_col]; for (int i = 0;i<m_row;i++) { for (int j=0;j<m_col;j++) { _newData[j*m_col+i] = m_data[i*m_row+j]; } } delete m_data; m_data = _newData; return *this; } void CMatrix::nonZero() { for (int i = 0;i<m_row;i++) { for (int j = 0;j<m_col;j++) { if(m_data[i*m_row+j]<0) { m_data[i*m_row+j]=0; } } } } #ifdef _DEBUG void CMatrix::display() { std::cout<<"[\n"; for (int i = 0;i<m_row*m_col;i++) { std::cout<<m_data[i]<<" ,"; if ((i+1)%m_row == 0) { std::cout<<std::endl; } } std::cout<<"]"<<std::endl; } #endif
哈尔小波变换函数
/******************************************** * HAAR小波变换 * ********************************************/ void CBitMapFile::haarTransform(CFile& file) { CMatrix imageMatrix(m_realImageArray,m_height,m_width); CMatrix haarMatrix_r(m_height,m_height); CMatrix haarMatrix_c(m_width,m_width); #ifdef _DEBUG imageMatrix.display(); #endif haarMatrix_c.inital(HAAR); haarMatrix_c = haarMatrix_c.transpose(); haarMatrix_r.inital(HAAR); imageMatrix = haarMatrix_r*imageMatrix; double _d = 0.25*sqrt(2.0); imageMatrix = imageMatrix*_d; imageMatrix = imageMatrix*haarMatrix_c; imageMatrix = imageMatrix*_d; imageMatrix.nonZero(); #ifdef _DEBUG imageMatrix.display(); #endif double* _correctData = imageMatrix.getData(); BYTE* _writeArray = new BYTE[m_sizeImage]; memset(_writeArray,0,sizeof(BYTE)*m_sizeImage); for (int i = 0,j = 0;i<m_sizeImage;) { _writeArray[i] = (BYTE)_correctData[j]; _writeArray[i+1] = (BYTE)_correctData[j]; _writeArray[i+2] = (BYTE)_correctData[j]; j++; i += 3; if (j%m_width == 0) { for (int k = 0;k<m_addByte;k++) { _writeArray[i+k] = 0; } i += m_addByte; } } file.Write(&m_file_header,sizeof(BITMAPFILEHEADER)); file.Write(&m_info_header,sizeof(BITMAPINFOHEADER)); file.Write(_writeArray,sizeof(BYTE)*m_sizeImage); delete _writeArray; //delete _correctData; }
处理后图像
由于bmp格式是倒着存储图像数据的,因为个人时间原因没有将其倒回,所以出现这种现象。
实验软件可以在这里下载:http://download.csdn.net/detail/smells2/4162515