何恺明的暗通道先验(dark channel prior)去雾算法是CV界去雾领域很有名的算法,关于该算法的论文"Single Image Haze Removal Using Dark Channel Prior"一举获得2009年CVPR最佳论文。作者统计了大量的无雾图像,发现一条规律:每一幅图像的RGB三个颜色通道中,总有一个通道的灰度值很低,几乎趋向于0。基于这个几乎可以视作是定理的先验知识,作者提出暗通道先验的去雾算法。
作者首先介绍去雾模型如下:
如果你不是CV界新手的话,应该对上式倒背如流,在此不再对上式中的各个参量作详细介绍。对于任意一幅输入图像,定义其暗通道的数学表达式为:
其中c表示rgb三通道中的某一通道。上式表示在一幅输入图像中,先取图像中每一个像素的三通道中的灰度值的最小值,得到一幅灰度图像,再在这幅灰度图像中,以每一个像素为中心取一定大小的矩形窗口,取矩形窗口中灰度值最小值代替中心像素灰度值,从而得到输入图像的暗通道图像。暗通道图像为灰度图像,通过大量统计并观察发现,暗通道图像的灰度值是很低的,所以将整幅暗通道图像中所有像素的灰度值近似为0,即:
作者在文中假设大气光A为已知量,以便节省篇幅介绍传输函数的求解方法。在此介绍一种简单普遍的求解大气光的方法:对于任意一幅输入图像,取其暗通道图像灰度值最大的0.1%的像素点对应于原输入图像的像素位置的每个通道的灰度值的平均值,从而计算出每个通道的大气光值,即大气光值A是一个三元素向量,每一个元素对应于每一个颜色通道。
对于成像模型,将其归一化,即两边同时除以每个通道的大气光值:
假设在图像中一定大小的矩形窗口内,传输函数的值为定值,对上式两边用最小化算子(minimum operators)作最小化运算
由于在矩形区域内为定值,故将其拿出运算符外部。由于场景辐射(scene radiance)是无雾图像,将暗通道先验应用于J,则有:
由于总是正值,则有:
将上式代入到最小化运算的式子中,即可得到传输函数的估计值为:
为了防止去雾太过彻底,恢复出的景物不自然,应引入参数,重新定义传输函数为:
对于求解得到的传输函数,应用滤波器对其进行细化,文章中介绍的方法是软抠图的方法,此方法过程复杂,速度缓慢,因此采用导向滤波对传输函数进行滤波。导向滤波的原理此处不再赘述,其伪代码为:
上述伪代码中,I表示导向图像(guided image),p为输入图像(input image),q为输出图像(output image),表示均值滤波,r为窗口半径。
将求解得到的去穷远处大气光值和传输函数代入到大气散射模型之中,反解即可得到恢复的目标图像,即场景辐射。本例使用C++实现上述去雾过程,代码如下:
#include <iostream> #include <stdlib.h> #include <time.h> #include <cmath> #include <algorithm> #include <opencv2\opencv.hpp> using namespace cv; using namespace std; typedef struct Pixel { int x, y; int data; }Pixel; bool structCmp(const Pixel &a, const Pixel &b) { return a.data > b.data;//descending降序 } Mat minFilter(Mat srcImage, int kernelSize); void makeDepth32f(Mat& source, Mat& output); void guidedFilter(Mat& source, Mat& guided_image, Mat& output, int radius, float epsilon); Mat getTransmission_dark(Mat& srcimg, Mat& darkimg, int *array, int windowsize); Mat recover(Mat& srcimg, Mat& t, float *array, int windowsize); int main() { string loc = "E:\\darkchannel\\firstopencv\\sky.jpg"; double scale = 1.0; string name = "forest"; clock_t start, finish; double duration; cout << "A defog program" << endl << "----------------" << endl; Mat image = imread(loc); Mat resizedImage; int originRows = image.rows; int originCols = image.cols; if (scale < 1.0) { resize(image, resizedImage, Size(originCols * scale, originRows * scale)); } else { scale = 1.0; resizedImage = image; } int rows = resizedImage.rows; int cols = resizedImage.cols; Mat convertImage; resizedImage.convertTo(convertImage, CV_32FC3, 1 / 255.0, 0); int kernelSize = 15 ? max((rows * 0.01), (cols * 0.01)) : 15 < max((rows * 0.01), (cols * 0.01)); //int kernelSize = 15; int parse = kernelSize / 2; Mat darkChannel(rows, cols, CV_8UC1); Mat normalDark(rows, cols, CV_32FC1); int nr = rows; int nl = cols; float b, g, r; start = clock(); cout << "generating dark channel image." << endl; if (resizedImage.isContinuous()) { nl = nr * nl; nr = 1; } for (int i = 0; i < nr; i++) { float min; const uchar* inData = resizedImage.ptr<uchar>(i); uchar* outData = darkChannel.ptr<uchar>(i); for (int j = 0; j < nl; j++) { b = *inData++; g = *inData++; r = *inData++; min = b > g ? g : b; min = min > r ? r : min; *outData++ = min; } } darkChannel = minFilter(darkChannel, kernelSize); imshow("darkChannel", darkChannel); cout << "dark channel generated." << endl; //estimate Airlight //开一个结构体数组存暗通道,再sort,取最大0.1%,利用结构体内存储的原始坐标在原图中取点 cout << "estimating airlight." << endl; rows = darkChannel.rows, cols = darkChannel.cols; int pixelTot = rows * cols * 0.001; int *A = new int[3]; Pixel *toppixels, *allpixels; toppixels = new Pixel[pixelTot]; allpixels = new Pixel[rows * cols]; for (unsigned int r = 0; r < rows; r++) { const uchar *data = darkChannel.ptr<uchar>(r); for (unsigned int c = 0; c < cols; c++) { allpixels[r*cols + c].data = *data; allpixels[r*cols + c].x = r; allpixels[r*cols + c].y = c; } } std::sort(allpixels, allpixels + rows * cols, structCmp); memcpy(toppixels, allpixels, pixelTot * sizeof(Pixel)); float A_r, A_g, A_b, avg, maximum = 0; int idx, idy, max_x, max_y; for (int i = 0; i < pixelTot; i++) { idx = allpixels[i].x; idy = allpixels[i].y; const uchar *data = resizedImage.ptr<uchar>(idx); data += 3 * idy; A_b = *data++; A_g = *data++; A_r = *data++; //cout << A_r << " " << A_g << " " << A_b << endl; avg = (A_r + A_g + A_b) / 3.0; if (maximum < avg) { maximum = avg; max_x