OpenCV---006(图像滤波(2))

边缘检测

图像中的物体边缘含有重要的信息 提取图像边缘的信息对分析图像中的内容 实现图像分割 定位 有重要的作用
Opencv提供了几个用于边缘检测的函数 本章内容将介绍原理和函数

边缘检测原理

图像边缘是图像中的像素值发生突变的区域 如果将图像的每一行每一列都描述为一个函数 则图像边缘对应的就是
灰度函数中的函数中突然变大的区域 函数值的变化趋势可以用函数的导数来表示 我们可以通过寻找导数值较大的区域来
寻找函数中突然变化的区域 因为图像是离散的 所以可以用两个邻近的像素的差来表示导数

f(x,y)' = f(x,y) - f(x-1,y)
同时在y轴上进行修正得到y的 其x过滤器应该为[-1 1] y[-1 1]^T

但是由于上式子的求导最接近的是其中间值的梯度 但是其中间并没有像素点 所以进行修正
f(x,y)' = (f(x+1,y) - f(x-1,y))/2

但是上式仅能求得x 和 y 方向的 边缘 我们还要 左上到右下 左下到右上的边缘
[1 0] [0 1]
[0 -1] [-1 0]

函数

因为图像的边缘 像素值可能突然高变低 也可能突然低变高 由上式可能获得正负 所以要求绝对值

所以 给出了求绝对值的函数 可以获得绝对值
img = cv.convertScaleAbs(src) 

利用前一篇博客的卷积函数   但是数据的格式 不能说-1了 因为得出来的差值可能小于0
利用上面的理论来求解一个图像的边缘
点击查看代码
        import cv2 as cv
        import sys
        
        import numpy as np
        
        img = cv.imread('../img_test.jpg')
        if img is None:
            print('读取失败')
            sys.exit()
        
        img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
        
        ker1 = np.array([-1, 0, 1])# 拿到x轴上的边缘
        ker2 = ker1.reshape((3, 1))# 拿到y轴上的边缘 
        ker3 = np.array([1, 0, 0, -1]).reshape((2, 2)) #拿到左上到右下的边缘
        ker4 = np.array([0, 1, -1, 0]).reshape((2, 2)) #拿到左下到右上的边缘
        
        
        res1 = cv.filter2D(img, cv.CV_16S, ker1)
        res1 = cv.convertScaleAbs(res1)
        
        res2 = cv.filter2D(img, cv.CV_16S, ker2)
        res2 = cv.convertScaleAbs(res2)
        
        res3 = cv.filter2D(img, cv.CV_16S, ker3)
        res3 = cv.convertScaleAbs(res3)
        
        res4 = cv.filter2D(img, cv.CV_16S, ker4)
        res4 = cv.convertScaleAbs(res4)
        
        #将各个方向的边缘全部合在一起
        res_all = res1+res2+res3+res4
        cv.imshow('1', img)
        cv.imshow('2', res1)
        cv.imshow('3', res2)
        cv.imshow('4', res3)
        cv.imshow('5', res4)
        cv.imshow('6',res_all)
        
        cv.waitKey(0)
        cv.destroyAllWindows()

image

Sobel算子

Sobel算子是通过离散微分方法来求取图像边缘的边缘检测算子,其思想和上面的思想一样 但是Sobel算子结合了高斯平滑
滤波的思想 将边缘检测滤波器的大小变为了 ksize*ksize 提高了对平缓区域的边缘检测 其效果更明显

方法:
    1.提取x方向的边缘 
        [-1,0,1,-2,0,2,-1,0,1].reshape(3,3) 不仅仅只看一行 而是三行的梯度 使边缘更加连贯

    2.提取y方向的边缘
        [-1,-2,-1,0,0,0,1,2,1].reshape(3,3)


    3.将两个方向的边缘信息进行整合
        可以直接相加 也可以求平方和的平方根

Opencv提供了函数:
    dst = cv.Sobel(src,ddepth,dx,dy,ksize)
    ddepth: 数据类型
    dx:x方向的差分阶数
    dy:y方向的差分阶数 
    ksize:算子的规模
    一般来说 差分阶数应该小于3 差分阶数为1时 ksize选择3
点击查看代码
            
            import cv2 as cv
            import sys
            
            import numpy as np
            
            img = cv.imread('../img_test.jpg')
            if img is None:
                print('读取失败')
                sys.exit()
            
            img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
            
            img1 = cv.Sobel(img, cv.CV_16S, 1, 0, 3)  # x方向进行差分
            img1 = cv.convertScaleAbs(img1)
            img2 = cv.Sobel(img, cv.CV_16S, 0, 1, 3)  # y方向进行差分
            img2 = cv.convertScaleAbs(img2)
            
            img_test = cv.Sobel(img, cv.CV_16S, 1, 1, 3)
            img_test = cv.convertScaleAbs(img_test)
            
            img3 = img1 + img2
            
            cv.imshow('old', img)
            cv.imshow('img1', img1)
            cv.imshow('img2', img2)
            cv.imshow('img3', img3)
            cv.imshow('img_test', img_test)
            
            
            cv.waitKey(0)
            cv.destroyAllWindows()

image

Scharr算子

虽然Sobel算子可以有效地提取图像的边缘 但是对于较弱的边缘 提取效果不好。所以为了提取较弱的边缘 要将
灰度值的差距增大 因此引入Scharr算子,它是Sobel算子的增强 原理和Sobel算子相同
[-3,0,3,-10,0,10,-3,0,3].reshape(3,3)

函数:
    dst = cv.Scharr(src,ddepth,dx,dy) 该算子默认是3*3 并且不能修改

image

生成边缘检测滤波器

有时候我们想得到边缘检测的滤波器(卷积核) 然后对其进行修改 用于边缘检测 OpenCV给出了生成卷积核的函数

kx,ky = cv.getDerivKernels(dx,dy,ksize,normalize,ktype)
normalize:是否归一化
ktype: 数据类型

    import cv2 as cv
    x, y = cv.getDerivKernels(1, 0, 3)
    print(x)
    print(y)
    print(y*x.T)
    # 拿到x方向检测器      

    x1, y1 = cv.getDerivKernels(0, 1, 3)
    print(x1)
    print(y1)
    print(y.T*x)
    # 拿到y方向的检测器

Laplacian 算子

上述边缘检测算子都具有方向性 需要对x和y方向的边缘分别进行求取 然后进行合并
而Laplacian算子 具有各向同性的特点 不需要对x和y进行分别的边缘检测
Laplacian算子是一种二阶导数算子 对噪声比较敏感 所以通常需要配合高斯滤波使用

Laplacian(f) = 对x求二阶偏导 + 对y求二阶偏导

opencv提供了函数:
    dst = cv.Laplacian(src,ddepth,ksize)
    kszie 必须为奇数
点击查看代码
    import cv2 as cv
    import sys
    
    import numpy as np
    
    img = cv.imread('../img_test.jpg')
    if img is None:
        print('读取失败')
        sys.exit()
    
    img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    
    cv.imshow('old', img)
    
    img1 = cv.Laplacian(img, cv.CV_16S, ksize=3)
    img1 = cv.convertScaleAbs(img1)
    cv.imshow('img1',img1)
    
    img = cv.GaussianBlur(img, (3, 3), 10, 10)
    
    cv.imshow('Gauss_noisy', img)
    
    img = cv.Laplacian(img, cv.CV_16S, ksize=3)
    img = cv.convertScaleAbs(img)
    
    cv.imshow('Lap', img)
    
    cv.waitKey(0)
    cv.destroyAllWindows()

image

Canny算法

Canny算法不容易受到图像中的噪音影响 能够识别图像中的弱边缘和强边缘 并结合强弱边缘的位置得到图像整体的边缘信息
Canny边缘检测算法 是目前最优秀的边缘检测算法之一 使用一般分为五步

1. 使用高斯滤波得到更加平滑的图像 一般使用5*5的高斯滤波器
2. 计算图像中每个像素灰度值梯度方向和幅值 通过Sobel算子检测x和y方向边缘 利用下式计算出角度 和 幅值
    角度 = arctan(Iy/Ix)
    G = (Ix^2+Iy^2)^(1/2)
3.应用非极大值抑制算法消除边缘检测带来的杂散响应
    将当前像素灰度值的梯度 与 正负梯度方向上的两个像素灰度值的梯度进行比较
    若比两个梯度上的灰度值大 则会保留该像素 否则会被抑制
4.应用双阈值划分强边缘和弱边缘
    将边缘处的梯度值与两阈值进行比较 
    小于小阈值会被去除 大于小阈值小于大阈值会被标记为弱边缘 大于大阈值会被标记为强边缘

5.消除孤立的弱边缘
    在弱边缘的八个领域寻找强边缘 若存在则保留弱边缘 否则 删除弱边缘 输出结果

Canny算法有较为复杂的流程 Opencv中给出了函数
dst = cv.Canny(img,threshold1,threshold2,apertureSize)
    threshold1 : 第一个阈值
    threshold2 : 第二个阈值
    apertureSize : Sobel算子大小

    大小阈值的比值一般在 2:1 --- 3:1 间

点击查看代码
    
    import cv2 as cv
    import sys
    
    import numpy as np
    
    img = cv.imread('../img_test.jpg')
    if img is None:
        print('读取失败')
        sys.exit()
    
    img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    cv.imshow('old', img)
    img = cv.GaussianBlur(img, (5, 5), 10, 10)
    cv.imshow('Gauss_noisy', img)
    
    img1 = cv.Canny(img, 10, 20, apertureSize=3)
    img1 = cv.convertScaleAbs(img1)
    img = cv.Canny(img, 50, 100, apertureSize=3)
    img = cv.convertScaleAbs(img)
    
    cv.imshow('Canny_min', img1)
    cv.imshow('Canny_max', img)
    
    cv.waitKey(0)
    cv.destroyAllWindows()


image

使用较小阈值和较大阈值的区别

posted @ 2022-03-13 13:39  cc学习之路  阅读(77)  评论(0)    收藏  举报