暗通道去雾
暗通道去雾
序言
毕业设计选了用fpga做图像处理,之前在做车牌识别的时候,碰巧发现做去雾处理的,发现这个蛮有意思的,当时考研没有时间呢,现在正好借毕设把这个想法实现一下。其中图像去雾处理,网上有好多方法,比较了一下,何恺明的暗通道去雾应该是效果最好的,后来的人也对他的算法进行了改进,基本能达到比较不错的结果。
原理
暗通道先验理论
暗通道图像就是三个颜色分量的最小值组成一副灰度图像,
对于没有雾的图像J_dark趋于零,这一结果是何博士分析了5000多张正常图片的暗通道结果,而对于有雾图像并不满足之一验证。
#####公式
在去雾处理中,下面去雾模型被广泛应用
其中I(x)代表输入的雾图,J(x)代表去雾后的图像,t(x)为透射图,A为大气参数。现在只有I(x)知道,想要求出来J(x),还要把tx,A求出来才可以
对公式两边求最小值操作,因为正常图像的暗通道趋于零,所以可以化简为
因为在实际景物中存在一定雾气,因此在前面加入一个参数w,这个因子一般取0.95
而大气参数A,可以根据暗通道求出,最后的去雾公式为
为了防止J(x)过大超过阈值,tx不能太小,因此对tx设置一个最小值t0=0.1
opencv实现
求暗通道
求三个通道中的最小值即可,用at遍历,然后输出为单通道图片,最后进行最小值滤波,最小值滤波在
int FindDarkChannel(Mat* scr_img, Mat* dst_img)
{
uchar r, g, b;
for (int row = 0; row < scr_img->rows; row++)
{
for (int col = 0; col < scr_img->cols; col++)
{
r = scr_img->at<Vec3b>(row, col)[0];
g = scr_img->at<Vec3b>(row, col)[1];
b = scr_img->at<Vec3b>(row, col)[2];
if (r < g&&r < b)
{
dst_img->at<uchar>(row,col) = r;
}
else if (g < r&&g < b)
{
dst_img->at<uchar>(row, col) = g;
}
else
{
dst_img->at<uchar>(row, col) = b;
}
}
}
return 0;
}
获取大气光值A
1、首先把暗通道分成一千份,然后找到这千分之一最亮的像素区域。
2、从这块区域中找到最亮的点就是大气光强。
分成1000份是为了防止把一些非天空区域的白色物体当成大气光值
int GetMosphereLight(Mat* dark_img, Mat* scr_img, int* A)
{
int top_size = dark_img->cols*dark_img->rows / 1000;
//cout << dark_img->cols*dark_img->rows << endl;
//cout << "top_size::" << top_size << endl;
int sum_pxiel[1010] = {0}; //多余的储存剩下的像素
int max_num = 0,cnt_num=0,cnt_pixel=0;//0开始
for (int row = 0; row < dark_img->rows; row++)
{
for (int col = 0; col < dark_img->cols; col++)
{
sum_pxiel[cnt_num] += dark_img->at<uchar>(row, col);
cnt_pixel++;
if (cnt_num == 0)
cout << cnt_pixel << endl;
if (cnt_pixel == top_size)
{
//cout << "cnt_num" << cnt_num <<" "<<sum_pxiel[cnt_num]<< endl;
cnt_pixel = 0;
cnt_num++;
}
}
}
max_num = 0;
//debug
for (int i = 0; i < 100; i++)
{
cout << i << " " << sum_pxiel[i] << endl;
}
for (int i = 0; i < 1000; i++)
{
if (sum_pxiel[i] > sum_pxiel[max_num])
{
max_num = i;
}
}
cout << "max_num " << max_num << endl;
int row = max_num * top_size / dark_img->cols;
int col = max_num * top_size % dark_img->cols;
cout << "row " << row << "col " << col << endl;
int max_row = row, max_col = col;
for (int i = 0; i < top_size; i++)
{
//cout << "i==" << i << " " << endl;
int row_temp = row + i / dark_img->cols;
int col_temp = col + i % dark_img->cols;
if (dark_img->at<uchar>(row_temp,col_temp) > dark_img->at<uchar>(max_row, max_col))
{
max_row = row_temp;
max_col = col_temp;
}
}
Mat a_img = scr_img->clone();
Point a_point;
a_point.x = max_col;
a_point.y = max_row;
circle(a_img, a_point, 10, (0, 255, 0),5);
imshow("mark大气光值", a_img);
for (int i = 0; i < 3; i++)
{
A[i] = scr_img->at<Vec3b>(max_row, max_col)[i];
}
return 0;
}
获取透射图
这个大气光值A是取得三个通道的平均值,这样可以加快运算速度,另外需要注意图像的类型
int GetTransmission(Mat* scr_img, Mat* trans_img, int* A, float w)
{
Mat trans_temp = Mat(scr_img->rows, scr_img->cols, CV_32FC3);
float a_average = (float)(A[0] + A[1] + A[2]) / 3;
if (a_average > 220)
a_average = 220;
cout << "average" << a_average << endl;
for (int row = 0; row < scr_img->rows; row++)
{
for (int col = 0; col < scr_img->cols; col++)
{
for (int i = 0; i < 3; i++)
{
trans_temp.at<Vec3f>(row, col)[i] = (float)scr_img->at<Vec3b>(row, col)[i] / a_average;
}
}
}
imshow("t_temp", trans_temp);
Mat dark_t_img = Mat(scr_img->size(), CV_32FC1);
FindDarkChannel_f(&trans_temp, &dark_t_img);
imshow("t暗通道", dark_t_img);
*trans_img = dark_t_img.clone();
MinFilter_f(&dark_t_img, trans_img, 6);
imshow("t_min", *trans_img);
int a = 0;
for (int row = 0; row < scr_img->rows; row++)
{
for (int col = 0; col < scr_img->cols; col++)
{
if ((1 - w * trans_img->at<float>(row, col)) < 0)
{
cout << " ww" << endl;
trans_img->at<float>(row, col) = 0;
}
else
trans_img->at<float>(row, col) = 1 - w * trans_img->at<float>(row, col);
if (trans_img->at<float>(row, col) < 0.1)
{
//cout << a++ << endl;
trans_img->at<float>(row, col) = 0.1;
}
}
}
return 0;
}
导向滤波
直接出来的透射图并不精细,用导向滤波后输出的透射图更加精细,导向滤波的引导图可以是灰度图,也可以是rgb图像。opencv里面有导向滤波的函数,但是需要经过编译后才可以使用,导向滤波的实现并不麻烦,这里可以自己实现
int GuideFilter(Mat* scr_img, Mat* guide_img, Mat* final_img, int radius, float e)
{
Mat guide_conver = Mat(guide_img->size(), CV_32FC1);
guide_img->convertTo(guide_conver, CV_32FC1, 1.0 / 255);
int size = radius * 2 + 1;
//1
Mat mean_i = Mat(guide_img->size(), CV_32FC1);
boxFilter(guide_conver, mean_i, CV_32FC1, Size(size, size));
Mat mean_p = Mat(guide_img->size(), CV_32FC1);
boxFilter(*scr_img, mean_p, CV_32FC1, Size(size, size));
Mat mean_ip = Mat(guide_img->size(), CV_32FC1);
boxFilter(guide_conver.mul(*scr_img), mean_ip, CV_32FC1, Size(size, size));
Mat mean_ii = Mat(guide_img->size(), CV_32FC1);
boxFilter(guide_conver.mul(guide_conver), mean_ii, CV_32FC1, Size(size, size));;
//2
Mat var_i = Mat(guide_img->size(), CV_32FC1);
var_i = mean_ii - mean_i.mul(mean_i);
Mat cov_ip = Mat(guide_img->size(), CV_32FC1);
cov_ip = mean_ip - mean_i.mul(mean_p);
//3
Mat a = Mat(guide_img->size(), CV_32FC1);
a = cov_ip / (var_i + e);
Mat b = Mat(guide_img->size(), CV_32FC1);
b = mean_p - a.mul(mean_i);
//4
Mat mean_a = Mat(guide_img->size(), CV_32FC1);
Mat mean_b = Mat(guide_img->size(), CV_32FC1);
boxFilter(a, mean_a, CV_32FC1, Size(size, size));
boxFilter(b, mean_b, CV_32FC1, Size(size, size));
//5
*final_img = mean_a.mul(guide_conver) + mean_b;
return 0;
}
恢复图像
这里需要注意的是,参与运算的图像有浮点型和整数型,整数型要归一化才可以和浮点型运算。
int RecoverPicture(Mat* scr_img, Mat* t_img, Mat* recover_img, int* A)
{
float average = (float)(A[0] + A[1] + A[2]) / 3;
cout <<"A"<< average/255 << endl;
if (average > 220)
average = 220;
for (int row = 0; row < scr_img->rows; row++)
{
for (int col = 0; col < scr_img->cols; col++)
{
for (int i = 0; i < 3; i++)
{
recover_img->at<Vec3f>(row, col)[i] = ((float)scr_img->at<Vec3b>(row, col)[i]/255 -average/255) / t_img->at<float>(row, col) + average /255;
}
}
}
return 0;
}
不足
如果图像中,天空区域过亮或者具有大面积天空区域,去雾图片会出现色斑效应,看网上有人指出可能是t0的最小值为to,当大量t趋于一个值时就会出现色斑效应,另外如果大气光值找的不准确,色彩就会失真,这些问题等后期再解决。