1 图像的卷积操作
卷积操作是图像处理中最常见的方式,体现在图像的特征增强这一点上面。可是我们却很少思考,为什么卷积是一个卷积核覆盖上图片移动然后加权相加?因此,我们必须首先从数学上理解矩阵的卷积计算,学过数字信号处理的同学应该都知道一维信号线性卷积的计算公式体现为信号的反转相乘相加,那么在矩阵中卷积的运算是什么样的?
参考博客:https://blog.youkuaiyun.com/bitcarmanlee/article/details/54729807
问题:图像进行卷积之后为什么大小不会变?
2 图像平滑处理
在数字信号处理中我们学到,离散时间系统中,第n个输出样本y[n]可以用输入x[n]与系统函数的冲击响应h[n]
的卷积和来表示
y[n]=∑k=−∞∞h[k]x[n−k]
y[n]=\sum_{k=-\infty}^\infty{h[k]x[n-k]}
y[n]=k=−∞∑∞h[k]x[n−k]
正是通过这个东西建立起卷积滤波的思路,模糊的本质上是在频域上对原始信号进行乘上一个低通滤波器,从而过滤高频信号,保留低频信号。而反应到时域上来看就是和另外一个信号进行卷积操作。例如,从这个角度来说,高斯函数的傅里叶变换是另外一个高斯函数,那么频域和高斯函数相乘,反映过来就是时域和高斯卷积,因此对时间域而言进行高斯卷积操作本质上就是高斯滤波。卷积的计算方式通过卷积核实现,也就是按照上面的公式,不过对于图像信号而言,由于是二维信号,变成了矩阵卷积操作,计算每一个离散点的卷积值变成了两个矩阵卷积和形式。
参看不同滤波器相关的文章:
https://zhuanlan.zhihu.com/p/125744132
在opencv中提供了下面几种常见滤波api:
均值模糊 - blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1,-1));
高斯模糊 GaussianBlur(Mat src, Mat dst, Size(11, 11), sigmax, sigmay);
其中sigma 为正奇数,对应于二维高斯函数中的sigmax,和sigmay,值的一提的是,根据高斯函数的特性,卷积核可以分解成为两个向量的积,因此应该有更加有效率的方法进行高斯滤波。
中值滤波/均值滤波 一个是中位数一个是平均数,均对于色彩突然相差很大的地方有很好的滤波效果,尤其是椒盐噪声。l中值模糊medianBlur(Mat src, Mat dst, ksize)
双边模糊:为了克服边缘信号丢失太多的情况,增加一个阈值,即计算像素值和初始像素值差达到一定级别之后才会纳入进行修改像素值。
l bilateralFilter(src, dst, 15, 150, 3);
- 15 –计算的半径,半径之内的像数都会被纳入计算,如果提供-1 则根据sigma space参数取值
- 150 – sigma color 决定多少差值之内的像素会被计算
- 3 – sigma space 如果d的值大于0则声明无效,否则根据它来计算d值
opencv中的主要api就这些。在图像处理中,为了保留边缘信息去除噪声信息,还有更多的方法,例如添加阈值、选择领域中灰度值邻近的K个邻点平均法,对于3*3领域只取6个点,梯度倒数加权平滑法(采用归一化的梯度倒数系数作为加权系数,从而相邻区域的权值高),最大均匀性平滑法(选取含有该像素点的灰度最均匀窗口)有选择保留边缘平滑法(选择不同方向的领域,从而保留边缘信息)空间低通滤波法(采取不同的卷积核模板)。
总而言之,这些方法,无非就是在邻域形状、大小、方向、参与计算的像素点的个数以及领域的权重系数方面进行修改。
以上平滑方法均是在时间域上完成,可以看见都是对于像素的操作,而根据上面的原理显然还有另外一种方法就是在频域进行滤波,其中涉及到的滤波器就有 巴普沃斯 指数低通 梯形低通 等各种低通函数,显然有很多种滤波器,但是进行滤波时候需要分析出噪声在频域中位于什么频率范围内,以及该频域范围内有无其他有用信号,这里的分析相比于空间域分析就更加地难以直观阐述。
3.形态学操作
最基本的形态学操作包括腐蚀、膨胀、开运算和闭运算。
在二值图像中
以B结构中心点为准心,在A中找能满足B结构的点即为腐蚀,腐蚀变小
把A结构的每个点放到B中心点,以B结构外扩即为膨胀,膨胀变大
在灰度图和RGB图像中
腐蚀是结构体覆盖下最小值替换锚点值,所以变白
膨胀是结构体覆盖下最大值替换锚点值,所以变黑
开操作表示先腐蚀后膨胀;闭操作表示先膨胀后腐蚀
开运算先变白后变黑,一些小的黑点被去除,闭运算去除空洞。与开运算和闭运算对应的还有顶帽和黑帽操作,形态学运算主要用于去除小的背景、提取某方向的线等操作。
数学知识不多,具体了解可以参考链接https://blog.youkuaiyun.com/Chaolei3/article/details/79618602
API
获取结构体
getStructuringElement(int shape, Size ksize, Point anchor)
- 形状 (MORPH_RECT \MORPH_CROSS \MORPH_ELLIPSE)//矩形 十字 椭圆
- 大小
- 锚点 默认是Point(-1, -1)意思就是中心像素
此api用来构造结构体,返回的是一个Mat对象 ,当然也可以用Mat构建结构体,但是需要注意的是赋值时候需要为uchar类型,也就是uint8无符号0-255数值,否则会报错。
例如:Mat strucure2 = (Mat_(3, 3) << 1, 1, 1, 0, 0, 0, 1, 1, 1);
形态学操作
dilate(src, dst, kernel) 膨胀
erode(src, dst, kernel) 腐蚀
lmorphologyEx(src, dst, CV_MOP_BLACKHAT, kernel); //形态学操作
- Mat src – 输入图像
- Mat dst – 输出结果
- int OPT – CV_MOP_OPEN/ CV_MOP_CLOSE/ CV_MOP_GRADIENT / CV_MOP_TOPHAT/ CV_MOP_BLACKHAT 形态学操作类型 开 闭 梯度 顶帽 黑帽
-Mat kernel 结构元素
-int Iteration 迭代次数,默认是1
代码案例:
#include"pch.h"
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int element_size = 3;
int max_size = 30;
Mat src,dst;
void erode_demo(int, void*);
void dilate_demo(int ,void*);
void open_demo(int, void*);
void close_demo(int, void*);
int main(int argc, char** argv)
{
src = imread("test.png");
if (!src.data) {
printf("could not load the image ...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
int *p = &element_size;
cvNamedWindow("output1", CV_WINDOW_AUTOSIZE);
createTrackbar("erode", "output1", p, max_size, erode_demo);
createTrackbar("dilate", "output1", p, max_size, dilate_demo);
createTrackbar("open", "output1", p, max_size, open_demo);
createTrackbar("close", "output1", p, max_size, close_demo);
erode_demo(0, 0);
dilate_demo(0, 0);
waitKey(0);
return 0;
}
void erode_demo(int,void*) {
int s = element_size * 2 + 1;//保证为奇数
Mat structureElement = getStructuringElement(MORPH_RECT, Size(s, s), Point(-1, -1));
erode(src, dst, structureElement);
imshow("output1", dst);
}
void dilate_demo(int, void*) {
int s = element_size * 2 + 1;
//Mat strucure2 = (Mat_<uchar>(3, 3) << 1, 1, 1, 0, 0, 0, 1, 1, 1);
Mat structure2;
structure2.create(Size(s, s), CV_8UC1);
structure2 = Scalar(1);
dilate(src, dst, structure2);
imshow("output1", dst);
}
void open_demo(int, void*)
{
int s = element_size * 2 + 1;
Mat structureElement3 = getStructuringElement(MORPH_ELLIPSE, Size(s, s), Point(-1, -1));
morphologyEx(src, dst, MORPH_OPEN, structureElement3);
imshow("output1", dst);
}
void close_demo(int, void*)
{
int s = element_size * 2 + 1;
Mat structureElement3 = getStructuringElement(MORPH_ELLIPSE, Size(s, s), Point(-1, -1));
morphologyEx(src, dst, MORPH_CLOSE, structureElement3);
imshow("output1", dst);
}
构造滑轨
CV_EXPORTS int createTrackbar(const String& trackbarname,
const String& winname,
int* value,
int count,
TrackbarCallback onChange = 0,
void* userdata = 0);
参数1:滑动条轨迹名
参数2:滑动条依附的窗口名
参数3:int*指针 滑块的位置,创建时,滑块初始位置就是这个变量当前的值
参数4:轨迹的最大值
参数5:回调函数,值改变一次执行一次。回调函数的形参转定义有规定如下
typedef void (TrackbarCallback)(int pos, void userdata);
回调函数的设置和应用参考博客:https://www.cnblogs.com/zjoch/p/4237197.html
参数6:默认0,用户传给回调函数的数据,如果第三个值为全局变量,忽略这个值.
如果使用第6个参数,则作为参数传给回调函数的usrdata