下面给出第3篇文章中代码的详细注释版本。
首先是头文件
#ifndef FUNCTION_H
#define FUNCTION_H
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/imgproc/types_c.h>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include<map>
using namespace std;
using namespace cv;
//导向滤波,用来优化t(x),针对单通道,代码来自何凯明博士
//这段代码大大改进了该方法的效率。使得该方法成为了去雾的最好算法
Mat guidedfilter(Mat& srcImage, Mat& srcClone, int r, double eps)
{
//转换源图像信息
srcImage.convertTo(srcImage, CV_32FC1, 1 / 255.0);
srcClone.convertTo(srcClone, CV_32FC1);
int nRows = srcImage.rows;
int nCols = srcImage.cols;
Mat boxResult;
//步骤一:计算均值
boxFilter(Mat::ones(nRows, nCols, srcImage.type()),
boxResult, CV_32FC1, Size(r, r));
//生成导向均值mean_I
Mat mean_I;
boxFilter(srcImage, mean_I, CV_32FC1, Size(r, r));
//生成原始均值mean_p
Mat mean_p;
boxFilter(srcClone, mean_p, CV_32FC1, Size(r, r));
//生成互相关均值mean_Ip
Mat mean_Ip;
boxFilter(srcImage.mul(srcClone), mean_Ip,
CV_32FC1, Size(r, r));
Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);
//生成自相关均值mean_II
Mat mean_II;
//应用盒滤波器计算相关的值
boxFilter(srcImage.mul(srcImage), mean_II,
CV_32FC1, Size(r, r));
//步骤二:计算相关系数
Mat var_I = mean_II - mean_I.mul(mean_I);
Mat var_Ip = mean_Ip - mean_I.mul(mean_p);
//步骤三:计算参数系数a,b
Mat a = cov_Ip / (var_I + eps);
Mat b = mean_p - a.mul(mean_I);
//步骤四:计算系数a\b的均值
Mat mean_a;
boxFilter(a, mean_a, CV_32FC1, Size(r, r));
mean_a = mean_a / boxResult;
Mat mean_b;
boxFilter(b, mean_b, CV_32FC1, Size(r, r));
mean_b = mean_b / boxResult;
//步骤五:生成输出矩阵
Mat resultMat = mean_a.mul(srcImage) + mean_b;
return resultMat;
}
//计算暗通道图像矩阵,针对三通道彩色图像
Mat dark_channel(Mat src)
{
//滤波半径越大,最后去雾的图片中出现色斑几率越小,但是半径的大小影响效率
int border = 15;
vector<Mat> rgbChannels;
//和原图尺寸一样的灰度图像
Mat min_mat(src.size(), CV_8UC1, Scalar(0)), min_mat_expansion;
Mat dark_channel_mat(src.size(), CV_8UC1, Scalar(0));
split(src, rgbChannels); //色彩分离
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
int min_val = 0;
int val_1, val_2, val_3;
val_1 = rgbChannels[0].at<uchar>(i, j);
val_2 = rgbChannels[1].at<uchar>(i, j);
val_3 = rgbChannels[2].at<uchar>(i, j);
min_val = min(val_1, val_2);
min_val = min(min_val, val_3);
//得到RGB分量中最小值组成的灰度图像
min_mat.at<uchar>(i, j) = min_val;
}
}
//扩充灰度图像的边缘,得到min_mat_expansion。向四周扩展的范围刚好是滤波半径
//BORDER_REPLICATE表示扩充边缘的方式是边界复制扩充
copyMakeBorder(min_mat, min_mat_expansion, border, border, border, border, BORDER_REPLICATE);
for (int m = border; m < min_mat_expansion.rows - border; m++)
{
for (int n = border; n < min_mat_expansion.cols - border; n++)
{
Mat imageROI;
int min_num = 256;
//掩膜是滤波半径加1,即:2*border+1。在这个大小(15*15)下的进行最小值滤波
imageROI = min_mat_expansion(Rect(n - border, m - border, 2 * border + 1, 2 * border + 1));
//最小值滤波:用邻域范围(15*15)内最小值去替换邻域中心的值。
for (int i = 0; i < imageROI.rows; i++)
{
for (int j = 0; j < imageROI.cols; j++)
{
int val_roi = imageROI.at<uchar>(i, j);
min_num = min(min_num, val_roi);
}
}
//生成暗通道图像
dark_channel_mat.at<uchar>(m - border, n - border) = min_num;
}
}
Mat temp = dark_channel_mat.clone();
bilateralFilter(temp, dark_channel_mat, 9, 18, 4.5);
return dark_channel_mat;
}
//全球大气光成分A
int calculate_A(Mat src, Mat dark_channel_mat)
{
std::vector<cv::Mat> rgbChannels(3);
split(src, rgbChannels); //分离原图颜色通道
//准备存储原图的图结构,STL实现的map本质是一棵红黑树,它具备自动排序的能力。
//这点将省去排序的工作,Point同时存储了像素的位置。
map<int, Point> pair_data;
map<int, Point>::iterator iter;
vector<Point> cord;
int max_val = 0;
for (int i = 0; i < dark_channel_mat.rows; i++)
{
for (int j = 0; j < dark_channel_mat.cols; j++)
{
int val = dark_channel_mat.at<uchar>(i, j);
Point pt;
pt.x = j;
pt.y = i;
//当我们向一颗极其严格的平衡二叉树中插入元素的时候,也就是完成排序的时候。
pair_data.insert(make_pair(val, pt));
}
}
//遍历这棵二叉树,
for (iter = pair_data.begin(); iter != pair_data.end(); iter++)
{
//取出Hash表中的value,而不是key。
cord.push_back(iter->second);
}
//根据按通道图像中像素的位置找出原图像中相应的像素,并求出具有最高亮度的像素值。
for (int m = 0; m < cord.size(); m++)
{
Point tmp = cord[m];
int val_1, val_2, val_3;
val_1 = rgbChannels[0].at<uchar>(tmp.y, tmp.x);
val_2 = rgbChannels[1].at<uchar>(tmp.y, tmp.x);
val_3 = rgbChannels[2].at<uchar>(tmp.y, tmp.x);
max_val = max(val_1, val_2);
max_val = max(max_val, val_3);
}
return max_val; //A的值求出来了。
}
//透射率t(x)
Mat calculate_tx(Mat& src, int A, Mat& dark_channel_mat)
{
Mat dst;//是用来计算t(x)
Mat tx;
float dark_channel_num;
dark_channel_num = A / 255.0;
dark_channel_mat.convertTo(dst, CV_32FC3, 1 / 255.0);
dst = dst / dark_channel_num;
tx = 1 - 0.7 * dst;//修正因子影响去雾的效果,这个因子越大去雾越好,但是也会使得图像变得失真
return tx;
}
//去雾结果
Mat haze_removal_img(Mat& src, int A, Mat& tx)
{
Mat result_img(src.rows, src.cols, CV_8UC3);
vector<Mat> srcChannels(3), resChannels(3);
split(src, srcChannels);
split(result_img, resChannels);
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
for (int m = 0; m < 3; m++)
{
int value_num = srcChannels[m].at<uchar>(i, j);
float max_t = tx.at<float>(i, j);
//阈值设置为0.1
if (max_t < 0.1)
{
max_t = 0.1;
}
//计算去雾结果
resChannels[m].at<uchar>(i, j) = (value_num - A) / max_t + A;
}
}
}
merge(resChannels, result_img);
return result_img;
}
#endif
下面是主函数
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include"function.h"
#include<algorithm>
#include<set>
#include<map>
using namespace std;
using namespace cv;
int main()
{
Mat src = imread("C:/Users/18301/Desktop/1.png");
Mat dst;
cvtColor(src, dst, CV_BGR2GRAY);
Mat dark_channel_mat = dark_channel(src);//输出的是暗通道图像
int A = calculate_A(src, dark_channel_mat);//计算大气光成分
Mat tx = calculate_tx(src, A, dark_channel_mat);//计算透光率
Mat tx_ = guidedfilter(dst, tx, 30, 0.001);//导向滤波后的tx,优化tx
Mat haze_removal_image;
haze_removal_image= haze_removal_img(src, A, tx_);//去雾
namedWindow("去雾后的图像", 0);
namedWindow("原始图像", 0);
imshow("原始图像", src);
imshow("去雾后的图像", haze_removal_image);
waitKey(0);
return 0;
}