为了还原一篇论文。里面用到了提取图像的低频信息、高频信息、及还原技术。现在使用opencv进行复现,里面有一些注意事项特此记录
参考链接:
OpenCV中对Mat矩阵加、减、乘、除、转置等操作的总结
OpenCV两个Mat相减的隐藏秘密
opencv学习笔记(四)——模糊(均值,高斯,双边)
OpenCV基本数据类型表示的范围大小
其实提取一张图像的低频、高频信息原理比较简单,但是不注意的时候还原出来的影像总有一些像素值对不上(特别是边缘像素)。
影像的低频和高频信息可用如下数学模型表述:
I=L+H
其中I表示影像,L和H分别表示影像对应的低频和高频信息。
图片低频提取其实就是对原始影像做一次模糊,这里的模糊可以选择均值模糊、高斯模糊、双边模糊,输出的图片即图片的低频信息。
我这里使用参考链接第三个(opencv学习笔记(四)——模糊(均值,高斯,双边)
),进行高斯模糊,提取图片低频信息。
cv::blur(img, img2, Size(11, 11)); //用opencv自带的API进行均值滤波操作
cv::GaussianBlur(RefMat, RefMat_L, cv::Size(GaussR, GaussR), 3, 3);//高斯滤波
根据公式H=I - L
把原始影像减去低频信息即可得到高频影像信息。Opencv Mat支持直接相减操作,如参考链接1和参考链接2都说明了可以直接使用两个Mat进行相减操作,代码如下:
cv::Mat SrcDownMat = cv::Mat(Srcdown_H, Srcdown_W, CV_8UC3, ImgThumbBufferMat);//使用buffer数据进行转换成Mat格式,也可以直接使用imread()函数读出Mat
cv::Mat SrcDownMat_L;
cv::GaussianBlur(SrcDownMat, SrcDownMat_L, cv::Size(5, 5), 3, 3);//低频提取
cv::Mat SrcDownMat_H = SrcDownMat - SrcDownMat_L;//高频提取
cv::Mat SrcDownMat_New = SrcDownMat_L + SrcDownMat_H;//还原影像
//存储影像
cv::imwrite("_Srcdown.tiff", SrcDownMat);
cv::imwrite("_Srcdown_L.tiff", SrcDownMat_L);
cv::imwrite("_Srcdown_H.tiff", SrcDownMat_H);
cv::imwrite("_Srcdown_New.tiff", SrcDownMat_New);
存出来的影像加载arcmap中发现边缘的像素值与原始影像数据值对不上,经过分析问题如下:
1、提取高频的时候,两个影像做差。其格式是CV_8UC3格式(8位,无符号整型,3通道影像)。在做差的时候如果原始影像像素是0,低频信息如果是23,则做差后的结果是0(0-23=0,原因就是其Mat格式是无符号整型),而我们想在后面利用相加还原影像信息,则想要高频信息为-23(0 - 23= -23 ,需要Mat格式是带符号整型的),所以必须在做差前将两个相减的Mat格式转换成带符号的,结果可以保留负数的,从第一篇博客中看到了如下这张图:
修改上面代码再次实验即可完全还原像素值,最后的代码如下:
cv::Mat SrcDownMat = cv::Mat(Srcdown_H, Srcdown_W, CV_8UC3, ImgThumbBufferMat);//使用buffer数据进行转换成Mat格式,也可以直接使用imread()函数读出Mat
cv::Mat SrcDownMat_L;
cv::GaussianBlur(SrcDownMat, SrcDownMat_L, cv::Size(5, 5), 3, 3);//低频提取
//对Mat格式转换,是为了做差能得到负数,可以更好的还原
SrcDownMat.convertTo(SrcDownMat, CV_32F);
SrcDownMat_L.convertTo(SrcDownMat_L, CV_32F);
cv::Mat SrcDownMat_H = SrcDownMat - SrcDownMat_L;//高频提取
cv::Mat SrcDownMat_New = SrcDownMat_L + SrcDownMat_H;//还原影像
//存储前必须对Mat格式再次转换成CV_8UC3,否则颜色看着很怪异
SrcDownMat_New.convertTo(SrcDownMat_New, CV_8UC3);
SrcDownMat_L.convertTo(SrcDownMat_L, CV_8UC3);
SrcDownMat_H.convertTo(SrcDownMat_H, CV_8UC3);
SrcDownMat.convertTo(SrcDownMat, CV_8UC3);
//存储影像
cv::imwrite("_Srcdown.tiff", SrcDownMat);
cv::imwrite("_Srcdown_L.tiff", SrcDownMat_L);
cv::imwrite("_Srcdown_H.tiff", SrcDownMat_H);
cv::imwrite("_Srcdown_New.tiff", SrcDownMat_New);
输出的影像加载arcMap,像素级别完全无损还原
转换的时候还要注意一下问题:
1、为了节约空间,我不使用 CV_32F格式,将CV_8UC3格式直接转换为CV_8SC3格式,S表示带符号,可以记录负数,感觉没有问题。实际操作也没有问题,但是会出现对半截断的情况。CV_8UC3格式表示范围(0-255),CV_8SC3格式表示范围(-128 - 127),详见:OpenCV基本数据类型表示的范围大小
转换的测试代码
//对Mat格式转换,是为了做差能得到负数,可以更好的还原
SrcDownMat.convertTo(SrcDownMat, CV_8SC3);
SrcDownMat_L.convertTo(SrcDownMat_L, CV_8SC3);
实际效果如下图:
由于ArcMap会自动拉伸,所以看起来比较亮,这个地方实际像素最大就是127
根据上面原理,想解约一点空间,将代码优化成16位带符号的是可以的,因为16位带符号存储8位无符号像素,不会对其进行截断。代码如下:
SrcDownMat.convertTo(SrcDownMat, CV_16SC3);//转换是为了做差能得到负数,可以更好的还原
SrcDownMat_L.convertTo(SrcDownMat_L, CV_16SC3);
以上,特此记录。2022.5.19