I(x) = J(x) * t(x) + (1 - t(x)) *A (1)
I(x):我们观测的图像。
J(x):就是那个客观存在的图像,对于去雾的案例,可以认为是没有被雾挡住的图像。
t:为大气的传输过程。
A:为空气中的光。
x:为像素的位置。
就是说我们看到的图像是空气的光线和真实图像的混合。这个混合的比例为t。
去雾就是在已知观测图像I(x)下恢复真实图像J(x)。
朴素的想法:
J(x) = (I(x)-A)/t(x) + A
因此问题就变成要估计t(x)和A。
先处理t(x)。
这个是一个随空间位置变化的量。对于一个具体点,可以假设附近的点上t(x)取常数。这个当然是不真实的,但可以解决一些问题。
I(x) = J(x) * t + (1 - t) * A
分成r,g,b三个通道(颜色)的方式:
Ic(x) = Jc(x) * t + (1 - t) * Ac c = r, g, b
min_omega( Ic(y)) = t * min_omega(Jc(y)) + ( 1 - t) * Ac
min_omega( Ic(y)) / Ac = t * min_omega(Jc(y)) / Ac + ( 1 - t)
min_omega函数表示,以x为中心的一个矩形区域中所有像素的最小值。可以用OpenCV实现为。论文中patch_size = 15。
double min_omega(Mat& I, int patch_size) {
Mat tmp_img;
getRectSubPix(I, Size(patch_size, patch_size), Point(x,y), tmp_img);
minMaxLoc(tmp_img, &tmpmin);
return tmpmin;
}
再做一步:
min_c( min_omega(Ic(y) ) ) /Ac = t * min_c( min_omega(Jc(y))) + (1 - t) (2)
min_c是对三个通道求最小:
uchar min_c(uchar r, uchar g, char b) {
return std::min(r ,std:: min(g,b));
}
方程(2)是无法编程实现的。因为 Jc(y)是不知道的。
幸运的事情发生了,
min_c( min_omega(Jc(y))) = dark_channle( . ) = 0
所以。t = 1 - min_c(min_omega(Ic(y)))/Ac。这样就得到了t。
dark_channle( .) 是论文中核心和要害。就是说对于自然的图像,尤其是户外的图像,任何一个像素,他附近的的所有通道中总存在一个接近于0的点。
比如说一个单色的图像,其他两个通道都会为0。 在阴影中像素也是这样的。dark_channel是一个刚开始让人觉得很诧异的东西,仔细一想也是自然的。
这样可以定义重新写一下公式:
t = 1 - dark_channel(I)/A
当A已知那么,t就是可以通过原来的图像计算出来。
#include </to/path/opencv>
#include </to/path/std ...>
//using C++ 11 feature, Visual Studio 2013 or GCC
Mat recover(const Mat& I, float A, const Mat& t) {
Mat J(I.size(), CV_32FC3);
auto it_dst = J.begin<Vec3f>();
auto it = t.begin<float>();
const float t0 = 0.1f;
for (auto it_src = I.begin<Vec3b>(); it_src != I.end<Vec3b>(); ++it_src, ++it, ++it_dst){
float tx = std::max(*it, t0);
float r_tx = 1 /tx;
float t1 = A *(r_tx - 1)
(*it_dst)[0] = ((*it_src)[0] )*r_tx - t1;
(*it_dst)[1] = ((*it_src)[1] )*r_tx - t1;
(*it_dst)[2] = ((*it_src)[2] )*r_tx - t1;
}
return std::move(J); //C++ 11 move semantics
}
Mat dark_channel(const Mat& I, int patch_size);
Mat dehaze(const Mat& I, int patch_size, uchar A) {
auto dc = dark_channel(I, patch_size);
Mat ones = Mat::ones(I.size(), CV_32F);
Mat t(ones.size(), ones.type());
cv::divide(ones, dc/A, t);
return recover(I, A, t);
}
int main(int argc, char *argv[]) {
uchar A = 220;
string in_file (argv[1]);
auto I = imread(in_file); //C++11 auto
Mat&& dst = dehaze(I, 15, A);
imwrite("out_" + in_file, dst);
}
uchar min_omega(const Mat& dc, int patch_size, int x, int y) {
double tmp_min;
Mat tmp_img;
getRectSubPix(dc, Size(patch_size, patch_size), Point(x, y), tmp_img);
minMaxLoc(tmp_img, &tmp_min);
return (uchar)tmp_min;
}
uchar min_c(uchar a, uchar b, uchar c) {
return std::min(a,min(b,c));
}
Mat dark_channel(const Mat& I, int patch_size){
Mat dc;
dc.create(I.size(), CV_8U);
for (int y = 0; y < dc.rows; ++y) {
const uchar *p = I.ptr<uchar>(y);
for (int x = 0; x < dc.cols; ++x) {
dc.at<uchar>(y, x) = min_c(p[3*x],p[3*x+1], p[3*x+2]);
}
}
Mat dc2;
dc2.create(dc.size(), dc.type());
for (int y = 0; y < dc.row; y++){
for (int x = 0; x < dc.cols; x++){
uchar tmpmin = min_omega(dc, patch_size, x, y);
dc2.at<uchar>(y, x) = (uchar)tmpmin;
}
}
return std::move(dc2);
}
Paper download