OPENCV第六章-图像滤波
图像卷积
图像卷积原理
模板运算操作:对应位置求乘积,之后再求和,得到卷积之后的结果替换掉卷积中心
结果中只有中心部分发生改变,为了解决这个问题,一般会对边缘进行填充
进行卷积操作之后,模板中心像素大小会超级加倍 ,所以一般会对卷积模板进行归一化既除以所有系数之和 ,保证了最终结果不会大于255
图像卷积函数
锚点是可以设置在任何一个位置,既anchor
注意:OPENCV内置函数并没有对卷积模板进行旋转,所以在进行卷积函数的时候需要人为的旋转180度
当然卷积模板在中心对称的时候不需要旋转
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//待卷积矩阵
uchar points[25] = { 1,2,3,4,5,
6,7,8,9,10,
11,12,13,14,15,
16,17,18,19,20,
21,22,23,24,25 };
Mat img(5, 5, CV_8UC1, points);
//卷积模板
Mat kernel = (Mat_<float>(3, 3) << 1, 2, 1,
2, 0, 2,
1, 2, 1);
Mat kernel_norm = kernel / 12; //卷积模板归一化
//未归一化卷积结果和归一化卷积结果
Mat result, result_norm;
filter2D(img, result, CV_32F, kernel, Point(-1, -1), 2, BORDER_CONSTANT);
filter2D(img, result_norm, CV_32F, kernel_norm, Point(-1, -1), 2, BORDER_CONSTANT);
cout << "result:" << endl << result << endl;
cout << "result_norm:" << endl << result_norm << endl;
//图像卷积
Mat lena = imread("lena.png");
if (lena.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat lena_fillter;
filter2D(lena, lena_fillter, -1, kernel_norm, Point(-1, -1), 2, BORDER_CONSTANT);
imshow("lena_fillter", lena_fillter);
imshow("lena", lena);
waitKey(0);
return 0;
}
通过对图像卷积处理可以对图像模糊,减少噪声增加后续处理精度
图像噪声的产生
图像噪声
椒盐噪声的产生
一般在比较大的范围随意的生成随机数,在让产生的随机数除以长度/高度在求余,保证随机性且保证其出现在图像内
同理也可以让产生的随机数除以2求余来随机决定噪声的颜色
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
//盐噪声函数
void saltAndPepper(cv::Mat image, int n)//噪声图像和数目
{
for (int k = 0; k < n / 2; k++)
{
//随机确定图像中位置
int i, j;
i = std::rand() % image.cols; //取余数运算,保证在图像的列数内
j = std::rand() % image.rows; //取余数运算,保证在图像的行数内
int write_black = std::rand() % 2; //判定为白色噪声还是黑色噪声的变量
if (write_black == 0) //添加白色噪声
{
if (image.type() == CV_8UC1) //处理灰度图像
{
image.at<uchar>(j, i) = 255; //白色噪声
}
else if (image.type() == CV_8UC3) //处理彩色图像
{
image.at<cv::Vec3b>(j, i)[0] = 255; //cv::Vec3b为opencv定义的一个3个值的向量类型
image.at<cv::Vec3b>(j, i)[1] = 255; //[]指定通道,B:0,G:1,R:2
image.at<cv::Vec3b>(j, i)[2] = 255;
}
}
else //添加黑色噪声
{
if (image.type() == CV_8UC1)
{
image.at<uchar>(j, i) = 0;
}
else if (image.type() == CV_8UC3)
{
image.at<cv::Vec3b>(j, i)[0] = 0; //cv::Vec3b为opencv定义的一个3个值的向量类型
image.at<cv::Vec3b>(j, i)[1] = 0; //[]指定通道,B:0,G:1,R:2
image.at<cv::Vec3b>(j, i)[2] = 0;
}
}
}
}
int main()
{
Mat lena = imread("lena.png");
Mat equalLena;
cvtColor(lena, equalLena, COLOR_BGR2GRAY);
if (lena.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
imshow("lena原图", lena);
imshow("equalLena原图", equalLena);
saltAndPepper(lena, 10000); //彩色图像添加椒盐噪声
saltAndPepper(equalLena, 10000); //灰度图像添加椒盐噪声
imshow("lena添加噪声", lena);
imshow("equalLena添加噪声", equalLena);
waitKey(0);
return 0;
}
高斯噪声的产生
第一个参数是产生随机数的矩阵
通过fill()函数可以得到一个包含高斯噪声的mat,和原mat相加即可
随机分布是上限和下限,高斯分布是标准值和方差
示例
distType只支持两种形式,一种为
RNG::UIFORM
均匀分布和RNG::NORMAL
高斯分布
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat lena = imread("lena.png");
Mat equalLena;
cvtColor(lena, equalLena, COLOR_BGR2GRAY);
if (lena.empty() || equalLena.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
//生成与原图像同尺寸、数据类型和通道数的矩阵
Mat lena_noise = Mat::zeros(lena.rows, lena.cols, lena.type());
Mat equalLena_noise = Mat::zeros(lena.rows, lena.cols, equalLena.type());
imshow("lena原图", lena);
imshow("equalLena原图", equalLena);
RNG rng; //创建一个RNG类,Random Number Generator
rng.fill(lena_noise, RNG::NORMAL, 10, 20); //生成三通道的高斯分布随机数
rng.fill(equalLena_noise, RNG::NORMAL, 15, 30); //生成三通道的高斯分布随机数
imshow("三通道高斯噪声", lena_noise);
imshow("单通道高斯噪声", equalLena_noise);
lena = lena + lena_noise; //在彩色图像中添加高斯噪声
equalLena = equalLena + equalLena_noise; //在灰度图像中添加高斯噪声
//显示添加高斯噪声后的图像
imshow("lena添加噪声", lena);
imshow("equalLena添加噪声", equalLena);
waitKey(0);
return 0;
}
图像的线性滤波
均值滤波
与卷积类似,该图相当于卷积为$$\frac{1}{9} \begin{bmatrix} 1&1&1\1&1&1&\1&1&1\end{bmatrix}$$
模板进行卷积
常用滤波器为方形奇数滤波器
平方相加的方框滤波,他的算法不是直接相加而是平方后相加
方框滤波
方框滤波可以选择是否进行归一化,在均值滤波中是强制执行归一化,当选择为默认值的时候和均值滤波的结果相同,输出图像的数据类型可以不同
高斯滤波
中间滤波器较大而边缘较小
示例(均值滤波)
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
Mat equalLena_gauss = imread("equalLena_gauss.png", IMREAD_ANYDEPTH);
Mat equalLena_salt = imread("equalLena_salt.png", IMREAD_ANYDEPTH);
if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat result_3, result_9; //存放不含噪声滤波结果,后面数字代表滤波器尺寸
Mat result_3gauss, result_9gauss; //存放含有高斯噪声滤波结果,后面数字代表滤波器尺寸
Mat result_3salt, result_9salt; //存放含有椒盐噪声滤波结果,后面数字代表滤波器尺寸
//调用均值滤波函数blur()进行滤波
blur(equalLena, result_3, Size(3, 3));
blur(equalLena, result_9, Size(9, 9));
blur(equalLena_gauss, result_3gauss, Size(3, 3));
blur(equalLena_gauss, result_9gauss, Size(9, 9));
blur(equalLena_salt, result_3salt, Size(3, 3));
blur(equalLena_salt, result_9salt, Size(9, 9));
//显示不含噪声图像
imshow("equalLena ", equalLena);
imshow("result_3", result_3);
imshow("result_9", result_9);
//显示含有高斯噪声图像
imshow("equalLena_gauss", equalLena_gauss);
imshow("result_3gauss", result_3gauss);
imshow("result_9gauss", result_9gauss);
//显示含有椒盐噪声图像
imshow("equalLena_salt", equalLena_salt);
imshow("result_3salt", result_3salt);
imshow("result_9salt", result_9salt);
waitKey(0);
return 0;
}
示例(方框滤波)
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH); //用于方框滤波的图像
if (equalLena.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
//验证方框滤波算法的数据矩阵
float points[25] = { 1,2,3,4,5,
6,7,8,9,10,
11,12,13,14,15,
16,17,18,19,20,
21,22,23,24,25 };
Mat data(5, 5, CV_32FC1, points);
//将CV_8U类型转换成CV_32F类型
Mat equalLena_32F;
equalLena.convertTo(equalLena_32F, CV_32F, 1.0 / 255);
Mat resultNorm, result, dataSqrNorm, dataSqr, equalLena_32FSqr;
//方框滤波boxFilter()和sqrBoxFilter()
boxFilter(equalLena, resultNorm, -1, Size(3, 3), Point(-1, -1), true); //进行归一化
boxFilter(equalLena, result, -1, Size(3, 3), Point(-1, -1), false); //不进行归一化
sqrBoxFilter(data, dataSqrNorm, -1, Size(3, 3), Point(-1, -1),
true, BORDER_CONSTANT); //进行归一化
sqrBoxFilter(data, dataSqr, -1, Size(3, 3), Point(-1, -1),
false, BORDER_CONSTANT); //不进行归一化
sqrBoxFilter(equalLena_32F, equalLena_32FSqr, -1, Size(3, 3), Point(-1, -1),
true, BORDER_CONSTANT);
//显示处理结果
imshow("resultNorm", resultNorm);
imshow("result", result);
imshow("equalLena_32FSqr", equalLena_32FSqr);
waitKey(0);
return 0;
}
示例(高斯滤波) 高斯噪声用高斯滤波效果非常理想
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
Mat equalLena_gauss = imread("equalLena_gauss.png", IMREAD_ANYDEPTH);
Mat equalLena_salt = imread("equalLena_salt.png", IMREAD_ANYDEPTH);
if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat result_5, result_9; //存放不含噪声滤波结果,后面数字代表滤波器尺寸
Mat result_5gauss, result_9gauss; //存放含有高斯噪声滤波结果,后面数字代表滤波器尺寸
Mat result_5salt, result_9salt; ////存放含有椒盐噪声滤波结果,后面数字代表滤波器尺寸
//调用均值滤波函数blur()进行滤波
GaussianBlur(equalLena, result_5, Size(5, 5), 10, 20);
GaussianBlur(equalLena, result_9, Size(9, 9), 10, 20);
GaussianBlur(equalLena_gauss, result_5gauss, Size(5, 5), 10, 20);
GaussianBlur(equalLena_gauss, result_9gauss, Size(9, 9), 10, 20);
GaussianBlur(equalLena_salt, result_5salt, Size(5, 5), 10, 20);
GaussianBlur(equalLena_salt, result_9salt, Size(9, 9), 10, 20);
//显示不含噪声图像
imshow("equalLena ", equalLena);
imshow("result_5", result_5);
imshow("result_9", result_9);
//显示含有高斯噪声图像
imshow("equalLena_gauss", equalLena_gauss);
imshow("result_5gauss", result_5gauss);
imshow("result_9gauss", result_9gauss);
//显示含有椒盐噪声图像
imshow("equalLena_salt", equalLena_salt);
imshow("result_5salt", result_5salt);
imshow("result_9salt", result_9salt);
waitKey(0);
return 0;
}
图像的可分离滤波(线性滤波的可分离性)
线性滤波可以分离为X方向和Y方向的线性滤波
滤波可以单独进行行操作和列操作,减少计算量
kernelX(Y)是单行或者单列的数据
无需特别处理到单行或者单列,只要保证是单行单列的情况下会自动按顺序处理为kernelX和kernelY
anchor是两个滤波器乘积之后的完整矩阵的位置
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
float points[25] = { 1,2,3,4,5,
6,7,8,9,10,
11,12,13,14,15,
16,17,18,19,20,
21,22,23,24,25 };
Mat data(5, 5, CV_32FC1, points);
//X方向、Y方向和联合滤波器的构建
Mat a = (Mat_<float>(3, 1) << -1, 3, -1);
Mat b = a.reshape(1, 1);//直接改变mat类的尺寸,第一个参数通道数,第二个参数是维度,第三个是数据类型
Mat ab = a * b;
//验证高斯滤波的可分离性
Mat gaussX = getGaussianKernel(3, 1);//自动生成一定尺寸的高斯卷积核,第一个是尺寸,第二个是sigma
Mat gaussData, gaussDataXY;
GaussianBlur(data, gaussData, Size(3, 3), 1, 1, BORDER_CONSTANT);
sepFilter2D(data, gaussDataXY, -1, gaussX, gaussX, Point(-1, -1), 0, BORDER_CONSTANT);
//输入两种高斯滤波的计算结果
cout << "gaussData=" << endl
<< gaussData << endl;
cout << "gaussDataXY=" << endl
<< gaussDataXY << endl;
//线性滤波的可分离性
Mat dataYX, dataY, dataXY, dataXY_sep;
filter2D(data, dataY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT);
filter2D(dataY, dataYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT);
filter2D(data, dataXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT);
sepFilter2D(data, dataXY_sep, -1, a, b, Point(-1, -1), 0, BORDER_CONSTANT);//b,b也行
//输出分离滤波和联合滤波的计算结果
cout << "dataY=" << endl
<< dataY << endl;
cout << "dataYX=" << endl
<< dataYX << endl;
cout << "dataXY=" << endl
<< dataXY << endl;
cout << "dataXY_sep=" << endl
<< dataXY_sep << endl;
//对图像的分离操作
Mat img = imread("lena.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat imgYX, imgY, imgXY;
filter2D(img, imgY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT);
filter2D(imgY, imgYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT);
filter2D(img, imgXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT);
imshow("img", img);
imshow("imgY", imgY);
imshow("imgYX", imgYX);
imshow("imgXY", imgXY);
waitKey(0);
return 0;
}
非线性滤波
中值滤波
中值滤波原理
中值滤波可以滤掉突然出现的最大值和最小值
示例(中值滤波) 看得出对椒盐噪声效果拔群
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat gray = imread("equalLena_salt.png", IMREAD_ANYCOLOR);
Mat img = imread("lena_salt.png", IMREAD_ANYCOLOR);
if (gray.empty() || img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat imgResult3, grayResult3, imgResult9, grayResult9;
//分别对含有椒盐噪声的彩色和灰度图像进行滤波,滤波模板为3×3
medianBlur(img, imgResult3, 3);
medianBlur(gray, grayResult3, 3);
//加大滤波模板,图像滤波结果会变模糊
medianBlur(img, imgResult9, 9);
medianBlur(gray, grayResult9, 9);
//显示滤波处理结果
imshow("img", img);
imshow("gray", gray);
imshow("imgResult3", imgResult3);
imshow("grayResult3", grayResult3);
imshow("imgResult9", imgResult9);
imshow("grayResult9", grayResult9);
waitKey(0);
return 0;
}
边缘检测
边缘检测原理
一般来说像素变化最快的区域就是边缘
第一个和第三个像素可以求出第二个像素的梯度
既通过$\begin{bmatrix} -1&0&1\end{bmatrix}$ 可以求出梯度,利用矩阵对图像进行卷积可以对图像这一行进行梯度检测
Sobel算子边缘检测
第三个参数为边缘检测输出图像的数据类型,由于有可能出现负数的情况,不推荐8U的形式,建议推荐16S的形式
dx和dy是sobel算子边缘检测梯度的阶次(求导数的次数)
ksize为算子的尺寸大小,常见的是3和5,输入1时默认为3
什么逆天设计
scale是缩放系数delta是偏移量
常见的方法是先进行行检测在进行列检测 示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//读取图像,黑白图像边缘检测结果较为明显
Mat img = imread("equalLena.png", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat resultX, resultY, resultXY;
//X方向一阶边缘
Sobel(img, resultX, CV_16S, 1, 0, 3);
convertScaleAbs(resultX, resultX);//求取绝对值,也可以用来数据线性放大或者缩小
//Y方向一阶边缘
Sobel(img, resultY, CV_16S, 0, 1, 3);
convertScaleAbs(resultY, resultY);
//整幅图像的一阶边缘
resultXY = resultX + resultY;
//显示图像
imshow("resultX", resultX);
imshow("resultY", resultY);
imshow("resultXY", resultXY);
waitKey(0);
return 0;
}
Scharr算子边缘检测
在sobel算子的基础上调整了算子的系数,把主要行调整为10辅助行调整为3,得到响应更强
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//读取图像,黑白图像边缘检测结果较为明显
Mat img = imread("equalLena.png", IMREAD_ANYDEPTH);
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat resultX, resultY, resultXY;
//X方向一阶边缘
Scharr(img, resultX, CV_16S, 1, 0);
convertScaleAbs(resultX, resultX);
//Y方向一阶边缘
Scharr(img, resultY, CV_16S, 0, 1);
convertScaleAbs(resultY, resultY);
//整幅图像的一阶边缘
resultXY = resultX + resultY;
//显示图像
imshow("resultX", resultX);
imshow("resultY", resultY);
imshow("resultXY", resultXY);
waitKey(0);
return 0;
}
两种算子的生成函数
kszie不为FILTER_SCHARR的时候默认为sobel算子
Sobel算子梯度的阶数与尺寸有关
当尺寸为3时候,dx和dy小于3
使用Scharr类型时候,dx+dy 小于等于1
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
Mat sobel_x1, sobel_y1, sobel_x2, sobel_y2, sobel_x3, sobel_y3; //存放分离的Sobel算子
Mat scharr_x, scharr_y; //存放分离的Scharr算子
Mat sobelX1, sobelX2, sobelX3, scharrX; //存放最终算子
//一阶X方向Sobel算子
getDerivKernels(sobel_x1, sobel_y1, 1, 0, 3);
sobel_x1 = sobel_x1.reshape(CV_8U, 1);//转置
sobelX1 = sobel_y1 * sobel_x1; //计算滤波器
//二阶X方向Sobel算子
getDerivKernels(sobel_x2, sobel_y2, 2, 0, 5);
sobel_x2 = sobel_x2.reshape(CV_8U, 1);
sobelX2 = sobel_y2 * sobel_x2; //计算滤波器
//三阶X方向Sobel算子
getDerivKernels(sobel_x3, sobel_y3, 3, 0, 7);
sobel_x3 = sobel_x3.reshape(CV_8U, 1);
sobelX3 = sobel_y3 * sobel_x3; //计算滤波器
//X方向Scharr算子
getDerivKernels(scharr_x, scharr_y, 1, 0, FILTER_SCHARR);//必须是一个1一个0
scharr_x = scharr_x.reshape(CV_8U, 1);//转置
scharrX = scharr_y * scharr_x; //计算滤波器
//输出结果
cout << "X方向一阶Sobel算子:" << endl << sobelX1 << endl;
cout << "X方向二阶Sobel算子:" << endl << sobelX2 << endl;
cout << "X方向三阶Sobel算子:" << endl << sobelX3 << endl;
cout << "X方向Scharr算子:" << endl << scharrX << endl;
waitKey(0);
return 0;
}
Laplacian算子
可以直接提取各个方向边缘,不必分别提取之后再叠加
第三个参数是输出图像的数据类型,根据需求选择输出类型
kszie默认为1,表示3×3尺寸的算子(3也是)
可以通过scale对边缘梯度的放大或缩小
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//读取图像,黑白图像边缘检测结果较为明显
Mat img = imread("equalLena.png", IMREAD_ANYDEPTH);
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat result, result_g, result_G;
//未滤波提取边缘
Laplacian(img, result, CV_16S, 3, 1, 0);
convertScaleAbs(result, result);
//滤波后提取Laplacian边缘
GaussianBlur(img, result_g, Size(3, 3), 5, 0); //高斯滤波
Laplacian(result_g, result_G, CV_16S, 3, 1, 0);
convertScaleAbs(result_G, result_G);
//显示图像
imshow("result", result);
imshow("result_G", result_G);
waitKey(0);
return 0;
}
Canny边缘检测算法
目标检测之—非极大抑制(NMS)综述_非极大值抑制-CSDN博客
双阈值法,底阈值下为非边缘,底阈值到高阈值为弱边缘,高阈值为强边缘
赋值计算法
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//读取图像,黑白图像边缘检测结果较为明显
Mat img = imread("equalLena.png", IMREAD_ANYDEPTH);
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat resultHigh, resultLow, resultG;
//大阈值检测图像边缘
Canny(img, resultHigh, 100, 200, 3);
//小阈值检测图像边缘
Canny(img, resultLow, 20, 40, 3);
//高斯模糊后检测图像边缘
GaussianBlur(img, resultG, Size(3, 3), 5);
Canny(resultG, resultG, 100, 200, 3);
//显示图像
imshow("resultHigh", resultHigh);
imshow("resultLow", resultLow);
imshow("resultG", resultG);
waitKey(0);
return 0;
}
滤波是有用的 太神奇了