k近邻分类算法(kNN)


1.课程论文题目

去雾。

本次论文我的选题是去雾。由于对《图像处理与分析》这门课很感兴趣,同时也为了锻炼一下自己学习新知识的能力,我没有选择一些容易的常规的图像处理与分析算法,而是选择较为前沿的去雾算法。作为一名计算机专业的学生,我认为仅仅掌握基本的程序设计思想与常规算法是远远不够的,还要培养自己不断学习的能力—学习新知识、学习新思想的能力。这方面很重要的体现就是了解相关科技前沿,尽自己最大努力研读新论文。此外,我认为一个好的课程报告应该有一定的实用价值,或者说写报告的人应该尽可能使自己的选题有实用价值。众所周知,伴随着我国经济的快速发展,一些一味追求GDP的粗制乱造的发展模式所带来的严重后果正在干扰着我们的正常生活方式。我这次报告所要探讨的主题—雾就是其中不可忽视的因素。我这里所说的雾,是广义的雾,包括雾、霾、沙尘、烟等一切导致视觉效果受限的物理现象。由于雾的存在,户外图像质量降低,如果不处理,往往满足不了相关研究、应用的的要求。在雾的影响下,经过物体表面的光被大气中的颗粒物吸收和反射,导致获取的图像质量差,细节模糊、色彩暗淡。通过一定的技术手段,去掉物体表面的雾霭,增强图片效果显得尤为重要。

2.程序设计思路

由于我的程序可处理的图片可以是灰度的、彩色的两种。所以我设计了两种算法实现不同格式图片的处理:直方图灰度分布均衡化去雾算法,基于单幅图像的快速去雾算法。

 

1)、直方图灰度分布均衡化去雾算法

在此我先谈谈我对直方图均衡化的认识。

直方图均衡化(英文名Histogram Equalization),通过使用累积函数对灰度值进行“调整”以实现对比度的增强。具体说来,即把原图像对应的灰度直方图从比较集中的灰度区间通过一定的转换变成在全部灰度范围内的均匀分布。直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度范围内的像素数量大致相同。

直方图均衡化的具体编程实现为

    A.统计每个灰度等级对应的像素点数 

    B.计算累计概率

    C.重新映射图像像素点灰度

在这里我首先统计0-255个灰度等级i的像素点数Color[i],然后分别除以图像总的像素点求出每个灰度等级对应的概率PerProbability[i];接着运用积分的思想来计算累计概率AccuProbability[i];然后映射求得新的像素值NewColor[i]

 k近邻分类算法(kNN)

 上述坐标图和积分式解释了积分思想求累计概率的正确性。下面举个例子展示整个过程。

a.假设下面使是图像对应的像素点:

 k近邻分类算法(kNN)

b.下面是中间处理工程

 k近邻分类算法(kNN)

c.经过处理后图像对应的像素点:

 k近邻分类算法(kNN)

为什么直方图灰度分布均衡化可以在一定成都上达到去雾的效果呢?这是因为通过使用累积函数对灰度值进行“调整”以实现对比度的增强,一定程度上使暗的地方更暗、亮的地方更亮,从而在视觉上达到去雾的效果。

但是上面的去雾效果仅仅局限于灰度图片,而不能处理更为实用的彩色图像。经过上述直方图处理后,达到均衡化图像的目的,但是图像会在原图基础上失真或称为走样,即整幅图像的颜色发生很大改变,严重偏离去雾的初衷(去雾是在原图基础上除去表面的薄纱从而增强整幅图像的视觉效果)。

 综上,直方图灰度分布均衡化去雾算法可以处理灰色图,但对于彩色图像就会出现走样,达不到去雾的效果。为了解决彩色图像的去雾问题,我又看了一些最新论文,其中包括何凯明博士的《Single Image Haze Removal Using Dark Channel Prior》论文,这是一篇非常深刻的文章,里面公式推导很多,我硬着头皮读完后看不懂,于是又到博客园、CSDN上筛选出几篇相对容易理解和实现的论文,其中我对清华大牛刘倩、陈茂银、周东华的《基于单幅图像的快速去雾算法》论文非常感兴趣,决定以其为突破点尝试彩色图像的去雾算法实现。

 

 

2.基于单幅图像的快速去雾算法

由于基于单幅图像的快速去雾算法涉及的学科非常多、理论性非常强,在此我的设计思路主要是取自清华大牛刘倩、陈茂银、周东华的《基于单幅图像的快速去雾算法》论文以及在编写程序的过程中在一定程度上参考了CSDN博客兰专家laviewpbt博客《一种可实时处理 O(1)复杂度图像去雾算法的实现》,由于所处理图片的格式不同,以及论文、博客里都没提供源码,所以在编写程序的过程中我并无抄袭。在读论文的过程中,真的是充满了对论文撰写者的佩服之情。之前徐老师在课堂上介绍说《图像处理与分析》是门交叉学科,博大精深,那时还愚昧的不以为然,一则是因为所学知识太少,眼界受限,没有了解相关的前言领域;二来是课下编程不够,虽说自己实现了图像几何变换、图像增强等算法,但总的难度很低,而且刚开始接触时不会还能参照先驱的程序。在看了几篇图像处理的前研论文之后,我感觉到自己的愚昧无知。下面就谈谈对我《基于单幅图像的快速去雾算法》论文的一些理解。

在计算机视觉领域,通常运用严密的数学物理方法来描述雾霾等自然现象,《基于单幅图像的快速去雾算法》论文中提出算法的出发点就是基于衰减模型和环境光模型的,并且提出了一个模型表达式:

 

x为空间坐标,F是待恢复的无雾图像,r是大气散射系数,d是景物深度,A是与x无关的全局大气光,H为有雾情况下的景物图像。

这是基于大气理论的光学表达式,由于知识有限,我只能照搬了。

 k近邻分类算法(kNN)

      上面是程序的算法流程,下面我将依次谈谈我对每个步骤的理解。

Step 1  读入待处理的图像

论文以及其他关于去雾的程序都是基于较高格式的图像的,普通bmp尤其是268色位图的处理没有涉及,这也是我选择去雾算法实现的一个重要原因—不简单机械地重复别人做过的东西。

 

 Step 2 求空间最小颜色通道

基本色通道(color channel),就是将构成整体图像的颜色信息整理并表现为单色图像的工具。我们知道现实世界中的颜色都是由红、绿、蓝3中颜色组成的。本步骤是要求出所有空间坐标位置对应的最小颜色通道,即Ri,Gi,Bi中最小者,得到新图像M

 

 Step 3 M进行均值滤波

     均值滤波是徐老师在课堂上重点讲过的。均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素,再用模板中的全体像素的平均值来代替原来像素值。 可见均值滤波的关键是选择模板,然后对应相乘相加相除,这个过程是比较简单。

 

Step 4 M所有像素点求均值Mav

直接把M中的下所有像素相加然后除以总像素数即可。

 

Step 5  求环境光估计值

去雾很大程度上就是要考虑环境光的影响,还远物体的庐山真面目。利用均值滤波求出的环境光具有深度缓变处平滑,深度跳变处随之跳变变的特点。当M的深度发生较大变化时M,Mav也随之发生较大变化,最终体现在L上。

 

Step 6 求出全局大气光

全局大气光也是对图像的重要影响因素。通常取最大灰度值为全局大气光A

 

Step 7 还原图像

     考虑环境光、全局大气光的影响,利用算法开头给出的模型表达式还原图片。

 

   上面就是整个过程。在原论文中,上述都给出较为严格的证明,由于相关专业知识有限,我就不交代推理证明过程了。

    

   

3.数据结构与算法设计

1)、直方图灰度分布均衡化去雾算法

     由于整个处理过程中没有特殊要求的操作,所以开辟二维数组就可解决问题。分别开辟3个二维数组

int *Color=new int[MAXNCOLOR];//Color[i]存储该灰度等级i对应的像素点数,同时为了节省内存空间,在映射后Color[i]则存储原来灰度等级i对应的新灰度等级,这样做可以少申请一个二维数组,由于图像处理很占内存空间,因此优化是必须的。

double *PerProbability=new double[MAXNCOLOR];//存储灰度等级i对应的概率PerProbability[i].

double* AccuProbability=new double[MAXNCOLOR];//存储灰度等级i对应的累加概率AccuProbability[i],此数组可以和上面的PerProbability[]数组合成一个,为了程序的可读性,我开了两个数组,但须明确此处可以优化。

 

此算法原理理解起来可能不是很容易,但实现起来却很简单,无需特殊编程技巧,在此就不给出伪代码,只给出大致流程:统计每个灰度等级对应的像素点数 ,计算累计概率,重新映射屏幕像素点灰度,具体实现见第4部分直方图灰度分布均衡化去雾算法源代码。此算法时间复杂度为O(n),空间复杂度为O(n),n为空间像素点数。

 

2.基于单幅图像的快速去雾算法

     上面的空间最小颜色通道和均值Mav可以在求所有点的颜色通道是一起求出,不必另写循环语句比较求得。由于编程实现较简单,在此就不多说了。

    具体算法步骤见程序设计思路部分,具体源代码见第4部分基于单幅图像的快速去雾算法。

 

4.程序关键代码

、直方图灰度分布均衡化去雾算法源代码
/*************************************************
*功能:直方图灰度分布均衡化去雾算法
**************************************************/
void CMyDIPView::OnMenuitem32790() 
{
	const int MAXNCOLOR=256+10;
	unsigned char *lpSrc;
	CMyDIPDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if(pDoc->m_hDIB == NULL)
		return ;
	LPSTR lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) pDoc->m_hDIB);
	LPSTR lpDIBBits=::FindDIBBits (lpDIB);
	int cxDIB = (int) ::DIBWidth(lpDIB);
	int cyDIB = (int) ::DIBHeight(lpDIB);
	int *Color=new int[MAXNCOLOR];
	memset(Color,0,sizeof(Color));
	long lLineBytes = WIDTHBYTES(cxDIB * 8);

	//统计每个灰度等级对应的像素点数
	for(int i = 0; i < cyDIB; i++)
	{
		for(int j = 0; j < cxDIB; j++)
		{
			Color[*(lpDIBBits + lLineBytes * (cyDIB - 1 - i) + j)]++;
		}
	}	

	//计算每个灰度等级对应的概率
	double *PerProbability=new double[MAXNCOLOR];
	double TotalPixelNumber=cxDIB*cyDIB*1.0;
	for(i=0;i<256;i++)
	{
		PerProbability[i]=Color[i]*1.0/TotalPixelNumber;
	}
    //计算每个灰度等级对应的累加概率
	double* AccuProbability=new double[MAXNCOLOR];
	AccuProbability[0]=PerProbability[0];
	Color[0]=(int)(AccuProbability[0]*255.0f+0.5f);
	for(i=1;i<256;i++)
	{
		AccuProbability[i]=AccuProbability[i-1]+PerProbability[i];
		Color[i]=(int)(AccuProbability[i]*255.0f);
	}
	
	//重新映射屏幕像素点灰度
	for(i = 0; i < cyDIB; i++)
	{
		for(int j = 0; j < cxDIB; j++)
		{
			unsigned char perpixel=*(lpDIBBits + lLineBytes * (cyDIB - 1 - i) + j);
			*(lpDIBBits + lLineBytes * (cyDIB - 1 - i) + j)=Color[perpixel];
			
		}
	}
	::GlobalUnlock((HGLOBAL) pDoc->m_hDIB);
	Invalidate(TRUE);
}



.基于单幅图像的快速去雾算法
/*************************************************
*功能:基于单幅图像的快速去雾算法
**************************************************/
void CMyDIPView::OnMenuitem32791() 
{
	unsigned char *lpSrc;
	CMyDIPDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if(pDoc->m_hDIB == NULL)
		return ;
	LPSTR lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) pDoc->m_hDIB);
	LPSTR lpDIBBits=::FindDIBBits (lpDIB);
	int cxDIB = (int) ::DIBWidth(lpDIB); 
	int cyDIB = (int) ::DIBHeight(lpDIB);  
	long lLineBytes = WIDTHBYTES(cxDIB * 8); 
	double sumMinColor=0;//所有颜色通道的累加和
	double aveMinColor=0;//所有颜色通道的平均值
	unsigned char minColor;//颜色通道最小值
	unsigned char maxColor=0;//所有颜色通道最大值
	int maxFlag=0;//标记颜色通道最大值权重
	int minFlag=0;//标记颜色通道最小值权重
	unsigned char *mNewPicture=new unsigned char[cxDIB*cyDIB];
	for(int i = 0; i < cyDIB; i++)
	{
		for(int j = 0; j < cxDIB; j++)
		{
			
			unsigned char perpixel=*(lpDIBBits + lLineBytes * (cyDIB - 1 - i) + j);
			unsigned char red=perpixel/32;
			minFlag=32;
            minColor=red;
			if(maxColor<red)
			{
				maxColor=red;
				maxFlag=32;
			}
			perpixel%=32;
			unsigned char green=perpixel/8;
			if(minColor>green)
			{
				minColor=green;	
				minFlag=8;
			}
			if(maxColor<green)
			{
				maxColor=green;
				maxFlag=8;
			}
			
			unsigned char blue=perpixel%8;
			if(minColor>blue)
			{
				minFlag=1;
				minColor=blue;
			}
			if(maxColor<blue)
			{
				maxColor=blue;
				maxFlag=1;
			}
			mNewPicture[cxDIB * (cyDIB - 1 - i) + j]=minColor*minFlag;
			sumMinColor+=minColor*minFlag;
		}
	}
	aveMinColor=sumMinColor/(cxDIB*cyDIB);//颜色通道平均值
	maxColor*=maxFlag;
	double maxAveColor=0;//均值滤波后颜色通道平均值
	double *mAveNewPicture=new double[cxDIB*cyDIB];
	double xishu=1.0/100.0;
	int h=cyDIB,w=cxDIB;
	int tem_w=10,tem_h=10;
	int dw = cxDIB;
	double sum;
    for(i=0;i<h;i++)
    {  
		for(int j=0;j<w;j++)
		{   	
			sum=0;	
			if( j<((tem_w-1)/2) || j>(w-(tem_w+1)/2) || i<((tem_h-1)/2) || i>(h-(tem_h+1)/2) )
				mAveNewPicture[i*dw+j]=mNewPicture[i*dw+j];
			else 
			{ 	
				for(int m=i-((tem_h-1)/2);m<=i+((tem_h-1)/2);m++)
				{
					for(int n=j-((tem_w-1)/2);n<=j+((tem_w-1)/2);n++)
					sum+=mNewPicture[m*dw+n];

				}	
				sum=sum*xishu;sum = fabs(sum);	
				if(sum<0)     sum=0;	
				if(sum>255)sum=255;
				mAveNewPicture[i*dw+j]=sum;
			}
			if(maxAveColor<mAveNewPicture[i*dw+j])
				maxAveColor=mAveNewPicture[i*dw+j];
		}
	} 
	double *lNewPicture=new double[cxDIB*cyDIB];
	for(i = 0; i < cyDIB; i++)
	{
		for(int j = 0; j < cxDIB; j++)
		{
          lNewPicture[i*cxDIB+j]=min(min(aveMinColor*1.28,0.9)*mAveNewPicture[i*cxDIB+j],mNewPicture[i*cxDIB+j]);
		}
	}
	double A=(maxColor+maxAveColor)/2.0;
	for(i = 0; i < cyDIB; i++)
	{
		for(int j = 0; j < cxDIB; j++)
		{
			double H=*(lpDIBBits + lLineBytes * (cyDIB - 1 - i) + j);
	        double L=lNewPicture[cxDIB * (cyDIB - 1 - i) + j];
			*(lpDIBBits + lLineBytes * (cyDIB - 1 - i) + j)=(H-L)/(1-L/A);
		}
	}
	::GlobalUnlock((HGLOBAL) pDoc->m_hDIB);
	Invalidate(TRUE);
}


 

 5.程序运行结果

(2)、直方图灰度分布均衡化去雾算法

图像1

  去雾前

 k近邻分类算法(kNN)

 

图像2

   去雾前:

 k近邻分类算法(kNN)


图像3

 k近邻分类算法(kNN)

 

    对于上面3张灰度图像,直方图灰度分布均衡化达到的去雾效果是不错的,但是对于彩色图像,却无法回避走样问题。下面给出的就是直方图灰度分布均衡化在处理彩色图像时遇到的问题。

图像4

   去雾前

 k近邻分类算法(kNN)

   图像4在直方图灰度分布均衡化后景物形状保持了,但色彩却严重偏离了,关于原因,上面已有解释。



(2).基于单幅图像的快速去雾算法

图像5

 k近邻分类算法(kNN)

 

图像6

  去雾前

 k近邻分类算法(kNN)

 

图像7

  去雾前

 k近邻分类算法(kNN)

 

6.编程中遇到的困难及解决方法

1)、选题

    由于徐老师没有要求具体做什么,这个长期习惯老师出题然后自己动手编程的我带来一定干扰。自主选题也是一种自主学习能力,结合自己的兴趣与能力选择适合自己的选题。由于之前做ACM,我认为自己有一定的解决问题能力,于是选择了较为前沿的去雾算法。

  

(2)、查资料

由于本次我的选题较为前沿,所以相关资料不好找,有用的思想、算法等都得通过最近几年的论文获得。之前没有读过多少论文,尤其是像这次这样理论性很强的论文,所以在这方面遇到一定的麻烦。最后,通过CSDN和博客园两个平台,我找的了相关论文和可供参考的博客(在此对大牛们表示感谢)。

 

(3)、关于bmp格式的256色位图的处理

在我看到的相关论文里讨论的都是较高格式的图像,而徐老师给的程序框架是基于bmp格式的256色位图。我首先想到的是修改老师的程序,使之变成具有处理较高格式图片的能力,但由于时间以及没有牢固的MFC编程基础,最终没能动手改。但通过仔细阅读相关论文发现算法是可以处理bmp格式的256色位图的。

 

(4)、直方图灰度分布均衡化去雾算法处理彩色图

开始我打算直接用直方图灰度分布均衡化去雾算法处理彩色图片,但总是达不到预期的效果。图像上景物的轮廓是保持了,但颜色却发生很大的变化,没有达到去雾的效果。在多次修改程序不见改善的情况下我选择了重新看课本,经过一番理论分析,我搞清楚了直接直方图均衡化能增强整幅图像的效果,但不能使彩色图像的黯淡部分在不走样的情况下变亮。当然后来看到博客园上专家Imageshop的

 《限制对比度自适应直方图均衡化算法原理、实现及效果》以及

 《局部自适应自动色阶/对比度算法在图像增强上的应用》文章发现用人工智能中自适应方法也可以使直方图均衡化具备处理彩色图像的能力。但自适应的相关理论对刚开始接触图像处理的我来说有难度,所以我只用直方图均衡化处理灰度图像。

 

(5)、获取bmp格式的256色位图的颜色通道

基于单幅图像的快速去雾算法有一个很重要的一点就是获取原图像空间坐标像素点处的颜色通道中的最小者。关于获取颜色通道,在24位存储格式时可以通过依次访问3个字节获取相应的颜色通,也可以通过GetRValueGetRValueGetRValue等函数直接取出红绿蓝3种颜色通道,等等,但这些都不能处理8位的图像,后来我在CSDN上论坛上问别人,有人说RGB在一个字节中分别占3,2,3个比特位,于是我选用想到位操作或求模两种方法。

 

(6)、均值滤波模板大小的设置

     关于这点,徐老师课堂上是可以结合实际情况自己设定。但当面对具体情况时,模板大小的设定很有技巧。均值滤波可以对图像进行模糊处理、平滑处理,在一定范围了随模板大小的增大而增大,当达到某个峰值之后随模板大小的增大而减小。上述是本次实验经验所得,可能不符合理论,但我的程序结果却支持这种结论。

 

(7)、ρ参数的大小设置

   ρ参数的大小控制了图像去雾能力的大小,越大,雾气越少,图像越显得暗,越小,图像偏白,雾气越浓。所以在合适的范围内参数ρ应该尽可能取得大点,实验中我去的是1.28

 

7.总结心得

     在本次写报告的过程中,我最大的感觉就是知识欠缺。在我目前接触的图像处理算法中,没有特别难编程实现的,大多可以通过基本的编程方法解决。但是,其中的指导思想却是很需要用心体会的。在编程时一定要上升到理论层次,多问几个为什么:为什么这么做,为什么会有这样的效果等等。

    在我看来,图像处理就是一个矩阵变换,变换的是矩阵中对应空间坐标的像素值。而变换过程中要用到很多模板、算子、算法等,这些是进行矩阵变换的依据。在理论工作完成以后,再进行动手编程,这样才能达到真正学习图像处理与分析的目的。有的理论看起来很简单,但却温含着丰富的专业知识,需要我们静下心来慢慢体会,仔细琢磨;有的理论看起来就很难,就像我选的《基于单幅图像的快速去雾算法》这篇论文,从头到尾都体现着丰富的专业知识、严密的逻辑推理能力与深厚的科学内涵,对于这样的理论,更是难得的思想启蒙,我们应该迎难而上,尽可能的读懂它。当然在编程的过程中,我们也要不断优化自己的程序,图像对象的数据量很大,稍不留神算法的时间或空间复杂度就会很高,即便常数级的优化也是很有必要的,能少开辟数组是就少开辟,能少用一层循环就少用一层循环。

有人的地方就会有审美需求,而图像处理就是一种美化过程。图像处理是指对图像信息进行加工,从而满足人类的心理、视觉或者应用需求的思想与技术。常用的图像处理方法有图像的采集与数字化、图像的编码、图像的增强、图像恢复、图像分割、图像分析等。在学习《图像处理与分析》课时要注重理论,多想想为什么;同时也应多动手,获得相应的编程体验。

 

 

 

8、参考文献

1.刘倩、陈茂银、周东华 《基于单幅图像的快速去雾算法》论文

 

2.CSDN博客兰专家laviewpbt博客《一种可实时处理 O(1)复杂度图像去雾算法的实现

 

3.何凯明 《Single Image Haze Removal Using Dark Channel Prior》论文

这是我图像处理与分析结课报告,参考了很多大牛的论文、博客,在此对他们表示感谢!文章和程序中可能有很多缺陷或错误,还请大牛斧正。


k近邻分类算法(kNN)

上一篇:Redmine安装todo list插件


下一篇:树莓派学习笔记——I2C设备载入和速率设置