1、简介
- 图像增强操作的作用是提高重要的图像细节,包括:降噪、平滑和边缘增强。图像校正是修复一副受损的图像。
- 图像滤波:图像平滑、图像锐化、图像金字塔(image pyramid)
- 形态学运算的应用:膨胀、腐蚀、开运算、闭运算
- 几何变换:仿射、透视变换
- 修复:重构图像的受损部分
2、图像滤波
- 滤波是一种邻域运算。
①平滑
- 将在(xi,yi)周围的输入像素值的加权和计算值作为(xi,yi)的输出像素值。通常权值存储在一个叫做 kernel的矩阵K中。
其中O是输出,I是输入,K为权值
- 中值滤波、高斯滤波和双边滤波是比较常见的OpenCV平滑滤波方法。
- 中值滤波适合去除椒盐噪声和斑点噪声,高斯滤波适合边缘检测的预处理阶段,双边滤波对平滑强边缘图像是一种很好的技术。
- boxFilter原型:void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1),bool normalize=ture, int borderType=BORDER_DEFAULT)
- 这是一种盒式滤波,normalize为真,表示K系数是相等的,此时输出是邻域的均值,即所有系数都是1/n。src是输入图像;滤波后图像放在dst;ddepth表示输出图像的深度(-1表示和原图相同);kernel的大小用ksize表示;anchor用于表示定位像素的位置(默认值(-1,-1)表示中心);边界类型用borderType表示。
- GaussianBlur原型:void GaussianBlur( InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )
- 对输入src中的每一个点,用一个高斯核计算卷积,输出dst。sigmaX和sigmaY表示在X和Y方向上的高斯核的标准偏差。sigamY为0的时候,自动设置和sigmaX相同;如果sigmaX和sigmaY都为0,则用ksize中给定的宽和高计算sigmaY和sigmaX。
- medianBlur原型:void medianBlur(InputArray src, OutputArray dst, int ksize)
- bilateraFilter原型:void bilateraFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)
- 与高斯滤波类似,将具有权值的邻域像素赋值给每个像素。每个权值都有两个分量,参数sigmaColor的一个较大值意味着像素邻域内相差更远的颜色会被混合,生成的半对等颜色的面积会更大;参数sigmaSpace的一个较大值意味着只要像素之间的颜色足够接近,则更远的像素之间会影响。
- void blur(InputArray src,OutputArray dst,Size ksize,Point anchor=Point(-1,-1,),int boderType=BORDER_DEFAULT)。使用归一化盒式滤波器来模糊一副图像,等同于normalize=ture的boxFilter函数
- 高斯和中值滤波例子:
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src;
src = imread("test.jpg");
//滤波
Mat dst, dst2;
GaussianBlur(src, dst, Size(9, 9), 0, 0);
medianBlur(src, dst2, 9);
//显示结果
namedWindow("Ori",WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("Gaussian",WINDOW_AUTOSIZE);
imshow("Gaussian", dst);
namedWindow("Median",WINDOW_AUTOSIZE);
imshow("Median", dst2);
waitKey();
return 0;
}
②锐化
- 锐化滤波器用于突出图像的边界和其他精细细节。锐化是基于一阶导数和二阶导数的。
- 一阶可产生粗的图像边缘,常用于边缘提取;二阶对精细细节的响应更好,常用于图像增强。常用算子是Sobel和Laplacian。
- Sobel原型:void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT)
- 希望的导数阶为dx、dy;在存入dst前可以增加一个delta值;scale 可建立用于计算导数值的尺度因子。
- Scharr原型:void Scharr(InputArray src, OutputArray dst, int ddepth, int dx, int dy, double scale=1, double delta=0, int borderType=BORDER_DEFAULT)
- 该函数计算一个大小为3*3的核更精确的导数
- Laplacian原型:void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1, double scale=1, double delta=0, int boderType=BORDER_DEFAULT)
- ksize>1时,使用Sobel算子通过累加x的二阶导数和y的二阶导数来计算src中的Laplacian值。k=1时,用一个3*3的核对图像滤波。
- Sobel和Laplacian 计算例子
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src;
src = imread("test.jpg");
//滤波
Mat dst, dst2;
Sobel(src, dst, -1,1,1);
Laplacian(src, dst2, -1);
//显示结果
namedWindow("Ori",WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("Sobel",WINDOW_AUTOSIZE);
imshow("Sobel", dst);
namedWindow("Laplacian",WINDOW_AUTOSIZE);
imshow("Laplacian", dst2);
waitKey();
return 0;
}
③图像金字塔
- 在对象检测问题中,通过检测整幅图像去试图找到对象太浪费时间了,而从一些较小分辨率图像开始对对象进行搜索可能会更有效。
- 图像金字塔是图像多尺度表达的一种,是一种以多分辨率来解释图像的有效但概念简单的结构。一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。
- 图像金字塔可分 高斯金字塔 和 拉普拉斯金字塔
- 高斯金字塔创建方式:在更低的一层通过交替地隔行和隔列删除像素,然后通过对底层邻域进行高斯滤波来或等更高一层的像素值。
- pyrDown原型:void pyrDown(InputArray src, OutputArray dst, const Size& dstsize=Size())。未设置dstsize时候,默认宽高为原图1/2。有一个pyrUp函数,为该函数的反过程。
- buildPyramid原型:void buildPyramid(InputArray src, OutputArrayOfArrays dst, int maxlevel, int borderType=BORDER_DEFAULT )。建立一个高斯金字塔,原图像src放在dst[0],后面依次放各层图像。共maxlevel+1副图像。
- 图像金字塔代码示例
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src;
src = imread("test.jpg");
//pyrDown两次
Mat dst, dst2;
pyrDown(src, dst);
pyrDown(dst, dst2);
//显示结果
namedWindow("Ori", WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("1st Pyr", WINDOW_AUTOSIZE);
imshow("1st Pyr", dst);
namedWindow("2nd Pyr", WINDOW_AUTOSIZE);
imshow("2nd Pyr", dst2);
//pyrUp两次
pyrUp(dst2, dst);
pyrUp(dst, src);
//显示结果
namedWindow("New Ori", WINDOW_AUTOSIZE);
imshow("New Ori", dst2);
namedWindow("1st PyrUp", WINDOW_AUTOSIZE);
imshow("1st PyrUp", dst);
namedWindow("2nd PyrUp", WINDOW_AUTOSIZE);
imshow("2nd PyrUp", src);
waitKey();
return 0;
}
3、形态学运算
- dilate原型:void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, intborderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )
- erode原型:void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, intborderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )
- morphologyEx原型:void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor=Point(-1,-1), intiterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )。提供一些高级形态学运算,op参数可选:MORPH_OPEN – 开运算;MORPH_CLOSE – 闭运算;MORPH_GRADIENT -形态学梯度;MORPH_TOPHAT - “顶帽”;MORPH_BLACKHAT - “黑帽”(“Black hat“)。
- getStructuringElement函数:Mat getStructuringElement(int shape,Size ksize,Point anchor=Point(-1,-1))返回一个指定大小和形状结构元素。shape参数可选:矩形MORPH_RECT、交叉形MORPH_CORSS、椭圆形MORPH_ELLIPSE。
4、几何变换
- 几何变换不改变图像内容,而是通过使网格发生形变而使图像发生形变。
- 变换式:O(x,y)=I(fx(x,y),fy(x,y))
- 几何变换:外插法和内插法。fx、fy在外插法中会获得图像边界外的值。支持的内插法:INTER_NEAREST最近邻插值;INTER_LINEAR双线性插值法,默认;INTER_AREA使用像素区域的关系进行重采样;INTER_CUBIC在一个4*4的像素邻域上双三次插值法;INTER_LANCZOS4在一个8*8的像素邻域上的Lanczos插值法。
①仿射变换
- 缩放、平移、旋转、倾斜和反射都属于仿射变换。仿射变换后,一条初始线段上所有点仍保留在一条线段上。
- 缩放函数:void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR ); dsize设置为一非零值时,缩放因子fx和fy都为0,根据dsize来计算fx和fy;如果fx和fy非零,则dsize为0。
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
Mat src,dst;
//读入图片并缩放
src = imread("test.jpg");
resize(src, dst, Size(0, 0), 0.5, 0.5);
//显示
namedWindow("Ori", WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("resize", WINDOW_AUTOSIZE);
imshow("resize", dst);
waitKey();
return 0;
}
- 平移函数:void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar()); M是将src变为dst的变化矩阵;flags指定内插法;borderMode是外插法,外插法时候使用borderValue。
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
Mat src,dst;
//读入图片
src = imread("test.jpg");
//平移矩阵
Mat M =Mat::zeros(2, 3, CV_32FC1);
M.at<float>(0, 0) = 1;
M.at<float>(0, 2) = 50; //水平平移量
M.at<float>(1, 1) = 1;
M.at<float>(1, 2) = 150; //竖直平移量
//平移
warpAffine(src,dst,M,src.size());
//显示
namedWindow("Ori", WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("move", WINDOW_AUTOSIZE);
imshow("move", dst);
waitKey();
return 0;
}
- 旋转函数:同样用warpAffine,但有Mat getRotationMatrix2D(Point2f center, double angle, double scale) 来获得旋转矩阵。center表示旋转的中心点;angle表示旋转的角度;scale图像缩放因子。
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
Mat src,dst;
//读入图片
src = imread("test.jpg");
//旋转矩阵
Mat M = getRotationMatrix2D(Point2f(src.cols / 2, src.rows / 2), 45, 1);
//旋转
warpAffine(src,dst,M,src.size());
//显示
namedWindow("Ori", WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("rotate", WINDOW_AUTOSIZE);
imshow("rotate", dst);
waitKey();
return 0;
}
- 倾斜函数:同样用warpAffine。
#include <opencv2/opencv.hpp>
#include <cmath>
#define M_PI 3.14159265358979323846
using namespace cv;
int main()
{
Mat src,dst;
//读入图片
src = imread("test.jpg");
//倾斜矩阵
double m = 1 / tan(M_PI/ 3);
Mat M = (Mat_<double>(2,3)<<1,m,0,0,1,0);
//倾斜
warpAffine(src,dst,M,src.size());
//显示
namedWindow("Ori", WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("skewed", WINDOW_AUTOSIZE);
imshow("skewed", dst);
waitKey();
return 0;
}
- 反射例程:同样用warpAffine。
#include <opencv2/opencv.hpp>
#include <cmath>
#define M_PI 3.14159265358979323846
using namespace cv;
int main()
{
Mat src,dst,dsth,dstv;
//读入图片
src = imread("test.jpg");
//水平、垂直、组合反射矩阵
Mat Mh = (Mat_<double>(2, 3) << -1, 0, src.cols, 0, 1, 0);
Mat Mv = (Mat_<double>(2, 3) << 1, 0, 0, 0, -1, src.rows);
Mat M = (Mat_<double>(2, 3) << -1, 0, src.cols, 0, -1, src.rows);
//反射
warpAffine(src,dsth,Mh,src.size());
warpAffine(src,dstv,Mv,src.size());
warpAffine(src,dst,M,src.size());
//显示
namedWindow("Ori", WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("H-reflect", WINDOW_AUTOSIZE);
imshow("H-reflect", dsth);
namedWindow("V-reflect", WINDOW_AUTOSIZE);
imshow("V-reflect", dstv);
namedWindow("reflect", WINDOW_AUTOSIZE);
imshow("reflect", dst);
waitKey();
return 0;
}
②透视变换
-
getPerspectiveTransform:Mat getPerspectiveTransform(InputArray src, InputArray dst); 或Mat getPerspectiveTransform(const Point2f src[], const Point2f dst[]); 根据输入和输出点获得图像透视变换的矩阵
-
warpPerspective:void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
#include <opencv2/opencv.hpp>
#include <cmath>
#define M_PI 3.14159265358979323846
using namespace cv;
int main()
{
Mat src,dst;
//读入图片
src = imread("test.jpg");
//找到透视变换矩阵
Point2f src_verts[4];
src_verts[2] = Point(195, 140);
src_verts[3] = Point(410, 120);
src_verts[1] = Point(220, 750);
src_verts[0] = Point(400, 750);
Point2f dst_verts[4];
dst_verts[2] = Point(160, 100);
dst_verts[3] = Point(530, 120);
dst_verts[1] = Point(220, 750);
dst_verts[0] = Point(400, 750);
Mat M = getPerspectiveTransform(src_verts, dst_verts);
warpPerspective(src, dst, M, src.size());
//显示
namedWindow("Ori", WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("perspective", WINDOW_AUTOSIZE);
imshow("perspective", dst);
waitKey();
return 0;
}
5、去噪
- 去噪算法有:非局部均值法 和 TVL1算法(Total Variation L1)
- 非局部均值法基本思想:用与构成该像素邻域相似的若干个图像子窗口的颜色的平均值来代替一个像素的颜色。
- TVL1变分去噪模型使用原对偶优化(primal-dual optimization)算法实现,将图像去噪过程看成一个变分问题。
- void fastN1MeansDenoising(InputArray src, OutputArray dst, float h=3, int templateWindowSize=7,int searchWindowSize=21)。去除一副灰度图像的噪声。templateWindowSize是用于计算权值的模版模块的像素数量;searchWindowSize用于计算给定像素权值平均值的窗口的像素数量,这两个参数都应该是奇数。h调解算法结果,越大去除噪声越多,但丢失细节也越多。
- void fastN1MeansDenoisingColored(InputArray src, OutputArray dst, float h=3, float hForColorComponents=3, int templateWindowSize=7,int searchWindowSize=21)。对上一个函数的修改,用于彩色图像。
- void fastN1MeansDenoisingMulti(InputArrayOfArrays srcImgs, OutputArray dst, int imgToDenoiseIndex, int temporalWindowSize, float h=3, int templateWindowSize=7,int searchWindowSize=21)。使用一个图像序列得到一副去噪后的图像。
- void fastN1MeansDenoisingColoredMulti(InputArrayOfArrays srcImgs, OutputArray dst, int imgToDenoiseIndex, int temporalWindowSize, float h=3, float hForColorComponents=3, int templateWindowSize=7,int searchWindowSize=21)。基于前两个函数。
-
void denoise_TVL1(const std::vector<Mat>& observations, Mat& result, double lambda, int niters)
- 示例代码
#include <opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
Mat src,dst;
//读入图片
src = imread("test.jpg");
//人为添加一些噪声
Mat noisy = src.clone();
Mat noise(src.size(), src.type());
randn(noise, 0, 50);
noisy += noise;
cout << "here1" << endl;
//去噪
fastNlMeansDenoisingColored(noisy, dst, 10, 10, 7, 21);
cout << "here2" << endl;
//显示
namedWindow("Ori", WINDOW_AUTOSIZE);
imshow("Ori", src);
namedWindow("Ori_noise", WINDOW_AUTOSIZE);
imshow("Ori_noise", noisy);
namedWindow("denoise", WINDOW_AUTOSIZE);
imshow("denoise", dst);
waitKey();
return 0;
}
6、其他
- 查找表函数:void LUT(InputArray src, InputArray lut, OutputArray dst)。
- 均值漂移 pyrMeanShiftFiltering函数:基本原理是对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心,即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。
7、参考资料
《OpenCV 图像处理》Gloria Bueno Garcia、Oscar Deniz Suarez、Jose Luis Espinosa Aranda著,刘冰 翻译,机械工业出版社出版,2016年11月