Brief description of OpenCV functions_Chapter 3 Filtering processing of digital images (box, mean, Gaussian, median and bilateral filtering)

Series Article Directory

  1. Brief description of OpenCV functions_Chapter 1 Basic concepts of digital images (neighborhood, connectivity, color space)
  2. Brief description of OpenCV functions_Chapter 2 Basic operations of digital images (image reading and writing, image pixel acquisition, image ROI acquisition, image mixing, graphics drawing)
  3. Brief description of OpenCV functions_Chapter 3 Filtering processing of digital images (box, mean, Gaussian, median and bilateral filtering) [this article]
  4. Brief description of OpenCV functions_Chapter 4 Morphological processing of digital images and image pyramids. (erosion, dilation, opening and closing operations, morphological gradients, top and black hats, and image pyramids)


foreword

This series of articles is only to record the relevant parameters and simple usage of commonly used OpenCV functions during the process of learning OpenCV, so as to facilitate query and use at any time. The specific algorithm principle will not be introduced too much.
本文C++使用的OpenCV版本是的4.5.5。
python使用的OpenCV版本是4.5.3.56
官方文档参考的是 4.6.0-dev
If you need to find a specific function, you can directly query it from the catalog. The [] symbol is the function name, including codes in C++ and python


Contents of this article :
This article describes the filtering process of digital images, which mainly consists of two parts: linear filtering and nonlinear filtering. Among them,
linear filtering includes: box filtering, mean filtering, and Gaussian filtering.
Nonlinear filtering includes: median filtering, bilateral filtering.

Reference materials:
1. OpenCV official document
2. Mao Xingyun-OpenCV3 programming introduction
3. Author SongpingWang: OpenCV-Python image filtering (mean, median, Gaussian, Gaussian bilateral, high-pass, etc. filtering)
4. Author Shen Ziheng: Bilateral filtering algorithm principle


1. The concept of filtering

If an image is regarded as a digital signal, the image can also be divided into a high-frequency part and a low-frequency part. The high-frequency part often refers to the details of the image that change rapidly, that is, features with large changes such as boundaries and noise, and the low-frequency part refers to the image. Gently changing subject parts, i.e. background and other features. (The bulk of the image is concentrated in the low and mid frequencies).

Filtering is the operation of filtering for the selected frequency, which can be divided into high-pass, low-pass, band-pass, and band-stop.

High-pass means that the high-frequency part is passed, and the low-frequency part is blocked, which is manifested as a sharpening operation on the image .
Low-pass means that the low-frequency part is passed, and the high-frequency part is blocked. It appears as a smooth (fuzzy) operation on the image and is often used to eliminate noise.
Bandpass refers to allowing frequencies within a specified range to pass through.
Band-stop refers to preventing the passage of frequencies within a specified range.

We can use the following figure to understand:
insert image description here

The implementation of filtering in the image is realized by using the neighborhood operator. That is, the value of the target pixel is obtained by weighting or non-linear processing of the values ​​of surrounding neighbor pixels.
insert image description here
The yellow box is the target pixel and its neighbor pixels, the green box is the filter kernel, and the implementation process is the domain operator.

2. Linear filtering

2.1 Box filtering [boxFilter]

boxFilter()
Function:
Use box filtering to blur the image. When the normalization parameter (normalize) is true, its function is equivalent to mean filtering. If you can see from the source code, mean filtering is implemented based on boxFilter.

Determine the quantity of the quantity in the range:
K = { 1 K . height ∗ K width [ 1 1 . . . . . . . . . 1 1 1 . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 . . . . . . . . . 1 ] normalize = true [ 1 1 . . . . . . . . . 1 1 1 . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 . . . . . . . . . 1 ] normalize = false K =\left\{ \begin{aligned} \frac{1}{K.height*K.width} \left[\begin{matrix} 1 & 1 & ... & 1\\ & 1 & ... & 1 \\ ... & ... & ... & ... \\ 1 & 1 & ... & 1 \ end { matrix } \ right ] & & normalize= true \ \ \left[\begin{matrix} 1&1&...&1\\1&1&...&1\\...&...&...&...\\1&1 & ... & 1 \ end { matrix } \ right ] & & normalize=false \ end { aligned } \ rightK= K.heightK.width1 11...111...1............11...1 11...111...1............11...1 normalize=truenormalize=false

Functional form:

C++:
void cv::boxFilter (InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor = Point(-1,-1), bool normalize = true, int borderType = BORDER_DEFAULT)
Python:
cv.boxFilter( src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]] ) -> dst

Parameter explanation (take the parameters displayed in C++ as an example):
1.InputArray src: input image, the image depth to be processed should be CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
2. OutputArray dst: The output image, the type is consistent with src.
3. int ddepth: The depth of the output image (input -1 means it is consistent with the original image, ie src.depth()).
4. Size ksize: Size type ksize, indicating the size of the core.
5. Point anchor = Point(-1,-1): The position of the anchor point (the smoothed point) on the nucleus, the default is Point(-1,-1), and the coordinates are negative to indicate the center of the nucleus.
6. bool normalize = true: Whether to normalize, the default is true, and its function is equivalent to mean filtering.
7. int borderType = BORDER_DEFAULT: Used to infer the type of image bounding box, the default is BORDER_DEFAULT. Other references to the table below:

borderType enumeration type Explanation [various border types, image borders are represented by "|"]
BORDER_CONSTANT
Python: cv.BORDER_CONSTANT
iiiiii|abcdefgh|iiiiiiii with some specific i values
BORDER_REPLICATE
Python: cv.BORDER_REPLICATE
aaaaaaa|abcdefgh|hhhhhh
BORDER_REFLECT
Python: cv.BORDER_REFLECT
fedcba|abcdefgh|hgfedcb
BORDER_WRAP
Python: cv.BORDER_WRAP
cdefgh|abcdefgh|abcdefg
BORDER_REFLECT_101
Python: cv.BORDER_REFLECT_101
gfedcb|abcdefgh|gfedcba
BORDER_TRANSPARENT
Python: cv.BORDER_TRANSPARENT
uvwxyz|abcdefgh|ijklmno
BORDER_REFLECT101
Python: cv.BORDER_REFLECT101
Same as BORDER_REFLECT_101
BORDER_DEFAULT
Python: cv.BORDER_DEFAULT
Same as BORDER_REFLECT_101
BORDER_ISOLATED
Python: cv.BORDER_ISOLATED
Do not look at values ​​other than ROI

Code example:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main()
{
    
    
	Mat dog = imread("./dog.jpg");
	namedWindow("srcImg", WINDOW_NORMAL);
	imshow("srcImg", dog);
	
	//生成高斯噪声
	Mat gaussianNoise = Mat::zeros(dog.rows, dog.cols, dog.type());
	randn(gaussianNoise, (15, 15, 15), (30, 30, 30));
	Mat gaussianNoiseDst;
	add(dog, gaussianNoise, gaussianNoiseDst);
	namedWindow("gaussianNoiseDst", WINDOW_NORMAL);
	imshow("gaussianNoiseDst", gaussianNoiseDst);
	//imwrite("cpp_gaussianNoiseDst.jpg", gaussianNoiseDst);
	
	//方框滤波
	Mat boxBlurImg;
	boxFilter(gaussianNoiseDst, boxBlurImg, gaussianNoiseDst.depth(), Size(7, 7), Point(-1,-1),true);
	namedWindow("boxBlurImg", WINDOW_NORMAL);
	imshow("boxBlurImg", boxBlurImg);
	waitKey(0);
	//imwrite("cpp_boxBlurImg.jpg", boxBlurImg);

	return 0;
}

The original image
insert image description here

The result after adding Gaussian noise insert image description here
and box filtering (normalize).
insert image description here

# PYTHON
import cv2
import numpy as np

def main():
    dog = cv2.imread("./dog.jpg")
    cv2.namedWindow("srcImg", cv2.WINDOW_NORMAL)
    cv2.imshow("srcImg", dog)
    #添加高斯噪声
    mean = np.array([15, 15, 15])
    std = np.array([30, 30, 30])
    gaussianNoise = np.zeros(dog.shape)
    cv2.randn(gaussianNoise, mean, std)
    gaussianNoiseDog = cv2.add(dog, gaussianNoise.astype(np.uint8))
    cv2.namedWindow("gaussianNoiseDog", cv2.WINDOW_NORMAL)
    cv2.imshow("gaussianNoiseDog", gaussianNoiseDog)
    # cv2.imwrite("py_gaussianNoiseDog.jpg", gaussianNoiseDog)
    #方框滤波(normalize=true)
    boxFilterImg = cv2.boxFilter(gaussianNoiseDog, 3, (7, 7)).astype(np.uint8)
    cv2.namedWindow("boxFilterImg", cv2.WINDOW_NORMAL)
    cv2.imshow("boxFilterImg", boxFilterImg)
    cv2.waitKey(0)
    #cv2.imwrite("py_boxFilterImg.jpg", boxFilterImg)

if __name__ =="__main__":
    main()

The result is the same as above.

2.2 Mean filtering [blur]

bulr()
Function:
Perform mean filtering on the input src image, and then output the filtered dst image.
Normalized box filtering is mean filtering, and mean filtering is a special case of box filtering.
That is, the blur(src, dst, ksize, anchor, borderType) function is equivalent to boxFilter(src, dst, src.type(), ksize, anchor,true, borderType).[normalize parameter is true]

Form:
K = 1 K height ∗ K width [ 1 1 . . . . . . . . . 1 1 1 . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 . . . . . . . . . 1 ] K = \frac{1}{K.height*K.width}\left[\begin{matrix}1&1&...&1\\1&1&...&1\\. . & ... & ... & ... \\ 1 & 1 & ... & 1 \ end{matrix} \right]K=K.heightK.width1 11...111...1............11...1

Functional form:

C++:
void cv::blur (InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT)
Python:
cv.blur(src, ksize[, dst[, anchor[, borderType]]] ) ->dst

Parameter explanation (take the parameters displayed in C++ as an example):
1.InputArray src: input image, the image depth to be processed should be CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
2. OutputArray dst: The output image, the type is consistent with src.
3. Size ksize: Size type ksize, indicating the size of the core.
4. Point anchor=Point(-1,-1): The position of the anchor point (the smoothed point) on the nucleus, the default is Point(-1,-1), and the coordinates are negative to indicate the center of the nucleus.
5. int borderType=BORDER_DEFAULT: Used to infer the type of image bounding box, the default is BORDER_DEFAULT. For other types, please refer to the table content in the box filter .

Code example:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main()
{
    
    
	Mat dog = imread("./dog.jpg");
	namedWindow("srcImg", WINDOW_NORMAL);
	imshow("srcImg", dog);
	//生成高斯噪声
	Mat gaussianNoise = Mat::zeros(dog.rows, dog.cols, dog.type());
	randn(gaussianNoise, (15, 15, 15), (30, 30, 30));
	Mat gaussianNoiseDst;
	add(dog, gaussianNoise, gaussianNoiseDst);
	namedWindow("gaussianNoiseDst", WINDOW_NORMAL);
	imshow("gaussianNoiseDst", gaussianNoiseDst);
	//均值滤波
	Mat BlurImg;
	blur(gaussianNoiseDst, BlurImg, Size(7, 7));
	namedWindow("BlurImg", WINDOW_NORMAL);
	imshow("BlurImg", BlurImg);
	waitKey(0);
	return 0;
}

The original image
insert image description here

Gaussian noise added
insert image description here

Mean filtering:It can be seen that the effect is the same as the box filter with normalize=true set
insert image description here

# PYTHON
import cv2
import numpy as np

def main():
    dog = cv2.imread("./dog.jpg")
    cv2.namedWindow("srcImg", cv2.WINDOW_NORMAL)
    cv2.imshow("srcImg", dog)
    #添加高斯噪声
    mean = np.array([15, 15, 15])
    std = np.array([30, 30, 30])
    gaussianNoise = np.zeros(dog.shape)
    cv2.randn(gaussianNoise, mean, std)
    gaussianNoiseDog = cv2.add(dog, gaussianNoise.astype(np.uint8))
    cv2.namedWindow("gaussianNoiseDog", cv2.WINDOW_NORMAL)
    cv2.imshow("gaussianNoiseDog", gaussianNoiseDog)
    # cv2.imwrite("py_gaussianNoiseDog.jpg", gaussianNoiseDog)
    #均值滤波
    blurFilterImg = cv2.blur(gaussianNoiseDog,(7, 7)).astype(np.uint8)
    cv2.namedWindow("blurFilterImg", cv2.WINDOW_NORMAL)
    cv2.imshow("blurFilterImg", blurFilterImg)
    cv2.waitKey(0)
    # cv2.imwrite("py_blurFilterImg.jpg", blurFilterImg)

if __name__ =="__main__":
    main()

The result is the same as above.

2.3 Gaussian filter [GaussianBlur]

GaussianBlur()
Function:
Use a Gaussian filter to blur the image.

This function convolves the source image with the specified Gaussian kernel, which can be used to remove Gaussian noise.
Functional form:

C++:
void cv::GaussianBlur (InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT)
Python:
cv.GaussianBlur( src, ksize, sigmaX[, dst[, sigmaY[, borderType]]] ) ->dst

Parameter explanation (take the parameters displayed in C++ as an example):
1.InputArray src: input image, the image depth to be processed should be CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
2. OutputArray dst: the output image, the type is consistent with src.
3. Size ksize: Size type ksize, indicating the size of the core. Must be positive and odd.
4. double sigmaX: Gaussian kernel standard deviation in the X direction.
5. double sigmaY = 0: Gaussian kernel standard deviation in the Y direction, if the value is zero, set to sigmaX. If both sigmaX and sigmaY are 0, ksize.width and ksize.height are calculated.
6. int borderType=BORDER_DEFAULT: used to infer the type of image bounding box, the default is BORDER_DEFAULT. For other types, please refer to the table content in the box filter .

Code example:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main()
{
    
    
	Mat dog = imread("./dog.jpg");
	namedWindow("srcImg", WINDOW_NORMAL);
	imshow("srcImg", dog);
	//生成高斯噪声
	Mat gaussianNoise = Mat::zeros(dog.rows, dog.cols, dog.type());
	randn(gaussianNoise, (15, 15, 15), (30, 30, 30));
	Mat gaussianNoiseDst;
	add(dog, gaussianNoise, gaussianNoiseDst);
	namedWindow("gaussianNoiseDst", WINDOW_NORMAL);
	imshow("gaussianNoiseDst", gaussianNoiseDst);

	//高斯滤波
	Mat gaussianBlurImg;
	GaussianBlur(gaussianNoiseDst, gaussianBlurImg, Size(7, 7), 30, 30);
	namedWindow("gaussianBlurImg", WINDOW_NORMAL);
	imshow("gaussianBlurImg", gaussianBlurImg);
	waitKey(0);
	// imwrite("cpp_gaussianBlurImg.jpg", gaussianBlurImg);
	return 0;
}

The original imageinsert image description here

Gaussian noise added
insert image description here

The result after Gaussian filtering:
insert image description here

# PYTHON
import cv2
import numpy as np

def main():
    dog = cv2.imread("./dog.jpg")
    cv2.namedWindow("srcImg", cv2.WINDOW_NORMAL)
    cv2.imshow("srcImg", dog)
    #添加高斯噪声
    mean = np.array([15, 15, 15])
    std = np.array([30, 30, 30])
    gaussianNoise = np.zeros(dog.shape)
    cv2.randn(gaussianNoise, mean, std)
    gaussianNoiseDog = cv2.add(dog, gaussianNoise.astype(np.uint8))
    cv2.namedWindow("gaussianNoiseDog", cv2.WINDOW_NORMAL)
    cv2.imshow("gaussianNoiseDog", gaussianNoiseDog)
    # cv2.imwrite("py_gaussianNoiseDog.jpg", gaussianNoiseDog)
    #高斯滤波
    gaussianBlurImg = cv2.GaussianBlur(gaussianNoiseDog,(7, 7), 30, 30).astype(np.uint8)
    cv2.namedWindow("gaussianBlurImg", cv2.WINDOW_NORMAL)
    cv2.imshow("gaussianBlurImg", gaussianBlurImg)
    cv2.waitKey(0)
    # cv2.imwrite("py_blurFilterImg.jpg", gaussianBlurImg)
if __name__ =="__main__":
    main()

The result is the same as above.

3. Nonlinear filtering

3.1 Median filtering [medianBlur]

medianBlur()
Function:
Compared with the mean value filter to obtain the average value of the neighboring pixels, the median filter obtains the median value of the field pixels as the output value. The specific operations are:

  1. Sort the domain pixels by size.
  2. Select the pixel in the middle as output. If the field pixels are even, take the average of the middle two pixels.

This method can effectively eliminate impulse noise and salt and pepper noise. These two types of noise are different from Gaussian noise. They are isolated noise points, which often cannot achieve good results when Gaussian filtering is used, while median filtering can relatively reduce image blur while eliminating noise. The effect is to protect a certain degree of edge information. The price is that the processing speed is more than 5 times that of mean filtering.

Functional form:

C++:
void cv::medianBlur (InputArray src, OutputArray dst, int ksize)
Python:
cv.medianBlur(src, ksize[, dst]) -> dst

Parameter explanation (take the parameters displayed in C++ as an example):
1.InputArray src: input image, input 1, 3, 4 channel image; when ksize is 3 or 5, the image depth should be CV_8U, CV_16U or CV_32F, for larger The aperture size can only be CV_8U.
2. OutputArray dst: The output image, the type is consistent with src.
3. int ksize: the linear size of the aperture; it must be an odd number and greater than 1, for example: 3,5,7…

Code example:
List the comparative effects of mean filtering and median filtering on salt and pepper noise removal.

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main()
{
    
    
	Mat dog = imread("./dog.jpg");
	namedWindow("srcImg", WINDOW_NORMAL);
	imshow("srcImg", dog);
	//添加椒盐噪声
	RNG rng(12345);
	Mat impulseNoise = dog.clone();
	int nums = 10000;
	for (int i = 0; i < nums; i++) {
    
    
		int x = rng.uniform(0, impulseNoise.cols);
		int y = rng.uniform(0, impulseNoise.rows);
		if (i % 2 == 1) {
    
    
			impulseNoise.at<Vec3b>(y, x) = Vec3b(255, 255, 255);
		}
		else {
    
    
			impulseNoise.at<Vec3b>(y, x) = Vec3b(0, 0, 0);
		}
	}
	namedWindow("impulseNoise", WINDOW_NORMAL);
	imshow("impulseNoise", impulseNoise);
	//imwrite("cpp_impulseNoise.jpg", impulseNoise);
	//均值滤波
	Mat BlurImg;
	blur(impulseNoise, BlurImg, Size(7, 7));
	namedWindow("BlurImg", WINDOW_NORMAL);
	imshow("BlurImg", BlurImg);
	//imwrite("cpp_impulseNoiseBlurImg.jpg", BlurImg);
	//中值滤波
	Mat medianBlurImg;
	medianBlur(impulseNoise, medianBlurImg, 7);
	namedWindow("medianBlurImg", WINDOW_NORMAL);
	imshow("medianBlurImg", medianBlurImg);
	//imwrite("cpp_medianBlurImg.jpg", medianBlurImg);
	waitKey(0);
	return 0;
}

The original image
insert image description here

Add salt and pepper (impulse) noise
insert image description here

The result of mean filtering
insert image description here

The result of the median filterMedian filtering can better remove salt and pepper noise than mean filtering, and the degree of blur is relatively low
insert image description here

# PYTHON
import cv2
import numpy as np
def main():
    dog = cv2.imread("./dog.jpg")
    cv2.namedWindow("srcImg", cv2.WINDOW_NORMAL)
    cv2.imshow("srcImg", dog)
    #添加椒盐噪声
    impulseNoiseDog = dog.copy()
    nums = 10000
    for i in range(0, nums):
        x = np.random.randint(0, impulseNoiseDog.shape[1])
        y = np.random.randint(0, impulseNoiseDog.shape[0])
        if (i % 2 == 1):
            impulseNoiseDog[y, x, :]= (255, 255, 255)

        else:
            impulseNoiseDog[y, x, :] = (0, 0, 0)
    cv2.namedWindow("impulseNoise", cv2.WINDOW_NORMAL)
    cv2.imshow("impulseNoise", impulseNoiseDog)
    # cv2.imwrite("py_gaussianNoiseDog.jpg", gaussianNoiseDog)
    # 均值滤波
    blurFilterImg = cv2.blur(impulseNoiseDog,(7, 7))
    cv2.namedWindow("impulseBlurFilterImg", cv2.WINDOW_NORMAL)
    cv2.imshow("impulseBlurFilterImg", blurFilterImg)
    # cv2.imwrite("py_impulseBlurFilterImg.jpg", blurFilterImg)
    # 中值滤波
    medianBlurImg = cv2.medianBlur(impulseNoiseDog, 7)
    cv2.namedWindow("medianBlurImg", cv2.WINDOW_NORMAL)
    cv2.imshow("medianBlurImg", medianBlurImg)
    # cv2.imwrite("py_medianBlurImg.jpg", medianBlurImg)
    cv2.waitKey(0)
    
if __name__ =="__main__":
    main()

The result is the same as above

3.2 Bilateral filtering [bilateralFilter]

bilateralFilter()
Function:
The bilateral filter is to perform Gaussian filtering on the spatial neighborhood and pixel value (color value) at the same time. That is, bilateral filtering not only considers the influence of spatial distance (same as the previous Gaussian filter), but also considers the influence of pixel similarity (which can effectively preserve edges). We can understand it from the following formula:

Bilateral filter formula:
g ( i , j ) = ∑ k , lf ( k , l ) w ( i , j , k , l ) ∑ k , lw ( i , j , k , l ) g(i,j) = \frac{\sum_{k, l}^{} f(k,l)w(i,j,k,l)}{\sum_{k,l}w(i,j,k,l)}g(i,j)=k,lw(i,j,k,l)k,lf(k,l)w(i,j,k,l)
Among them i , ji, ji,j represents the coordinate position of the target pixel,k , lk, lk,l represents the pixel position of the neighbor pixel. w ( i , j , k , l ) w(i,j,k,l)w(i,j,k,l ) represents the bilateral filtering kernel value corresponding to the target pixel and neighboring pixels.

Bilateral filter kernel www is determined by the spatial filter kernelwd w_dwdAnd pixel similarity filtering kernel wr w_rwrThe product of is formed. where the spatial filter kernel wd w_dwdAnd pixel similarity filtering kernel wr w_rwrThe expression of is as follows:
wd ( i , j , k , l ) = exp ⁡ ( − ( i − k ) 2 + ( j − l ) 2 2 σ d 2 ) w_d(i,j,k,l) ​​= \ exp(-\frac{(ik)^2+(jl)^2}{2\sigma_d^2})wd(i,j,k,l)=exp(2 pd2(ik)2+(jl)2)
w r ( i , j , k , l ) = exp ⁡ ( − ( f ( i , j ) − f ( k , l ) ) 2 2 σ r 2 ) w_r(i,j,k,l) = \exp(-\frac{(f(i,j)-f(k,l))^2}{2\sigma_r^2}) wr(i,j,k,l)=exp(2 pr2(f(i,j)f(k,l))2)
of whichf ( i , j ) f(i,j)f(i,j ) represents the pixel value of the target pixel,f ( k , l ) f(k,l)f(k,l ) represents the pixel value of the neighboring pixels. From the pixel similarity filtering kernelwr w_rwrIt can be seen from the formula that the larger the difference between pixels, the smaller the kernel value, and vice versa, the smaller the pixel difference, the larger the kernel value . In this way, a "waterfall-shaped" filter kernel is formed at the image boundary, so that the edge information of the image can be effectively maintained.

Understand the content of the kernel from an example:
Suppose the original image scale is 7 × 7 7\times77×7 , the value is:

2D pixels 3D image
|0.000|0.000|0.000|0.000|255.000|255.000|255.000|
|0.000|0.000|0.000|0.000|255.000|255.000|255.000|
|0.000|0.000|0.000|0.000|255.000|255.000|255.000|
|0.000|0.000|0.000|0.000|255.000|255.000|255.000|
|0.000|0.000|0.000|0.000|255.000|255.000|255.000|
|0.000|0.000|0.000|0.000|255.000|255.000|255.000|
|0.000|0.000|0.000|0.000|255.000|255.000|255.000|
insert image description here

Then, after formula calculation, its wd w_dwdSum wr w_rwrThe filter kernel 2D and 3D images are shown in the figure below:
the spatial position filter kernel is shown in the figure below:

2D pixels 3D image
|0.368|0.486|0.574|0.607|0.574|0.486|0.368|
|0.486|0.641|0.757|0.801|0.757|0.641|0.486|
|0.574|0.757|0.895|0.946|0.895|0.757|0.574|
|0.607|0.801|0.946|1.000|0.946|0.801|0.607|
|0.574|0.757|0.895|0.946|0.895|0.757|0.574|
|0.486|0.641|0.757|0.801|0.757|0.641|0.486|
|0.368|0.486|0.574|0.607|0.574|0.486|0.368|
insert image description here

Pixel similarity filter kernel:

2D pixels 3D image
|1.000|1.000|1.000|1.000|0.000|0.000|0.000|
|1.000|1.000|1.000|1.000|0.000|0.000|0.000|
|1.000|1.000|1.000|1.000|0.000|0.000|0.000|
|1.000|1.000|1.000|1.000|0.000|0.000|0.000|
|1.000|1.000|1.000|1.000|0.000|0.000|0.000|
|1.000|1.000|1.000|1.000|0.000|0.000|0.000|
|1.000|1.000|1.000|1.000|0.000|0.000|0.000|
insert image description here

Bilateral filter kernel www的公式如下:
w ( i , j , k , l ) = exp ⁡ ( − ( i − k ) 2 + ( j − l ) 2 2 σ d 2 − ( f ( i , j ) − f ( k , l ) ) 2 2 σ r 2 ) w(i,j,k,l) = \exp(-\frac{(i-k)^2+(j-l)^2}{2\sigma_d^2}-\frac{(f(i,j)-f(k,l))^2}{2\sigma_r^2}) w(i,j,k,l)=exp(2σd2(ik)2+(jl)22σr2(f(i,j)f(k,l))2)

双边滤波器滤波核:

2D像素 3D图像
|0.368|0.486|0.574|0.607|0.000|0.000|0.000|
|0.486|0.641|0.757|0.801|0.000|0.000|0.000|
|0.574|0.757|0.895|0.946|0.000|0.000|0.000|
|0.607|0.801|0.946|1.000|0.000|0.000|0.000|
|0.574|0.757|0.895|0.946|0.000|0.000|0.000|
|0.486|0.641|0.757|0.801|0.000|0.000|0.000|
|0.368|0.486|0.574|0.607|0.000|0.000|0.000|
insert image description here

从双边滤波核中可以看到,双边滤波器可以在消除噪声的同时尽可能地保留图像的边界信息。但是如果高频噪声处于边界附近的话,则不能很好的过滤,所以该滤波器最好是处理图像低频信息。

如果读者也想生成双边滤波器的核,则可以通过附录代码,自行测试。

官方文档中解释:
1.双边滤波可以很好地减少不必要的噪声,同时保持边缘相当尖锐。然而,与大多数过滤器相比,它非常慢。
2.sigma值:为简单起见,可以将两个sigma值设为相同。如果它们很小(< 10),过滤不会有太大的效果,而如果它们很大(> 150),它们会有非常强的效果,使图像看起来“卡通化”。
3.过滤器大小:较大的过滤器(d > 5)非常慢,因此建议对实时应用程序使用d=5,对需要大量噪声过滤的离线应用程序可能使用d=9。

函数形式:

C++:
void cv::bilateralFilter (InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT)
Python:
cv.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) -> dst

参数解释(以C++展示的参数为例):
1.InputArray src: 输入图像,需要为8位或浮点类型的单通道或3通道图像。
2. OutputArray dst: 输出图像,类型和src一致。
3. int d:滤波时每个像素邻域的直径。如果它是非正的,则从sigmspace计算。
4. double sigmaColor:颜色空间滤波器的sigma值(高斯核的标准差,标准差约大,高斯核越扁平),这个参数值越大,就表明像素邻域有越宽广的颜色会被混合在一起,产生较大的半相等颜色空间。
5. double sigmaSpace:坐标空间滤波器的sigma值(高斯核的标准差,标准差约大,高斯核越扁平)。这个参数值越大,就表明有着更远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。当d>0时,d指定邻域大小,与sigmspace无关。否则,d与sigmspace成比例。
6. int borderType = BORDER_DEFAULT:用于推断图像边界框的类型,默认BORDER_DEFAULT。其他类型请查看方框滤波中表格内容。

Code example:

List the comparative effects of bilateral filtering and Gaussian filtering on Gaussian noise removal, and also show the cartoon effect mentioned on the official website.

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main()
{
    
    
	Mat dog = imread("./dog.jpg");
	namedWindow("srcImg", WINDOW_NORMAL);
	imshow("srcImg", dog);
	//生成高斯噪声
	Mat gaussianNoise = Mat::zeros(dog.rows, dog.cols, dog.type());
	randn(gaussianNoise, (15, 15, 15), (30, 30, 30));
	Mat gaussianNoiseDst;
	add(dog, gaussianNoise, gaussianNoiseDst);
	namedWindow("gaussianNoiseDst", WINDOW_NORMAL);
	imshow("gaussianNoiseDst", gaussianNoiseDst);
	//高斯滤波
	Mat gaussianBlurImg;
	GaussianBlur(gaussianNoiseDst, gaussianBlurImg, Size(7, 7), 100, 100);
	namedWindow("gaussianBlurImg", WINDOW_NORMAL);
	imshow("gaussianBlurImg", gaussianBlurImg);
	//双边滤波
	Mat bilateralBlurImg;
	bilateralFilter(gaussianNoiseDst, bilateralBlurImg, 7, 100, 100);
	namedWindow("bilateralBlurImg", WINDOW_NORMAL);
	imshow("bilateralBlurImg", bilateralBlurImg);
	//imwrite("cpp_bilateralBlurImg.jpg", bilateralBlurImg);
	//卡通效果
	Mat animeImg;
	bilateralFilter(dog, animeImg, 50, 200, 200);
	namedWindow("animeImg", WINDOW_NORMAL);
	imshow("animeImg", animeImg);
	//imwrite("cpp_animeImg.jpg", animeImg);
	waitKey(0);

	return 0;
}

The original image
insert image description here

Gaussian noise added
insert image description here

The result after Gaussian filtering
insert image description here

The result after bilateral filtering (== It can be clearly seen that bilateral filtering can remove Gaussian noise while maintaining the edge)
insert image description here

cartoon effect
insert image description here

# PYTHON

def main():
    dog = cv2.imread("./dog.jpg")
    cv2.namedWindow("srcImg", cv2.WINDOW_NORMAL)
    cv2.imshow("srcImg", dog)
    #添加高斯噪声
    mean = np.array([15, 15, 15])
    std = np.array([30, 30, 30])
    gaussianNoise = np.zeros(dog.shape)
    cv2.randn(gaussianNoise, mean, std)
    gaussianNoiseDog = cv2.add(dog, gaussianNoise.astype(np.uint8))
    cv2.namedWindow("gaussianNoiseDog", cv2.WINDOW_NORMAL)
    cv2.imshow("gaussianNoiseDog", gaussianNoiseDog)
    # cv2.imwrite("py_gaussianNoiseDog.jpg", gaussianNoiseDog)
    #高斯滤波
    gaussianBlurImg = cv2.GaussianBlur(gaussianNoiseDog,(7, 7), 30, 30).astype(np.uint8)
    cv2.namedWindow("gaussianBlurImg", cv2.WINDOW_NORMAL)
    cv2.imshow("gaussianBlurImg", gaussianBlurImg)
    # cv2.imwrite("py_GaussianBlurImg.jpg", gaussianBlurImg)
	# 双边滤波
    bilateralBlurImg = cv2.bilateralFilter(gaussianNoiseDog, 30, 180, 300)
    cv2.namedWindow("bilateralBlurImg", cv2.WINDOW_NORMAL)
    cv2.imshow("bilateralBlurImg", bilateralBlurImg)
    # cv2.imwrite("py_bilateralBlurImg.jpg", bilateralBlurImg)
	#卡通效果
    animeImg = cv2.bilateralFilter(dog, 50, 200, 200)
    cv2.namedWindow("animeImg", cv2.WINDOW_NORMAL)
    cv2.imshow("animeImg", animeImg)
    # cv2.imwrite("py_animeImg.jpg", animeImg)
    cv2.waitKey(0)
if __name__ =="__main__":
    main()

The original image
insert image description here

Gaussian noise added
insert image description here

The result after Gaussian filtering
insert image description here

The result after bilateral filtering (== It can be clearly seen that bilateral filtering can remove Gaussian noise while maintaining the edge)
insert image description here

cartoon effect
insert image description here


Summarize

This article only summarizes the functions that I think are commonly used. In the official Opencv documentation, there are many functions that have not been introduced. If this article is not written, you can query the official OpenCV documentation . Thank you readers for reading. If you think it is helpful to you, you can give the little brother a thumbs up.

Finally, remember Mr. Mao Xingyun and thank Mr. Mao Xingyun for his guidance. This article mainly refers to Mr. Mao Xingyun's "Introduction to OpenCV3 Programming".

appendix

generate kernel function

  1. Generate a kernel function for a bilateral filter:
//C++
#include<opencv2/opencv.hpp>
#include<math.h>
#include<iostream>
#include<typeinfo>
#include<vector>
#include<string>

using namespace std;
using namespace cv;

int main()
{
    
    
	//-----------------------------
	// 只需修改此处的图像尺寸即可
	//-----------------------------
	int imgSize = 7;
	Mat srcImg(7, 7, CV_64FC1 ,Scalar::all(255));

	for (int row=0;row<srcImg.rows;row++)
	{
    
    
		double* PtrSrc = srcImg.ptr<double>(row);
		for (int col=0; col < (srcImg.cols * srcImg.channels())/2+1; col++)
		{
    
    
			PtrSrc[col] = 0;
		}
	}
	//用来打印分割符号“-”*30
	vector<char> spiltStr;
	for (int i = 0; i < 30; i++)
	{
    
    
		spiltStr.push_back('-');
	}
	cout << "原始图像:" << endl;
	//-----------------------------
	// 打印原始图像
	//-----------------------------
	for (int i = 0; i < 30; i++) cout << spiltStr[i];
	printf("\n");
	for (int row = 0; row < srcImg.rows; row++)
	{
    
    
		printf("|");
		for (int col = 0; col < srcImg.cols * srcImg.channels(); col++)
		{
    
    
			printf("%.3f|", srcImg.at<double>(row, col));
		}
		printf("\n");
		for (int i = 0; i < 30; i++) cout << spiltStr[i];
		printf("\n");
	}
	//-----------------------------
	//计算空间位置的高斯核
	//-----------------------------
	int sigmad = 3;
	Mat spaceKernel = Mat::zeros(imgSize, imgSize, CV_64FC1);
	for (int row = 0; row < spaceKernel.rows; row++)
	{
    
    
		double* PtrSpace = spaceKernel.ptr<double>(row);
		for (int col = 0; col < spaceKernel.cols * srcImg.channels(); col++)
		{
    
    
			PtrSpace[col] = exp(-(pow((row - imgSize / 2), 2) + pow((col - imgSize / 2), 2)) / (2 * pow((double)sigmad, 2)));		
		}
	}
	//-----------------------------
	//打印空间位置的高斯核的结果
	//-----------------------------
	cout << "空间位置滤波核:" << endl;
	for (int i = 0; i < 30; i++) cout << spiltStr[i];
	printf("\n");
	for (int row = 0; row < spaceKernel.rows; row++)
	{
    
    	
		printf("|");
		for (int col = 0; col < spaceKernel.cols * srcImg.channels(); col++)
		{
    
    
			printf("%.3f|", spaceKernel.at<double>(row, col));
		}
		printf("\n");
		for (int i = 0; i < 30; i++) cout << spiltStr[i];
		printf("\n");
	}
	//-----------------------------
	//计算像素相似度的高斯核
	//-----------------------------
	int sigmaR = sigmad;
	Mat pixKernel = Mat::zeros(imgSize, imgSize, CV_64FC1);
	for (int row = 0; row < pixKernel.rows; row++)
	{
    
    
		double* PtrPix = pixKernel.ptr<double>(row);
		for (int col = 0; col < pixKernel.cols * srcImg.channels(); col++)
		{
    
    
			PtrPix[col] = exp(-(pow(srcImg.at<double>(imgSize / 2, imgSize / 2) - srcImg.at<double>(row, col),2)) / (2 * pow((double)sigmaR, 2)));
		}
	}
	//-----------------------------
	//打印像素相似度的高斯核的结果
	//-----------------------------
	cout << "像素相似度滤波核:" << endl;
	for (int i = 0; i < 30; i++) cout << spiltStr[i];
	printf("\n");
	for (int row = 0; row < pixKernel.rows; row++)
	{
    
    
		printf("|");
		for (int col = 0; col < pixKernel.cols * srcImg.channels(); col++)
		{
    
    
			printf("%.3f|", pixKernel.at<double>(row, col));
		}
		printf("\n");
		for (int i = 0; i < 30; i++) cout << spiltStr[i];
		printf("\n");
	}
	//-----------------------------
	//计算双边滤波高斯核
	//-----------------------------
	Mat bilateralKernel = Mat::zeros(imgSize, imgSize, CV_64FC1);
	for (int row = 0; row < bilateralKernel.rows; row++)
	{
    
    
		double* PtrbiLateralKernel = bilateralKernel.ptr<double>(row);
		for (int col = 0; col < bilateralKernel.cols * srcImg.channels(); col++)
		{
    
    
			PtrbiLateralKernel[col] = pixKernel.at<double>(row, col) * spaceKernel.at<double>(row, col);
		}
	}
	//-----------------------------
	//打印双边滤波高斯核的结果
	//-----------------------------
	cout << "双边滤波器滤波核:" << endl;
	for (int i = 0; i < 30; i++) cout << spiltStr[i];
	printf("\n");
	for (int row = 0; row < bilateralKernel.rows; row++)
	{
    
    
		printf(",");
		for (int col = 0; col < bilateralKernel.cols * srcImg.channels(); col++)
		{
    
    
			printf("%.3f|", bilateralKernel.at<double>(row, col));
		}
		printf("\n");
		for (int i = 0; i < 30; i++) cout << spiltStr[i];
		printf("\n");
	}
};
  1. 3D display of kernel functions generated by C++
# PYTHON
import numpy as np

d = np.array([0.000,0.000,0.000,0.000,255.000,255.000,255.000,
0.000,0.000,0.000,0.000,255.000,255.000,255.000,
0.000,0.000,0.000,0.000,255.000,255.000,255.000,
0.000,0.000,0.000,0.000,255.000,255.000,255.000,
0.000,0.000,0.000,0.000,255.000,255.000,255.000,
0.000,0.000,0.000,0.000,255.000,255.000,255.000,
0.000,0.000,0.000,0.000,255.000,255.000,255.000])

a = np.array([0.368,0.486,0.574,0.607,0.574,0.486,0.368,
0.486,0.641,0.757,0.801,0.757,0.641,0.486,
0.574,0.757,0.895,0.946,0.895,0.757,0.574,
0.607,0.801,0.946,1.000,0.946,0.801,0.607,
0.574,0.757,0.895,0.946,0.895,0.757,0.574,
0.486,0.641,0.757,0.801,0.757,0.641,0.486,
0.368,0.486,0.574,0.607,0.574,0.486,0.368,])

b = np.array([1.000,1.000,1.000,1.000,0.000,0.000,0.000,
1.000,1.000,1.000,1.000,0.000,0.000,0.000,
1.000,1.000,1.000,1.000,0.000,0.000,0.000,
1.000,1.000,1.000,1.000,0.000,0.000,0.000,
1.000,1.000,1.000,1.000,0.000,0.000,0.000,
1.000,1.000,1.000,1.000,0.000,0.000,0.000,
1.000,1.000,1.000,1.000,0.000,0.000,0.000,])

c = np.array([00.368,0.486,0.574,0.607,0.000,0.000,0.000,
0.486,0.641,0.757,0.801,0.000,0.000,0.000,
0.574,0.757,0.895,0.946,0.000,0.000,0.000,
0.607,0.801,0.946,1.000,0.000,0.000,0.000,
0.574,0.757,0.895,0.946,0.000,0.000,0.000,
0.486,0.641,0.757,0.801,0.000,0.000,0.000,
0.368,0.486,0.574,0.607,0.000,0.000,0.000,])
#-----------------------------
#原始图像高斯核
#-----------------------------
#利用三维轴方法
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
#定义图像和三维格式坐标轴
fig0 = plt.figure()  #定义新的三维坐标轴
ax0 = plt.axes(projection='3d')
xx = np.arange(-3, 4, 1)
yy = np.arange(-3, 4, 1)
X, Y = np.meshgrid(xx, yy)
Z = d.reshape(7, 7)
#作图
ax0.plot_surface(X, Y, Z, cmap='gray')
plt.show()
#-----------------------------
#空间位置高斯核
#-----------------------------
#定义图像和三维格式坐标轴
fig = plt.figure()  #定义新的三维坐标轴
ax1 = plt.axes(projection='3d')
xx = np.arange(-3, 4, 1)
yy = np.arange(-3, 4, 1)
X, Y = np.meshgrid(xx, yy)
Z = a.reshape(7, 7)
#作图
ax1.plot_surface(X, Y, Z, cmap='gray')
plt.show()
#-----------------------------
#像素相似度高斯核
#-----------------------------
fig2 = plt.figure()  #定义新的三维坐标轴
ax2 = plt.axes(projection='3d')
xx = np.arange(-3, 4, 1)
yy = np.arange(-3, 4, 1)
X, Y = np.meshgrid(xx, yy)
Z = b.reshape(7, 7)
#作图
ax2.plot_surface(X,Y,Z,cmap='gray')
plt.show()
#-----------------------------
#双边滤波高斯核
#-----------------------------
fig3 = plt.figure()  #定义新的三维坐标轴
ax3 = plt.axes(projection='3d')
xx = np.arange(-3, 4, 1)
yy = np.arange(-3, 4, 1)
X, Y = np.meshgrid(xx, yy)
Z = c.reshape(7, 7)
#作图
ax3.plot_surface(X,Y,Z,cmap='gray')
plt.show()

Guess you like

Origin blog.csdn.net/weixin_43610114/article/details/126383936