《Learning Opencv3》第十章 滤波和卷积
第十章 滤波和卷积(一)
文章目录
一、卷积
**卷积convolution:**对图像的操作;
卷积核kernel:一个矩阵,用于卷积运算,一般中心是锚点anchor point;
**填充pad:**由于卷积时边缘像素没有被卷积核覆盖,所以会有边缘问题。所以在卷积之前,需要对原图像进行边缘填充。
int cv::borderInterpolate( // Returns coordinate of "donor" pixel
int p, // 0-based coordinate of extrapolated pixel
int len, // Length of array (on relevant axis)
int borderType // Pixel extrapolation method
);
填充方式:
BORDER_REPLICATE: aaaaaa|abcdefgh|hhhhhhh
BORDER_REFLECT: fedcba|abcdefgh|hgfedcb
BORDER_REFLECT_101: gfedcb|abcdefgh|gfedcba
BORDER_WRAP: cdefgh|abcdefgh|abcdefg
BORDER_CONSTANT: iiiiii|abcdefgh|iiiiiii with some specified ‘i’
二、阈值化操作
2.1 阈值化
阈值化操作就是要从图像中分离对象(前景和背景分离)。
double cv::threshold(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
double thresh, // Threshold value
double maxValue, // Max value for upward operations
int thresholdType // Threshold type
);
阈值化操作有如下:
THRESH_BINARY二进制阈值化
THRESH_BINARY_INV反二进制阈值化
THRESH_TRUNC截断阈值化
THRESH_TOZERO阈值化为0
THRESH_TOZERO_INV反阈值化为0
void sum_rgb(const Mat& src, Mat& dst) {
// Split image onto the color planes.
vector< Mat> planes;
split(src, planes);
Mat b = planes[0], g = planes[1], r = planes[2], s;
// Add equally weighted rgb values.
addWeighted(r, 1. / 3., g, 1. / 3., 0.0, s);
addWeighted(s, 1., b, 1. / 3., 0.0, s);
// Truncate values above 100.
threshold(s, dst, 100, 100, THRESH_TRUNC);
}
int main() {
Mat src = imread("1.jpg"), dst;
imshow("org img", src);
sum_rgb(src, dst);
imshow("dst img", dst);
waitKey(0);
return 0;
}
之所以要把三个颜色的通道分开是因为,高位容易溢出?(没看懂)
2.2 Otsu算法
两类像素方差的权值由两类像素的个数决定。这种阈值化的结果相对来说比较理想,可以避免寻找合适阈值的操作,但是这种方式运算量较大,费时。
2.3 自适应阈值
在图像阈值化操作中,我们更关心的是从二值化图像中分离目标区域和背景区域,仅仅通过固定阈值很难达到理想的分割效果。在图片中的灰度是不均匀的,所以通常情况下图片中不同区域的阈值时不一样的。
自适应阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。
void cv::adaptiveThreshold(
cv::InputArray src, // 输入图像
cv::OutputArray dst, // 输出图像
double maxValue, // 向上最大值
int adaptiveMethod, // 自适应方法,平均或高斯
int thresholdType // 阈值化类型
int blockSize, // 块大小
double C // 常量
);
int main() {
Mat src = imread("2.jpg", IMREAD_GRAYSCALE);
Mat dst;
imshow("src img", src);
int maxVal = 255;
int blockSize = 53;
double C = 0;
adaptiveThreshold(src, dst, maxVal, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, blockSize, C);
imshow("threshold", dst);
waitKey(0);
return 0;
}
三、平滑(模糊)
作用:减少噪声、降低分辨率。
OpenCV提供了五种平滑方式。
3.1 均值滤波(box filter)
void cv::blur(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
cv::Size ksize, // Kernel size(滤波器的大小)
cv::Point anchor = cv::Point(-1,-1), // Location of anchor point(锚点,被平滑点,默认是负值取中心)
int borderType = cv::BORDER_DEFAULT // Border extrapolation to use(边缘)
);
void cv::boxFilter(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
int ddepth, // Output depth (e.g., CV_8U),输出图像的深度,-1代表使用原图深度
cv::Size ksize, // Kernel size
cv::Point anchor = cv::Point(-1,-1), // Location of anchor point
bool normalize = true, // If true, divide by box area,表示内核是否被其区域归一化(normalized)了
int borderType = cv::BORDER_DEFAULT // Border extrapolation to use
);
3.2 均值滤波(Median filter)
均值滤波就是把将像素的数据从小到大排序,如何取中间值。
有效去除椒盐噪声。
c++void cv::medianBlur(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
cv::Size ksize // Kernel size
);
3.3 高斯噪声(Gaussian filter)
高斯滤波就是对整幅图像进行加权平均,注意此处是高斯低通滤波器。
有效消除高斯噪声。
void cv::GaussianBlur(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
cv::Size ksize, // Kernel size
double sigmaX, // Gaussian half-width in x-direction
double sigmaY = 0.0, // Gaussian half-width in y-direction
int borderType = cv::BORDER_DEFAULT // Border extrapolation to use
);
3.4 双边滤波(Bilateral filter)
双边滤波和高斯滤波很相似,但是有两个权值,第一个和高斯滤波一样,是像素值的权值,第二个是考虑相对于中心像素的空间距离得到的权值。通俗来说,双边滤波就是相似的像素点比不相似的权值更高,从而让边缘锐化(边缘不模糊),而且还能够去除噪声。
void cv::bilateralFilter(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
int d, // Pixel neighborhood size (max distance)
double sigmaColor, // Width param for color weight function,颜色空间滤波器的sigma值。方差越大,权重差别越小,就表明该像素邻域内有更宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
double sigmaSpace, // Width param for spatial weight function,坐标空间的标注方差。他的数值越大,意味着越远的像素会相互影响,从而使更大的区域足够相似的颜色获取相同的颜色
int borderType = cv::BORDER_DEFAULT // Border extrapolation to use
);
四、导数(Deriviative)和梯度(Gradient)
求导数也是卷积的一种,现在有很多求导的方法,但是只有少部分能够很好的应用在实际中。
4.1 Sobel导数
Sobel算子定义在离散的空间上,所以称Sobel算子是严格的求导。Sobel算子是对多项式的拟合,也就是说Sobel算子中xorder=2,并不是求二阶导数,而是对抛物线的拟合。这也就解释了为什么需要更大的卷积核,因为卷积核大在很多像素的情况下拟合的更好,卷积核小会对噪声比较敏感。
需要注意的是,如果源图像是8位的,目标图像的深度至少应该是CV_16S,因为可能会溢出。
void cv::Sobel(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
int ddepth, // Pixel depth of output (e.g., CV_8U)
int xorder, // order of corresponding derivative in x(阶数)
int yorder, // order of corresponding derivative in y(阶数)
//xorder和yorder一般是0,1,2
cv::Size ksize = 3, // Kernel size(odd)
double scale = 1, // Scale (applied before assignment)
double delta = 0, // Offset (applied before assignment)
int borderType = cv::BORDER_DEFAULT // Border extrapolation
);
4.2 Scharr滤波
Scharr仅作用于3x3大小的内核。具有和sobel算子一样的速度,但结果更为精确。Scharr算子与Sobel的不同在于中心像素的权重更大,这可能是相对于图像这种随机性较强的信号,邻域相关性不大,所以邻域平滑应该使用相对较小的标准差的高斯函数,也就是更瘦高的模板
4.3 Laplacian算子
Laplace的函数和Sobel的函数参数很相似,至少其中的orders(阶数)没有,这是因为Laplacian很像二阶Sobel算子。
Laplacian算子可以检测边缘。
void cv::Laplacian(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
int ddepth, // Depth of output image (e.g., CV_8U)
cv::Size ksize = 3, // Kernel size
double scale = 1, // Scale applied before assignment to dst
double delta = 0, // Offset applied before assignment to dst
int borderType = cv::BORDER_DEFAULT // Border extrapolation to use
);
注意ksize=1。
但是在棋盘的图上表现较为糟糕。
五、图像形态学
所有的图像形态学操作都是建立在两种运算上,腐蚀(erosion)和膨胀(dilation),这些都是卷积运算!
以下是所有的形态学操作:
5.1 膨胀(Dilation)和腐蚀(Erosion)
作用:
1、减少噪声
2、分割独立元素(isolating individual elements)
3、连接不同的元素(joining disparate elemets)
4、复杂的形态学操作还可以:找到一幅图像密度的最大值和最小值
5、求形态学梯度
**膨胀(Dilation):**保留卷积核中的最大值像素,是非线性的操作,所以卷积核不能像之前那样表示。
**效果:**将图像光亮部分放大,黑暗部分缩小。
**作用:**可以用来填补物体中的空洞。
**腐蚀(Erosion):**保留局部最小值。
**效果:**将图像黑暗部分放大,光亮部分缩小。
**作用:**消除小于结构元素的噪声点
void cv::erode(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
cv::InputArray element, // Structuring, a cv::Mat()
cv::Point anchor = cv::Point(-1,-1), // Location of anchor point
int iterations = 1, // Number of times to apply
int borderType = cv::BORDER_CONSTANT // Border extrapolation
const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue()
);
void cv::dilate(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
cv::InputArray element, // Structuring, a cv::Mat()
cv::Point anchor = cv::Point(-1,-1), // Location of anchor point
int iterations = 1, // Number of times to apply
int borderType = cv::BORDER_CONSTANT // Border extrapolation
const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue()
);
int main() {
Mat src = imread("1.jpg", IMREAD_GRAYSCALE);
Mat dst;
imshow("src img", src);
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
erode(src, dst, element);
imshow("erode", dst);
waitKey(0);
return 0;
}