本文选自CVPR 2016, 文章链接Dana Berman, Tali Treibitz, Shai Avidan. Non-Local Image Dehazing

复现源码见我的Github

无雾图像和有雾图像的RGB空间表示

一幅没有雾霾的图像可以用几百个不同的颜色很好的近似。将没有雾霾的图像每一个像素值表示为RGB空间的一个点,一幅图像的所有像素在RGB空间中的位置会发生如下图(b)所示的聚类现象:

\[\mathbf{\mathit{I}}(x) = t(x)\mathbf{\mathit{J}}(x) + [1 - t(x)]\mathbf{\mathit{A}}(x)$$(1)

每一个聚类包含的像素点分布在整副图像区域内,他们具有相近的RGB值,但是距离摄像机的距离远近不同。根据雾霾模型的公式,由于在同一聚类里的像素点分布在远近不同的位置所以同一聚类里不同的像素点 $t$ 取不同值( $t$ 是一个只和景物据摄像机距离有关的量),因此无雾霾图像的聚类在有雾霾图像中被拉申成直线,称为雾霾线。直线的一端坐标值为无雾霾图像的聚类点的坐标,另一端为环境光 $\mathbf{\mathit{A}}$ 。对于雾霾图像中的所有聚类点,其对应雾霾线相交与一点,该点坐标即为环境光 $\mathbf{\mathit{A}}$ 。

<div align="center">
<img src="http://images2015.cnblogs.com/blog/810956/201705/810956-20170518222948510-1621450583.png" alt="picture_1" />
</div>

## **雾霾去除算法**
### **检测雾霾线**
在本实验中,选取[Single image haze removal using dark channel prior](http://cn.bing.com/academic/profile?id=00c685d62fb1b326466854c7302cfa20&encoded=0&v=paper_preview&mkt=zh-cn)的环境光估计算法,对于一幅雾霾图像计算其暗通道,然后取暗通道值前0.1%的像素点中最亮的像素值作为环境光的估计。\
然后定义$\mathbf{\mathit{I}}_{A}$如下:
$$\mathbf{\mathit{I}}_{A}(x) = \mathbf{\mathit{I}}(x) - \mathbf{\mathit{A}} = t(x)[\mathbf{\mathit{J}}(x) - \mathbf{\mathit{A}}]$$ (2)
上面的公式将图像在RGB空间中的像素坐标进行了平移变换,环境光的坐标被变换到了原点。将$\mathbf{\mathit{I}}_{A}$表示成球坐标的形式:
$$\mathbf{\mathit{I}}_{A}(x) = [r(x),\theta(x),\phi(x)]$$(3)
这样雾霾图像的像素坐标就被表示成为球坐标空间中围绕着环境光(坐标原点)分布的坐标点。我们来观察(2)式,对于具有相同$\mathbf{\mathit{J}}$和$\mathbf{\mathit{A}}$的景物点,其离摄像机的距离只影响 $t$ 的取值,并且在球坐标系中改变 $t$ 只影响 $r$ 。因此对于无雾霾图像中的每一个聚类点,其对应的雾霾线在上述变换以后的球坐标系中都具有相同的$\theta,\phi$值。也就是说,具有相同的$\theta,\phi$值的像素点其对应的无雾霾图像的像素具有近似的值。为了确定哪些像素具有相同的$\theta,\phi$值,需要将图像根据$\theta,\phi$进行聚类。为此要先要对球面进行等距离剖分,需要注意的是等分$[0,2\pi]\times[0,\pi]$是无法得到均匀剖分的点的,你可以看一下地球仪的经纬剖分结果是否是这样。理论上说如果想要在一个球面上得到20个以上等距离剖分的点是不可能的,原因按住不表,有兴趣的可以搜索伯努利多面体。但是可以通过迭代细分正二十面体的方法来近似得到等距离剖分,具体步骤见[Colorado State University General Circulation Model](http://kiwi.atmos.colostate.edu/rr/groupPIX/ross/ross1/ross1.html)。下面给出C++实现剖分的方法:
```C++
void subdivide(icosahedron& src, polyhedron& dst, int num)
{
dst = src.i; double r = src.radius;

while(dst.vertex_table.size() < num)
{
std::map<std::vector<int>, int> mid_table;
const int size = dst.plane_table.size();
for(int i = 0; i != size; ++i)
{
auto f = *(dst.plane_table.begin());
dst.plane_table.pop_front();
int mid_idx12, mid_idx13, mid_idx23;

if(!is_present(f[0], f[1], mid_table, mid_idx12))
{
cv::Point3d pt1 = dst.vertex_table[f[0]], pt2 = dst.vertex_table[f[1]];
dst.vertex_table.emplace_back((pt1.x + pt2.x)/2, (pt1.y + pt2.y)/2, (pt1.z + pt2.z)/2);
mid_idx12 = static_cast<int>(dst.vertex_table.size()) - 1;
scale2unit(dst.vertex_table[mid_idx12], r);
std::vector<int> temp = { f[0], f[1] };
mid_table[temp] = mid_idx12;
}

if(!is_present(f[0], f[2], mid_table, mid_idx13))
{
cv::Point3d pt1 = dst.vertex_table[f[0]], pt2 = dst.vertex_table[f[2]];
dst.vertex_table.emplace_back((pt1.x + pt2.x)/2, (pt1.y + pt2.y)/2, (pt1.z + pt2.z)/2);
mid_idx13 = static_cast<int>(dst.vertex_table.size()) - 1;
scale2unit(dst.vertex_table[mid_idx13], r);
std::vector<int> temp = { f[0], f[2] };
mid_table[temp] = mid_idx13;
}

if(!is_present(f[1], f[2], mid_table, mid_idx23))
{
cv::Point3d pt1 = dst.vertex_table[f[1]], pt2 = dst.vertex_table[f[2]];
dst.vertex_table.emplace_back((pt1.x + pt2.x)/2, (pt1.y + pt2.y)/2, (pt1.z + pt2.z)/2);
mid_idx23 = static_cast<int>(dst.vertex_table.size()) - 1;
scale2unit(dst.vertex_table[mid_idx23], r);
std::vector<int> temp = { f[1], f[2] };
mid_table[temp] = mid_idx23;
}

std::vector<int> t = { f[0], mid_idx12, mid_idx13 };
dst.plane_table.push_back(t);
t[0] = mid_idx23;
dst.plane_table.push_back(t);
t[2] = f[1];
dst.plane_table.push_back(t);
t[1] = mid_idx13; t[2] = f[2];
dst.plane_table.push_back(t);
}
}
}

```
为了验证剖分的正确性,将剖分结果用MATLAB绘制出来,如下:

<div align="center">
<img src="http://images2015.cnblogs.com/blog/810956/201705/810956-20170518175049369-1063660519.jpg" alt="picture_2“ />
</div>

为了实现快速的查找,对还需对剖分以后的点集建立KD树(因为我们只在乎每一个像素点的$\theta,\phi$值,且剖分后的点都在球面上具有相同的 $r$ ,所以只需要对剖分后的点的$(\theta,\phi)$坐标建立KD树)。下面是建树的C++实现:
```C++
kd_node* build_kdTree(std::vector<cv::Point2d>& sph_table, kd_node* p, std::vector<int>& subset)
{
kd_node* r = new kd_node; r->parent = p;
if(subset.size() == 1)
{
r->data = subset[0];
r->dimension = 0;
r->left = r->right = nullptr;
r->is_leaf = true;
return r;
}

std::vector<std::vector<int>> subsets;
r->dimension = dimension_choice(sph_table, subset);
r->data = split(sph_table, subset, subsets, r->dimension);
r->is_leaf = false;

r->left = subsets[0].size() != 0 ? build_kdTree(sph_table, r, subsets[0]) : nullptr;
r->right = subsets[1].size() != 0 ? build_kdTree(sph_table, r, subsets[1]) : nullptr;

return r;
}
```
为了验证聚类结果, 将聚类结果用不同的颜色显示如下(因为一共使用了三种颜色,聚类的数目远大于3,所以同一种颜色并不一定是同一个聚类):

<div align="center">
<img src="http://images2015.cnblogs.com/blog/810956/201705/810956-20170518175212728-434800058.jpg" alt="picture_3“ />
</div>

其对应的原图如下:

<div align="center">
<img src="http://images2015.cnblogs.com/blog/810956/201705/810956-20170518175229885-1867568387.jpg" alt="picture_4“ />
</div>

### **传输系数初始值的估计**
对于一条给定的雾霾线,里面的所有像素具有近似相等的$J$和$A$的值, $r(x)$有下式决定:
$$r(x) = t(x)\left \| J - A \right \|$$(4)
当$t = 1$对应最大的径向坐标:
$$r_{max} = \left \| J - A \right \|$$(5)
联合(4)(5)得:
$$t(x) = r(x)/r_{max}$$(6)
对于每一条聚类线,由下式估计 $ r_{max} $:
$$\hat{r}_{max} = \max_{x\in H}[r(x)]$$(7)
其中$H$表示每一条雾霾线。\
所以每一个像素点可以估计出对应的传输系数为:
$$\tilde{t}(x) = r/\hat{r}_{max}$$(8)
本文估计的传输系数如下:

<div align="center">
<img src="http://images2015.cnblogs.com/blog/810956/201705/810956-20170518175244947-1624899983.jpg" alt="picture_5“ />
</div>

### **传输系数正则化**
由于$J$始终取正值,所以根据(1)式可以给出传输系数的下界:
$$t_{LB}(x) = 1 - \min_{c \in {R,G,B}}{I_{c}(x)/A_{c}}$$ (9)
所以对传输系数进行下界约定后有:
$$\tilde{t}_{LB}(x) = \max[\tilde{t}(x),t_{LB}(x)]$$(10)
除此之外,上面对传输系数的估计只是基于雾霾线的假设。可以预见的是一个聚类里面的像素点数目越少,上面的假设的可信度越小。还有,传输系数的估计还要考虑道原始像素的空间相似性,我们有理由相信领域内像素值越接近的两个像素点其对应的传输系数也越接近。所以为了权衡上面的两个条件,最终的传输系数有下面的最小化问题给出:
$$\sum_{x}\frac{[\tilde{t}(x) - \tilde{t}_{LB}(x)]^2}{\delta^2(x)} + \lambda\sum_{x}\sum_{y\in N_{x}}\frac{[\tilde{t}(x) - \tilde{t}(y)]^2}{\left \| I(x) - I(y) \right \|^2}$$(11)
其中$\lambda$的作用是用来权衡两个假设之间的关系,这里面取0.1。$N_{x}$表示 $x$ 像素的四领域。$\delta(x)$是每一条雾霾线得到的传输系数估计值的标准差。原文中指出$\delta(x)$扮演着一个重要角色,因为如果一个聚类里面的点数分布随着$\delta(x)$变大而减小,这里说的是像素点的分布情况而不是点数。
这个最优化问题的解法很简单,因为是一个很简单的凸函数,很容易通过求导为零来解,实际上就是求解下面一个线性方程组:
$$(\frac{2}{\delta^2_{i}} + 4\lambda \sum_{p}\frac{1}{\left \| I_{i} - I_{p} \right \|^2})\tilde{t}_{i} - 4\lambda\sum_{p}\frac{\tilde{t}_{p}}{\left \| I_{i} - I_{p} \right \|^2} = \frac{2}{\delta^2_{i}}\tilde{t}_{LBi}$$(12)
本文利用的数值解法是GSL库提供的GMRES算法。正则化结果如下:

<div align="center">
<img src="http://images2015.cnblogs.com/blog/810956/201705/810956-20170518175304682-2012579814.jpg" alt="picture_6“ />
</div>

### **去雾**
去雾步骤很简单,将上面估计出来的传输系数和环境光强度带入下面公式即可达到去雾的目的。
$$\tilde{J} = (I(x) - [1 - \tilde{t}(x)]A)/\tilde{t}(x)\]

注意的是,一定要防止数值的一出,如果单一通道的数值溢出很容易出现色斑。本人前面的工作已经在两个星期之前完成,但是由于没有检测道溢出的问题使这个工作一直道今天才完成

去雾结果如下,这个结果没有做论文中所说的对比度的拉伸,所以结果有些偏暗:

这些工作是本人研一代陪用来打发时间的,有错误欢迎指正,谢谢!

Non-Local Image Dehazing 复现的更多相关文章

  1. Samba远程代码执行漏洞(CVE-2017-7494)本地复现

    一.复现环境搭建 搭建Debian和kali两个虚拟机: 攻击机:kali (192.168.217.162): 靶机:debian (192.168.217.150). 二.Debian安装并配置s ...

  2. Node.js CVE-2017-1484复现(详细步骤)

    0x00 前言 早上看Sec-news安全文摘的时候,发现腾讯安全应急响应中心发表了一篇文章,Node.js CVE-2017-14849 漏洞分析(https://security.tencent. ...

  3. ghostscript远程代码执行漏洞复现

    这两天网上ghostscript又又有漏洞信息了,但是没有poc,于是找找资料把今年8月21日的那个验证下 1.关于ghostscript Ghostscript是一套建基于Adobe.PostScr ...

  4. Windows Shell远程执行代码漏洞((CVE-2018-8414)复现

    0x00   SettingContent-ms文件介绍 .SettingContent-ms是在Windows 10中引入的一种文件类型,它的内容是XML格式进行编写的,主要用于创建Windows设 ...

  5. [漏洞复现]CVE-2010-2883 Adobe Reader 打开pdf电脑即刻中招

    1.漏洞概述: CVE-2010-2883漏洞原理:“Adobe Reader在处理CoolType字体文件的sing表时,存在栈溢出漏洞,当打开特制的恶意PDF文件时,可允许任意代码远程执行.” 影 ...

  6. Node.js CVE-2017-14849复现(详细步骤)

    0x00 前言 早上看Sec-news安全文摘的时候,发现腾讯安全应急响应中心发表了一篇文章,Node.js CVE-2017-14849 漏洞分析(https://security.tencent. ...

  7. 8.Struts2-057漏洞复现

    漏洞信息: 定义XML配置时如果namespace值未设置且上层动作配置(Action Configuration)中未设置或用通配符namespace时可能会导致远程代码执行. url标签未设置va ...

  8. nginx目录穿越漏洞复现

    nginx目录穿越漏洞复现 一.漏洞描述 Nginx在配置别名(Alias)的时候,如果忘记加/,将造成一个目录穿越漏洞. 二.漏洞原理 1. 修改nginx.conf,在如下图位置添加如下配置 在如 ...

  9. nginx CRLF(换行回车)注入漏洞复现

    nginx CRLF(换行回车)注入漏洞复现 一.漏洞描述 CRLF是”回车+换行”(\r\n)的简称,其十六进制编码分别为0x0d和0x0a.在HTTP协议中,HTTP header与HTTP Bo ...

随机推荐

  1. eclipse xml文件中按alt+/没有提示信息

    转载地址:http://blog.sina.com.cn/s/blog_972ddc1b01012mmh.html 今天要写这篇博文是因为遇到这样的不是技术的问题,但找到问题根源再解决这个问题又花费很 ...

  2. Python List Comprehension

    (一)使用List Comprehension的好处 在了解Python的List Comprehension之前,我们习惯使用for循环创建列表,比如下面的例子: numbers = range(1 ...

  3. iOS开发日常遇到问题记录

    1. [self.navigationController.navigationBar setTranslucent:NO]; iOS 7 之后,setTranslucent=yes 默认的   则状 ...

  4. PSP总结报告1

    回答作业问题 1.回想一下你曾经对计算机专业的畅想 我高考后报考的是计算机科学与技术,当时对计算机技术基本了解为零,当时以为什么东西都会用到计算机,学计算机以后不会找不到工作,刚开学的时候对计算机一窍 ...

  5. Data truncation: Truncated incorrect DOUBLE value:

    在写sql查询语句queryRunner.update(connection,"update account set balance=? where name=?",account ...

  6. 20135234mqy-——信息安全系统设计基础第七周学习总结

    第六章 存储器层次结构 存储器系统是一个具有不同容量,成本和访问时间的存储设备的层次结构. CPU寄存器保存着最常用的数据. 靠近CPU的小的,快速的高速缓存存储器作为一部分存储在相对较慢的主存储器( ...

  7. KMP算法之next数组的求解思路

    2.next数组的求解思路 本部分内容转自:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algo ...

  8. spring冲刺第八天

    昨天使人物成功的在地图上运动,并设计炸弹爆炸效果. 今天使炸弹可以炸死人物并可以炸没砖块,并试着将小怪加入地图. 遇到的问题:现在还没有将小怪加入地图,各个模块的整合是比较麻烦的,我还要在这方面下点功 ...

  9. Git管理分支

    管理分支:git branch 直至现在为止,我们的项目版本库一直都是只有一个分支 master.在 git 版本库中创建分支的成本几乎为零,所以,不必吝啬多创建几个分支.下面列举一些常见的分支策略, ...

  10. android--实现通过点击链接打开apk(应用图标在桌面消失)

    首先在AndroidManifest.xml的MAIN Activity下追加以下内容.(启动Activity时给予) ※必须添加项 <intent-filter> <action ...