代碼開源在 https://github.com/BBuf/Image-processing-algorithm,感興趣給我來個星星呗。
1. 前言
這是OpenCV圖像處理算法樸素實作專欄的第17篇文章。今天為大家帶來一篇之前看到的用于單幅圖像去霧的算法,作者來自清華大學,論文原文見附錄。
2. 霧天退化模型
之前在介紹何凱明博士的暗通道去霧論文OpenCV圖像處理專欄六 | 來自何凱明博士的暗通道去霧算法(CVPR 2009最佳論文)的時候已經講到了這個霧天退化模型,我們這裡再來回顧一下。在計算機視覺領域,通常使用霧天圖像退化模型來描述霧霾等惡劣天氣條件對圖像造成的影響,該模型是McCartney首先提出。該模型包括衰減模型和環境光模型兩部分。模型表達式為:
其中,是圖像像素的空間坐标,是觀察到的有霧圖像,是待恢複的無霧圖像,表示大氣散射系數,代表景物深度,是全局大氣光,通常情況下假設為全局常量,與空間坐标無關。
公式(1)中的表示坐标空間處的透射率,我們使用來表示透射率,于是得到公式(2):
由此可見,圖像去霧過程就是根據求解的過程。要求解出,還需要根據求解出透射率和全局大氣光。實際上,所有基于霧天退化模型的去霧算法就是是根據已知的有霧圖像求解出透射率和全局大氣光。
對于暗通道去霧算法來說,先從暗原色通道中選取最亮的0.1%比例的像素點,然後選取原輸入圖像中這些像素具有的最大灰階值作為全局大氣光值。RGB三通道中每一個通道都有一個大氣光值。
然後根據公式(2)可以得出:
首先可以确定的是的範圍是,的範圍是,的範圍是。和是已知的,可以根據的範圍進而确定的範圍。已知的條件有:
根據(4)和(5)推出:
是以初略估計透射率的計算公式:
最後為了保證圖檔的自然性,增加一個參數來調整透射率 :
好了,上面複習完了何凱明博士的暗通道去霧,我們一起來看看清華大學這篇論文。
3. 算法流程

算法流程
實際上有了這個算法流程就可以寫出代碼了,不過為了加深了解可以看下面的一些推導。
4. 一些推導
我們知道去霧的步驟主要就是估計全局大氣光值和透射率,是以,本文就是根據輸入圖像估計和(這篇論文使用了來代替),然後根據霧天退化模型求取去霧後的圖像。
4.1 估計透射率t(x)
從第二節的介紹我們知道
然後這篇論文使用了來代替,即:
。
我們取三個通道的最小值并記為:
是以公式2變換為
對公式(4)右邊進行均值濾波:
其中代表均值濾波的視窗大小,表示像素的的鄰域。
均值濾波後的結果可以反映的大緻趨勢,但與真實的還差一定的絕對值,是以,我們先得出透射率的粗略估計值:
其中,是以。
為了防止去霧後圖像出現整體畫面偏暗,這裡根據圖像的均值來調整,即:
其中是中所有元素的均值,是調節因子。
是以可以得到透射率的計算公式:
結合公式(1)推出:
。
4.2 估計全球大氣光值
公式(5)中第一個等式左側的表達式取值範圍為,由此得出
一般情況下又存在
(KaiMing He的暗通道先驗理論)。這樣就初步确定了全局大氣光的範圍,為了能快速擷取全局大氣光,文章直接取兩者的平均值作為全局大氣光值,即:
...(9)。
然後大氣光值和都搞定了,那麼帶入算法流程中的最後一個公式就可以擷取最後的圖像了。
5. 代碼實作
下面是代碼實作。
#include <opencv2/opencv.hpp>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace cv;
using namespace std;
int getMax(Mat src) {
int row = src.rows;
int col = src.cols;
int temp = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
temp = max((int)src.at<uchar>(i, j), temp);
}
if (temp == 255) return temp;
}
return temp;
}
Mat dehaze(Mat src) {
double eps;
int row = src.rows;
int col = src.cols;
Mat M = Mat::zeros(row, col, CV_8UC1);
Mat M_max = Mat::zeros(row, col, CV_8UC1);
Mat M_ave = Mat::zeros(row, col, CV_8UC1);
Mat L = Mat::zeros(row, col, CV_8UC1);
Mat dst = Mat::zeros(row, col, CV_8UC3);
double m_av, A;
//get M
double sum = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
uchar r, g, b, temp1, temp2;
b = src.at<Vec3b>(i, j)[0];
g = src.at<Vec3b>(i, j)[1];
r = src.at<Vec3b>(i, j)[2];
temp1 = min(min(r, g), b);
temp2 = max(max(r, g), b);
M.at<uchar>(i, j) = temp1;
M_max.at<uchar>(i, j) = temp2;
sum += temp1;
}
}
m_av = sum / (row * col * 255);
eps = 0.85 / m_av;
boxFilter(M, M_ave, CV_8UC1, Size(51, 51));
double delta = min(0.9, eps*m_av);
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
L.at<uchar>(i, j) = min((int)(delta * M_ave.at<uchar>(i, j)), (int)M.at<uchar>(i, j));
}
}
A = (getMax(M_max) + getMax(M_ave)) * 0.5;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
int temp = L.at<uchar>(i, j);
for (int k = 0; k < 3; k++) {
int val = A * (src.at<Vec3b>(i, j)[k] - temp) / (A - temp);
if (val > 255) val = 255;
if (val < 0) val = 0;
dst.at<Vec3b>(i, j)[k] = val;
}
}
}
return dst;
}
int main() {
Mat src = imread("F:\\fog\\1.jpg");
Mat dst = dehaze(src);
cv::imshow("origin", src);
cv::imshow("result", dst);
cv::imwrite("F:\\fog\\res.jpg", dst);
waitKey(0);
return 0;
}
複制
6. 結果
原圖1
結果圖1
原圖2
結果圖2
原圖3
結果圖3
原圖4
結果圖4
原圖5
結果圖5
7. 結論
算法裡面有2個參數可以自己調節,濾波的半徑和。具體如何調節?我就不放在這裡說了,這個算法後面會在我的新專題裡面進行一遍優化,到時候再來回答這個問題。如果你迫切需要這個算法的實作或者對它感興趣,可以自己嘗試調整這兩個參數獲得想要的效果。這裡的均值濾波也可以換成我們之前講的Side Window Filter說不定可以獲得更好的效果。
8. 參考
- https://blog.csdn.net/u013684730/article/details/76640321
- https://www.cnblogs.com/Imageshop/p/3410279.html
歡迎關注GiantPandaCV, 在這裡你将看到獨家的深度學習分享,堅持原創,每天分享我們學習到的新鮮知識。( • ̀ω•́ )✧