OpenCV 4.x API 详解与C++实例-图像滤波

第二节 图像滤波

Opencv库的imgproc模块提供了很多经典的图像滤波函数,比如双边滤波、高斯滤波、Box滤波等等,同时也支持自定义滤波。

本节中描述的函数和类用于对2D图像(表示为Mat)进行各种线性或非线性滤波操作。 这意味着对于源图像(通常为矩形)中的每个像素位置(x,y),都将考虑其邻域并将其用于计算响应。 对于线性滤波器,它是像素值的加权和。 在形态操作的情况下,它是最小值或最大值,依此类推。 计算出的响应存储在目标图像中相同位置(x,y)处。 这意味着输出图像将具有与输入图像相同的尺寸。 通常,这些功能支持多通道数组,在这种情况下,每个通道都是独立处理的。 因此,输出图像还将具有与输入图像相同数量的通道。

1)cv::bilateralFilter 双边滤波


cv::bilateralFilter函数对输入进行双边滤波。双边滤波可以参考论文http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html,或博客数字图像处理Python语言实现-图像增强-双边滤波(Bilateral Filter)

双边滤波可以很好地减少不必要的噪声,同时保持边缘相当清晰。 但是,与大多数过滤器相比,它非常慢。

void cv::bilateralFilter(InputArray src,OutputArray dst,int d,double sigmaColor,double sigmaSpace,int borderType = BORDER_DEFAULT)

其中参数:

  • src:输入图像,单通道、多通道8位或单精度图像
  • dst:输出图像,类型大小与src相同
  • d:滤波期间使用的每个像素邻域的直径,如果为非正值,则根据sigmaSpace计算得出。
  • sigmaColor:颜色空间滤波的sigma值。参数的较大值表示像素邻域内的其他颜色将混合在一起,从而导致较大区域的半均等颜色。
  • sigmaSpace:空间坐标滤波的sigma值。该参数的值越大,意味着越远的像素就会相互影响,只要它们的颜色足够接近即可。当d> 0时,它指定邻域大小,而不考虑sigmaSpace。 否则,d与sigmaSpace成比例。
  • borderType:边界模式用于推断图像外部的像素。具体可参考BorderTypes

对于sigma值,可以将2个西格玛值设置为相同。 如果它们很小(<10),则滤镜效果不大;而如果它们很大(> 150),则滤镜效果会很强,使图像看起来“卡通化”。

对滤波窗口大小,d> 5时非常慢,因此建议对于实时应用程序使用d = 5,对于需要重噪声过滤的离线应用程序建议使用d = 9。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int sigmaColor = 3;
int sigmaSpace = 3;
int kernelSize = 3;

void applyFilter();
void onSigmaColorChanged(int pos,void* userData);
void onSigmaSpaceChanged(int pos,void* userData);
void onKernelSizeChanged(int pos,void* userData);

cv::Mat image,dst;

void applyFilter(){
    double sc = (double)sigmaColor / 10.0;
    double sp = (double)sigmaSpace / 10.0;
    int d = (int)kernelSize / 10;
    cout << "sigmaColor = " << sc << ",sigmaSpace = " << sp << ",d = " << d << endl;
    cv::bilateralFilter(image,dst,d,sc,sp);
    cv::imshow("dst",dst);
}

void onSigmaColorChanged(int pos,void* userData){
    applyFilter();
}

void onSigmaSpaceChanged(int pos,void* userData){
    applyFilter();
}

void onKernelSizeChanged(int pos,void* userData){
    kernelSize = 2 * kernelSize + 1;
    applyFilter();
}

using namespace std;

int main()
{
    // 读取图片
    image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot read image from file\n";
        exit(0);
    }

    // 创建窗口
    cv::namedWindow("image");

    // 创建TraceBar
    cv::createTrackbar("Sigma Color","image",&sigmaColor,255,onSigmaColorChanged);
    cv::createTrackbar("Sigma Space","image",&sigmaSpace,255,onSigmaSpaceChanged);
    cv::createTrackbar("Kernel Size","image",&kernelSize,255,onKernelSizeChanged);

    cv::imshow("image",image);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

程序结果结果

在这里插入图片描述

2、cv::boxFilter、cv::blur、cv::sqrBoxFilter


1)cv::boxFilter:使用方框滤波(Box Filter)模糊图像。

void cv::boxFilter(InputArray src,OutputArray dst,int ddepth,Size ksize,Point anchor = Point(-1,-1),bool normalize = true,int borderType = BORDER_DEFAULT)

该函数按以下方式模糊图像:

K = α [ 1 1 1 ⋯ 1 1 1 1 1 ⋯ 1 1 ⋯ 1 1 1 ⋯ 1 1 ] \texttt{K} = \alpha \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \cdots \\ 1 & 1 & 1 & \cdots & 1 & 1 \end{bmatrix} K=α111111111111111

其中, α = { 1 ksize.width*ksize.height when   normalize=true 1 otherwise \alpha = \begin{cases} \frac{1}{\texttt{ksize.width*ksize.height}} & \texttt{when } \texttt{normalize=true} \\1 & \texttt{otherwise}\end{cases} α={ ksize.width*ksize.height11when normalize=trueotherwise

未归一化的框滤波器可用于计算每个像素邻域上的各种积分特性,例如图像导数的协方差矩阵(用于密集光流算法等)。 如果需要计算可变大小窗口上的像素总和,请使用积分。

参数描述如下:

  • src:输入图像。
  • dst:输出图像,与src具有相同的类型和大小
  • ddepth:输出图像深度,当值为-1时,默认与src.depth()相同。
  • ksize:模糊窗口大小
  • anchor:窗口锚点位置,默认为(-1,-1),表示窗口中心
  • normalize:是否归一化。
  • borderType:用于推断图像外部像素的边框模式,请参见BorderTypes。 不支持BORDER_WRAP。

2)cv::blur:使用归一化框滤镜模糊图像。

void cv::blur(InputArray src,OutputArray dst,Size ksize,Point anchor = Point(-1,-1),int borderType = BORDER_DEFAULT)

该函数使用如下内核对图像进行平滑处理:

K = 1 ksize.width*ksize.height [ 1 1 1 ⋯ 1 1 1 1 1 ⋯ 1 1 ⋯ 1 1 1 ⋯ 1 1 ] \texttt{K} = \frac{1}{\texttt{ksize.width*ksize.height}} \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \cdots \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \end{bmatrix} K=ksize.width*ksize.height1111111111111111

调用blur(src,dst,ksize,anchor,borderType)等效于boxFilter(src,dst,src.type(),ksize,anchor,true,borderType)。

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

using namespace std;

int kernelSize = 3;

void applyFilter();
void onKernelSizeChanged(int pos,void* userData);

cv::Mat image,dstBoxFilter,dstBlur;

void applyFilter(){

    // 方框滤波,默认归一化处理
    cv::boxFilter(image,dstBoxFilter,-1,cv::Size(kernelSize,kernelSize));
    // 图像模糊
    cv::blur(image,dstBlur,cv::Size(kernelSize,kernelSize));
    cv::imshow("boxFilter",dstBoxFilter);
    cv::imshow("blur",dstBlur);
}


void onKernelSizeChanged(int pos,void* userData){
    kernelSize = 2 * kernelSize + 1;
    applyFilter();
}

using namespace std;

int main()
{
    // 读取图片
    image = cv::imread("resources/images/f1.jpg");
    if(image.empty()){
        cerr << "cannot read image from file\n";
        exit(0);
    }

    // 创建窗口
    cv::namedWindow("image");

    // 创建TraceBar
    cv::createTrackbar("Kernel Size","image",&kernelSize,15,onKernelSizeChanged);

    cv::imshow("image",image);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

3)cv::sqrBoxFilter:计算与滤波器重叠的像素值的平方和。其效果与boxFilter区别不大。

void cv::sqrBoxFilter(InputArray src,OutputArray dst,int ddepth,Size ksize,Point anchor = Point(-1, -1),bool normalize = true,int borderType = BORDER_DEFAULT)

对于源图像中的每个像素(x,y),该函数都会计算与放置在像素(x,y)上的滤波器重叠的那些相邻像素值的平方和。

未归一化的方框滤波器(Box Filter)可用于计算局部图像统计信息,例如像素附近的局部方差和标准偏差。

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

using namespace std;

int kernelSize = 3;

void applyFilter();
void onKernelSizeChanged(int pos,void* userData);

cv::Mat image,dstBoxFilter,dstBlur;

void applyFilter(){

    // sqrBox滤波
    cv::sqrBoxFilter(image,dstBoxFilter,-1,cv::Size(kernelSize,kernelSize));

    // 转换为8UC3类型
    cv::convertScaleAbs(dstBoxFilter,dstBoxFilter);
    cv::imshow("image",dstBoxFilter);
}


void onKernelSizeChanged(int pos,void* userData){
    kernelSize = 2 * kernelSize + 1;
    applyFilter();
}

using namespace std;

int main()
{
    // 读取图片
    image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot read image from file\n";
        exit(0);
    }

    // 创建窗口
    cv::namedWindow("image");

    // 创建TraceBar
    cv::createTrackbar("Kernel Size","image",&kernelSize,15,onKernelSizeChanged);

    cv::imshow("image",image);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

3、cv::buildPyramid、pyDown、pyUp


1)cv::buildPyramid:用于构建图像高斯金字塔。

void cv::buildPyramid(InputArray src,OutputArrayOfArrays dst,int maxlevel,int borderType = BORDER_DEFAULT)

该函数从dst [0] == src开始,将pyrDown递归地应用于先前构建的金字塔层,从而构造图像矢量并构建高斯金字塔。

关于高斯金字塔的详解,可以参考维基百科

参数详解如下:

  • src:输入图像。
  • dst:输出结果,与src类型相同的maxlevel + 1个图像的目标向量。 dst [0]将与src相同。 dst [1]是下一个金字塔层,是经过平滑和缩小的src,依此类推。
  • maxlevel:最后(最小)金字塔层的从0开始的索引。 它必须是非负的。
  • borderType:像素外推方法,请参见BorderTypes(不支持BORDER_CONSTANT)。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    // 读取图像
    cv::Mat image = cv::imread("resources/images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        exit(0);
    }

    // 构建高斯金字塔
    std::vector<cv::Mat> dst;
    int maxLevel = 4;
    cv::buildPyramid(image,dst,maxLevel);

    // 显示不同层次的高斯金字塔
    for(int i = 0;i < maxLevel + 1;i++){
        stringstream title;
        title << "pyramid:" << i;
        std::string winName = title.str();

        cv::imshow(winName,dst[i]);
    }
    cv::imshow("image-src",image);
    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdwNqsrA-1609749446031)(images/04-1-3.png)]

2)、cv::pyrDown:模糊图像并对其进行下采样。

void cv::pyrDown(InputArray src,OutputArray dst,const Size & dstsize = Size(),int borderType = BORDER_DEFAULT)

默认情况下,输出图像的大小计算为Size((src.cols + 1)/ 2,(src.rows + 1)/ 2),但是在任何情况下,都应满足以下条件:

∣ dstsize.width ∗ 2 − s r c . c o l s ∣ ≤ 2 ∣ dstsize.height ∗ 2 − s r c . r o w s ∣ ≤ 2 \begin{array}{l} | \texttt{dstsize.width} *2-src.cols| \leq 2 \\ | \texttt{dstsize.height} *2-src.rows| \leq 2 \end{array} dstsize.width2src.cols2dstsize.height2src.rows2

该函数执行高斯金字塔构造的下采样步骤。 首先,它将源映像与内核进行卷积:

1 256 [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ] \frac{1}{256} \begin{bmatrix} 1 & 4 & 6 & 4 & 1 \\ 4 & 16 & 24 & 16 & 4 \\ 6 & 24 & 36 & 24 & 6 \\ 4 & 16 & 24 & 16 & 4 \\ 1 & 4 & 6 & 4 & 1 \end{bmatrix} 25611464141624164624362464162416414641

然后,它通过拒绝偶数行和列对图像进行下采样。

参数说明:

  • src:输入图像
  • dst:输出图像; 它具有指定的大小,并且与src类型相同。
  • dstsize:输出图像大小。
  • borderType:像素外推方法,请参见BorderTypes(不支持BORDER_CONSTANT)

3)、cv::pyUp:上采样图像,然后使其模糊。

void cv::pyrUp(InputArray src,OutputArray dst,const Size & dstsize = Size(),int borderType = BORDER_DEFAULT)

默认情况下,输出图像的大小计算为Size(src.cols \ * 2,(src.rows \ * 2),但是在任何情况下,都应满足以下条件:

∣ dstsize.width − s r c . c o l s ∗ 2 ∣ ≤ ( dstsize.width m o d    2 ) ∣ dstsize.height − s r c . r o w s ∗ 2 ∣ ≤ ( dstsize.height m o d    2 ) \begin{array}{l} | \texttt{dstsize.width} -src.cols*2| \leq ( \texttt{dstsize.width} \mod 2) \\ | \texttt{dstsize.height} -src.rows*2| \leq ( \texttt{dstsize.height} \mod 2) \end{array} dstsize.widthsrc.cols2(dstsize.widthmod2)dstsize.heightsrc.rows2(dstsize.heightmod2)

该函数执行高斯金字塔构造的升采样步骤,尽管它实际上可以用于构造拉普拉斯金字塔。 首先,它甚至通过注入零行和零列来对源图像进行升采样,然后将结果与与pyrDown中相同的内核乘以4进行卷积。

参数描述:

  • src:输入图像
  • dst:输出图像; 它具有指定的大小,并且与src类型相同。
  • dstsize:输出图像大小。
  • borderType:像素外推方法,请参见BorderTypes(不支持BORDER_CONSTANT)
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    // 读取图像
    cv::Mat image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }

    cv::Mat dst;
    char key = -1;

    while (true) {
        cv::imshow("src",image);

        key = cv::waitKey(10);
        if(key == 27){
            break;
        }
        if(key == 'i'){
            // 向上采样
            cv::pyrUp(image,dst,cv::Size(image.cols*2,image.rows*2));
            cv::imshow("dst",dst);
        }
        if(key == 'o'){
            // 向下采样
           cv::pyrDown(image,dst,cv::Size(image.cols / 2,image.rows / 2));
           cv::imshow("dst",dst);
        }

    }
    cv::destroyAllWindows();
    return 0;
}

4、cv::getStructuringElement、cv::dilate、cv::erode、cv::morphologyEx


1)cv::getStructuringElement:返回指定大小和形状的结构元素以进行形态学操作。

Mat cv::getStructuringElement(int shape,Size ksize,Point anchor = Point(-1,-1))

该函数构造并返回可进一步传递给erode、dilate或morphologyEx的构造元素。 但是您也可以自己构造一个任意的二进制掩码,并将其用作构造元素。

参数如下:

  • shape:结构的形状,由MorphShapes指定,支持MORPH_RECT 、MORPH_CROSS 、MORPH_ELLIPSE 类型。

  • ksize:形状大小

  • anchor:元素内的锚位置。 默认值(-1,-1)表示锚点位于中心。 注意,只有十字形元件的形状取决于锚位置。 在其他情况下,锚点仅调节形态运算结果偏移了多少。

// 创建矩形结构
cv::Mat rect = cv::getStructuringElement(cv::MorphShapes::MORPH_RECT,cv::Size(128,128));
// 创建十字结构
cv::Mat cross = cv::getStructuringElement(cv::MorphShapes::MORPH_CROSS,cv::Size(128,128));
// 创建椭圆结构
cv::Mat eclipse = cv::getStructuringElement(cv::MorphShapes::MORPH_ELLIPSE,cv::Size(128,128));

在这里插入图片描述

2)cv::dilate:通过使用特定的结构元素来膨胀图像。

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

该函数使用指定的结构化元素来膨胀源图像,该结构化元素确定在其上获取最大值的像素邻域的形状:

dst ( x , y ) = max ⁡ ( x ′ , y ′ ) :   element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \max_{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=max(x,y):element(x,y)=0src(x+x,y+y)

该函数支持in-place模式。 可以进行几次(迭代)膨胀。 在多通道图像的情况下,每个通道都是独立处理的。参数如下:

  • src:输入图像; 通道数可以是任意的,但深度应为CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。
  • dst:输出图像;与src具有相同大小和类型的图像。
  • kernel:用于膨胀的结构元素; 如果elemenat = Mat(),则使用3 x 3的矩形结构元素。 可以使用getStructuringElement创建结构元素。
  • anchor:锚在元素内的位置; 默认值(-1,-1)表示锚点位于元素中心。
  • iterations:迭代次数
  • borderType:像素外推法,可以参考BorderTypes。 不支持BORDER WRAP。
  • borderValue:边界类型常量时的边界值。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int kerneSize = 3,iterations = 1;
cv::Mat image,dst,element;
int elemType = cv::MorphShapes::MORPH_RECT;

// 对图像进行膨胀操作
void apply(){
    int ksize = kerneSize * 2 + 1;
    element = cv::getStructuringElement(elemType,cv::Size(ksize,ksize));
    cv::dilate(image,dst,element,cv::Point(-1,-1),iterations);
    cv::imshow("image",dst);
}

void onKernelSizeChanged(int pos,void* userData){
    apply();
}

void onIterationChanged(int pos,void* userData){
    apply();
}

int main()
{
    // 读取图像
    image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }

    // 创建窗口与TraceBar
    cv::namedWindow("image");
    cv::createTrackbar("kerenel:2n+1","image",&kerneSize,16,onKernelSizeChanged);
    cv::createTrackbar("iterations","image",&iterations,100,onIterationChanged);
    cv::setTrackbarMin("iterations","image",1);

    cv::imshow("image",image);
    char key = 0;
    while(true){

        key = cv::waitKey(10);

        // 切换不同形状
        if(key == 'r'){
            elemType = cv::MorphShapes::MORPH_ELLIPSE;
            apply();
        }
        if(key == 'c'){
            elemType = cv::MorphShapes::MORPH_CROSS;
             apply();
        }
        if(key == 'e'){
            elemType = cv::MorphShapes::MORPH_ELLIPSE;
             apply();
        }
        if(key == 27){
            break;
        }
    }

    return 0;
}

在这里插入图片描述

3)cv::erode:通过使用特定的结构元素来腐蚀图像。

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

该函数使用指定的结构化元素腐蚀源图像,该结构化元素确定在其上采用最小值的像素邻域的形状:

dst ( x , y ) = min ⁡ ( x ′ , y ′ ) :   element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=min(x,y):element(x,y)=0src(x+x,y+y)

该函数支持in-place模式。 可以进行几次(迭代)腐蚀。 在多通道图像的情况下,每个通道都是独立处理的。参数如下:

  • src:输入图像; 通道数可以是任意的,但深度应为CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。
  • dst:输出图像;与src具有相同大小和类型的图像。
  • kernel:用于腐蚀的结构元素; 如果elemenat = Mat(),则使用3 x 3的矩形结构元素。 可以使用getStructuringElement创建结构元素。
  • anchor:锚在元素内的位置; 默认值(-1,-1)表示锚点位于元素中心。
  • iterations:迭代次数
  • borderType:像素外推法,可以参考BorderTypes。 不支持BORDER WRAP。
  • borderValue:边界类型常量时的边界值。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int kerneSize = 3,iterations = 1;
cv::Mat image,dst,element;
int elemType = cv::MorphShapes::MORPH_RECT;

// 对图像进行腐蚀操作
void apply(){
    int ksize = kerneSize * 2 + 1;
    element = cv::getStructuringElement(elemType,cv::Size(ksize,ksize));
    cv::erode(image,dst,element,cv::Point(-1,-1),iterations);
    cv::imshow("image",dst);
}

void onKernelSizeChanged(int pos,void* userData){
    apply();
}

void onIterationChanged(int pos,void* userData){
    apply();
}

int main()
{
    // 读取图像
    image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }

    // 创建窗口与TraceBar
    cv::namedWindow("image");
    cv::createTrackbar("kerenel:2n+1","image",&kerneSize,16,onKernelSizeChanged);
    cv::createTrackbar("iterations","image",&iterations,100,onIterationChanged);
    cv::setTrackbarMin("iterations","image",1);

    cv::imshow("image",image);
    char key = 0;
    while(true){

        key = cv::waitKey(10);

        // 切换不同形状
        if(key == 'r'){
            elemType = cv::MorphShapes::MORPH_ELLIPSE;
             apply();
        }
        if(key == 'c'){
            elemType = cv::MorphShapes::MORPH_CROSS;
             apply();
        }
        if(key == 'e'){
            elemType = cv::MorphShapes::MORPH_ELLIPSE;
             apply();
        }
        if(key == 27){
            break;
        }
    }

    return 0;
}

在这里插入图片描述

4)cv::morphologyEx:执行高级形态学变换。

void cv::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())

函数cv :: morphologyEx可以使用侵蚀和膨胀作为基本操作来执行高级形态转换。

任何操作都可以就地(in-place)完成。 在多通道图像的情况下,每个通道都是独立处理的。参数如下:

  • src:输入图像; 通道数可以是任意的,但深度应为CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。

  • dst:输出图像;与src具有相同大小和类型的图像。

  • op:形态学操作的类型。MorphTypes支持的类型如下:

    **MORPH_ERODE:**腐蚀操作,与cv::erode相同
    **MORPH_DILATE:**膨胀操作,与cv::dilate相同
    **MORPH_OPEN:**形态学开操作。 dst = o p e n ( src , element ) = d i l a t e ( e r o d e ( src , element ) ) \texttt{dst} = \mathrm{open} ( \texttt{src} , \texttt{element} )= \mathrm{dilate} ( \mathrm{erode} ( \texttt{src} , \texttt{element} )) dst=open(src,element)=dilate(erode(src,element))
    **MORPH_CLOSE:**形态学闭操作。 dst = c l o s e ( src , element ) = e r o d e ( d i l a t e ( src , element ) ) \texttt{dst} = \mathrm{close} ( \texttt{src} , \texttt{element} )= \mathrm{erode} ( \mathrm{dilate} ( \texttt{src} , \texttt{element} )) dst=close(src,element)=erode(dilate(src,element))
    **MORPH_GRADIENT:**形态学梯度操作。 dst = m o r p h _ g r a d ( src , element ) = d i l a t e ( src , element ) − e r o d e ( src , element ) \texttt{dst} = \mathrm{morph\_grad} ( \texttt{src} , \texttt{element} )= \mathrm{dilate} ( \texttt{src} , \texttt{element} )- \mathrm{erode} ( \texttt{src} , \texttt{element} ) dst=morph_grad(src,element)=dilate(src,element)erode(src,element)
    **MORPH_TOPHAT:**形态学顶帽操作。 dst = t o p h a t ( src , element ) = src − o p e n ( src , element ) \texttt{dst} = \mathrm{tophat} ( \texttt{src} , \texttt{element} )= \texttt{src} - \mathrm{open} ( \texttt{src} , \texttt{element} ) dst=tophat(src,element)=srcopen(src,element)
    **MORPH_BLACKHAT:**形态学黑帽操作。 dst = b l a c k h a t ( src , element ) = c l o s e ( src , element ) − src \texttt{dst} = \mathrm{blackhat} ( \texttt{src} , \texttt{element} )= \mathrm{close} ( \texttt{src} , \texttt{element} )- \texttt{src} dst=blackhat(src,element)=close(src,element)src
    **MORPH_HITMISS:**形态学击中与不击中操作。只对CV_8UC1图像有效

  • kernel:用于腐蚀的结构元素; 如果elemenat = Mat(),则使用3 x 3的矩形结构元素。 可以使用getStructuringElement创建结构元素。

  • anchor:锚在元素内的位置; 默认值(-1,-1)表示锚点位于元素中心。

  • iterations:迭代次数

  • borderType:像素外推法,可以参考BorderTypes。 不支持BORDER WRAP。

  • borderValue:边界类型常量时的边界值。

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

using namespace std;

int kerneSize = 3,iterations = 1;
cv::Mat image,dst,element;
int elemType = cv::MorphShapes::MORPH_RECT;
int operationType = cv::MORPH_ERODE;

// 对图像进行腐蚀操作
void apply(){
    int ksize = kerneSize * 2 + 1;
    element = cv::getStructuringElement(elemType,cv::Size(ksize,ksize));
    cv::morphologyEx(image,dst,operationType,element,cv::Point(-1,-1),iterations);
    cv::imshow("image",dst);
}

void onKernelSizeChanged(int pos,void* userData){
    apply();
}

void onIterationChanged(int pos,void* userData){
    apply();
}

int main()
{
    // 读取图像
    image = cv::imread("images/f1.jpg",0);
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }

    // 创建窗口与TraceBar
    cv::namedWindow("image");
    cv::createTrackbar("kerenel:2n+1","image",&kerneSize,16,onKernelSizeChanged);
    cv::createTrackbar("iterations","image",&iterations,100,onIterationChanged);
    cv::setTrackbarMin("iterations","image",1);

    cv::imshow("image",image);
    char key = 0;
    while(true){

        key = cv::waitKey(10);

        // 切换不同形状
        if(key == 'r'){
            elemType = cv::MorphShapes::MORPH_ELLIPSE;
            apply();
        }
        if(key == 'c'){
            elemType = cv::MorphShapes::MORPH_CROSS;
            apply();
        }
        if(key == 'e'){
            elemType = cv::MorphShapes::MORPH_ELLIPSE;
            apply();
        }
        if(key == 27){
            break;
        }

        if(key == '1'){
            operationType = cv::MORPH_ERODE;
            apply();
        }
        if(key == '2'){
            operationType = cv::MORPH_DILATE;
            apply();
        }
        if(key == '3'){
            operationType = cv::MORPH_OPEN;
            apply();
        }
        if(key == '4'){
            operationType = cv::MORPH_CLOSE;
            apply();
        }
        if(key == '5'){
            operationType = cv::MORPH_TOPHAT;
            apply();
        }
        if(key == '6'){
            operationType = cv::MORPH_BLACKHAT;
            apply();
        }
        if(key == '7'){
            operationType = cv::MORPH_ERODE;
            apply();
        }
        if(key == '8'){
            operationType = cv::MORPH_HITMISS;
            apply();
        }
    }

    return 0;
}

在这里插入图片描述

5、cv::filter2D、cv::sepFilter2D


1)cv::filter2D:将图像与内核卷积。

void cv::filter2D(InputArray src,OutputArray dst,int ddepth,InputArray kernel,Point anchor = Point(-1,-1),double delta = 0,int borderType = BORDER_DEFAULT)

该函数将任意线性滤波器应用于图像。 支持就地操作。 当窗口部分位于图像外部时,该函数会根据指定的边框模式对异常像素值进行插值。

该函数实际上计算相关性,而不是卷积:

KaTeX parse error: Undefined control sequence: \substack at position 30: …x,y) = \sum _{ \̲s̲u̲b̲s̲t̲a̲c̲k̲{0\leq x' < \te…

也就是说,内核不会围绕定位点进行镜像。 如果需要真正的卷积,请使用flip翻转内核,并将新锚点设置为(kernel.cols-anchor.x-1,kernel.rows-anchor.y-1)。

在内核足够大(〜11 x 11或更大)的情况下,该函数使用基于DFT的算法,而对于小内核则使用直接算法。

参数如下:

  • src:输入图像。
  • dst:输出与src具有相同大小和相同通道数的图像。
  • ddepth:目标图像的所需深度。
  • kernel:卷积核(或相关核),单通道浮点矩阵; 如果要将不同的内核应用于不同的通道,请使用split将图像分为单独的色彩平面,然后分别进行处理。
  • anchor:内核的锚点,指示内核中已过滤点的相对位置; 锚点应位于内核内; 默认值(-1,-1)表示锚位于内核中心。
  • delta:在将像素存储到dst之前将其添加到过滤后的像素的可选值,默认值为0。
  • borderType:像素外推方法,请参见BorderTypes。 不支持BORDER_WRAP。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    // 读取图像
    cv::Mat image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot read image from file.\n";
        return EXIT_FAILURE;
    }
    cv::namedWindow("image", cv::WINDOW_AUTOSIZE);
    cv::imshow("image", image);

    // 创建卷积内核
    cv::Mat kernel = (cv::Mat_<char>(3,3) << 0, -1 ,0,
                                   -1, 5, -1,
                                   0, -1, 0);
    // 执行卷积操作
    cv::Mat dst;
    cv::filter2D(image,dst,-1,kernel);
    imshow("filter2d",dst);

    cv::waitKey(0);
    cv::destroyAllWindows();
    return 0;
}

在这里插入图片描述

常见操作的内核有:

  • 模糊: [ 0.0625 0.125 0.0625 0.125 0.25 0.125 0.0625 0.125 0.125 ] \begin{bmatrix} 0.0625&0.125&0.0625\\ 0.125&0.25&0.125\\0.0625&0.125 &0.125\end{bmatrix} 0.06250.1250.06250.1250.250.1250.06250.1250.125
  • Sobel: [ − 1 − 2 − 1 0 0 0 1 2 1 ] \begin{bmatrix}-1&-2&-1\\0&0&0\\1&2&1 \end{bmatrix} 101202101
  • 边缘: [ − 1 − 1 − 1 − 1 8 − 1 − 1 − 1 − 1 ] \begin{bmatrix} -1&-1&-1\\-1&8&-1\\-1&-1&-1\end{bmatrix} 111181111
  • 锐化: [ 0 − 1 0 − 1 5 − 1 0 − 1 0 ] \begin{bmatrix} 0&-1&0\\-1&5&-1\\0&-1&0\end{bmatrix} 010151010
  • 浮雕: [ − 2 − 1 0 − 1 1 1 0 1 2 ] \begin{bmatrix} -2&-1&0\\-1&1&1\\0&1&2\end{bmatrix} 210111012
  • 拉普拉斯算子: [ 0 1 0 1 − 4 1 0 1 0 ] \begin{bmatrix}0&1&0\\1&-4&1\\0&1&0 \end{bmatrix} 010141010

2)cv::sepFilter2D:对图像应用可分离的线性滤波器。

void cv::sepFilter2D(InputArray src,OutputArray dst,int ddepth,InputArray kernelX,InputArray kernelY,Point anchor = Point(-1,-1),double delta = 0,int borderType = BORDER_DEFAULT)

该功能将可分离的线性滤波器应用于图像。 也就是说,首先,使用1D内核kernelX过滤src的每一行。 然后,结果的每一列都使用一维内核kernelY进行过滤。 偏移了delta的最终结果存储在dst中。

参数如下:

  • src:输入图像
  • dst:输出图像,大小与src相同
  • ddepth:输出图像深度
  • kernelX:用于过滤每一行的系数。
  • kernelY:用于过滤每一列的系数。
  • anchor:内核中的锚点位置。 默认值(-1,-1)表示锚位于内核中心。
  • delta:在存储结果之前,将值添加到过滤结果中。
  • borderType:像素外推方法,请参见BorderTypes。 不支持BORDER_WRAP。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    // 读取图像
    cv::Mat image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }
    cv::Mat kx = (cv::Mat_<float>(1, 3) << 0, -1, 0);
    cv::Mat ky = (cv::Mat_<float>(1, 3) << -1, 0, -1);

    // 执行滤波
    cv::Mat dst,imageF;
    image.convertTo(imageF,CV_32FC3);
    cv::sepFilter2D(imageF, dst, -1, kx, ky);
    cv::convertScaleAbs(dst,dst);
    cv::imshow("src",image);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}

在这里插入图片描述

6、cv::GaussianBlur、cv::getGaussianKernel


1)cv::GaussianBlur:对图像进行高斯模糊处理。

void cv::GaussianBlur(InputArray src,OutputArray dst,Size ksize,double sigmaX,double sigmaY = 0,int borderType = BORDER_DEFAULT)

该函数将源图像与指定的高斯内核进行卷积。支持就地过滤。

参数描述如下:

  • src:输入图像; 图像可以具有任意数量的通道,这些通道可以独立处理,但深度应为CV_8U,CV_16U,CV_16S,CV_32F或CV_64F。
  • dst:输出与src具有相同大小和类型的图像。
  • ksize:高斯核大小。 ksize.width和ksize.height可以不同,但它们都必须为正数和奇数。 或者,它们可以为零,然后根据sigma计算得出。
  • sigmaX:X方向上的高斯核标准偏差。
  • sigmaY:Y方向的高斯核标准差; 如果sigmaY为零,则将其设置为等于sigmaX;如果两个sigmas为零,则分别从ksize.width和ksize.height计算得出(有关详细信息,请参见getGaussianKernel); 为了完全控制结果,而不考虑将来可能对所有这些语义的修改,建议指定所有ksize,sigmaX和sigmaY。
  • borderType:像素外推方法,请参见BorderTypes。 不支持BORDER_WRAP。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int kerneSize = 3,sigmaX = 0,sigmaY = 0;
cv::Mat image,dst,element;
int elemType = cv::MorphShapes::MORPH_RECT;

// 对图像进行高斯模糊操作
void apply(){
    int ksize = kerneSize * 2 + 1;
    double sgx = sigmaX / 10.0;
    double sgy = sigmaY / 10.0;
    cv::GaussianBlur(image,dst,cv::Size(ksize,ksize),sgx,sgy);
    cv::imshow("image",dst);
}

void onKernelSizeChanged(int pos,void* userData){
    apply();
}

void onSigmaXChanged(int pos,void* userData){
    apply();
}

void onSigmaYChanged(int pos,void* userData){
    apply();
}

int main()
{
    // 读取图像
    image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }

    // 创建窗口与TraceBar
    cv::namedWindow("image");
    cv::createTrackbar("kerenel:2n+1","image",&kerneSize,16,onKernelSizeChanged);
    cv::createTrackbar("sigmaX/10","image",&sigmaX,100,onSigmaXChanged);
    cv::createTrackbar("sigmaY/10","image",&sigmaY,100,onSigmaYChanged);

    cv::imshow("image",image);
    char key = 0;
    while(true){

        key = cv::waitKey(10);
        if(key == 27){
            break;
        }
    }

    return 0;
}

在这里插入图片描述

2)cv::getGaussianKernel:返回高斯滤波器系数。

Mat cv::getGaussianKernel(int ksize,double sigma,int ktype = CV_64F)

该函数计算并返回高斯滤波器系数的ksize×1矩阵:

G i = α ∗ e − ( i − ( ksize − 1 ) / 2 ) 2 / ( 2 ∗ sigma 2 ) , G_i= \alpha *e^{-(i-( \texttt{ksize} -1)/2)^2/(2* \texttt{sigma}^2)}, Gi=αe(i(ksize1)/2)2/(2sigma2),

其中, i = 0.. ksize − 1 i=0..\texttt{ksize}-1 i=0..ksize1 α \alpha α是选择的比例因子,因此 ∑ i G i = 1 \sum_i G_i=1 iGi=1

可以将其中两个这样生成的内核传递给sepFilter2D。 这些函数自动识别平滑核(权重之和等于1的对称核)并相应地进行处理。 也可以使用更高级别的GaussianBlur。

#include <iostream>
#include <opencv2/opencv.hpp>
#include <cmath>
#include <numeric>
using namespace std;

// 生成2D高斯内核
cv::Mat getGaussianKernel2D(int rows, int cols, double sigmax, double sigmay)
{
    auto gauss_x = cv::getGaussianKernel(cols, sigmax, CV_32F);
    auto gauss_y = cv::getGaussianKernel(rows, sigmay, CV_32F);
    return gauss_x * gauss_y.t();
}

int main()
{
    // 读取图像
    cv::Mat image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }
    // 生成一维高斯内核
    cv::Mat kernel = cv::getGaussianKernel(3,2.5);
    cout << "kernel = " << kernel << endl;
    cv::Mat kernel2D = getGaussianKernel2D(7,7,2.5,2.5);
    cout << "kernel2D = " << kernel2D << endl;

    // 对高斯内核进行卷积
    cv::Mat dst;
    cv::filter2D(image,dst,-1,kernel2D);
    cv::Mat dstGaussian;

    // 相同参数的高斯模糊
    cv::GaussianBlur(image,dstGaussian,cv::Size(7,7),2.5,2.5);

    // 显示图像
    cv::imshow("image",image);
    cv::imshow("dstFilter2D",dst);
    cv::imshow("dstGaussian",dstGaussian);
    cv::waitKey(0);
    cv::destroyAllWindows();
    return 0;
}

在这里插入图片描述

7、cv::getGaborKernel


返回Gabor滤波器系数。

*Mat cv::getGaborKernel(Size ksize,double sigma,double theta,double lambd,double gamma,double psi = CV_PI 0.5int ktype = CV_64F)

有关gabor滤波器方程式和参数的更多详细信息,请参见:Gabor Filter.

参数如下:

参数名称 说明
ksize 返回的过滤器的大小
sigma 高斯包络线的标准偏差。
theta 法线与Gabor函数的平行条纹的方向。
lambd 正弦因子的波长。
gamma 空间纵横比。
psi 相位偏移。
ktype 滤波器系数的类型。 它可以是CV_32F或CV_64F。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
const string WIN_NAME = "gabor filter";
cv::Mat image,dst,kernel;
int kernelSize = 3;
int sigmaI = 0;
int thetaI = 0;
int lambdI = 0;
int gammaI = 0;

void apply(){
    int ksize = kernelSize * 2 + 1;
    double sigma = sigmaI / 10.0;
    double theta = thetaI / 10.0;
    double lambd = lambdI / 10.0;
    double gamma = gammaI / 10.0;

    // 获取Gabor滤波内核
    kernel = cv::getGaborKernel(cv::Size(ksize,ksize),sigma,theta,lambd,gamma);
    // 执行滤波
    cv::filter2D(image,dst,-1,kernel);
    cv::imshow(WIN_NAME,dst);

}

void onKernelSizeChanged(int pos,void* userData){
    apply();
}

void onSigmaChanged(int pos,void* userData){
    apply();
}

void onThetaChanged(int pos,void* userData){
    apply();
}

void onLambdChanged(int pos,void* userData){
    apply();
}

void onGammaChanged(int pos,void* userData){
    apply();
}

int main()
{
    // 读取图像
    image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }

    // 创建窗口及TraceBar
    cv::namedWindow(WIN_NAME);
    cv::createTrackbar("kernelSize:2n+1",WIN_NAME,&kernelSize,16,onKernelSizeChanged);
    cv::createTrackbar("sigma/10",WIN_NAME,&sigmaI,100,onSigmaChanged);
    cv::createTrackbar("theta/10",WIN_NAME,&thetaI,100,onSigmaChanged);
    cv::createTrackbar("lambd/10",WIN_NAME,&lambdI,100,onLambdChanged);
    cv::createTrackbar("gamma/10",WIN_NAME,&gammaI,100,onGammaChanged);
    cv::imshow(WIN_NAME,image);

    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

8、cv::getDerivKernels、cv::Scharr、cv::Sobel、cv::spatialGradient


1)cv::getDerivKernels:返回用于计算空间图像导数的滤波器系数。

void cv::getDerivKernels(OutputArray kx,OutputArray ky,int dx,int dy,int ksize,bool normalize = false,int ktype = CV_32F)

该函数计算并返回空间图像导数的滤波器系数。 当ksize = FILTER_SCHARR时,将生成Scharr 3×3内核(请参见Scharr)。 否则,将生成Sobel内核(请参阅Sobel)。 过滤器通常传递给sepFilter2D。

参数如下:

参数名称 参数描述
kx 行滤波器系数的输出矩阵。 它的类型为ktype。
ky 列滤波器系数的输出矩阵。 它的类型为ktype。
dx 关于x的导数阶。
dy 关于y的导数阶。
ksize 光圈大小。 可以是FILTER_SCHARR,1、3、5或7。
normalize 指示是否规范化(按比例缩小)滤波器系数的标志。 从理论上讲,系数应具有分母= 2ksize * 2-dx-dy-2。 如果要过滤浮点图像,则可能使用归一化的内核。 但是,如果您计算8位图像的导数,将结果存储在16位图像中,并希望保留所有小数位,则可能需要设置normalize = false。
ktype 滤波器系数的类型。 它可以是CV_32f或CV_64F。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    // 读取图像
    cv::Mat image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }

    // 执行图像滤波
    cv::Mat kx, ky,dst;
    cv::getDerivKernels(kx,ky,2,2,7,true);
    cv::sepFilter2D(image,dst,-1,kx,ky);
    cout << dst;
    cv::imshow("image",image);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

2)cv::Scharr:使用Scharr运算符计算一阶x或y图像导数。

void cv::Scharr(InputArray src,OutputArray dst,int ddepth,int dx,int dy,double scale = 1,double delta = 0,int borderType = BORDER_DEFAULT)

该函数使用Scharr运算符计算第一x或y空间图像导数。

Scharr(src,   dst,   ddepth,   dx,   dy,   scale,   delta,   borderType) \texttt{Scharr(src, dst, ddepth, dx, dy, scale, delta, borderType)} Scharr(src, dst, ddepth, dx, dy, scale, delta, borderType)

与下面的调用相同:

KaTeX parse error: Expected '}', got '_' at position 47: … dx, dy, FILTER_̲SCHARR, scale, …

参数如下:

参数名称 参数描述
src 输入图像
dst 输出图像,与src的类型、大小相同
ddepth 输出图像的深度
dx 导数x的阶数。
dy 导数y的阶数。
scale 计算得出的导数值的可选比例因子; 默认情况下,不应用缩放(有关详细信息,请参见getDerivKernels
delta 在将结果存储到dst之前将其添加到结果中的可选增量值。
borderType 像素外推方法,请参见BorderTypes。 不支持BORDER_WRAP
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    // 读取灰度图像
    cv::Mat gray = cv::imread("images/f1.jpg",0);
    if(gray.empty()){
        cerr << "cannot open image from file.\n";
        return EXIT_FAILURE;
    }
    cv::Mat dx,dy;
    cv::Mat blurImage;
    // 模糊图像
    cv::blur(gray, blurImage,cv::Size(3,3));
    // 对图像进行Scharr求导
    Scharr(blurImage,dx,CV_16S,1,0);
    Scharr(blurImage,dy,CV_16S,0,1);

    // 显示图像
    cv::imshow("image",gray);
    cv::imshow("dx",dx);
    cv::imshow("dy",dy);
    cv::waitKey();
    return 0;
}

在这里插入图片描述

3)cv::Sobel:使用扩展的Sobel运算符计算第一,第二,第三或混合图像导数。

void cv::Sobel(InputArray src,OutputArray dst,int ddepth,int dx,int dy,int ksize = 3,double scale = 1,double delta = 0,int borderType = BORDER_DEFAULT)

除一种情况外,在所有情况下,均使用ksize×ksize可分离内核来计算导数。 当ksize = 1时,使用3×1或1×3内核(即,不进行高斯平滑)。 ksize = 1只能用于一阶或二阶x或y导数。

还有一个特殊值ksize = FILTER_SCHARR(-1)对应于3×3 Scharr滤波器,它可能比3×3 Sobel给出更准确的结果。Scharr算子为: [ − 3 0 3 − 10 0 10 − 3 0 3 ] \begin{bmatrix} -3&0&3\\-10&0&10\\-3&0&3\\ \end{bmatrix} 31030003103

对于x导数,或换位为y导数。

该函数通过将图像与适当的内核进行卷积来计算图像导数:

dst = ∂ x o r d e r + y o r d e r src ∂ x x o r d e r ∂ y y o r d e r \texttt{dst} = \frac{\partial^{xorder+yorder} \texttt{src}}{\partial x^{xorder} \partial y^{yorder}} dst=xxorderyyorderxorder+yordersrc

Sobel算子将高斯平滑和微分相结合,因此结果或多或少具有抗噪性。 通常,使用(xorder = 1,yorder = 0,ksize = 3)或(xorder = 0,yorder = 1,ksize = 3)调用函数以计算第一个x或y图像导数。

第一种情况对应于以下内核:

[ − 1 0 1 − 2 0 2 − 1 0 1 ] \begin{bmatrix}-1&0&1\\-2&0&2\\-1&0&1\\ \end{bmatrix} 121000121

第二种情况对应于以下内核:

[ − 1 − 2 − 1 0 0 0 1 2 1 ] \begin{bmatrix}-1&-2&-1\\0&0&0\\1&2&1 \end{bmatrix} 101202101

参数如下:

参数名称 参数描述
src 输入图像
dst 输出图像,与src的类型、大小相同
ddepth 输出图像的深度
dx 导数x的阶数。
dy 导数y的阶数。
ksize 扩展的Sobel内核的大小; 它必须是1、3、5或7。
scale 计算得出的导数值的可选比例因子; 默认情况下,不应用缩放(有关详细信息,请参见getDerivKernels
delta 在将结果存储到dst之前将其添加到结果中的可选增量值。
borderType 像素外推方法,请参见BorderTypes。 不支持BORDER_WRAP
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
  cv::CommandLineParser parser(argc, argv,
                               "{@input   |lena.jpg|input image}"
                               "{ksize   k|1|ksize (hit 'K' to increase its value at run time)}"
                               "{scale   s|1|scale (hit 'S' to increase its value at run time)}"
                               "{delta   d|0|delta (hit 'D' to increase its value at run time)}"
                               "{help    h|false|show help message}");
  cout << "The sample uses Sobel or Scharr OpenCV functions for edge detection\n\n";
  parser.printMessage();
  cout << "\nPress 'ESC' to exit program.\nPress 'R' to reset values ( ksize will be -1 equal to Scharr function )";
  // First we declare the variables we are going to use
  Mat image,src, src_gray;
  Mat grad;
  const String window_name = "Sobel Demo - Simple Edge Detector";
  int ksize = parser.get<int>("ksize");
  int scale = parser.get<int>("scale");
  int delta = parser.get<int>("delta");
  int ddepth = CV_16S;
  String imageName = parser.get<String>("@input");
  // As usual we load our source image (src)
  image = imread( samples::findFile( "d:/develop/machine-vision-workbench/resources/images/f1.jpg" ), IMREAD_COLOR ); // Load an image
  // Check if image is loaded fine
  if( image.empty() )
  {
    printf("Error opening image: %s\n", imageName.c_str());
    return EXIT_FAILURE;
  }
  for (;;)
  {
    // Remove noise by blurring with a Gaussian filter ( kernel size = 3 )
    GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
    // Convert the image to grayscale
    cvtColor(src, src_gray, COLOR_BGR2GRAY);
    Mat grad_x, grad_y;
    Mat abs_grad_x, abs_grad_y;
    Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT);
    Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT);
    // converting back to CV_8U
    convertScaleAbs(grad_x, abs_grad_x);
    convertScaleAbs(grad_y, abs_grad_y);
    addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
    imshow(window_name, grad);
    char key = (char)waitKey(0);
    if(key == 27)
    {
      return EXIT_SUCCESS;
    }
    if (key == 'k' || key == 'K')
    {
      ksize = ksize < 30 ? ksize+2 : -1;
    }
    if (key == 's' || key == 'S')
    {
      scale++;
    }
    if (key == 'd' || key == 'D')
    {
      delta++;
    }
    if (key == 'r' || key == 'R')
    {
      scale =  1;
      ksize = -1;
      delta =  0;
    }
  }
  return EXIT_SUCCESS;
}

在这里插入图片描述

4)、cv::spatialGradient:使用Sobel运算符计算x和y中的一阶图像导数。

void cv::spatialGradient(InputArray src,OutputArray dy,int ksize = 3,int borderType = BORDER_DEFAULT)

相当于调用Sobel函数:

Sobel( src, dx, CV_16SC1, 1, 0, 3 );
Sobel( src, dy, CV_16SC1, 0, 1, 3 );

示例如下:

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

using namespace std;

int main()
{
    // 读取灰度图像
    cv::Mat src = cv::imread("images/f1.jpg",0);
    if(src.empty()){
        cerr << "cannot read imgage from file.\n";
        return EXIT_FAILURE;
    }

    // 计算图像梯度
    cv::Mat dx,dy,abs_dx,abs_dy,dst;
    cv::spatialGradient(src,dx,dy);

    // 转换成8UC1
    convertScaleAbs(dx, abs_dx);
    convertScaleAbs(dy, abs_dy);
    addWeighted(abs_dx, 0.5, abs_dy, 0.5, 0, dst);

    cv::imshow("src",src);
    cv::imshow("dst",dst);

    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wujuxKkoolerter/article/details/112185494
今日推荐