图像卷积

图像卷积原理

模板运算操作:对应位置求乘积,之后再求和,得到卷积之后的结果替换掉卷积中心 结果中只有中心部分发生改变,为了解决这个问题,一般会对边缘进行填充 进行卷积操作之后,模板中心像素大小会超级加倍 ,所以一般会对卷积模板进行归一化既除以所有系数之和 ,保证了最终结果不会大于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;
}

滤波是有用的 太神奇了