1.相关博客介绍

1.https://zhuanlan.zhihu.com/p/413747554
2.https://github.com/wendell-0x0/ImageAlgorithmDraft/tree/master 非作者源代码
3.作者论文和源代码,可能需要翻墙:http://mcl.korea.ac.kr/projects/dehazing/#userconsent#

里面有相关算法的去雾效果比较

4.博客介绍https://zhuanlan.zhihu.com/p/149947284

主要=环节,大气光估计,透射率粗估计只估计一个数,引导滤波精估计,gamma增强,根据估计进行去雾
在这里插入图片描述
在这里插入图片描述

5. https://blog.csdn.net/xx116213/article/details/51848429 这篇博客介绍的更深入一些,虽然有些公式错别,看不到原论文的可以看下这篇文章有些贴图。

在这里插入图片描述

6. 一个比较清晰的代码:https://github.com/Accustomer/TheThingsWithImage/tree/main/Code/6_OCEDehazing
7. https://www.cnblogs.com/Imageshop/p/3925461.html 有一个大概介绍
8. https://github.com/BBuf/giantpandacv.com/blob/master/docs/academic/%E4%BC%A0%E7%BB%9F%E5%9B%BE%E5%83%8F/%E4%B8%80%E4%BA%9B%E6%9C%89%E8%B6%A3%E7%9A%84%E5%9B%BE%E5%83%8F%E7%AE%97%E6%B3%95/%E3%80%8AOptimized%20contrast%20enhancement%20for%20real-time%20image%20and%20video%20dehazin%E3%80%8B%E8%AE%BA%E6%96%87C%2B%2B%E5%A4%8D%E7%8E%B0.md知名博主giantpanda

2.官方核心代码介绍

void dehazing::ImageHazeRemoval(IplImage* imInput, IplImage* imOutput)
{
	IplImage* imAir;
	IplImage* imSmallInput;
	
	// 步骤1:查找表初始化 - 预计算常用数学函数以提高运行效率
	MakeExpLUT();        // 指数函数查找表,用于传输图计算
	GuideLUTMaker();     // 引导滤波查找表,用于边缘保持滤波
	GammaLUTMaker(0.7f); // 伽马校正查找表,用于图像增强

	// 步骤2:大气光估计准备
	// 2.1 设置大气光估计的感兴趣区域(ROI)
	// 通常选择图像上方区域,因为天空区域更可能包含大气光
	cvSetImageROI(imInput, cvRect(m_nTopLeftX, m_nTopLeftY, 
		m_nBottomRightX-m_nTopLeftX, m_nBottomRightY-m_nTopLeftY));

	// 2.2 创建临时图像用于大气光估计和小尺寸处理
	imAir = cvCreateImage(cvSize(m_nBottomRightX-m_nTopLeftX, 
		m_nBottomRightY-m_nTopLeftY), IPL_DEPTH_8U, 3);
	imSmallInput = cvCreateImage(cvSize(320, 240), IPL_DEPTH_8U, 3);
	cvCopyImage(imInput, imAir);
	
	// 2.3 执行大气光估计:基于四叉树递归分解的大气光估计算法
	AirlightEstimation(imAir);
	
	// 2.4 清理大气光估计临时资源
	cvReleaseImage(&imAir);
	cvResetImageROI(imInput);
	
	// 步骤3:图像格式转换 - 将IplImage转换为RGB整型数组便于数值计算
	IplImageToIntColor(imInput);
		
	// 步骤4:传输图估计 - 基于暗通道先验的彩色图像传输图估计
	// 参数说明:R通道、G通道、B通道、传输图输出、R通道、G通道、B通道、传输图输出、
	//          帧序号(单幅图像为0)、图像宽度、图像高度
	// 注意:单幅图像处理直接在原始分辨率下进行,无需下采样
	TransmissionEstimationColor(m_pnRImg, m_pnGImg, m_pnBImg, m_pfTransmission, 
		m_pnRImg, m_pnGImg, m_pnBImg, m_pfTransmission, 0, m_nWid, m_nHei);
	
	// 步骤5:引导滤波 - 对传输图进行边缘保持的平滑处理
	// 使用标准引导滤波算法,参数0.001为正则化参数,控制平滑程度
	GuidedFilter(m_nWid, m_nHei, 0.001);
	
	// 备选方案:可移动窗口引导滤波(注释掉的代码)
	//GuidedFilterShiftableWindow(0.001);
	
	/*
	// 调试代码:可视化传输图R通道
	IplImage *test = cvCreateImage(cvSize(m_nWid, m_nHei),IPL_DEPTH_8U, 1);
	for(int nK = 0; nK < m_nWid*m_nHei; nK++)
		test->imageData[nK] = (uchar)(m_pfTransmissionR[nK]*255);
	cvNamedWindow("tests");
	cvShowImage("tests", test);
	cvWaitKey(-1);
	*/
	
	// 步骤6:图像复原 - 基于大气散射模型的逆变换恢复无雾图像
	RestoreImage(imInput, imOutput);
	
	// 步骤7:清理资源
	cvReleaseImage(&imSmallInput);
}

上面代码主要可以分为4个核心步骤
在这里插入图片描述

步骤一 Atmospheric light estimation

在这里插入图片描述

/*
	=============================================================================
	大气光估计函数 (Atmospheric Light Estimation)
	=============================================================================
	
	【算法原理】
	基于分治策略的递归大气光估计算法,通过四叉树分解找到最接近纯白色的像素点。
	该算法假设大气光在图像中表现为最亮且最均匀的区域。
	
	【理论基础】
	1. 大气散射模型:I(x) = J(x)t(x) + A(1-t(x))
	   其中A为大气光值,是去雾算法的关键参数
	2. 大气光特征:通常出现在天空区域,具有高亮度、低方差特性
	3. 四叉树分解:递归细分图像,逐步缩小搜索范围
	
	【算法流程】
	第一阶段:递归分块评估(当块大小 > 200像素时)
	1. 将当前图像块分为4个子块(左上、右上、左下、右下)
	2. 对每个子块计算评分:Score = Σ(Mean_channel - Std_channel)
	3. 选择评分最高的子块进行递归处理
	
	第二阶段:像素级精确搜索(当块大小 ≤ 200像素时)
	1. 遍历当前块中的每个像素
	2. 计算到纯白色(255,255,255)的欧氏距离
	3. 选择距离最小的像素作为大气光值
	
	【评分函数设计】
	Score = Σ(μ_c - σ_c),其中c ∈ {R,G,B}
	- μ_c:通道c的均值(反映亮度)
	- σ_c:通道c的标准差(反映均匀性)
	- 高评分区域:亮度高且纹理均匀,符合天空特征
	
	【距离计算公式】
	Distance = √[(255-R)² + (255-G)² + (255-B)²]
	选择距离最小的像素,即最接近纯白色的点
	
	【算法优势】
	1. 自适应性:根据图像内容自动调整搜索精度
	2. 鲁棒性:通过统计特征避免噪声干扰
	3. 效率性:分治策略减少计算复杂度
	4. 准确性:结合全局和局部信息提高估计精度
	
	【参数说明】
	@param imInput - 输入的有雾图像 (IplImage格式)
	                BGR通道顺序,8位无符号整数
	
	【返回值】
	通过成员变量m_anAirlight[3]返回估计的大气光值:
	- m_anAirlight[0]:B通道大气光值 (0-255)
	- m_anAirlight[1]:G通道大气光值 (0-255)  
	- m_anAirlight[2]:R通道大气光值 (0-255)
	
	【性能特性】
	- 时间复杂度:O(log(W×H) + 200) ≈ O(log n)
	- 空间复杂度:O(log(min(W,H))) (递归栈深度)
	- 阈值200:平衡精度与效率的经验值
	
	【应用场景】
	- 单张图像去雾:提供准确的大气光估计
	- 视频去雾:为每帧提供稳定的大气光参考
	- 自适应去雾:根据场景特征动态调整
 */
void dehazing::AirlightEstimation(IplImage* imInput)
{
	// =========================================================================
	// 变量初始化:距离计算和统计分析
	// =========================================================================
	int nMinDistance = 65536;           // 到纯白色的最小距离(初始化为最大值)
	int nDistance;                      // 当前像素到纯白色的距离

	int nX, nY;                         // 像素坐标索引

	int nMaxIndex;                      // 最高评分子块的索引
	double dpScore[3];                  // 各通道评分 (Mean - Std)
	double dpMean[3];                   // 各通道均值
	double dpStds[3];                   // 各通道标准差

	float afMean[4] = {0};              // 四个子块的均值(预留)
	float afScore[4] = {0};             // 四个子块的综合评分
	float nMaxScore = 0;                // 当前最高评分

	// =========================================================================
	// 图像参数获取
	// =========================================================================
	int nWid = imInput->width;          // 图像宽度
	int nHei = imInput->height;         // 图像高度
	int nStep = imInput->widthStep;     // 图像行步长(字节)

	// =========================================================================
	// 四个子块图像创建:实现四叉树分解
	// =========================================================================
	IplImage *iplUpperLeft = cvCreateImage(cvSize(nWid/2, nHei/2),IPL_DEPTH_8U, 3);   // 左上子块
	IplImage *iplUpperRight = cvCreateImage(cvSize(nWid/2, nHei/2),IPL_DEPTH_8U, 3);  // 右上子块
	IplImage *iplLowerLeft = cvCreateImage(cvSize(nWid/2, nHei/2),IPL_DEPTH_8U, 3);   // 左下子块
	IplImage *iplLowerRight = cvCreateImage(cvSize(nWid/2, nHei/2),IPL_DEPTH_8U, 3);  // 右下子块

	// 单通道图像:用于统计分析
	IplImage *iplR = cvCreateImage(cvSize(nWid/2, nHei/2),IPL_DEPTH_8U, 1);  // R通道
	IplImage *iplG = cvCreateImage(cvSize(nWid/2, nHei/2),IPL_DEPTH_8U, 1);  // G通道
	IplImage *iplB = cvCreateImage(cvSize(nWid/2, nHei/2),IPL_DEPTH_8U, 1);  // B通道

	// =========================================================================
	// 图像分块:将原图分为四个相等子块
	// =========================================================================
	cvSetImageROI(imInput, cvRect(0, 0, nWid/2, nHei/2));                           // 左上区域
	cvCopyImage(imInput, iplUpperLeft);
	cvSetImageROI(imInput, cvRect(nWid/2+nWid%2, 0, nWid, nHei/2));                 // 右上区域
	cvCopyImage(imInput, iplUpperRight);
	cvSetImageROI(imInput, cvRect(0, nHei/2+nHei%2, nWid/2, nHei));                 // 左下区域
	cvCopyImage(imInput, iplLowerLeft);
	cvSetImageROI(imInput, cvRect(nWid/2+nWid%2, nHei/2+nHei%2, nWid, nHei));       // 右下区域
	cvCopyImage(imInput, iplLowerRight);

	// =========================================================================
	// 递归终止条件判断:块大小阈值为200像素
	// =========================================================================
	if(nHei*nWid > 200)
	{
		// =====================================================================
		// 第一阶段:统计分析各子块,寻找最优候选区域
		// =====================================================================
		
		// ---------------------------------------------------------------------
		// 左上子块统计分析
		// ---------------------------------------------------------------------
		cvCvtPixToPlane(iplUpperLeft, iplR, iplG, iplB, 0);  // 分离RGB通道

		cvMean_StdDev(iplR, dpMean, dpStds);        // R通道均值和标准差
		cvMean_StdDev(iplG, dpMean+1, dpStds+1);    // G通道均值和标准差
		cvMean_StdDev(iplB, dpMean+2, dpStds+2);    // B通道均值和标准差
		
		// 计算评分:均值-标准差(高亮度低方差区域得高分)
		dpScore[0] = dpMean[0]-dpStds[0];           // R通道评分
		dpScore[1] = dpMean[1]-dpStds[1];           // G通道评分
		dpScore[2] = dpMean[2]-dpStds[2];           // B通道评分

		afScore[0] = (float)(dpScore[0]+dpScore[1]+dpScore[2]);  // 综合评分

		nMaxScore = afScore[0];                     // 初始化最高评分
		nMaxIndex = 0;                              // 初始化最优子块索引

		// ---------------------------------------------------------------------
		// 右上子块统计分析
		// ---------------------------------------------------------------------
		cvCvtPixToPlane(iplUpperRight, iplR, iplG, iplB, 0);

		cvMean_StdDev(iplR, dpMean, dpStds);
		cvMean_StdDev(iplG, dpMean+1, dpStds+1);
		cvMean_StdDev(iplB, dpMean+2, dpStds+2);
		
		dpScore[0] = dpMean[0]-dpStds[0];
		dpScore[1] = dpMean[1]-dpStds[1];
		dpScore[2] = dpMean[2]-dpStds[2];

		afScore[1] = (float)(dpScore[0]+dpScore[1]+dpScore[2]);
		
		if(afScore[1] > nMaxScore)                  // 更新最优子块
		{
			nMaxScore = afScore[1];
			nMaxIndex = 1;
		}

		// ---------------------------------------------------------------------
		// 左下子块统计分析
		// ---------------------------------------------------------------------
		cvCvtPixToPlane(iplLowerLeft, iplR, iplG, iplB, 0);

		cvMean_StdDev(iplR, dpMean, dpStds);
		cvMean_StdDev(iplG, dpMean+1, dpStds+1);
		cvMean_StdDev(iplB, dpMean+2, dpStds+2);
		
		dpScore[0] = dpMean[0]-dpStds[0];
		dpScore[1] = dpMean[1]-dpStds[1];
		dpScore[2] = dpMean[2]-dpStds[2];

		afScore[2] = (float)(dpScore[0]+dpScore[1]+dpScore[2]);
		
		if(afScore[2] > nMaxScore)                  // 更新最优子块
		{
			nMaxScore = afScore[2];
			nMaxIndex = 2;
		}

		// ---------------------------------------------------------------------
		// 右下子块统计分析
		// ---------------------------------------------------------------------
		cvCvtPixToPlane(iplLowerRight, iplR, iplG, iplB, 0);

		cvMean_StdDev(iplR, dpMean, dpStds);
		cvMean_StdDev(iplG, dpMean+1, dpStds+1);
		cvMean_StdDev(iplB, dpMean+2, dpStds+2);
		
		dpScore[0] = dpMean[0]-dpStds[0];
		dpScore[1] = dpMean[1]-dpStds[1];
		dpScore[2] = dpMean[2]-dpStds[2];

		afScore[3] = (float)(dpScore[0]+dpScore[1]+dpScore[2]);

		if(afScore[3] > nMaxScore)                  // 更新最优子块
		{
			nMaxScore = afScore[3];
			nMaxIndex = 3;
		}

		// =====================================================================
		// 递归调用:对最优子块继续细分搜索
		// =====================================================================
		switch (nMaxIndex)
		{
		case 0:
			AirlightEstimation(iplUpperLeft); break;    // 递归处理左上子块
		case 1:
			AirlightEstimation(iplUpperRight); break;   // 递归处理右上子块
		case 2:
			AirlightEstimation(iplLowerLeft); break;    // 递归处理左下子块
		case 3:
			AirlightEstimation(iplLowerRight); break;   // 递归处理右下子块
		}
	}
	else
	{
		// =====================================================================
		// 第二阶段:像素级精确搜索,寻找最接近纯白色的像素
		// =====================================================================
		for(nY=0; nY<nHei; nY++)                    // 遍历所有行
		{
			for(nX=0; nX<nWid; nX++)                // 遍历所有列
			{
				// 计算当前像素到纯白色(255,255,255)的欧氏距离
				// 距离公式:√[(255-B)² + (255-G)² + (255-R)²]
				nDistance = int(sqrt(float(255-(uchar)imInput->imageData[nY*nStep+nX*3])*float(255-(uchar)imInput->imageData[nY*nStep+nX*3])
					+float(255-(uchar)imInput->imageData[nY*nStep+nX*3+1])*float(255-(uchar)imInput->imageData[nY*nStep+nX*3+1])
					+float(255-(uchar)imInput->imageData[nY*nStep+nX*3+2])*float(255-(uchar)imInput->imageData[nY*nStep+nX*3+2])));
				
				if(nMinDistance > nDistance)        // 找到更接近纯白色的像素
				{
					nMinDistance = nDistance;       // 更新最小距离
					// 保存大气光值(BGR顺序)
					m_anAirlight[0] = (uchar)imInput->imageData[nY*nStep+nX*3];      // B通道
					m_anAirlight[1] = (uchar)imInput->imageData[nY*nStep+nX*3+1];    // G通道
					m_anAirlight[2] = (uchar)imInput->imageData[nY*nStep+nX*3+2];    // R通道
				}
			}
		}
	}
	
	// =========================================================================
	// 内存清理:释放临时创建的图像资源
	// =========================================================================
	cvReleaseImage(&iplUpperLeft);      // 释放左上子块
	cvReleaseImage(&iplUpperRight);     // 释放右上子块
	cvReleaseImage(&iplLowerLeft);      // 释放左下子块
	cvReleaseImage(&iplLowerRight);     // 释放右下子块

	cvReleaseImage(&iplR);              // 释放R通道图像
	cvReleaseImage(&iplG);              // 释放G通道图像
	cvReleaseImage(&iplB);              // 释放B通道图像
}

步骤二Optimal transmission estimation

主要是 信息loss和对比度loss
在这里插入图片描述
在这里插入图片描述
然后代码就是遍历 t从0.3到1,计算E,然后确定最好的t也就是粗糙的透射率估计,每块都有一个估计。

/*
	函数名:TransmissionEstimation
	功能:灰度图像的传输估计
	
	算法原理:
	--------
	该函数实现了灰度图像的传输估计,是彩色版本的简化形式。
	对于灰度图像,只需要处理单一的亮度通道(Y通道)。
	
	处理流程:
	1. 将图像分割成固定大小的块(m_nTBlockSize × m_nTBlockSize)
	2. 对每个块调用相应的传输估计函数:
	   - 如果启用前帧信息且不是第一帧:调用NFTrsEstimationP
	   - 否则:调用NFTrsEstimation
	3. 将估计的传输值赋给块内所有像素
	
	与彩色版本的区别:
	- 只处理单一通道,计算量更小
	- 适用于灰度图像或亮度通道处理
	- 算法逻辑与彩色版本完全一致
	
	参数说明:
	- pnImageY: 当前帧的灰度/亮度通道数据
	- pfTransmission: 输出的传输图
	- pnImageYP: 前一帧的灰度/亮度通道数据(用于时间一致性)
	- pfTransmissionP: 前一帧的传输图
	- nFrame: 帧编号
	- nWid, nHei: 图像宽度和高度
	
	返回值:
	- 通过pfTransmission参数返回估计的传输图
 */
void dehazing::TransmissionEstimation(int *pnImageY, float *pfTransmission, int *pnImageYP, float *pfTransmissionP, int nFrame, int nWid, int nHei)
{
	int nX, nY, nXstep, nYstep;  // 循环变量:块的起始位置和块内像素位置
	float fTrans;                // 当前块估计的传输值

	// 判断是否使用前帧信息进行时间一致性优化
	if(m_bPreviousFlag == true&&nFrame>0)
	{
		// 使用前帧信息的传输估计
		for(nY=0; nY<nHei; nY+=m_nTBlockSize)  // 按块大小遍历Y方向
		{
			for(nX=0; nX<nWid; nX+=m_nTBlockSize)  // 按块大小遍历X方向
			{
				// 调用基于前帧信息的灰度传输估计函数
				// __max确保坐标不为负数
				fTrans = NFTrsEstimationP(pnImageY, pnImageYP, pfTransmissionP, __max(nX, 0), __max(nY, 0), nWid, nHei);
				
				// 将估计的传输值赋给当前块内的所有像素
				for(nYstep=nY; nYstep<nY+m_nTBlockSize; nYstep++)
				{
					for(nXstep=nX; nXstep<nX+m_nTBlockSize; nXstep++)
					{
						pfTransmission[nYstep*nWid+nXstep] = fTrans;
					}
				}
			}
		}
	}
	else
	{
		// 不使用前帧信息的传输估计(第一帧或禁用时间一致性)
		for(nY=0; nY<nHei; nY+=m_nTBlockSize)
		{
			for(nX=0; nX<nWid; nX+=m_nTBlockSize)
			{
				// 调用基础的灰度传输估计函数
				fTrans = NFTrsEstimation(pnImageY, __max(nX, 0), __max(nY, 0), nWid, nHei);

				// 将估计的传输值赋给当前块内的所有像素
				for(nYstep=nY; nYstep<nY+m_nTBlockSize; nYstep++)
				{
					for(nXstep=nX; nXstep<nX+m_nTBlockSize; nXstep++)
					{
						pfTransmission[nYstep*nWid+nXstep] = fTrans;
					}
				}
			}
		}
	}
}

/*
	函数名:NFTrsEstimation
	功能:基础传输估计算法(灰度图像)
	
	算法原理:
	--------
	这是传输估计的核心算法,使用穷举搜索方法在指定块内寻找最优传输值。
	
	数学模型:
	基于大气散射模型:I(x) = J(x) * t(x) + A * (1 - t(x))
	恢复公式:J(x) = (I(x) - A) / t(x) + A
	
	优化目标函数:
	Cost(t) = λ₁ * InformationLoss(t) - Contrast(t)
	
	其中:
	1. InformationLoss(t) = Σ(max(0, J-255)² + max(0, -J)²) / N
	   - 惩罚恢复像素值超出[0,255]范围的情况
	   - 防止过度恢复导致的信息丢失
	
	2. Contrast(t) = Var(J) = E[J²] - E[J]²
	   - 使用方差衡量恢复图像的对比度
	   - 最大化对比度以获得更清晰的图像
	
	搜索策略:
	- 搜索范围:t ∈ [0.3, 1.0]
	- 搜索步长:0.1
	- 搜索次数:7次(0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9)
	- 选择使代价函数最小的传输值
	
	实现细节:
	- 使用整数运算提高效率:nTrans = (int)(1/t * 128)
	- 恢复公式:nOut = ((I - A) * nTrans + 128 * A) >> 7
	- 这相当于:nOut = (I - A) / t + A,但使用整数运算
	
	参数说明:
	- pnImageY: 输入的灰度图像数据
	- nStartX, nStartY: 处理块的左上角坐标
	- nWid, nHei: 图像宽度和高度
	
	返回值:
	- fOptTrs: 最优传输值
 */
float dehazing::NFTrsEstimation(int *pnImageY, int nStartX, int nStartY, int nWid, int nHei)
{
	int nCounter;	                // 传输值搜索计数器(0.3~0.9,共7次迭代)
	int nX, nY;		                // 块内像素坐标索引
	int nEndX;                      // 块的右边界
	int nEndY;                      // 块的下边界

	int nOut;						// 恢复后的像素值 J(x)
	int nSquaredOut;				// 恢复像素值的平方 J²(x)
	int nSumofOuts;					// 恢复像素值的总和 Σ J(x)
	int nSumofSquaredOuts;			// 恢复像素值平方的总和 Σ J²(x)
	float fTrans, fOptTrs;			// 当前传输值和最优传输值
	int nTrans;						// 传输值的整数表示(用于快速计算)
	int nSumofSLoss;				// 信息损失的总和
	float fCost, fMinCost, fMean;	// 代价函数值、最小代价、像素均值
	int nNumberofPixels, nLossCount; // 像素总数、损失像素计数

	// 计算处理块的边界,确保不超出图像范围
	nEndX = __min(nStartX+m_nTBlockSize, nWid); 
	nEndY = __min(nStartY+m_nTBlockSize, nHei); 

	nNumberofPixels = (nEndY-nStartY)*(nEndX-nStartX);	// 块内像素总数

	fTrans = 0.3f;	// 初始传输值从0.3开始
	nTrans = 427;	// 对应的整数表示:1/0.3*128 ≈ 427

	// 穷举搜索最优传输值
	for(nCounter=0; nCounter<7; nCounter++)
	{
		// 初始化统计变量
		nSumofSLoss = 0;        // 信息损失总和
		nLossCount = 0;         // 损失像素计数
		nSumofSquaredOuts = 0;  // 像素平方和
		nSumofOuts = 0;         // 像素总和
		
		// 遍历块内所有像素
		for(nY=nStartY; nY<nEndY; nY++)
		{
			for(nX=nStartX; nX<nEndX; nX++)
			{
				// 使用整数运算计算恢复像素值
				// 公式:J = (I-A)/t + A = ((I-A)*k*128 + A*128)/128
				// 其中 k = 1/t,右移7位相当于除以128
				nOut = ((pnImageY[nY*nWid+nX] - m_nAirlight)*nTrans + 128*m_nAirlight)>>7;
				nSquaredOut = nOut * nOut;

				// 计算信息损失:像素值超出[0,255]范围的惩罚
				if(nOut>255)
				{
					nSumofSLoss += (nOut - 255)*(nOut - 255);  // 超出255的惩罚
					nLossCount++;
				}
				else if(nOut < 0)
				{
					nSumofSLoss += nSquaredOut;  // 小于0的惩罚
					nLossCount++;
				}
				
				// 累积统计量用于计算对比度
				nSumofSquaredOuts += nSquaredOut;  // Σ J²
				nSumofOuts += nOut;                // Σ J
			}
		}
		
		// 计算像素均值:E[J] = Σ J / N
		fMean = (float)(nSumofOuts)/(float)(nNumberofPixels);  
		
		// 计算代价函数:Cost = λ₁ * InformationLoss - Contrast
		// Contrast = Var(J) = E[J²] - E[J]²
		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;                           // 传输值增加0.1
		nTrans = (int)(1.0f/fTrans*128.0f);      // 更新对应的整数表示
	}
	return fOptTrs;  // 返回最优传输值
}

步骤三,对初始估计的穿透率进行refine,利用引导滤波,原图作为引导图像

具体代码就不贴了,可以通过http://mcl.korea.ac.kr/projects/dehazing/#userconsent#下载作者原始引导滤波代码

步骤四,图像去雾

已知 大气估计值和 透射率估计图,就可以利用公式恢复 图像

/*
	=============================================================================
	图像恢复函数 (Image Restoration)
	=============================================================================
	
	【算法原理】
	基于大气散射模型的图像恢复算法,通过估计的传输图和大气光值恢复清晰图像。
	该函数是去雾算法的核心步骤,将有雾图像转换为无雾的清晰图像。
	
	【理论基础】
	1. 大气散射模型:I(x) = J(x)t(x) + A(1-t(x))
	   - I(x):观测到的有雾图像
	   - J(x):场景辐射(无雾图像)
	   - t(x):传输图(透射率)
	   - A:大气光值
	
	2. 图像恢复公式:J(x) = (I(x) - A) / t(x) + A
	   通过逆向求解大气散射模型得到清晰图像
	
	【算法流程】
	第一阶段:后处理判断
	- 如果启用后处理标志(m_bPostFlag),调用PostProcessing函数
	- 否则执行标准恢复流程
	
	第二阶段:像素级图像恢复(并行处理)
	1. 对每个像素应用恢复公式:J = (I - A) / t + A
	2. 使用CLIP函数确保像素值在有效范围内
	3. 使用CLIP_Z函数避免传输图为零的除零错误
	4. 应用伽马校正查找表优化视觉效果
	
	【数学模型详解】
	恢复公式推导:
	I(x) = J(x)t(x) + A(1-t(x))
	I(x) = J(x)t(x) + A - At(x)
	I(x) - A = J(x)t(x) - At(x)
	I(x) - A = t(x)(J(x) - A)
	J(x) = (I(x) - A) / t(x) + A
	
	【关键技术】
	1. 传输图约束:使用CLIP_Z确保t(x) ≥ t_min,避免过度增强
	2. 像素值约束:使用CLIP确保输出在[0,255]范围内
	3. 伽马校正:通过查找表m_pucGammaLUT调整亮度和对比度
	4. 并行处理:使用OpenMP提高计算效率
	
	【性能优化】
	1. 预计算查找表:伽马校正使用LUT避免重复计算
	2. 内存访问优化:按行优先顺序访问提高缓存命中率
	3. 并行计算:OpenMP并行化像素级操作
	4. 数据类型优化:使用float进行中间计算,uchar存储结果
	
	【参数说明】
	@param imInput  - 输入的有雾图像 (IplImage格式)
	                  BGR通道顺序,8位无符号整数
	@param imOutput - 输出的去雾图像 (IplImage格式)
	                  与输入图像相同格式和尺寸
	
	【算法特性】
	- 时间复杂度:O(W×H) - 线性复杂度
	- 空间复杂度:O(1) - 原地处理,不需要额外存储
	- 并行度:像素级并行,理论加速比接近CPU核心数
	- 数值稳定性:通过约束函数避免数值异常
	
	【应用场景】
	- 单张图像去雾:恢复清晰的无雾图像
	- 视频去雾:逐帧处理视频序列
	- 实时去雾:结合优化技术实现实时处理
	- 批量处理:大规模图像去雾任务
	
	【质量控制】
	1. 传输图质量:直接影响恢复效果
	2. 大气光估计:准确性决定色彩还原度
	3. 伽马参数:影响最终图像的视觉效果
	4. 约束参数:平衡去雾强度和噪声抑制
 */
void dehazing::RestoreImage(IplImage* imInput, IplImage* imOutput)
{
	// =========================================================================
	// 图像参数获取和大气光值初始化
	// =========================================================================
	int nStep = imInput->widthStep;     // 图像行步长(字节)

	int nX, nY;                         // 像素坐标索引
	float fA_R, fA_G, fA_B;            // 各通道大气光值(浮点型)

	// 将整型大气光值转换为浮点型,便于后续计算
	fA_B = (float)m_anAirlight[0];      // B通道大气光值
	fA_G = (float)m_anAirlight[1];      // G通道大气光值
	fA_R = (float)m_anAirlight[2];      // R通道大气光值

	// =========================================================================
	// 处理模式选择:后处理 vs 标准恢复
	// =========================================================================
	if(m_bPostFlag == true)             // 启用后处理模式
	{
		// 调用专门的后处理函数,处理视频序列的块效应等问题
		PostProcessing(imInput,imOutput);
	}
	else                                // 标准图像恢复模式
	{
		// =====================================================================
		// 并行图像恢复:应用大气散射逆模型
		// =====================================================================
#pragma omp parallel for            // OpenMP并行化处理,提高计算效率
		for(nY=0; nY<m_nHei; nY++)      // 遍历图像的每一行
		{
			for(nX=0; nX<m_nWid; nX++)  // 遍历图像的每一列
			{			
				// =============================================================
				// 图像恢复公式:J = (I - A) / t + A
				// 同时应用伽马校正查找表优化视觉效果
				// =============================================================
				
				// B通道恢复:
				// 1. 提取输入像素值:(uchar)imInput->imageData[nY*nStep+nX*3+0]
				// 2. 减去大气光:((float)pixel - fA_B)
				// 3. 除以传输图:result / CLIP_Z(transmission)
				// 4. 加回大气光:result + fA_B
				// 5. 像素值约束:CLIP(result) 确保在[0,255]范围
				// 6. 伽马校正:m_pucGammaLUT[pixel] 应用预计算的伽马表
				imOutput->imageData[nY*nStep+nX*3]	 = (uchar)m_pucGammaLUT[(uchar)CLIP((((float)((uchar)imInput->imageData[nY*nStep+nX*3+0])-fA_B)/CLIP_Z(m_pfTransmissionR[nY*m_nWid+nX]) + fA_B))];
				
				// G通道恢复(处理流程同B通道)
				imOutput->imageData[nY*nStep+nX*3+1] = (uchar)m_pucGammaLUT[(uchar)CLIP((((float)((uchar)imInput->imageData[nY*nStep+nX*3+1])-fA_G)/CLIP_Z(m_pfTransmissionR[nY*m_nWid+nX]) + fA_G))];
				
				// R通道恢复(处理流程同B通道)
				imOutput->imageData[nY*nStep+nX*3+2] = (uchar)m_pucGammaLUT[(uchar)CLIP((((float)((uchar)imInput->imageData[nY*nStep+nX*3+2])-fA_R)/CLIP_Z(m_pfTransmissionR[nY*m_nWid+nX]) + fA_R))];
			}
		}
	}
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐