Opencv4(C++)实战案例1:将朦胧的图像变清晰(去雾)

本文的主要思想参考了以下文章,学习过程中对各个api进行了了解和分析。
原文
CV_Assert
检查运行情况,如果出现错误,则显示错误信息。
在括号内填入想要检测的信息然后错误则抛出一个异常,否则就返回true
在这里就相当于我们做的一个image.empty()检测。
src.convertTo(dst, type, scale, shift)
注意也不是所有格式的Mat型数据都能被使用保存为图片,目前OpenCV主要只支持单通道和3通道的图像,并且此时要求其深度为8bit和16bit无符号(即CV_16U),所以其他一些数据类型是不支持的,比如说float型等。
convertTo()函数负责转换数据类型不同的Mat,即可以将类似float型的Mat转换到imwrite()函数能够接受的类型。
而cvtColor()函数是负责转换不同通道的Mat,因为该函数的第4个参数就可以设置目的Mat数据的通道数(只是我们一般没有用到它,一般情况下这个函数是用来进行色彩空间转换的)。
缩放并转换到另外一种数据类型:
dst:目的矩阵;
type:需要的输出矩阵类型,或者更明确的,是输出矩阵的深度,如果是负值(常用-1)则输出矩阵和输入矩阵类型相同;
scale:比例因子;
shift:将输入数组元素按比例缩放后添加的值;
dst(i)=src(i)xscale+(shift,shift,…)
在去雾操作的第一步里面就需要做一个转化数据类型的操作
image.convertTo(fImage, CV_32FC3, 1.0 / 255.0);
那么在这里我们需要知道一个知识点:
就是在浮点数的图像数据类型中,其灰度值的范围都是在0-1之间的,而原图像是BGR的,所有像素点的灰度值都在0-255之间,所以如果要保持原图像不变,要乘于一个比例因子:(float)(1/255)
copyMakeBorder
函数原型:
void copyMakeBorder( const Mat& src, Mat& dst,
int top, int bottom, int left, int right,
int borderType, const Scalar& value=Scalar() );
功能
扩充src的边缘,将图像变大,然后以各种外插方式自动填充图像边界,这个函数实际上调用了函数cv::borderInterpolate,这个函数最重要的功能就是为了处理边界,比如均值滤波或者中值滤波中,使用copyMakeBorder将原图稍微放大,然后我们就可以处理边界的情况了
参数解释
src,dst:原图与目标图像
top,bottom,left,right分别表示在原图四周扩充边缘的大小
borderType:扩充边缘的类型,就是外插的类型,OpenCV中给出以下几种方式
- BORDER_REPLICATE
- BORDER_REFLECT
- BORDER_REFLECT_101
- BORDER_WRAP
- BORDER_CONSTANT
实际中,还有其他的宏定义
//! various border interpolation methods
enum { BORDER_REPLICATE=IPL_BORDER_REPLICATE, BORDER_CONSTANT=IPL_BORDER_CONSTANT,
BORDER_REFLECT=IPL_BORDER_REFLECT, BORDER_WRAP=IPL_BORDER_WRAP,
BORDER_REFLECT_101=IPL_BORDER_REFLECT_101, BORDER_REFLECT101=BORDER_REFLECT_101,
BORDER_TRANSPARENT=IPL_BORDER_TRANSPARENT,
BORDER_DEFAULT=BORDER_REFLECT_101, BORDER_ISOLATED=16 };
总而言之,在进行图像卷积操作的时候为了处理图像边缘等像素的问题都是需要通过设定重复边缘来达到我们想要的效果的
reshape
reshape有两个参数:Mat::reshape(int cn, int rows=0)
其中,参数:cn为新的通道数,如果cn = 0,表示通道数不会改变。
参数rows为新的行数,如果rows = 0,表示行数不会改变。
注意:新的行列必须与原来的行列相等。就是说,如果原来是5行3列,新的行和列可以是1行15列,3行5列,5行3列,15行1列。仅此几种,否则会报错。
Mat_
在读取矩阵元素时,以及获取矩阵某行的地址时,需要指定数据类型。这样首先需要不停地写“”,让人感觉很繁琐,在繁琐和烦躁中容易犯错,如下面代码中的错误,用 at()获取矩阵元素时错误的使用了 double 类型。这种错误不是语法错误,因此在编译时编译器不会提醒。在程序运行时, at()函数获取到的不是期望的(i,j)位置处的元素,数据已经越界,但是运行时也未必会报错。这样的错误使得你的程序忽而看上去正常,忽而弹出“段错误”,特别是在代码规模很大时,难以查错。
如果使用 Mat_类,那么就可以在变量声明时确定元素的类型, 访问元素时不再需要指定元素类型,即使得代码简洁,又减少了出错的可能性。
sortIdx
在 MATLAB 里,返回排序后的矩阵以及对应原矩阵的索引是在 sort 一个函数搞定,但在 OpenCV 中,其功能分别被分配到了 cv::sort 和 cv::sortIdx 里,cv::sort 负责返回排序后的矩阵,cv::sortIdx 负责返回对应原矩阵的索引。
在 OpenCV 中,我们用类似于 CV_SORT_EVERY_ROW + CV_SORT_ASCENDING 这样的方式来一并指定对列还是对行以及升序还是降序,其指示值定义如下,所以可以组合出 4 种不同的方式:
#define CV_SORT_EVERY_ROW 0
#define CV_SORT_EVERY_COLUMN 1
#define CV_SORT_ASCENDING 0
#define CV_SORT_DESCENDING 16
//CV_SORT_EVERY_ROW + CV_SORT_ASCENDING:对矩阵的每行按照升序排序;
//CV_SORT_EVERY_ROW + CV_SORT_DESCENDING:对矩阵的每行按照降序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_ASCENDING:对矩阵的每列按照升序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_DESCENDING:对矩阵的每列按照降序排序;
————————————————
版权声明:本文为CSDN博主「JoannaJuanCV」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zfjBIT/article/details/88397910
其他的就都没有新知识点了,靠着论文去编码就行
通过测试代码
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
#include <cstring>
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#define CV_SORT_EVERY_ROW 0
#define CV_SORT_EVERY_COLUMN 1
#define CV_SORT_ASCENDING 0
#define CV_SORT_DESCENDING 16
//CV_SORT_EVERY_ROW + CV_SORT_ASCENDING:对矩阵的每行按照升序排序;
//CV_SORT_EVERY_ROW + CV_SORT_DESCENDING:对矩阵的每行按照降序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_ASCENDING:对矩阵的每列按照升序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_DESCENDING:对矩阵的每列按照降序排序;
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("E:/data/h1.jpg");
if (image.empty())
{
cout << "error!";
return 0;
}
cout << "complete" << endl;
imshow("src", image);
//imshow("src", src);
//一、求暗通道
//1.首先对图像进行归一化
Mat fImage;
image.convertTo(fImage, CV_32FC3, 1.0 / 255.0);//就是在浮点数的图像数据类型中,其灰度值的范围都是在0-1之间的,而原图像是BGR的,所有像素点的灰度值都在0-255之间,所以如果要保持原图像不变,要乘于一个比例因子:(float)(1/255)
//2.设定运算窗口
int hPatch = 15;
int vPatch = 15;
//3.给归一化的图片设定边界
Mat fImageBorder;
//此边界是为了便于我们处理边缘数据,在后续的卷积操作中,由于部分卷积算子的尺寸特殊性,有些边缘像素没办法处理到,所以就要用此api进行边缘的复制
copyMakeBorder(fImage, fImageBorder, vPatch / 2, vPatch / 2, hPatch / 2, hPatch / 2, BORDER_REPLICATE);
//4.分离通道
std::vector<Mat> fImageBorderVector(3);
split(fImageBorder,fImageBorderVector);//把三个通道push到vector中去
//5.创建并且计算暗通道
Mat darkChannel(image.rows,image.cols, CV_32FC1);//单通道图像
double minTemp, minPixel;
//计算暗通道
for (unsigned int r = 0; r < darkChannel.rows; r++)
{
for (unsigned int c = 0; c < darkChannel.cols; c++)
{
minPixel = 1.0;
for (std::vector<Mat>::iterator it = fImageBorderVector.begin(); it != fImageBorderVector.end(); it++)
{
Mat roi(*it, Rect(c,r,hPatch,vPatch));
minMaxLoc(roi,&minTemp);//相当于一个打擂算法,在这个窗口里面找最小值
minPixel = std::min(minPixel, minTemp);
}
//打擂结束,设定最小值,把暗通道算出来,并且赋值
darkChannel.at<float>(r, c) = float(minPixel);
}
}
/*这一段代码是用来测试我们的暗通道是否提取成功
imshow("dst", darkChannel);
Mat darkChannel8U;
darkChannel.convertTo(darkChannel8U, CV_8UC1, 255, 0);
imwrite("E:/outputdata/darkchannel_h1.jpg", darkChannel8U);
success!
*/
//二、通过暗通道来实现A的过程,求的是大气的那个值
//1.计算出darkChannel中, 前top个亮的值, 论文中取值为0.1 %
float top = 0.001;
float numberTop = top * darkChannel.rows * darkChannel.cols;
Mat darkChannelVectorOneRow = darkChannel.reshape(1, 1);//单通道,一行
Mat_<int> darkChannelVectorIndex;
//Mat_类
sortIdx(darkChannelVectorOneRow, darkChannelVectorIndex, CV_SORT_EVERY_ROW + CV_SORT_DESCENDING);//获取数组的索引
//这个函数是用来先对原来的进行一次sort,然后返回这个sort的数组的中每一个元素所对应元素数组的index,再形成一个arrary赋值到第二个参数中去。
//2.制作掩码,设定一个二值mask
Mat mask(darkChannelVectorIndex.rows, darkChannelVectorIndex.cols, CV_8UC1);//注意mask的类型必须是CV_8UC1
//然后要做的是,找出原图像中亮度在前百分之1的点,把对应的点设置为1,其他的都设置成0
//然后由于每行是已经通过sortIdx序列化之后的结果
for (unsigned int r = 0; r < darkChannelVectorIndex.rows; r++)
{
for (unsigned int c = 0; c < darkChannelVectorIndex.cols; c++)
{
if (darkChannelVectorIndex.at<int>(r, c) <= numberTop)
mask.at<uchar>(r, c) = 1;
else
mask.at<uchar>(r, c) = 0;
}
}
Mat darkChannelIndex = mask.reshape(1, darkChannel.rows);//单通道
vector<double> A(3);//分别存取A_b,A_g,A_r
vector<double>::iterator itA = A.begin();
vector<Mat>::iterator it = fImageBorderVector.begin();
//2.2在求第三步的t(x)时,会用到以下的矩阵,这里可以提前求出
vector<Mat> fImageBorderVectorA(3);
vector<Mat>::iterator itAA = fImageBorderVectorA.begin();
for (it= fImageBorderVector.begin() ; it != fImageBorderVector.end() && itA != A.end() && itAA != fImageBorderVectorA.end(); it++, itA++, itAA++)
{
Mat roi(*it, Rect(hPatch / 2, vPatch / 2, darkChannel.cols, darkChannel.rows));
minMaxLoc(roi, 0, &(*itA), 0, 0, darkChannelIndex);//
(*itAA) = (*it) / (*itA); //[注意:这个地方有除号,但是没有判断是否等于0]
}
/*第三步:求t(x)*/
Mat darkChannelA(darkChannel.rows, darkChannel.cols, CV_32FC1);
float omega = 0.95;//0<w<=1,论文中取值为0.95
//代码和求darkChannel的时候,代码差不多
for (unsigned int r = 0; r < darkChannel.rows; r++)
{
for (unsigned int c = 0; c < darkChannel.cols; c++)
{
minPixel = 1.0;
for (itAA = fImageBorderVectorA.begin(); itAA != fImageBorderVectorA.end(); itAA++)
{
Mat roi(*itAA, Rect(c, r, hPatch, vPatch));
minMaxLoc(roi, &minTemp);
minPixel = min(minPixel, minTemp);
}
darkChannelA.at<float>(r, c) = float(minPixel);
}
}
Mat tx = 1.0 - omega * darkChannelA;
/*第四步:我们可以求J(x)*/
float t0 = 0.1;//论文中取t0 = 0.1
Mat jx(image.rows, image.cols, CV_32FC3);
for (size_t r = 0; r < jx.rows; r++)
{
for (size_t c = 0; c < jx.cols; c++)
{
jx.at<Vec3f>(r, c) = Vec3f((fImage.at<Vec3f>(r, c)[0] - A[0]) / max(tx.at<float>(r, c), t0) + A[0], (fImage.at<Vec3f>(r, c)[1] - A[1]) / max(tx.at<float>(r, c), t0) + A[1], (fImage.at<Vec3f>(r, c)[2] - A[2]) / max(tx.at<float>(r, c), t0) + A[2]);
}
}
namedWindow("jx", 1);
imshow("jx", jx);
Mat jx8u;
jx.convertTo(jx8u, CV_8UC3, 255, 0);
imwrite("E:/data-output/h1_res.jpg", jx8u);
waitKey(0);
}




更多推荐
所有评论(0)