一、直方图均衡化
1. 如何用在MFC中画直方图:
这是一个对话框类的成员函数,IDC_PIC2是对话框中picture control的控件ID,目标直方图计划要画在picture control中。
source_image是CImage类型的一个对象,因为是对灰度级为256的图片进行直方图均衡化,所以默认数据数组大小256.
plot_hist()函数得到该图像各点的频数,并把频数的数组存到data数组中。
source_image.plot_hist();
LONG* data = source_image.data;
void IDD_GRAPH::OnDraw(){
CRect rectClient;
CWnd *pWnd = GetDlgItem(IDC_PIC2);
pWnd->GetClientRect(&rectClient);
CDC *pDC = pWnd->GetDC();
HDC hDC= pDC->GetSafeHdc();
SIZE sz;
sz.cx = rectClient.Width()-30;
sz.cy = rectClient.Height()-70;
int nLength = 256;
source_image.plot_hist();
LONG* data = source_image.data;
CPoint origin(10,sz.cy+10);//原点坐标(10, sz高度+20)
CString str;
pDC->Rectangle(0,0,sz.cx+30,sz.cy+70);//轮廓大小,上空10,下空20
CPen cyPen(PS_SOLID,2,RGB(0,0,0));
CPen *oldPen=pDC->SelectObject(&cyPen);
pDC->MoveTo(origin); // 移到原点
pDC->LineTo(origin.x+sz.cx+5,origin.y);//画X轴
pDC->LineTo(origin.x+sz.cx,origin.y+5);
pDC->MoveTo(origin.x+sz.cx+5,origin.y);//画X轴箭头
pDC->LineTo(origin.x+sz.cx,origin.y-5);
pDC->MoveTo(origin); // 移到原点
pDC->LineTo(origin.x,origin.y-sz.cy);
pDC->MoveTo(origin.x-5,origin.y-sz.cy+5);//画Y轴箭头
pDC->LineTo(origin.x,origin.y-sz.cy);
pDC->LineTo(origin.x+5,origin.y-sz.cy+5);
pDC->TextOut(origin.x+10,origin.y+20,"Histogram of source image");//图名
for(int i=0;iTextOut(origin.x+i,origin.y+3,str);
}
CPen redPen(PS_SOLID,1,RGB(255,0,0)); //红
oldPen=pDC->SelectObject(&redPen);
long total=0;
long lMaxCount=0;
int idMax;
lMaxCount=Max(data,nLength,idMax);
if(lMaxCount>0)
{
for(int i=0;iMoveTo(origin.x+i,origin.y);
pDC->LineTo(origin.x+i,origin.y+1-(int)(data[i]*sz.cy/lMaxCount));
total+=data[i];
}
}
CString strmax;
strmax.Format("最值为%d,数目为%d",idMax,lMaxCount);
pDC->TextOut(origin.x+10,origin.y+40,strmax);
str.Format("总像元数%d",total);
pDC->TextOut(0,0,str);
pDC->SelectObject(oldPen);
ReleaseDC( pDC );
}
2. 统计灰度图像各像素值的频数
·对于函数void plot_hist(),它的主要作用是计算每个像素值的频数,并把值保存到LONG data[256]的数组中。
1 初始化数组LONGdata[256],把里面所有值都置零。
2 用x,y遍历图片的每一个像素,统计0-255像素值的频数。
void MYCImage::plot_hist(){//画直方图函数
int maxY=this->GetHeight();
int maxX=this->GetWidth();
int r,g,b,gray;
for(int i = 0;i<256;i++)//initialize
data[i] = 0;
for(int x=0;xGetPixel(x,y);
r=GetRValue(pixel);
g=GetGValue(pixel);
b=GetBValue(pixel);
gray = (int)(0.28965 * r + 0.60581 * g + 0.10454 * b + 0.5);//四舍五入
data[gray]++;
}
}
}
·对于函数void equalize_hist(CStringPathName),它的主要作用是找到直方图均衡化后的对应像素值,并对图片的像素点进行赋值。
1 传入参数是当前页面打开的图片的路径PathName,目的是为了避免CImage的一些接口句柄问题。
2 调用plot_hist()函数,用于计算每个像素值的频数,得到记录每个像素值频数的数组data。
3 一个双层的嵌套循环。对于0-255的每个像素值,运用公式 sk = (L - 1) * (p0 + p1+ … + pk)进行计算,得到原像素值 r 经过直方图均衡化后对应的像素值 s ,贮存于数组result中,result[r] = s。如直方图均衡化前值为4的像素值,经过均衡化后,像素值变成了0。就写作result[4]= 0.
4 用x,y嵌套循环遍历图片的每一个像素,为其赋上直方图均衡化变化之后的像素值。
void MYCImage::equalize_hist(CString PathName){
MYCImage src;
src.Load(PathName);
if(IsNull())
return;
this->Destroy();
this->Create(src.GetWidth(),src.GetHeight(),src.GetBPP());
int result[256] = {0};
src.plot_hist();
int total_pixel = src.GetWidth() * src.GetHeight();
for(int i = 0;i<256;i++){//直方图均衡化
for(int j = 0;j <= i;j++){
result[i] += src.data[j];
}
result[i] = (int)((double)result[i] * 255 / (double)total_pixel + 0.5 );//要四舍五入
}
for(int x = 0;x < src.GetWidth();x++){
for(int y = 0;y < src.GetHeight();y++){
COLORREF pixel;
int r,g,b,gray;
pixel=src.GetPixel(x,y);
r=GetRValue(pixel);
g=GetGValue(pixel);
b=GetBValue(pixel);
gray = (int)(0.28965 * r + 0.60581 * g + 0.10454 * b + 0.5);//四舍五入
this->SetPixelRGB(x,y,result[gray],result[gray],result[gray]);
}
}
}
二、空间滤波SpatialFiltering
1. 均值滤波
均值滤波降低了图像灰度的尖锐变化,常见的均值滤波应用就是降低噪声。
对于那些为了对感兴趣的物体得到一个粗略的描述而模糊的图像,经过均值滤波之后,可以使较小的物体的灰度与背景混合在一起,而较大的物体变得明显而易于检测。
具体均值滤波函数在void MYCImage::averaging_filter(CStringPathName,int width,intheight)中。传入的参数是原图片的路径,目的是为了避免CImage句柄的一些问题。Width和height是滤波盒子的大小。
首先设置x,y的一个二层嵌套循环,是针对目标图像的每一个像素点经行赋值的。
在这个循环里,还有一个I,j的二层循环,是用来遍历滤波盒子里的所有像素值的。
其中,关于i,j的范围:
for(int i = -width/2;i<=width/2;i++)
for(int j = -height/2;j<=height/2;j++)
在这里,我设置了一个判断条件解决边界问题:
if(x + i < 0 || x + i > src.GetWidth()-1 || y+ j < 0 || y + j > src.GetHeight()-1)
其中(x + i ,y + j)表示在滤波盒子(i ,j)位置上的坐标点对应于原图像的坐标位置。所以显然,如果它们小于零或者超出了宽度或者高度,这是不合法的。所以对于这样的点,我对其进行舍弃,即不对其进行累加。
为了记录到实际上参加累加的像素点的个数,便于最后取平均值,我声明了一个变量count,每次对目标值result进行累加时,count都会自增一。最后只需要result = result / count 即可,然后再把result值赋给新的图像的对应坐标点。this->SetPixelRGB(x,y,result,result,result);
void MYCImage::averaging_filter(CString PathName,int width,int height){
MYCImage src;
src.Load(PathName);
if(IsNull())
return ;
this->Destroy();
this->Create(src.GetWidth(),src.GetHeight(),src.GetBPP());
for(int x = 0;x src.GetWidth()-1 || y + j < 0 || y + j > src.GetHeight()-1){
}
else{
COLORREF pixel;
pixel=src.GetPixel(x + i,y + j);
int gray;
gray = GetRValue(pixel);
result += gray;
count ++;
}
}
}
result = result / count;
this->SetPixelRGB(x,y,result,result,result);
}
}
}
2.Laplacian filter拉普拉斯滤波:
拉普拉斯算子滤波属于二阶微分对图像进行锐化的一种方法。强调图像中灰度的突变,所以比较适用于突出图像细节部分的处理。
拉普拉斯滤波的函数处理方法与均值滤波有异曲同工之妙,它们的函数结构相似。而我对他们的边界点处理也是一样的,所以在此我就不详细说明。
需要说明的一点是,对于我选取的拉普拉斯算子的3*3矩阵,它的中心点值是-8,其余值是1,所以在循环中特地判断找出中心点对其进行* (-8)的特殊处理,其他点和均值滤波方法一样直接累加即可。
最后还涉及到像素值的标定,如果像素值大于255,我令其为255;如果像素值小于0,我令其为0;
void MYCImage::Laplacian_filter(CString PathName,int width,int height){
MYCImage src;
src.Load(PathName);
if(IsNull())
return ;
this->Destroy();
this->Create(src.GetWidth(),src.GetHeight(),src.GetBPP());
for(int x = 0;x src.GetWidth()-1 || y + j < 0 || y + j > src.GetHeight()-1){
}
else if(i == 0 && j == 0){
COLORREF pixel;
pixel=src.GetPixel(x + i,y + j);
int gray;
gray = GetRValue(pixel);
result += gray * (-8);
}
else{
COLORREF pixel;
pixel=src.GetPixel(x + i,y + j);
int gray;
gray = GetRValue(pixel);
result += gray;
}
}
}
if(result < 0)
result = 0;
else if(result > 255)
result = 255;
this->SetPixelRGB(x,y,result,result,result);
}
}
}
利用Soble算子滤波属于一阶微分图像锐化的一种方法(梯度处理)。梯度处理常用于工业检测,可以辅助人工检测产品的缺陷,也可以作为自动检测的预处理。
Soble滤波的方法的函数处理与拉普拉斯滤波也很像。
需要说明的一点是,对于我选取的Soble算子的3*3矩阵,当取的值是滤波模板的最左边一列的时候,它们都是负数;当取的值是滤波模板的中间一列的时候,它们都是0;当取的值是右边一列的时候,它们都是整数。而且第一行与第三行的数绝对值是1,而第二行的绝对值是2。对于这点,我在函数中作出了如下判断:
void MYCImage::Soble_filter(CString PathName,int width,int height){
MYCImage src;
src.Load(PathName);
if(IsNull())
return ;
this->Destroy();
this->Create(src.GetWidth(),src.GetHeight(),src.GetBPP());
for(int x = 0;x src.GetWidth()-1 || y + j < 0 || y + j > src.GetHeight()-1){
}
else if(j == -height/2 || j == height/2){
COLORREF pixel;
pixel=src.GetPixel(x + i,y + j);
int gray;
gray = GetRValue(pixel);
result += gray * 1 * i;
}
else if(j==0){
COLORREF pixel;
pixel=src.GetPixel(x + i,y + j);
int gray;
gray = GetRValue(pixel);
result += gray * 2 * i;
}
}
}
if(result < 0)
result = 0;
else if(result > 255)
result = 255;
this->SetPixelRGB(x,y,result,result,result);
}
}
}