基于暗通道去雾算法的实现与优化(二)opencv在pc上的实现

注:这一系列的文章是我大四时写的,现在回顾,发现当时的布局和公式实在是太难看了,读者可移步至这里查看舒服的排版github同步博客。


上一篇中,学习了何的论文中的去雾方法,这一篇中,我按照何的论文思路借助opencv 2.4.10 进行了实现,效果的确很好,就是耗时太多了,效果见下图:蓝色圆圈代表大气光值的取值点。



点我查看测试样例


突然发现上一篇中忘了介绍大气光值A的求解了,论文中是这样做的:

1.首先取暗通道图中最亮的千分之一的像素点。

2.根据这些像素点的位置在原图中搜索一个最亮的点,这个点的强度(intensity)就是我们要求的A啦。

论文作者何认为这样做的好处就是避免了原图中比较亮的物体作为A的值,比如图片中的白色的汽车,如果从原图中搜索最亮的点不一定能搜到真实的大气光值,可能是白色物体的反射光(感觉我说的好啰嗦)


开始本篇。。首先分享一下我在实现的过程中发现的几点:

1.大气光值A的解释,原论文中说那个最亮的点的强度就是我们要找的A,那么问题来了,最亮的点的强度是啥?大气光值A是一个值还是一个RGB三元组呢?

    可以肯定的是,A是RGB的一个三元组,RGB三个通道有各自的A,这一点可以从论文的公式中确认。

    那么搜索的时候,如何定义一个点的强度呢?这里,我采用的是求RGB的平均值。

2.透射率t(X)不是一个值,是一个同原图一样大小的矩阵,不同的位置有不同的透射率t

    在具体求解的时候我进行了一点点简化,即:直接把暗通道图输入,这样减少了一次最小值滤波的过程,而且对结果几乎没有影响的,小小的提速。

3.透射率图片的精细化

   这里我采用了别人写的引导滤波算法,这是一种边缘保持算法;实现的时候需要注意的是引导图需要实现归一化为0.0-1.0之间,因为t在0-1之间。

    附连接:http://blog.csdn.net/pi9nc/article/details/26592377 引导滤波算法

4.最后求J的时候注意设置0-255的门限,因为按照公式求时可能出现很大的值也可能有负值出现!!!(调了半天才发现J可能为负值啊亲!)

5.opencv中遍历图片,最好要用指针啊亲!使用at随机取元素 和使用指针取元素在我的测试中相差了0.5s呢。

6.网上流传的一个opencv的c++代码中,在计算暗通道的最小值滤波时把原图分成了若干个window,每一个window赋予相同的值,这显然是不符合论文公式的,希望大家不要被误导!!可能那个博主是想降低时间复杂度吧,但是那样的操作太粗糙了,违背了滤波的概念。

一 暗通道的计算

     我的想法是,先求出每个像素中最暗的那个通道值,最后再统一进行最小值滤波。minfliter是最小值滤波函数,大家自己实现一下就好啦。

 Mat Producedarkimg(Mat& I,int windowsize)
 {
      int min=255;
      Mat dark_img(I.rows,I.cols,CV_8UC1);
      int radius=(windowsize-1)/2;
      int nr= I.rows; // number of rows  
    int nl=I.cols;
      int b,g,r;
      if (I.isContinuous()) {
        nl = nr * nl;
        nr = 1;
     }
      for(int i=0;i<nr;i++)
     {
        const uchar* inData=I.ptr<uchar>(i);        
            uchar* outData=dark_img.ptr<uchar>(i);        
         for(int j=0;j<nl;j++)
         {
                  b=*inData++;
                  g=*inData++;
                  r=*inData++;
                  min=min>b?b:min;
                  min=min>g?g:min;
                  min=min>r?r:min;
                  *outData++=min;
                  min=255;
         }
     }
       dark_img=minfliter(dark_img,windowsize); 
       return dark_img;
 }


二   计算大气光值A(airlight)

输入:暗通道图,原图,窗口大小(必须奇数)

输出:大气光值A,一个包含三个元素的一维数组头

其中,Pixel是我定义的结构体,定义在下边

注意c++返回数组类型时,一定要用new为数组分配空间,不能用int A[3]={0,0,0};这种方式!!否则返回的数组可能会被释放掉!(别问我为什么知道这么多,都是我爬过的坑啊)

int* getatmospheric_light(Mat& darkimg,Mat& srcimg,int windowsize)
 {
      int radius=(windowsize-1)/2;
      int nr=darkimg.rows,nl=darkimg.cols;
      int darksize=nr*nl;
      int topsize=darksize/1000;
      int *A=new int[3];
      int sum[3]={0,0,0};
      Pixel *toppixels,*allpixels;
      toppixels=new Pixel[topsize];
      allpixels=new Pixel[darksize];


      for(int i=0;i<nr;i++){
           const uchar* outData=darkimg.ptr<uchar>(i);
           for(int j=0;j<nl;j++)
             {
                   allpixels[i*nl+j].value=*outData++;
                   allpixels[i*nl+j].x=i;
                   allpixels[i*nl+j].y=j;
              }
       }
      //std::qsort(allpixels,darksize,sizeof(Pixel),qcmp);
    std::sort(allpixels,allpixels+darksize,cmp);
   
      memcpy(toppixels,allpixels,(topsize)*sizeof(Pixel)); //找到了在darkimg中最亮的0.1%个

      int val0,val1,val2,avg,max=0,maxi,maxj,x,y;
      for(int i=0;i<topsize;i++)
      {
           x=allpixels[i].x;y=allpixels[i].y;
           const uchar* outData=srcimg.ptr<uchar>(x);
           outData+=3*y;
           val0=*outData++;
           val1=*outData++;
           val2=*outData++;
           avg=(val0+val1+val2)/3;
           if(max<avg){max=avg;maxi=x;maxj=y;}
        }
      for(int i=0;i<3;i++)
      {
            A[i]=srcimg.at<Vec3b>(maxi,maxj)[i];
            //A[i]=srcimg.at<Vec4b>(maxi,maxj)[i];
            //A[i]=A[i]>220?220:A[i];
       }
       return A;
   }
结构体:
typedef struct Pixel
{
	int x;
	int y;
	int value;
}Pixel;

三 计算透射图(transmission)并精细(refine)化

输入:原图,暗通道图,大气光值A,窗口大小

输出:透射图t

之所以需要暗通道图参数,是因为我进行了简化,使用暗通道图加速计算

最后的引导滤波使用原图的灰度图进行引导,具体实现参考上述链接。

Mat getTransmission_dark(Mat& srcimg,Mat& darkimg,int *array,int windowsize)
 {
	 float test;	 
	 float avg_A=(array[0]+array[1]+array[2])/3.0;
	 float w=0.95;
	 int radius=(windowsize-1)/2;
	 int nr=srcimg.rows,nl=srcimg.cols;
	 Mat transmission(nr,nl,CV_32FC1);

	for(int k=0;k<nr;k++){
		const uchar* inData=darkimg.ptr<uchar>(k);  
		for(int l=0;l<nl;l++)
		{
			transmission.at<float>(k,l)=1-w*(*inData++/avg_A);
		}
	}
	Mat trans(nr,nl,CV_32FC1);
	
	
	Mat graymat(nr,nl,CV_8UC1);
	Mat graymat_32F(nr,nl,CV_32FC1);
	cvtColor(srcimg,graymat, CV_BGR2GRAY);
	for(int i=0;i<nr;i++){
		const uchar* inData=graymat.ptr<uchar>(i);  
		for(int j=0;j<nl;j++)
			graymat_32F.at<float>(i,j)=*inData++/255.0;
	}
	guidedFilter(transmission,graymat_32F,trans,6*windowsize,0.001);
	//bilateralFilter(transmission,trans,10,30,100);
	//GaussianBlur(transmission,trans,Size(11,11),0,0);
	return trans;
 }

四 计算J(X)

输入:原图,透射图,大气光值,窗口

输出:去雾图

 Mat recover(Mat& srcimg,Mat& t,int *array,int windowsize)
 {
	 int test;
	 int radius=(windowsize-1)/2;
	 int nr=srcimg.rows,nl=srcimg.cols;
	 float tnow=t.at<float>(radius,radius);
	 float t0=0.1;
	 Mat finalimg=Mat::zeros(nr,nl,CV_8UC3);
	 int val=0;
	 for(int i=0;i<3;i++){
		 for(int k=radius;k<nr-radius;k++){
			 const float* inData=t.ptr<float>(k);  inData+=radius;
			 const uchar* srcData=srcimg.ptr<uchar>(k);  srcData+=radius*3+i;
			 uchar* outData=finalimg.ptr<uchar>(k);  outData+=radius*3+i;
			 for(int l=radius;l<nl-radius;l++)
			 {				 
				 tnow=*inData++;
				 tnow=tnow>t0?tnow:t0;
				 val=(int)((*srcData-array[i])/tnow+array[i]);	
				 srcData+=3;
				 val=val<0?0:val;
				 *outData=val>255?255:val;
				 outData+=3;
			 }
		 }

	 }
	 return finalimg;
 }


实例效果:




天安门的透射图(transmission)如下:可以看到效果的确很好,很精细,这就是使用原图像的灰度图就行引导的好处。



下一篇,介绍一下我的几点优化,主要是执行时间和透射图的优化。


评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值