# 【OpenCV教程】滤波和边缘检测的过程

@[TOC](https://github.com/UnderTurrets/notes/blob/master/Computer_Vision/OpenCV/%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95/README.md)

***

## 1.均值滤波

### 1.1 卷积核形状

```cpp
Mat kernal=Mat::ones(Size(ksize,ksize),CV_64F)/(ksize*ksize);
```

### 1.2 API

```cpp
CV_EXPORTS_W void blur( InputArray src, OutputArray dst,
                        Size ksize, Point anchor = Point(-1,-1),
                        int borderType = BORDER_DEFAULT );
```

* 参数如下

| 参数                 | 含义                       |
| ------------------ | ------------------------ |
| src(source)        | 输入图片                     |
| dst(destination)   | 输出图片                     |
| ksize(kernal size) | 卷积核宽高，**必须是正奇数**         |
| anchor             | 滤波器中心像素位置，取（-1，-1）表示几何中心 |
| borderType         | 边界填充方式，默认为黑边             |

### 1.3 效果

```cpp
Mat xuenai = imread("xuenai.jpg");
imshow("xuenai",xuenai);
Mat xuenai_blur(xuenai.size(),xuenai.type());
blur(xuenai,xuenai_blur,Size(3,5));
imshow("xuenai_blur",xuenai_blur);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/poacn1231asc.png)

## 2.高斯滤波

### 2.1 卷积核形状

二维高斯函数表述为：

$$
G(x,y)=\frac{1}{2 \pi \sigma^{2}}e^{- \frac{(x-x_{0})^{2}+(y-y_{0})^{2}}{2\sigma^{2}}}
$$

对应图形：

![在这里插入图片描述](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/lvihaloi1283.png)

代码实现（不区分sigmaX与sigmaY）

```cpp
void GetGaussianKernel(Mat kernal, const int ksize,const double sigma)  
{  
    const double PI=4.0*atan(1.0); //圆周率π赋值  
    int center= ksize/2;  
    double sum=0;  
    for(int i=0;i<ksize;i++)  
    {  
        for(int j=0;j<ksize;j++)  
        {  
            kernal.ptr(i,j)=(1/(2*PI*sigma*sigma))*exp(-((i-center)*(i-center)+(j-center)*(j-center))/(2*sigma*sigma));  
            sum+=kernal.ptr(i,j);  
        }  
    }  

    for(int i=0;i<ksize;i++)  
    {  
        for(int j=0;j<ksize;j++)  
        {  
            kernal.ptr(i,j)/=sum;  
        }  
    }  
    return ;  
}
```

### 2.2 API

```cpp
CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                                double sigmaX, double sigmaY = 0,
                                int borderType = BORDER_DEFAULT );
```

* 参数如下

| 参数                 | 含义                                                                    |
| ------------------ | --------------------------------------------------------------------- |
| src(source)        | 输入图片                                                                  |
| dst(destination)   | 输出图片                                                                  |
| ksize(kernal size) | 卷积核宽高。**如果这个尺寸我们设其为非正数，那么OpenCV会从第五个参数sigmaSpace来计算出它来。**             |
| sigmaX             | x方向上的标准差                                                              |
| sigmaY             | y方向上的标准差。**默认输入量为0，则将其设置为等于sigmaX，如果两个轴的标准差均为0，则根据输入的高斯滤波器尺寸计算标准偏差。** |
| borderType         | 边界填充方式，默认为黑边                                                          |

### 2.3 效果

```cpp
Mat xuenai = imread("xuenai.jpg");
imshow("xuenai",xuenai);
Mat xuenai_Gauss(xuenai.size(),xuenai.type());
GaussianBlur(xuenai,xuenai_Gauss,Size(-1,-1),10);
imshow("xuenai_Gauss",xuenai_Gauss);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/vihasklnio123874.png)

## 3.中值滤波

### 3.1 原理

取滤波器内的中值作为输出，**可以很好的抑制椒盐噪声**

### 3.2 API

```cpp
CV_EXPORTS_W void medianBlur( InputArray src, OutputArray dst, int ksize );
```

* 参数如下

| 参数                 | 含义               |
| ------------------ | ---------------- |
| src(source)        | 输入图片             |
| dst(destination)   | 输出图片             |
| ksize(kernal size) | 卷积核边长，**必须是正奇数** |

### 3.3 效果

```cpp
Mat xuenai = imread("xuenai.jpg");
imshow("xuenai",xuenai);
Mat xuenai_median(xuenai.size(),xuenai.type());
medianBlur(xuenai,xuenai_median,5);
imshow("xuenai_median",xuenai_median);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/aloksn12387.png)

## 4.高斯双边滤波

### 4.1 原理

双边滤波器的好处是可以做边缘保存（edge preserving），一般用高斯滤波去降噪，会较明显地模糊边缘，对于高频细节的保护效果并不明显。双边滤波器顾名思义比高斯滤波多了一个高斯方差sigma－d，它是基于空间分布的高斯滤波函数，所以在边缘附近，离的较远的像素不会太多影响到边缘上的像素值，这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息，对于彩色图像里的高频噪声，双边滤波器不能够干净的滤掉，只能够对于低频信息进行较好的滤波。

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/aioch12381.png)

### 4.2 API

```cpp
CV_EXPORTS_W void bilateralFilter( InputArray src, OutputArray dst, int d,
                                   double sigmaColor, double sigmaSpace,
                                   int borderType = BORDER_DEFAULT );
```

* 参数如下

| 参数               | 含义                                                                                                                    |
| ---------------- | --------------------------------------------------------------------------------------------------------------------- |
| src(source)      | 输入图片                                                                                                                  |
| dst(destination) | 输出图片                                                                                                                  |
| d                | 卷积核边长。**如果这个值我们设其为非正数，那么OpenCV会从第五个参数sigmaSpace来计算出它来。**                                                              |
| sigmaColor       | 颜色空间滤波器的sigma值。这个参数的值越大，就表明该像素邻域内有更宽广的颜色会被混合到一起，产生较大的半相等颜色区域。                                                         |
| sigmaSpace       | 坐标空间中滤波器的sigma值，坐标空间的标注方差。他的数值越大，意味着越远的像素会相互影响，从而使更大的区域足够相似的颜色获取相同的颜色。当d>0，卷积核大小已被指定且与sigmaSpace无关。否则，d正比于sigmaSpace。 |
| borderType       | 边界填充方式，默认为黑边                                                                                                          |

### 4.3 效果

```cpp
Mat xuenai = imread("xuenai.jpg");
imshow("xuenai",xuenai);
Mat xuenai_bilateral(xuenai.size(),xuenai.type());
bilateralFilter(xuenai,xuenai_bilateral,-1,100,10);
imshow("xuenai_bilateral",xuenai_bilateral);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/voisl123asdas.png)

## 5.获取用来形态学操作的滤波器

```cpp
CV_EXPORTS_W Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
enum MorphShapes {
    MORPH_RECT    = 0, //!< a rectangular structuring element:  \f[E_{ij}=1\f]
    MORPH_CROSS   = 1, //!< a cross-shaped structuring element:
                       //!< \f[E_{ij} = \begin{cases} 1 & \texttt{if } {i=\texttt{anchor.y } {or } {j=\texttt{anchor.x}}} \\0 & \texttt{otherwise} \end{cases}\f]
    MORPH_ELLIPSE = 2 //!< an elliptic structuring element, that is, a filled ellipse inscribed
                      //!< into the rectangle Rect(0, 0, esize.width, 0.esize.height)
};
```

shape：滤波器形状

ksize(kernal size)：滤波器大小

anchor：滤波器中心像素位置，取（-1，-1）表示几何中心

## 6.腐蚀和膨胀（对二值图）

### 6.1 原理

腐蚀：取滤波器内的最小值作为输出

膨胀：取滤波器内的最大值作为输出

### 6.2 腐蚀API

```cpp
CV_EXPORTS_W void erode( InputArray src, OutputArray dst, InputArray kernel,
                         Point anchor = Point(-1,-1), int iterations = 1,
                         int borderType = BORDER_CONSTANT,
                         const Scalar& borderValue = morphologyDefaultBorderValue() );
```

* 参数如下

| 参数               | 含义                       |
| ---------------- | ------------------------ |
| src(source)      | 输入图片，**尽量是二值图**          |
| dst(destination) | 输出图片                     |
| kernal           | 滤波器矩阵                    |
| anchor           | 滤波器中心像素位置，取（-1，-1）表示几何中心 |
| iterations       | 执行erode函数的次数，默认执行一次      |
| borderType       | 边界填充方式，默认为黑边             |
| borderValue      | 填充边界的值                   |

### 6.3 效果

```cpp
Mat xuenai = imread("xuenai.jpg");
Mat xuenai_gray(xuenai.size(),xuenai.type());
cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);
Mat xuenai_threshold(xuenai.size(),xuenai.type());
threshold(xuenai_gray,xuenai_threshold,100,255,THRESH_BINARY);
imshow("xuenai_threshold",xuenai_threshold);

Mat kernal=getStructuringElement(MORPH_RECT,Size(3,3));

Mat xuenai_erode(xuenai.size(),xuenai.type());
erode(xuenai_threshold,xuenai_erode,kernal);
imshow("xuenai_erode",xuenai_erode);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/vrnkl12.png)

### 6.4 膨胀API

```cpp
CV_EXPORTS_W void dilate( InputArray src, OutputArray dst, InputArray kernel,
                          Point anchor = Point(-1,-1), int iterations = 1,
                          int borderType = BORDER_CONSTANT,
                          const Scalar& borderValue = morphologyDefaultBorderValue() );
```

* 参数如下

| 参数               | 含义                       |
| ---------------- | ------------------------ |
| src(source)      | 输入图片，**尽量是二值图**          |
| dst(destination) | 输出图片                     |
| kernal           | 滤波器矩阵                    |
| anchor           | 滤波器中心像素位置，取（-1，-1）表示几何中心 |
| iterations       | 执行erode函数的次数，默认执行一次      |
| borderType       | 边界填充方式，默认为黑边             |
| borderValue      | 填充边界的值                   |

### 6.5 效果

```cpp
Mat xuenai = imread("xuenai.jpg");
Mat xuenai_gray(xuenai.size(),xuenai.type());
cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);
Mat xuenai_threshold(xuenai.size(),xuenai.type());
threshold(xuenai_gray,xuenai_threshold,100,255,THRESH_BINARY);
imshow("xuenai_threshold",xuenai_threshold);

Mat kernal=getStructuringElement(MORPH_RECT,Size(3,3));

Mat xuenai_dilate(xuenai.size(),xuenai.type());
dilate(xuenai_threshold,xuenai_dilate,kernal);
imshow("xuenai_dilate",xuenai_dilate);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/ioufyvbbkj2.png)

## 7.形态学操作（对二值图）

### 7.1 API

```cpp
CV_EXPORTS_W void morphologyEx( InputArray src, OutputArray dst,
                                int op, InputArray kernel,
                                Point anchor = Point(-1,-1), int iterations = 1,
                                int borderType = BORDER_CONSTANT,
                                const Scalar& borderValue = morphologyDefaultBorderValue() );
```

* 参数如下

| 参数               | 含义                       |
| ---------------- | ------------------------ |
| src(source)      | 输入图片，**尽量是二值图**          |
| dst(destination) | 输出图片                     |
| op(option)       | 变换类型                     |
| kernal           | 滤波器矩阵                    |
| anchor           | 滤波器中心像素位置，取（-1，-1）表示几何中心 |
| iterations       | 执行erode函数的次数，默认执行一次      |
| borderType       | 边界填充方式，默认为黑边             |
| borderValue      | 填充边界的值                   |

### 7.2 变换类型

```cpp
enum MorphTypes{
    MORPH_ERODE    = 0, //腐蚀

    MORPH_DILATE   = 1, //膨胀

    MORPH_OPEN     = 2, //开
                    
    MORPH_CLOSE    = 3, //闭
                    
    MORPH_GRADIENT = 4, //形态学梯度
                    
    MORPH_TOPHAT   = 5, //顶帽
                    
    MORPH_BLACKHAT = 6, //黑帽

    MORPH_HITMISS  = 7  //击中击不中变换

};
```

### 7.3 开

#### 原理

对输入图片先进行腐蚀，然后进行膨胀。可以用来**屏蔽与滤波器大小相当的亮部**。

#### 效果

```cpp
Mat xuenai = imread("xuenai.jpg");
Mat xuenai_gray(xuenai.size(),xuenai.type());
cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);
Mat xuenai_threshold(xuenai.size(),xuenai.type());
threshold(xuenai_gray,xuenai_threshold,100,255,THRESH_BINARY);
imshow("xuenai_threshold",xuenai_threshold);

Mat kernal=getStructuringElement(MORPH_RECT,Size(3,3));

Mat xuenai_morphology(xuenai.size(),xuenai.type());
morphologyEx(xuenai_threshold,xuenai_morphology,MORPH_OPEN,kernal);
imshow("xuenai_morphology",xuenai_morphology);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/askv12837s.png)

### 7.4 闭

#### 原理

对输入图片先进行膨胀，然后进行腐蚀。可以用来**屏蔽与滤波器大小相当的暗部**。

#### 效果

```cpp
Mat xuenai = imread("xuenai.jpg");

Mat xuenai_gray(xuenai.size(),xuenai.type());
cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);
Mat xuenai_threshold(xuenai.size(),xuenai.type());
threshold(xuenai_gray,xuenai_threshold,100,255,THRESH_BINARY);
imshow("xuenai_threshold",xuenai_threshold);

Mat kernal=getStructuringElement(MORPH_RECT,Size(3,3));

Mat xuenai_morphology(xuenai.size(),xuenai.type());
morphologyEx(xuenai_threshold,xuenai_morphology,MORPH_CLOSE,kernal);
imshow("xuenai_morphology",xuenai_morphology);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/voih1276sk.png)

### 7.5 顶帽

#### 原理

对输入图片先进行开操作，然后原图-开操作图。可以用来**提取与滤波器大小相当的亮部**。

#### 效果

```cpp
Mat xuenai = imread("xuenai.jpg");

Mat xuenai_gray(xuenai.size(),xuenai.type());
cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);
Mat xuenai_threshold(xuenai.size(),xuenai.type());
threshold(xuenai_gray,xuenai_threshold,100,255,THRESH_BINARY);
imshow("xuenai_threshold",xuenai_threshold);

Mat kernal=getStructuringElement(MORPH_RECT,Size(3,3));

Mat xuenai_morphology(xuenai.size(),xuenai.type());
morphologyEx(xuenai_threshold,xuenai_morphology,MORPH_TOPHAT,kernal);
imshow("xuenai_morphology",xuenai_morphology);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/lakvnakis1236.png)

### 7.6 黑帽

#### 原理

对输入图片先进行闭操作，然后闭操作图-原图。可以用来**提取与滤波器大小相当的暗部**。

#### 效果

```cpp
Mat xuenai = imread("xuenai.jpg");

Mat xuenai_gray(xuenai.size(),xuenai.type());
cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);
Mat xuenai_threshold(xuenai.size(),xuenai.type());
threshold(xuenai_gray,xuenai_threshold,100,255,THRESH_BINARY);
imshow("xuenai_threshold",xuenai_threshold);

Mat kernal=getStructuringElement(MORPH_RECT,Size(3,3));

Mat xuenai_morphology(xuenai.size(),xuenai.type());
morphologyEx(xuenai_threshold,xuenai_morphology,MORPH_BLACKHAT,kernal);
imshow("xuenai_morphology",xuenai_morphology);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/9812asnlcka.png)

### 7.7 形态学梯度

#### 原理

膨胀图与腐蚀图之差。可以用来 **提取边界轮廓** ，但提取效果比不上专业的边缘检测算法。

#### 效果

```cpp
Mat xuenai = imread("xuenai.jpg");

Mat xuenai_gray(xuenai.size(),xuenai.type());
cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);
Mat xuenai_threshold(xuenai.size(),xuenai.type());
threshold(xuenai_gray,xuenai_threshold,100,255,THRESH_BINARY);
imshow("xuenai_threshold",xuenai_threshold);

Mat kernal=getStructuringElement(MORPH_RECT,Size(3,3));

Mat xuenai_morphology(xuenai.size(),xuenai.type());
morphologyEx(xuenai_threshold,xuenai_morphology,MORPH_GRADIENT,kernal);
imshow("xuenai_morphology",xuenai_morphology);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/loansi1238a.png)

### 7.8 击中击不中变换

#### 原理

击中击不中变换由下面三步构成：

用结构元素B1来腐蚀输入图像

用结构元素B2来腐蚀输入图像的补集

前两步结果的与运算

结构元素B1和B2可以结合为一个元素B。例如：

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/loibkldnjkqb32.png)

结构元素：左B1（击中元素），中B2（击不中元素），右B（两者结合）

本例中，我们寻找这样一种结构模式，中间像素属于背景，其上下左右属于前景，其余领域像素忽略不计（背景为黑色，前景为白色）。然后用上面的核在输入图像中找这种结构。从下面的输出图像中可以看到，输入图像中只有一个位置满足要求。

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/lvsaklnd1283.png)

输入二值图像

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/kas123gks.png)

输出二值图像

## 8.边缘检测：选择合适的输出深度

* 参照以下表格

| int sdepth      | int ddepth              |
| --------------- | ----------------------- |
| CV\_8U          | CV\_16S/CV\_32F/CV\_64F |
| CV\_16U/CV\_16S | CV\_32F/CV\_64F         |
| CV\_32F         | CV\_32F/CV\_64F         |
| CV\_64F         | CV\_64F                 |

### 8.1 normalize归一化函数

```cpp
CV_EXPORTS_W void normalize( InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0,
                             int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());
```

* 参数如下

| 参数               | 含义                                                                                          |
| ---------------- | ------------------------------------------------------------------------------------------- |
| src(source)      | 输入数组                                                                                        |
| dst(destination) | 输出数组                                                                                        |
| alpha            | **如果norm\_type为NORM\_MINMAX ，则alpha为最小值或最大值；如果norm\_type为其他类型，则为归一化要乘的系数**                  |
| beta             | **如果norm\_type为NORM\_MINMAX ，则beta为最小值或最大值；如果norm\_type为其他类型，beta被忽略.**                     |
| norm\_type       | 归一化类型，详见下面的内容                                                                               |
| iterations       | 执行erode函数的次数，默认执行一次                                                                         |
| dtype            | 输出数组的深度，若输入-1则表示与src一致。**如果不能判断需要的深度，则可以输入-1然后使用convertScaleAbs绝对值化，这也是最推荐的做法，而不推荐自己判断深度**。 |
| mask             | 掩码，用于指示函数是否仅仅对指定的元素进行操作。大小必须与src保持一致。具体用法见8.1.4                                             |

#### 归一化类型（只介绍常用的四种）

```cpp
enum NormTypes {
                NORM_INF       = 1,
                NORM_L1        = 2,
                NORM_L2        = 4,
                NORM_L2SQR     = 5,
                NORM_HAMMING   = 6,
                NORM_HAMMING2  = 7,
                NORM_TYPE_MASK = 7, //!< bit-mask which can be used to separate norm type from norm flags
                NORM_RELATIVE  = 8, //!< flag
                NORM_MINMAX    = 32 //!< flag
};
```

* NORM\_L1

$$
P=\frac{A_i}{\sum\left | A_i \right | } \cdot alpha
$$

* NORM\_L2

$$
P=\frac{A_i}{ \sqrt{\sum A_i^2} } \cdot alpha
$$

* NORM\_INF

$$
P=\frac{A_i}{ \max \left | A_i \right | } \cdot alpha
$$

* NORM\_MINMAX(recommended)

$$
P=\frac{A_k}{ \max A_i - \min A_i } \cdot \left | alpha-beta \right | + \min(alpha,beta)
$$

### 8.2 convertScaleAbs绝对值化

```cpp
CV_EXPORTS_W void convertScaleAbs(InputArray src, OutputArray dst,
                                  double alpha = 1, double beta = 0);
```

* 参数如下

| 参数               | 含义   |
| ---------------- | ---- |
| src(source)      | 输入图片 |
| dst(destination) | 输出图片 |

## 9.sobel（对灰度图）

### 9.1 卷积核形状（ksize=3）

```cpp
Mat kernalX=Mat_<int>(Size(3,3))<<(-1,0,1
                                    -2,0,2
                                    -1,0,1);
Mat kernalY=Mat_<int>(Size(3,3))<<(-1,-2,1
                                     0,0,0
                                     1,2,1);
```

### 9.2 API

```cpp
CV_EXPORTS_W 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 );
```

* 参数如下

| 参数                        | 含义               |
| ------------------------- | ---------------- |
| src(source)               | 输入图片，数据类型Mat     |
| dst(destination)          | 输出图片，数据类型Mat     |
| ddepth(destination depth) | 输出图片的深度（CV\_16F） |
| dx                        | x方向导数的阶数，一般取1    |
| dy                        | y方向导数的阶数，一般取1    |
| ksize(kernal size)        | 卷积核边长，默认为3       |
| scale                     | 生成图与原图的缩放比例，默认为1 |
| delta                     | 额外的增量，默认为0       |
| borderType                | 边界填充方式，默认为黑边     |

### 9.3 流程

1. 用cvtColor函数转**灰度图**
2. **在x,y方向上分别各调用一次Sobel**
3. **用convertScaleAbs函数转换到CV\_8U，否则无法显示**
4. **用addWeighted函数把两张输出图片加在一起**

### 9.4 同时在x,y方向上调用Sobel和分开调用的效果对比

```cpp
Mat xuenai = imread("xuenai.jpg");
imshow("xuenai", xuenai);

//转灰度图
Mat xuenai_gray(xuenai.size(),xuenai.type());
cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);

//同时在x,y方向上调用Sobel
Mat xuenai_sobel1(xuenai.size(),xuenai.type());
Sobel(xuenai_gray,xuenai_sobel1,CV_16S,1,1,3);
convertScaleAbs(xuenai_sobel1,xuenai_sobel1);
imshow("xuenai_sobel1",xuenai_sobel1);

//在x,y方向上分别各调用一次Sobel
Mat xuenai_xsobel(xuenai.size(),xuenai.type());Mat xuenai_ysobel(xuenai.size(),xuenai.type());Mat xuenai_sobel2(xuenai.size(),xuenai.type());
Sobel(xuenai_gray,xuenai_xsobel,CV_16S,1,0,3);
convertScaleAbs(xuenai_xsobel,xuenai_xsobel);
Sobel(xuenai_gray,xuenai_ysobel,CV_16S,0,1,3);
convertScaleAbs(xuenai_ysobel,xuenai_ysobel);
addWeighted(xuenai_xsobel,0.5,xuenai_ysobel,0.5,0,xuenai_sobel2);
imshow("xuenai_sobel2",xuenai_sobel2);
waitKey();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/lvlkn123.png)

**可以看到效果差了很多**

## 10.scharr（对灰度图）

### 10.1 卷积核形状（ksize恒定为3）

虽然Sobel算子可以有效的提取图像边缘，但是对图像中较弱的边缘提取效果较差。因此为了能够有效的提取出较弱的边缘，需要将像素值间的差距增大，因此引入Scharr算子。Scharr算子是对Sobel算子差异性的增强，因此两者之间的在检测图像边缘的原理和使用方式上相同。

```cpp
Mat kernalX=Mat_<int>(Size(3,3))<<(-3,0,3
                                    -10,0,10
                                    -3,0,3);
Mat kernalY=Mat_<int>(Size(3,3))<<(-3,-10,3
                                     0,0,0
                                     3,10,3);
```

### 10.2 API

```cpp
CV_EXPORTS_W void Scharr( InputArray src, OutputArray dst, int ddepth,
                          int dx, int dy, double scale = 1, double delta = 0,
                          int borderType = BORDER_DEFAULT );
```

* 参数如下

| 参数                        | 含义               |
| ------------------------- | ---------------- |
| src(source)               | 输入图片，数据类型Mat     |
| dst(destination)          | 输出图片，数据类型Mat     |
| ddepth(destination depth) | 输出图片的深度（CV\_16F） |
| dx                        | x方向导数的阶数，一般取1    |
| dy                        | y方向导数的阶数，一般取1    |
| scale                     | 生成图与原图的缩放比例，默认为1 |
| delta                     | 额外的增量，默认为0       |
| borderType                | 边界填充方式，默认为黑边     |

### 10.3 流程

1. 用cvtColor函数转**灰度图**
2. **在x,y方向上分别各调用一次Scharr**
3. **用convertScaleAbs函数转换到CV\_8U，否则无法显示**
4. **用addWeighted函数把两张输出图片加在一起**

## 11.Laplacian（对灰度图）

### 11.1 卷积核形状（ksize=3）

```cpp
Mat kernal=Mat_<int>(Size(3,3))<<(0,-1,0
                                   -1,4,-1
                                   0,-1,0);
```

Laplacian算子的卷积核形状决定了它 **对噪声非常敏感** ，因此，通常需要通过 **滤波平滑处理** 。

### 11.2 API

```cpp
CV_EXPORTS_W void Laplacian( InputArray src, OutputArray dst, int ddepth,
                             int ksize = 1, double scale = 1, double delta = 0,
                             int borderType = BORDER_DEFAULT );
```

* 参数如下

| 参数                        | 含义               |
| ------------------------- | ---------------- |
| src(source)               | 输入图片，数据类型Mat     |
| dst(destination)          | 输出图片，数据类型Mat     |
| ddepth(destination depth) | 输出图片的深度（CV\_16F） |
| scale                     | 生成图与原图的缩放比例，默认为1 |
| delta                     | 额外的增量，默认为0       |
| borderType                | 边界填充方式，默认为黑边     |

### 11.3 流程

1. **用中值滤波等操作平滑处理**
2. 用cvtColor函数转**灰度图**
3. 用Laplacian函数处理
4. **用convertScaleAbs函数转换到CV\_8U，否则无法显示**

## 12.Canny（recommended）

### 12.1 API

```cpp
CV_EXPORTS_W void Canny( InputArray image, OutputArray edges,
                         double threshold1, double threshold2,
                         int apertureSize = 3, bool L2gradient = false );
```

* 参数如下

| 参数           | 含义                                                                            |
| ------------ | ----------------------------------------------------------------------------- |
| image        | 输入图片，数据类型Mat                                                                  |
| edges        | 输出图片，数据类型Mat                                                                  |
| threshold1   | 最小阈值                                                                          |
| threshold2   | 最大阈值                                                                          |
| apertureSize | Sobel卷积核的大小，默认为3。**核越大，对噪声越不敏感，但是边缘检测的错误也会随之增加**                              |
| L2gradient   | 计算图像梯度幅度的标识，默认为false，表示L1范数（直接将两个方向的导数的绝对值相加）。如果使用true，表示L2范数（两个方向的导数的平方和再开方） |

* **高于threshold2被认为是真边界，低于threshold1被抛弃，介于二者之间，则取决于是否与真边界相连。**

### 12.2 流程

1. **用中值滤波等操作平滑处理**
2. 用Canny函数处理 **（不支持原地运算）**

### 12.3 效果

```cpp
Mat xuenai = imread("xuenai.jpg");
imshow("xuenai",xuenai);

Mat xuenai_canny(xuenai.size(),xuenai.type());
Canny(xuenai,xuenai_canny,60,150);
imshow("xuenai_canny",xuenai_canny);
waitKet();
```

![](https://cdn.jsdelivr.net/gh/UnderTurrets/ImageHosting/Computer_Vision/OpenCV/assets%E3%80%90OpenCV%E6%95%99%E7%A8%8B%E3%80%91%E6%BB%A4%E6%B3%A2%E5%92%8C%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E8%BF%87%E7%A8%8B/aklsvb123g.png)

## 13.添加噪声

为了检测算法的稳定性，常常需要在图片中人为地添加一些噪声来进行检验。

### 13.1 椒盐噪声

```cpp
static void addSaltNoise(const Mat& src,Mat& dst,int num=1000)
{
    dst=src.clone();
    for (int k = 0; k < num; k++)
    {
        //随机取值行列，得到像素点(i,j)
        int i = rand() % dst.rows;
        int j = rand() % dst.cols;
        //修改像素点(i,j)的像素值
        for(int channel=0;channel<src.channels();channel++){
            dst.ptr(i,j)[channel]=255;
        }
    }
    for (int k = 0; k < num; k++)
    {
        //随机取值行列
        default_random_engine engine;
        uniform_int_distribution<unsigned>u(0,10000);
        int i = rand() % dst.rows;
        int j = rand() % dst.cols;
        //修改像素点(i,j)的像素值
        for(int channel=0;channel<src.channels();channel++){
            dst.ptr(i,j)[channel]=0;
        }
    }
    return;
}
```

src(source)：输入图片 dst(destination)：输出图片 num(number)：噪声的个数

### 13.2 高斯噪声

```cpp
static void addGaussianNoise(const Mat& src,Mat& dst,InputArray meanValue=10,InputArray std=36){
    dst=src.clone();
    //构造高斯噪声矩阵
    Mat noise(dst.size(),dst.type());
    RNG rng(time(NULL));
    rng.fill(noise, RNG::NORMAL, meanValue, std);
    //将高斯噪声矩阵与原图像叠加得到含噪图像
    dst+=noise;
    return ;
}
```

src(source)：输入图片 dst(destination)：输出图片 meanValue：高斯函数均值 std(standard deviation)：高斯函数标准差

#### 随机数填充矩阵

```cpp
void RNG::fill( InputOutputArray mat, int distType, InputArray a, InputArray b, bool saturateRange = false );
```

* 参数如下

| 参数                         | 含义                                                                          |
| -------------------------- | --------------------------------------------------------------------------- |
| mat                        | 输入输出矩阵，最多支持4通道，超过4通道先用reshape()改变结构                                         |
| distType(distination type) | 可选UNIFORM 或 NORMAL，分别表示均匀分布和高斯分布                                            |
| a                          | disType是UNIFORM,a表示下界(闭区间)；disType是NORMAL,a表示均值                             |
| b                          | disType是UNIFORM,b表示上界(开区间)；disType是NORMAL,b表示标准差                            |
| saturateRange              | 只针对均匀分布有效。当为真的时候，会先把产生随机数的范围变换到数据类型的范围，再产生随机数；如果为假，会先产生随机数，再进行截断到数据类型的有效区间。 |
