最近对图像去雾算法产生了兴趣,查阅学习了大量论文和大牛博客后,决定动手用自己熟悉的opencv来编写程序完成。文章专门记录了具体算法实现过程以及其中遇见的大量问题。供自己以后参考,当然也希望能给广大博友提供一些参考。本文着重讲实现过程,图像去雾理论会同过参考文献形式给出。整个内容拟大概分成三个章节来介绍。
开发环境:
计算机系统:win10(64-bit)
编译器:MSVC 2013(64 bit)
Qt版本:5.4.2(64 bit)
OpenCV:3.0.0
1、去雾原理(简述)
其中,I(X)就是我们现在已经有的图像(待去雾的图像),J(x)是我们要恢复的无雾的图像,A是全球大气光成分(未知), t(x)为透射率(未知)。现在的已知条件就是I(X),要求目标值J(x),显然,这是个有无数解的方程。所以要求解J(x),必须通过一系列的先验知识求取t(x)和A.
由先验知识,A是一个全局变量,它可以选取含雾图像的最大像素值来作为A值,这是最简单的办法,还有一些求解方法可以见参考文献1.
t(x)的理论依据。该理论认为,在绝大多数户外无雾图像的局部区域里,至少存在一些像素, 其一个颜色通道的强度值很低。通过对每个像素块R、G和B三通道同时取最小化可求得Jdark,
具体的理论描述可以见参考文献1
t(x)的求解公式如下:
其中,后半部分是含雾图像的暗原色图。 w系数用来保留一部分雾。本文取w=0.9
通过一系列先验知识求取了A和t(x)就可以根据公式(1)的变形求取J(x),其中t0是透射率的最小值,防止出现为0的情况,本文取t0=0.1
2、代码实现:
有了上面的原理就可以实现一个基于暗原色先验的图像去雾算法。该算法的核心是设计最小值滤波函数(minFilter).该函数有一个重要的参数滤波核ksize需要设置,为了能适应不同图像的处理,采取了一种自适应滤波核,其思想也很简单:选取图像行、列的1%中的最大值作为滤波核,同时设置最小滤波核min_ksize=3作为下限 算法代码如下:
1. #include <QCoreApplication>
2. #include <opencv2/opencv.hpp>
3. #include <vector>
4. #include <QDebug>
5. using namespace cv;
6.
7. const double kernRatio=0.01; //自适应核比例
8. const int minAtomLigth=220; //最小大气光强
9. const double wFactor=0.95; //w系数 用来调节
10. const double min_t =0.1; //最小透射率
11. void minFilter(cv::Mat src, Mat &dst, int ksize=3);
12. int main(int argc, char *argv[])
13. {
14. QCoreApplication a(argc, argv);
15. //读取数据
16. "f:/fog8.png");
17. "[picture]",src);
18.
19. //--------------------基于暗原色先验去雾算法---------------------
20. //[1] --求取min(R,G,B)
21. Mat minRgb = Mat::zeros(src.rows,src.cols,CV_8UC1);
22. for(int i=0;i<src.rows;i++)
23. for(int j=0;j<src.cols;j++)
24. {
25. uchar g_minvalue =255;
26. for(int c=0;c<3;c++)
27. {
28. if(g_minvalue>src.at<Vec3b>(i,j)[c])
29. g_minvalue=src.at<Vec3b>(i,j)[c];
30. }
31. minRgb.at<uchar>(i,j)=g_minvalue;
32. }
33. "min[r,g,b]",minRgb);
34. //[1]
35.
36. //[2] --求取暗源色图像
37. Mat darkChannelImage;
38. int ksize=std::max(3,std::max((int)(src.cols*kernRatio),(int)(src.rows*kernRatio))); //求取自适应核大小
39. minFilter(minRgb,darkChannelImage,ksize);
40. "Dark_Channel_Image",darkChannelImage);
41. //[2]
42.
43. //[3] --求取投射率图(根据暗原色先验原理)
44. Mat t=Mat::zeros(src.rows,src.cols,CV_64FC1);
45. for(int i=0;i<src.rows;i++)
46. for(int j=0;j<src.cols;j++)
47. {
48. double>(i,j)=(255.0-
49. double)darkChannelImage.at<uchar>(i,j)*wFactor)/255;
50. }
51. "T_Image",t);//浮点图像 如果要显示 只有范围在0到1之间才有意义
52. //[3]
53.
54. //[4] --求取全球大气光强A(全局量)
55. double A;Point maxLoc;
56. minMaxLoc(darkChannelImage,0,&A,0,&maxLoc);
57. A=std::max(src.at<Vec3b>(maxLoc.y,maxLoc.x)[0],
58. std::max(src.at<Vec3b>(maxLoc.y,maxLoc.x)[1],
59. src.at<Vec3b>(maxLoc.y,maxLoc.x)[2]));
60. //[4]
61.
62. //[5] --根据去雾公式求取去雾图像 J=(I-(1-t)*A)/max(t,min_t)
63. Mat deFog=Mat::zeros(src.rows,src.cols,CV_8UC3);
64. for(int i=0;i<src.rows;i++)
65. for(int j=0;j<src.cols;j++)
66. for(int c=0;c<src.channels();c++)
67. deFog.at<Vec3b>(i,j)[c]=(src.at<Vec3b>(i,j)[c]-
68. double>(i,j))*A)/
69. double>(i,j),min_t);
70. "defog",deFog);
71. //[5]<pre name="code" class="cpp">//最小值滤波 模式模板大小为3*3
72. void minFilter(const cv::Mat src,cv::Mat &dst,int ksize)
73. {
74. //[1] --检测原始图像
75. if(src.channels()!=1)
76. return;
77. if(src.depth()>8)
78. return;
79. //[1]
80.
81. int r=(ksize-1)/2; //核半径
82. //初始化目标图像
83. dst=Mat::zeros(src.rows,src.cols,CV_8UC1);
84. //[3] --最小值滤波
85. for(int i=0;i<src.rows;i++)
86. for(int j=0;j<src.cols;j++)
87. {
88. //[1] --初始化滤波核的上下左右边界
89. int top=i-r;
90. int bottom=i+r;
91. int left=j-r;
92. int right=j+r;
93. //[1]
94.
95. //[2] --检查滤波核是否超出边界
96. if(i-r<0)
97. top=0;
98. if(i+r>src.rows)
99. bottom=src.rows;
100. if(j-r<0)
101. left=0;
102. if(j+r>src.cols)
103. right=src.cols;
104. //[2]
105.
106. //[3] --求取模板下的最小值
107. Mat ImROI=src(Range(top,bottom),Range(left,right));
108. double min,max;
109. minMaxLoc(ImROI,&min,&max,0,0);
110. dst.at<uchar>(i,j)=min;
111. //[3]
112. }
113. //[3]
114. }
3、效果图:
注:左边为原图 中间为投射率图 右面为去雾图像
参考文献:
【1】去雾原理