转载地址 :文章链接:http://blog.csdn.net/xingchenbingbuyu/article/details/50836506
首先还是简单说下暗通道先验去雾算法,这里只是记录性质,详细推荐看论文,写的非常好:
雾图模型
I(x) ——待去雾的图像
J(x)——无雾图像
A——全球大气光成分
t——折射率(大气传递系数)
暗通道先验
在无雾图像中,每一个局部区域都很有可能会有阴影,或者是纯颜色的东西,又或者是黑色的东西。因此,每一个局部区域都很有可能有至少一个颜色通道会有很低的值。把这个统计规律叫做Dark Channel Prior。
暗通道定义
Jc表示彩色图像的每个通道
Ω(x)表示以像素X为中心的一个窗口
意义:首先求出每个像素RGB分量中的最小值,存入一副和原始图像大小相同的灰度图中,然后再对这幅灰度图进行最小值滤波
对于两个最小化的顺序,我看了下,何凯明的两遍论文用了不同的顺序。
计算折射率
右边第二项其实就是有雾图像的暗通道。
由于空间透视现象/浓淡远近,部分雾的存在有助于我们感知距离和深度,加权值修正:
估计大气光
1.选取暗通道图像暗通道最亮的0.1%的像素(一般来说,这些像素表示雾浓度最大的地方)
2.取输入图像里面这些像素对应的像素里面最亮的作为大气光
注:选中的像素未必是全图最亮的,而且要比选取全图最亮的方式鲁棒性更好。
去雾
大致就是这个流程:
1.求图像暗通道
2.利用暗通道计算出折射率
3.利用暗通道估计大气光
4.代回雾图公式去雾
代码如下,比较简陋:
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
//求暗通道
Mat darkChannel(Mat src)
{
Mat rgbmin = Mat::zeros(src.rows, src.cols, CV_8UC1);
Mat dark = Mat::zeros(src.rows, src.cols, CV_8UC1);
Vec3b intensity;
for (int m = 0; m(m, n);
rgbmin.at(m, n) = min(min(intensity.val[0], intensity.val[1]), intensity.val[2]);
}
}
//模板尺寸
int scale = 7;
//cout << "Please enter the mask scale: " << endl;
//cin >> scale;
//边界扩充
int radius = (scale - 1) / 2;
Mat border;
//由于要求最小值,所以扩充的边界可以用复制边界填充
copyMakeBorder(rgbmin, border, radius, radius, radius, radius, BORDER_REPLICATE);
//最小值滤波
for (int i = 0; i < src.cols; i++)
{
for (int j = 0; j < src.rows; j++)
{
//选取兴趣区域
Mat roi;
roi = border(Rect(i, j, scale, scale));
//求兴趣区域的最小值
double minVal = 0; double maxVal = 0;
Point minLoc = 0; Point maxLoc = 0;
minMaxLoc(roi, &minVal, &maxVal, &minLoc, &maxLoc, noArray());
dark.at(Point(i, j)) = (uchar)minVal;
}
}
return dark;
}
uchar light(vector inputIamgeMax)
{
uchar maxA=0;
for (int i = 0; i < inputIamgeMax.size() - 1; i++)
{
if (maxA < inputIamgeMax[i + 1])
{
maxA = inputIamgeMax[i + 1];
}
}
return maxA;
}
//Mat dark(Mat image)
//{
// Mat minColor(image.rows, image.cols, CV_8UC1, Scalar(180, 120, 50));
// Mat darkChannel(image.rows, image.cols, CV_8UC1, Scalar(180, 120, 50));
//
// //求每个像素BGR三通道最小值
// for (int i = 0; i < image.cols; i++)
// {
// for (int j = 0; j < image.rows; j++)
// {
// uchar blue, green, red;
// blue = image.at(Point(i, j))[0];
// green = image.at(Point(i, j))[1];
// red = image.at(Point(i, j))[2];
// minColor.at(Point(i, j)) = minBGR(blue, green, red);
// }
// }
//
// //模板尺寸
// int scale;
// cout << "Please enter the mask scale: " << endl;
// cin >> scale;
//
// //边界扩充
// int radius = (scale - 1) / 2;
// Mat border;
// //由于要求最小值,所以扩充的边界可以用复制边界填充
// copyMakeBorder(minColor, border, radius, radius, radius, radius, BORDER_REPLICATE);
//
// //最小值滤波
// for (int i = 0; i < image.cols; i++)
// {
// for (int j = 0; j < image.rows; j++)
// {
// //选取兴趣区域
// Mat roi;
// roi = border(Rect(i, j, scale, scale));
//
// //求兴趣区域的最小值
// double minVal = 0; double maxVal = 0;
// Point minLoc = 0; Point maxLoc = 0;
// minMaxLoc(roi, &minVal, &maxVal, &minLoc, &maxLoc, noArray());
//
// darkChannel.at(Point(i, j)) = (uchar)minVal;
// }
// }
// return darkChannel;
//}
int main(int argc, char* argv[])
{
Mat image = imread("mai4.jpg");
imshow("image",image);
Mat darkChannel1 = darkChannel(image);
imshow("darkChannel1", darkChannel1);
namedWindow("dehazed");
//估计大气光
Mat temp; darkChannel1.copyTo(temp);
vector darkMaxPoint;
vector inputMax;
for (long i = 0; i < ((darkChannel1.rows*darkChannel1.cols) / 1000); i++)
{
double minVal = 0; double maxVal = 0;
Point minLoc = 0; Point maxLoc = 0;
minMaxLoc(temp, &minVal, &maxVal, &minLoc, &maxLoc, noArray());
darkMaxPoint.push_back(maxLoc);
inputMax.push_back(image.at(maxLoc));
circle(temp, maxLoc,5, Scalar(0), 1, 8, 0);
temp.at(maxLoc) = temp.at(minLoc);
}
uchar A = light(inputMax);
double w = 0.65;
//createTrackbar("w1", "dehazed", &w1, 100, NULL);
//求折射率
Mat T = Mat::zeros(image.rows, image.cols, CV_8UC3);
Scalar intensity;
for (int m = 0; m(m, n);
T.at(m, n)[0] = (1 - w * intensity.val[0] / A) * 255;
T.at(m, n)[1] = (1 - w * intensity.val[0] / A) * 255;
T.at(m, n)[2] = (1 - w * intensity.val[0] / A) * 255;
}
}
//去雾
Mat J(image.rows, image.cols, CV_8UC3, Scalar(180, 120, 50));
Mat temp1(image.rows, image.cols, CV_8UC3, Scalar(180, 120, 50));
//subtract(image, Scalar(A, A, A), temp1);
temp1 = abs(image - Scalar(A, A, A));
double t0 = 0.1;
Scalar T1;
Vec3b intsrc;
for (int i = 0; i < image.cols; i++)
{
for (int j = 0; j < image.rows; j++)
{
T1 = T.at(Point(i, j));
intsrc = image.at(Point(i, j));
double tmax = (T1.val[0] / 255) < t0 ? t0 : (T1.val[0] / 255);
for (int k = 0; k < 3; k++)
{
J.at(Point(i, j))[k] = abs((intsrc.val[k] - A) / tmax + A) > 255 ? 255 : abs((intsrc.val[k] - A) / tmax + A);
}
}
}
imshow("dehazed", J);
while (char(waitKey(1)) != 'q') {}
return 0;
}
这个有时对图像有些去雾效果,比如下面这样:
右上角的亮光是什么鬼?还有下面这样莫名其妙的事情:
这都是什么鬼??看来还需要进行细部调节和更深入的研究。改进之后再补充吧。
2016.03,24 再补充几个搜索到的资料:
论文的翻译,不太好,凑合看 http://wenku.baidu.com/link?url=SwSoHn-7IfxY7pp-VEway9fRMM5AOlc1j2xvIQytUivfqSSHm-j5moJVr3EYDOUblLo-VMDOF2sxCvNGYgFBUNJ4-zIhBEJTwn7ATfXcO7q
论文的原文: http://wenku.baidu.com/view/5391746365ce0508773213b9.html
用OpenCV实现的,有代码和详细的解释,但是效率非常低,代码有很大的改进空间,仅作参考吧
http://www.cnblogs.com/changkaizhao/p/3266798.html
1 首先是何博士2009年在CVPR上的一篇paper
转自
http://blog.csdn.NET/baimafujinji/article/details/27206237?ticket=ST-264104-uEM9GFfaos96lqSCMO4R-passport.csdn.net
论文中解释为什么能用暗通道来去雾:在不包括天空的绝大部分局部区域,总会存在一些我们称之为“dark pixels”的像素,至少有一个颜色通道具备很低的强度值。在被雾干扰的图像里,这些暗像素的强度值会被大气中的白光成分所充斥而变得较高。 因此,这些暗像素能够直接用来评估雾光的透射信息
方程右边的第一项J(x)t(x) 叫做直接衰减项,第二项A(1-t(x)) 则是大气光成分。直接衰减项描述的是景物光线在透射媒介中经衰减后的部分,而大气光则是由前方散射引 起的,会导致景物颜色的偏移。
现在结果已经比较细腻了,但是显然图像有些暗。何博士在论文中也有提及直接暗通道算法的结果会是比较暗的。下一篇文章中,我们将给出在MATLAB中实现的源代码,并对过暗的图像增加曝光和自动色阶,从而得到完美的去雾图像。
补充:http://www.cnblogs.com/Imageshop/category/535365.html
何的算法效果以及普遍的实用性都比其他的去雾算法要好,而主要的问题就是其速度还是不够快,有着太多的浮点计算。鉴于此,作者也多次试着对代码进行深层次的优化,包括SSE处理、并行运行等,但由于算法本身的顺序执行,无法全程并行,偶尔一个小函数可以并行,但由于其本身执行就特别快,比如不要5ms,你去用并行算法可能耗时还会大一些。因此,一直没有什么大的进步,对于一副1024*768的彩图进行去雾需要90ms,这肯定无法满足需求。
最近,在思考,既然暗通道去雾的透射率图比其他的算法都来的精细,如果适当的降低一点点其精度,其去雾的效果理论上应该不会有太大的区别,于是我想到了一种方式,即求取透射率的时候不是对原图进行求取,而是先对原图进行下采样,比如缩小为原图的1/4,计算出小图的透射率,之后在通过插值的方式的获取原图大概的透射率,则应该也可以获得效果。经过实践,这种方式大大的提高了执行速度,而且效果和原始的方案基本一致,对于1024*768的图像大约只需要(I3CPU)30ms了,如果进一步取1/9的缩放,则只需要大约20ms,完全可以满足工业实时性要求高的场合。
当然,如果你的缩小系数不是特别大的话,比如缩小为原来的0.5大小,可能两次缩放所用的耗时还抵消了计算小图的透射率图所换来的盈利,因此必须合理选择这个下采样率。
要实现这样的速度,当然还是需要很高的优化技巧的,这些东西还是有所保留比较好。
这个算法的原理来自于文章《Optimized contrast enhancement for real-time image and video dehazing》,作者是韩国人。
这个算法也是基于大气散射模型:
和现在一些常见的去雾文章有明显的不同的是,这篇文章的并不是基于暗通道原理的,也不是把重点强调在透射率图的细化上,而是提出了一种新的得到粗透射率图的方法。并且文章分别讲到了静态图像和视频图像的去雾,这里我只研究了静态图的去雾。
对于透射率图,文章提出了一个cost function,这个cost function是基于以下两点考虑的:
1、对于有雾图像,其整体的对比比较低,因此去雾后的对比度要尽量的高,文中给出了三种测评一幅图像对比度的方式,这里选用的是第一种:
公式具体的意义可见论文。注意上面的公式都是对去雾图进行的处理。
2、 由于对比度得到增强,可能会导致部分像素的调整值超出了0和255的范围,这样就会造成信息的损失以及视觉上的瑕疵。因此提出了一个信息量损失的计算公式:
一个好的透射率图应该使得总的损失最小:
其中Lamda值用于控制对比度和信息损失之间的重要性。
进行上述过程还有一个重要的前提就是:对于一小块图像,我们认为他的透射率是一样的,以下作者提供的代码表面了这一点:
nEndX = __min(nStartX+m_nTBlockSize, nWid); // End point of the block
nEndY = __min(nStartY+m_nTBlockSize, nHei); // End point of the block
nNumberofPixels = (nEndY-nStartY)*(nEndX-nStartX);
fTrans = 0.3f; // Init trans is started from 0.3
nTrans = 427; // Convert transmission to integer
for(nCounter=0; nCounter<7; nCounter++)
{
nSumofSLoss = 0;
nLossCount = 0;
nSumofSquaredOuts = 0;
nSumofOuts = 0;
for(nY=nStartY; nY>7; // (I-A)/t + A --> ((I-A)*k*128 + A*128)/128
nSquaredOut = nOut * nOut;
if(nOut>255)
{
nSumofSLoss += (nOut - 255)*(nOut - 255);
nLossCount++;
}
else if(nOut < 0)
{
nSumofSLoss += nSquaredOut;
nLossCount++;
}
nSumofSquaredOuts += nSquaredOut;
nSumofOuts += nOut;
}
}
fMean = (float)(nSumofOuts)/(float)(nNumberofPixels);
fCost = m_fLambda1 * (float)nSumofSLoss/(float)(nNumberofPixels)
- ((float)nSumofSquaredOuts/(float)nNumberofPixels - fMean*fMean);
if(nCounter==0 || fMinCost > fCost)
{
fMinCost = fCost;
fOptTrs = fTrans;
}
fTrans += 0.1f;
nTrans = (int)(1.0f/fTrans*128.0f);
}
朋友们有没有看到上面的代码中的最小透射率是0.3,我个人认为这个只能够有效的避免天空部位被过增强。
文中提到了这个方法也可以看成是何凯明的暗通道去雾算法的一个更广义的定义。
在这个文章,还提出了另外一个和其他算法不同的东西,就是全局大气光A的获取,其主要原理是: the variance of pixel values is generally low in hazy regions, e.g. sky. 具体的操作流程是:
we first divide an input image into four rectangular regions. We then define the score of each region as the average pixel value subtracted by the standard deviation of the pixel values within the region. Then, we select the region with the highest score and divide it further into four smaller regions.We repeat this process until the size of the selected region is smaller than a pre-specified threshold. Within the selected region, we choose the color vector, which minimizes the distance ||(R,G,B) -(255,255,255)||as the atmospheric light. By minimizing the distance from the pure white vector(255,255,255), we attempt to choose the atmospheric light that is as bright as possible.
结合上述描述以及论文配套的代码可以很容易的理解这里的道理。论文的配套代码的实现也很好。
具体的流程还是请各位仔细的阅读论文及其代码,经过我自己的优化和实践,这个算法确实能得到很不错的效果,在速度上也能够达到实时。
在贴一些效果图(有的时候只有看到这些图,才很有成就感)。
转自:http://blog.csdn.net/baimafujinji/article/details/53026812
算法核心1:计算大气光值
通常,图像去雾问题的基本模型可以用下面这个公式来表示(这一点在基于暗通道先验的图像去雾中我们也使用过):
其中, J(p)=(Jr(p),Jg(p),Jb(p))T 表示原始图像(也就是没有雾的图像); I(p)=(Ir(p),Ig(p),Ib(p))T 表示我们观察到的图像(也就是有雾的图像)。 r 、 g 、 b 表示位置 p 处的像素的三个分量。 A=(Ar,Ag,Ab)T 是全球大气光,它表示周围环境中的大气光。
此外, t(p)∈[0,1] 是反射光的透射率, 由场景点到照相机镜头之间的距离所决定。因为光传播的距离越远,那么通常光就约分散而且越发被削弱。所以上面这个公式的意思就是,本来没有被雾所笼罩的图像 J 与大气光 A 按一定比例进行混合后就得到我们最终所观察到的有雾图像。
大气光 A 通常用图像中最明亮的颜色来作为估计。因为大量的灰霾通常会导致一个发亮(发白)的颜色。然而,在这个框架下,那些颜色比大气光更加明亮的物体通常会被选中,因而便会导致一个本来不应该作为大气光参考值的结果被用作大气光的估计。为了更加可靠的对大气光进行估计,算法的作者利用了这样一个事实:通常,那些灰蒙蒙的区域(也就是天空)中像素的方差(或者变动)总体来说就比较小。
基于这个认识,算法的作者提出了一个基于四叉树子空间划分的层次搜索方法。如下图所示,我们首先把输入图像划分成四个矩形区域。然后,为每个子区域进行评分,这个评分的计算方法是“用区域内像素的平均值减去这些像素的标准差”(the average pixel value subtracted by the standard deviation
of the pixel values within the region)。记下来,选择具有最高得分的区域,并将其继续划分为更小的四个子矩形。我们重复这个过程直到被选中的区域小于某个提前指定的阈值。例如下图中的红色部分就是最终被选定的区域。在这被选定的区域里,我们选择使得距离 ||(Ir(p),Ig(p),Ib(p))−(255,255,255)|| 最小化的颜色(包含 r,g,b 三个分量)来作为大气光的参考值。注意,这样做的意义在于我们希望选择那个离纯白色最近的颜色(也就是最亮的颜色)来作为大气光的参考值。
我们假设在一个局部的小范围内,场景深度是相同的(也就是场景内的各点到相机镜头的距离相同),所以在一个小块内(例如 32×32 )我们就可以使用一个固定的透射率 t ,所以前面给出的有雾图像与原始(没有雾的)图像之间的关系模型就可以改写为
可见,在求得大气光 A 的估计值之后,我们希望复原得到的原始(没有雾的)图像 J(p) 将依赖于散射率 t 。
总的来说,一个有雾的块内,对比度都是比较低的,而被恢复的块内的对比度则随着 t 的估计值的变小而增大,我们将设法来估计一个最优的 t 值,从而使得去雾后的块能够得到最大的对比度。
下面是原作者给出的大气光计算函数代码(C/C++版)
我们首先给出图像对比度度量的方法(论文中,原作者给出了三个对比度定义式,我们只讨论其中第一个):
其中 c∈{r,g,b} 是颜色通道的索引标签, J¯c 是 Jc(p) 的平均值,并且 p=1,⋯,N , N 是块中像素的数量。
根据之前给出的有雾图像与原始(没有雾的)图像之间的关系模型
既然我们希望通过增强对比度的方法来去雾,那么不妨将一个区块B内三个颜色通道上的MSE对比度加总,然后再取负,如下
由于加了负号,所以取对比度最大就等同于取上式最小。
另外一方面,因为对比度得到增强,可能会导致部分像素的调整值超出了0和255的范围,这样就会造成信息的损失以及视觉上的瑕疵。所以算法作者又提出了一个信息量损失的计算公式:
主要结论:
论文及原作者的代码下载地址:http://mcl.korea.ac.kr/projects/dehazing/#userconsent# (这个源代码是opencv写的,主要针对视频去雾的,已实现)