OpenCV暗通道去雾算法在内窥镜视频流中的应用
本文算法基于大气去雾模型,以及何凯明博士的暗通道先验的理论思想,细化到内窥镜图像处理领域,做了一些速度上的提升以及优化。针对内窥镜视频必有反光,且手术腔内光强不会发生剧烈变化的特点,因此讲光强估计值A直接定义为255,减少了每帧图像在估算光强值时的时间。其他领域可能不适用该算法。
#include<opencv2\opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
//block模板大小与omega作用程度这两个参数可调整
int block = 3;//每个像素点的周围block*block的矩形,block越大,速度越快,失真越明显
double omega = 0.7;//除雾的程度,取值范围为[0,1],值越大,处理后图像颜色越深
Mat ori, out;//原始Mat对象ori,处理后Mat对象out
//定义三通道最小值函数min_3
double min_3(double g, double b, double r)
{
double min;
if (g <= b && g <= r)min = g;
if (b <= g && b <= r)min = b;
if (r <= g && r <= b)min = r;
return min;
}
//定义矩阵内的最小值函数
double mat_min(Mat matrix)
{
double min_in_matrix = 255.0;
for (int i = 0; i < matrix.cols; i++)
{
for (int j = 0; j < matrix.rows; j++)
{
if (min_in_matrix > matrix.at<uchar>(i, j))
min_in_matrix = matrix.at<uchar>(i, j);
}
}
return min_in_matrix;
}
//定义去雾函数如下
Mat defogging(Mat image_in, int block, double omega)
{
//创建一个Mat类型的vector out_temp,其中维数是输入图像image_in的通道数
vector<Mat> out_temp(3);
split(image_in, out_temp);//将原图ori的三通道分割并存储到out_temp内
//创建一个Mat类型的vector out_ROI
vector<Mat>out_ROI(3);
out_ROI[0] = Mat(block, block, CV_8UC1);//绿色8位单通道
out_ROI[1] = Mat(block, block, CV_8UC1);//蓝色8位单通道
out_ROI[2] = Mat(block, block, CV_8UC1);//红色8位单通道
//创建暗通道ROI对象,暗通道对象,输出图像的Mat对象。
Mat dark_ROI, dark_channel, out;
dark_ROI = Mat(block, block, CV_8UC1);//暗通道ROI的8位单通道
dark_channel = Mat(image_in.rows, image_in.cols, CV_8UC1);//给暗通道分配大小
out = Mat(image_in.rows, image_in.cols, CV_8UC3);//去雾后的图像,三通道合并成out的Mat对象
//创建每次循环的处理区域work_ROI,并初始化坐标极其宽度高度
Rect work_ROI;
work_ROI.x = 0;//矩形左上角横坐标
work_ROI.y = 0;//矩形左上角纵坐标
work_ROI.width = block;//矩形宽为block
work_ROI.height = block;//矩形高为block
//原图像ROI位置以及大小Rect类型储存
//按照论文方法的大气光强估计算法计算手术腔内光强估计值。
//在原始有雾图像中寻找对应的具有最高亮度的点的值,作为A值。
//并且由于是固定光源,腔内光强不会发生剧烈变化,因此无需每一帧的计算一次A。
//因为必定会有镜面反射点,于是直接令A_0,A_1,A_2都为255。
//简化排序每帧对于求A的一个大排序时间,优化在该领域算法复杂度。
//定义完毕手术腔内光强估计值
std::vector<double>A(3);
A = { 255.0,255.0,255.0 };//公式中每个通道的光强估计值A
//下面求暗通道dark_channel。
double min0 = 0, min1 = 0, min2 = 0, min = 0;//每个通道的最小值,以及整体最小值。
for (int i = 0; i < image_in.cols / block; i++)//外层循环循环原图列数/block 次
{
for (int j = 0; j < image_in.rows / block; j++) //内层循环原图行数/block 次
{
//分别计算三个通道内ROI的最小值
out_ROI[0] = out_temp[0](work_ROI);//在矩阵work_ROI内的绿色分量
min0 = mat_min(out_ROI[0]);//求出第一个像素点在out_ROI_0绿色分量的最小值
out_ROI[1] = out_temp[1](work_ROI);//蓝色分量在矩阵work_ROI内的小值
min1 = mat_min(out_ROI[1]);//求出第一个像素点蓝色分量的最小值
out_ROI[2] = out_temp[2](work_ROI);//红色分量在矩阵work_ROI内的小值
min2 = mat_min(out_ROI[2]);//求出第一个像素点红色分量的最小值
//求三个通道内最小值的最小值 即求min1 min2 min3中的最小值赋值给min
min = min_3(min0, min1, min2);//min为这个ROI中暗原色
dark_ROI = dark_channel(work_ROI);//拷贝work_ROI给dark_channel后再赋给dark_ROI
Mat temp = Mat(block, block, CV_8UC1, Scalar(min));//将最小值赋给temp
temp.copyTo(dark_ROI);//按原色拷贝给dark_ROI
//rect_ROI坐标转换到下一个矩形区块。
work_ROI.x = block * i;//工作区域向后移动block个像素
work_ROI.y = block * j;//工作区域向后移动block个像素
}
}
//下面求处理过后的图像
double tx;//透射率t(x)
uchar ori_0, ori_1, ori_2;//输出图像每个通道像素值
for (int i = 0; i < image_in.rows; i++)
{
for (int j = 0; j < image_in.cols; j++)
{
tx = 1 - omega * dark_channel.at<uchar>(i, j)/255.0;
//根据公式透射率=1-omega*min(dark_channel)/255,omega的值不要太大,否则颜色过暗视觉效果不好,得到每个点的透射率
if (tx < 0.1)tx = 0.1;
//防止精度不够之后会出现除0的错误
//根据雾图模型 I(x)=J(x)t(x)+A(1-t(x))可以得到:J(x)=(I(x)-A)/t(x)+A 根据上式计算每个通道
ori_0 = (image_in.at<Vec3b>(i, j)[0] - A[0]) / tx + A[0];
//绿色通道每个像素点=(输入图像绿色通道值-光强估计值)/透射率+光强估计值
ori_1 = (image_in.at<Vec3b>(i, j)[1] - A[1]) / tx + A[1];
//蓝色通道每个像素点=(输入图像蓝色通道值-光强估计值)/透射率+光强估计值
ori_2 = (image_in.at<Vec3b>(i, j)[2] - A[2]) / tx + A[2];
//红色通道每个像素点=(输入图像红色通道值-光强估计值)/透射率+光强估计值
out.at<Vec3b>(i, j)[0] = ori_0;
//输出图像绿色通道值写入
out.at<Vec3b>(i, j)[1] = ori_1;
//输出图像蓝色通道值写入
out.at<Vec3b>(i, j)[2] = ori_2;
//输出图像红色通道值写入
}
}
return out;
}
int main()
{
cout << "请输入视频流路径: << endl;
string PATH;
cin >> PATH;
VideoCapture capture;
capture.open(PATH);
while (true)
{
Mat ori;
capture >> ori;
imshow("原图像ori", ori);
waitKey(20);
//算法实现
out = defogging(ori, block, omega);
imshow("处理后图像out", out);
//算法结束
waitKey(20);
}
return 0;
}
实现效果图如下:
可以看出效果差别。参数选取了block=3以及omega=0.7
总结:该算法思路,循环执行3*3的每一小块图片,将其在块中每个通道的最小值赋值给暗通道,在经过论文公式计算即可还输出去雾后图像,这也间接实现了对比度增强。