OpenCV学习 day9 自定义线性滤波 图像边缘补偿 边缘算子

一、自定义滤波器

代码演示:

//Sboel算子
Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1 ,0, 1);  //X方向
Mat kernel_y = (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);  //Y方向
    
filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);

 

自定义高斯滤波

// 5*5大小的高斯核
Mat kernel = Mat::ones(Size(5, 5), CV_32F) / (float)(5* 5);
filter2D(src, dst, -1, kernel, Point(-1, -1));

 

 

二、边缘补偿

在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3x3在四周各填充1个像素的边缘,这样就确保图像的边缘被处理,在卷积处理之后再去掉这些边缘。

openCV中默认的处理方法是: BORDER_DEFAULT,

此外常用的还有如下几种:
- BORDER_CONSTANT – 填充边缘用指定像素值
- BORDER_REPLICATE – 填充边缘像素用已知的边缘像素值。
- BORDER_WRAP – 用另外一边的像素来补偿填充

 

API:

copyMakeBorder(
 - Mat src, // 输入图像
 - Mat dst, // 添加边缘图像
 - int top, // 边缘填充长度,一般上下左右都取相同值,
 - int bottom,
 - int left,
 - int right, 
 - int borderType // 边缘类型
 - Scalar value 
)

几种填充类型:

enum BorderTypes {
    BORDER_CONSTANT    = 0, //常值填充
    BORDER_REPLICATE   = 1, //原图边缘复制
    BORDER_REFLECT     = 2, //
    BORDER_WRAP        = 3, //原图的另一边复制到这边
    BORDER_REFLECT_101 = 4, //
    BORDER_TRANSPARENT = 5, //

    BORDER_REFLECT101  = BORDER_REFLECT_101, //
    BORDER_DEFAULT     = BORDER_REFLECT_101, //
    BORDER_ISOLATED    = 16 //
};

 

三、边缘算子

如下图所示,相邻像素之间发生较大改变时,它它们方向上的导数值也会很大

 

 

1,Sobel算子

是离散微分算子(discrete differentiation operator),用来计算图像灰度的近似梯度

Soble算子功能集合高斯平滑和微分求导

又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方向与Y方向梯度图像

 

opencv中使用改进版Scharr的算子如下:

 

 API:

cv::Sobel (
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度. 通常深度比输入图像要高 防止像素值被截断
int dx.  // X方向,几阶导数
int dy // Y方向,几阶导数. 
int ksize // SOBEL算子kernel大小,必须是1、3、5、7、
double scale  = 1
double delta = 0
int borderType = BORDER_DEFAULT
)

 

 Scharr:

cv::Scharr (
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度. 
int dx.  // X方向,几阶导数
int dy // Y方向,几阶导数. 
double scale  = 1
double delta = 0
int borderType = BORDER_DEFAULT
)

代码演示:

思路:

  • 高斯平滑
  • 转灰度
  • 求X、Y方向梯度
  • 梯度图融合
int sobel_boder(Mat src) {
    Mat blur_src, gray_src, dst;
    Mat xgrad, ygrad;
    GaussianBlur(src, blur_src, Size(3, 3), 0, 0);  //高斯模糊
    cvtColor(blur_src, gray_src, COLOR_BGR2GRAY);  //转灰度
    
    Sobel(gray_src, xgrad, CV_16S, 1, 0, 3); //X方向求梯度
    Sobel(gray_src, ygrad, CV_16S, 0, 1, 3); //Y方向求梯度
    //Scharr(gray_src, xgrad, CV_16S, 1, 0, 3); //Y方向求梯度
    //Scharr(gray_src, ygrad, CV_16S, 0, 1, 3); //Y方向求梯度

    convertScaleAbs(xgrad, xgrad);  //计算图像的像素绝对值,输出到图像B
    convertScaleAbs(ygrad, ygrad);
    
    imshow("x grad", xgrad);
    imshow("y grad", ygrad);

    addWeighted(xgrad, 0.5, ygrad, 0.5,0,dst);  // X Y方向梯度融合
    imshow("x y add result", dst);
    
    //标准的Sobel最后梯度计算
    Mat xygrad = Mat(xgrad.size(), xgrad.type());
    printf("type: %d\n", xgrad.type());
    for (int row = 0; row < xgrad.rows; row++) {
        for (int col = 0; col < xgrad.cols; col++) {
            int xg = xgrad.at<uchar>(row, col);
            int yg = ygrad.at<uchar>(row, col);
            int xyg = xg + yg;
            xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xyg);
        }
    }
    imshow("final image", xygrad);

    waitKey(0);
    return 0;
}

 

2,Laplance算子


二阶导数的时候,最大变化处的值为零即边缘是零值。通过二阶导数计算,依据此理论我们可以计算图像二阶导数,提取边缘。

 

 

 

x,y方向的二阶导数之和

 

代码演示:

思路:

  • 高斯平滑(模糊)
  • 转灰度
  • 拉普拉斯-二阶导数计算
  • 取绝对值
  • 后续对梯度图的处理
int Laplance_boder(Mat src) {
    
    Mat gray_src, blur_src, dst;
    GaussianBlur(src, blur_src, Size(3, 3), 0, 0);
    cvtColor(blur_src, gray_src, COLOR_BGR2GRAY);
    
    Laplacian(gray_src, dst, CV_16S, 3);
    convertScaleAbs(dst, dst); //取绝对值

    threshold(dst, dst, 0, 255, THRESH_OTSU | THRESH_BINARY); //通过阈值减少噪声 只能是灰度图

    namedWindow("laplance result", WINDOW_AUTOSIZE);
    imshow("laplance result", dst);
    
    waitKey(0);
    return 0;
}

 

 Canny边缘检测

Canny是边缘检测算法,在1986年提出的。

是一个很好的边缘检测器

很常用也很实用的图像处理方法

 

算法步骤:

 

  1. 首先,图像降噪。我们知道梯度算子可以用于增强图像,本质上是通过增强边缘轮廓来实现的,也就是说是可以检测到边缘的。但是,它们受噪声的影响都很大。那么,我们第一步就是想到要先去除噪声,因为噪声就是灰度变化很大的地方,所以容易被识别为伪边缘。
  2. 第二步,计算图像梯度,得到可能边缘。我们在前面的关于《图像梯度》文章中有所介绍,计算图像梯度能够得到图像的边缘,因为梯度是灰度变化明显的地方,而边缘也是灰度变化明显的地方。当然这一步只能得到可能的边缘。因为灰度变化的地方可能是边缘,也可能不是边缘。这一步就有了所有可能是边缘的集合。
  3. 第三步,非极大值抑制。通常灰度变化的地方都比较集中,将局部范围内的梯度方向上,灰度变化最大的保留下来,其它的不保留,这样可以剔除掉一大部分的点。将有多个像素宽的边缘变成一个单像素宽的边缘。即“胖边缘”变成“瘦边缘”。
  4. 第四步,双阈值筛选。通过非极大值抑制后,仍然有很多的可能边缘点,进一步的设置一个双阈值,即低阈值(low),高阈值(high)。灰度变化大于high的,设置为强边缘像素,低于low的,剔除。在low和high之间的设置为弱边缘。进一步判断,如果其领域内有强边缘像素,保留,如果没有,剔除。

具体地:

 

  • 高斯模糊 - GaussianBlur
  • 灰度转换 - cvtColor
  • 计算梯度 – Sobel/Scharr
  • 非最大信号抑制
  • 高低阈值输出二值图像

 

非最大信号抑制

 

θ是梯度的方向,0-180°之间,表示哪个方向上的变化率最大。找出梯度方向所对应的值,与其周围的值进比较,进行抑制,如下图所示。

 

 

 高低阈值输出:


T1, T2为阈值,凡是高于T2的都保留,凡是小于T1都丢弃。凡是大于T1而且与高于T2像素相互连接的,都保留。最终得到一个输出二值图像。
推荐的高低阈值比值为 T2: T1 = 3:1或者2:1 其中T2为高阈值,T1为低阈值

 

API:

CV::Canny

必须是八位的灰度图像

Canny(
InputArray src, // 8-bit的输入图像
OutputArray edges,// 输出边缘图像, 一般都是二值图像,背景是黑色
double threshold1,// 低阈值,常取高阈值的1/2或者1/3
double threshold2,// 高阈值
int aptertureSize,// Soble算子的size,通常3x3,取值3
bool L2gradient // 选择 true表示是L2来归一化,否则用L1归一化

代码演示:

void Canny_Demo(int pos, void* usrdata);
int Canny_boder(Mat src) {
    int t1_value = 50;
    int max_value = 255;
    namedWindow("Canny Result", WINDOW_AUTOSIZE);
    createTrackbar("Treshold Value", "Canny Result", &t1_value, max_value, Canny_Demo, &src);
    Canny_Demo(t1_value, &src);

    waitKey(0);
    return 0;
}

void Canny_Demo(int pos, void* usrdata) {
    
    Mat gray_src, edge_output, dst;
    Mat src = *(Mat*)(usrdata);  //强转Mat类型
    cvtColor(src, gray_src, COLOR_BGR2GRAY);
    blur(gray_src, gray_src, Size(3, 3), Point(-1,-1), BORDER_DEFAULT);
    Canny(gray_src, edge_output, pos, pos*2, 3, false);

    //dst = Scalar::all(0);
    dst.create(src.size(), src.type()); //初始化dst
    src.copyTo(dst, edge_output);  //将非0的像素值copy过去
    
    imshow("Canny Result", dst);
}

 

 

 

关于回调函数,可以参考例子:https://www.cnblogs.com/Fsiswo/p/8030649.html

 

posted @ 2020-05-24 15:43  xyfun72  阅读(471)  评论(0编辑  收藏  举报