【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)

目录

 

前言

一、边缘检测算法

1、一阶算子

2、二阶算子

二、一阶算子

原图像lena

1、Roberts算子

不同方向的算子模板

梯度的计算

系统代码:

自定义函数代码

结果

2、Prewitt

不同方向的算子

梯度计算

系统自带代码

自定义函数代码

结果

3、Sobel

不同方向的算子

梯度计算

系统自带代码(opencv和skimage)

自定义函数代码

结果

4、 Kirsch

不同方向的算子

梯度计算

自定义函数代码

结果

5、Canny

canny主要计算步骤:

不同方向的算子

梯度计算

系统自带代码

自定义函数代码

结果

三、填充补零操作(究竟补多少行)

1、why&how

2、cv2.copyMakeBorder()

3、创建零图像矩阵

四、不同一阶算子的比较


前言

耐心看完一定会有收获的,大部分内容也会在代码中体现,结合理论知识和代码进行理解会更有效

一、边缘检测算法

边缘检测算法是指利用灰度值的不连续性质,以灰度突变为基础分割出目标区域。对铝铸件表面进行成像后会产生一些带缺陷的区域,这些区域的灰度值比较低,与背景图像相比在灰度上会有突变,这是由于这些区域对光线产生散射所引起的。因此边缘检测算子可以用来对特征的提取。

1、一阶算子

一种是基于一阶微分的算子,也称基于搜索的算子,首先通过一阶导数计算边缘强度,然后采用梯度的方向来对边缘的局部方向进行寻找,同时根据该方向来寻找出局部梯度模的最大值,由此定位边缘,如Roberts Cross算子,Prewitt算子Sobel算子,Kirsch算子,Canny算子,罗盘算子等;

图像中的边缘区域,像素值会发生“跳跃”,对这些像素求导,在其一阶导数在边缘位置为极值,这就是Sobel算子使用的原理——极值处就是边缘。

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第1张图片

2、二阶算子

另一种是基于二阶微分的算子,也称基于零交叉的算子,通过寻找由图像得到的二阶导数的过零点来定位检测边缘,如Marr-Hildreth算子,Laplacian算子,LOG算子等

二阶算子可见:《【图像处理】——Python图像分割边缘检测算法之二阶梯度算子(laplace、log、dog算子)》

如果对像素值求二阶导数,会发现边缘处的导数值为0

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第2张图片

二、一阶算子

参考:《一阶算子opencv中函数的参数解读与举例》

一阶微分算子进行边缘检测的思路大致就是通过指定大小的核(kernal)(也称为算子)与图像进行卷积,将得到的梯度进行平方和或者最大值作为新的梯度赋值给对应的像素点,不同的一阶微分算子主要的不同在于其算子即核的元素不同以及核的大小不一样

以下是连续函数的一阶导数求导公式因为图像是一个面,就相当于是灰度值关于x,y两个方向的函数,要求某一点的导数,则是各个方向的偏导数的平方和再进行开方运算

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第3张图片

离散函数的一阶导数公式:

y'=[y(x0+h)-y(x0-h)]/(2h);这是一维函数的一阶求导,h是步长,在图像处理中一般为1

首先复习一下什么是卷积?

卷积就是对应的元素相乘再进行累加的过程

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第4张图片

原图像lena

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第5张图片

1、Roberts算子

Robert算子是用于求解对角线方向的梯度,因为根据算子GX和GY的元素设置可以看到,只有对角线上的元素非零

不同方向的算子模板

梯度的计算

系统代码:

def sys_robert(img):
    '''
    直接调用系统skimage模块给的函数
    :param img: 待处理图片
    :return: 返回的是边缘图像矩阵
    '''
    gray = cv2.imread(img,0)#读取的时候直接将其读取成灰色的图片,变成一个二维灰度值矩阵
    edge_img = filters.roberts(gray)#进行边缘检测,得到边缘图像
    return edge_img

自定义函数代码

其中对于下列函数def_col可以直接利用cv2.filter2D(灰度图,-1表示与原图一样的size,核)

def def_col(gray,kernal):
    '''
    对指定的图像用指定的核对齐其进行卷积,得到卷积后的图像
    :param gray: 补零操作后的灰度图像
    :param kernal: 卷积核
    :return: 返回卷积后的图像,即边缘图像
    '''
    edge_img = cv2.filter2D(gray, -1, kernal)
    #上面这句和下面的代码有一样的效果
    # h,w = gray.shape
    # edge_img = np.zeros((h-1,w-1))#用于存放卷积后的边缘图像矩阵
    # for i in range(h-1):
    #     for j in range(w-1):
    #         #用卷积核与图像上对应大小的块进行卷积,这里是以左上角的格子为基准进行的卷积
    #         edge_img[i,j]=gray[i,j]*kernal[0,0]+gray[i,j+1]*kernal[0,1]+gray[i+1,j]*kernal[1,0]+gray[i,j]*kernal[1,1]
    return edge_img

def def_robert(img):
    '''
    对指定的图像进行边缘检测
    :param img: 要检测的图像
    :return: 返回边缘图像
    '''
    gray = cv2.imread(img,0)
    # np.savetxt("result2.txt", gray, fmt="%d")
    h = gray.shape[0]
    w = gray.shape[1]
    #定义Robert算子的卷积核,这个是对角线方向
    x_kernal = np.array([[1,0],
                         [0,-1]])
    y_kernal = np.array([[0,1],
                         [-1,0]])
    #由于卷积核和图像进行卷积是以右下角的像素进行定位卷积核和目标像素点的位置关系,因此为了能够遍历整个图像,
    #需要在图像的第一行和第一列进行补零操作
    gray_zero = np.zeros((h+1,w+1))#先生成一个(h+1,w+1)的零图像矩阵
    #将原始图像去填充零矩阵,第一行和第一列不填充
    for i in range(1,h+1):
        for j in range(1,w+1):
            gray_zero[i,j]=gray[i-1,j-1]
    gray = gray_zero#将填充后的矩阵复制给gray
    #通过卷积,得到x和y两个方向的边缘检测图像
    x_edge = def_col(gray,x_kernal)
    y_edge = def_col(gray,y_kernal)
    #创建一个与原始图像大小一致的空图像,用于存放经过Robert算子的边缘图像矩阵
    edge_img = np.zeros((h,w),np.uint8)
    #根据计算公式得到最终的像素点灰度值
    for i in range(h):
        for j in range(w):
            edge_img[i,j] = (np.sqrt(x_edge[i,j]**2+y_edge[i,j]**2))
    return edge_img


if __name__ == '__main__':
    img = "test1.png"
    edge = def_robert(img)
    edge1 = sys_robert(img)
    #以下是将看一下两种方法图像各点的像素值的比值,用c存放
    # h,w = edge1.shape
    # c = np.zeros((h,w))
    # c[0:h,0:w] = edge1[0:h,0:w]/edge1[0:h,0:w]
    # for i in range(h):
    #     for j in range(w):
    #         if edge1[i,j] != 0:
    #             c[i,j] = edge[i,j]/edge1[i,j]
    #         else:
    #             c[i,j] = edge[i,j]
    # print(max(c.ravel()))
    #进行numpy保存到TXT中的操作
    # np.savetxt("result.txt", edge, fmt="%d")
    # np.savetxt("c.txt", c)
    # np.savetxt("result1.txt", edge1)
    cv2.imshow('',edge)
    cv2.imshow('222',edge1)
    cv2.waitKey(0)

结果

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第6张图片

2、Prewitt

Prewitt算子利用像素点上下、左右邻点的灰度差,在边缘处达到极值检测边缘,去掉部分伪边缘,对噪声具有平滑作用 。其原理是在图像空间利用两个方向模板与图像进行邻域卷积来完成的,这两个方向模板一个检测水平边缘,一个检测垂直边缘。

不同方向的算子

梯度计算

用某点的x向梯度和y向梯度的最大值代表该点梯度;2.用x向梯度和y向梯度的和代替该点梯度;3.用x向梯度和y向梯度的平方根代替该点的梯度(同python自带的运算);4.用x向梯度和y向梯度绝对值的和代替该点的梯度

系统自带代码

from skimage import data,color,filters
import matplotlib.pyplot as plt
import numpy as np
import cv2


def sys_prewitt(img):
    '''
    prewitt系统自带
    :param img: 原始图像
    :return: 返回边缘图像
    '''
    img = cv2.imread(img,0)
    edge_img=filters.prewitt(img)
    return edge_img

自定义函数代码

def def_col(img,kernal):
    '''
    对指定的图像用指定的核对齐其进行卷积,得到卷积后的图像
    :param img: 补零操作后的灰度图像
    :param kernal: 卷积核
    :return: 返回卷积后的图像,即边缘图像
    '''
    edge_img = cv2.filter2D(img,-1,kernal)
    # h,w = img.shape
    # edge_img=np.zeros([h-2,w-2])
    # for i in range(h-2):
    #     for j in range(w-2):
    #         edge_img[i,j]=img[i,j]*kernal[0,0]+img[i,j+1]*kernal[0,1]+img[i,j+2]*kernal[0,2]+\
    #                 img[i+1,j]*kernal[1,0]+img[i+1,j+1]*kernal[1,1]+img[i+1,j+2]*kernal[1,2]+\
    #                 img[i+2,j]*kernal[2,0]+img[i+2,j+1]*kernal[2,1]+img[i+2,j+2]*kernal[2,2]
    return edge_img

def def_prewitt(img,type_flags):
    gray = cv2.imread(img,0)
    h = gray.shape[0]
    w = gray.shape[1]
    x_prewitt=np.array([[1,0,-1],
                       [1,0,-1],
                       [1,0,-1]])
    y_prewitt=np.array([[1,1,1],
                       [0,0,0],
                       [-1,-1,-1]])

    img=np.zeros([h+2,w+2])
    img[2:h+2,2:w+2]=gray[0:h,0:w]
    edge_x_img=def_col(img,x_prewitt)
    edge_y_img=def_col(img,y_prewitt)

    #p(i,j)=max[edge_x_img,edge_y_img]这里是将x,y中最大的梯度来代替该点的梯度
    edge_img_max=np.zeros([h,w],np.uint8)
    for i in range(h):
        for j in range(w):
            if edge_x_img[i][j]>edge_y_img[i][j]:
                edge_img_max=edge_x_img[i][j]
            else:
                edge_img_max=edge_y_img

    #p(i,j)=edge_x_img+edge_y_img#将梯度和替代该点梯度
    edge_img_sum=np.zeros([h,w],np.uint8)
    for i in range(h):
        for j in range(w):
            edge_img_sum[i][j]=edge_x_img[i][j]+edge_y_img[i][j]

    # p(i,j)=|edge_x_img|+|edge_y_img|将绝对值的和作为梯度
    edge_img_abs = np.zeros([h, w],np.uint8)
    for i in range(h):
        for j in range(w):
            edge_img_abs[i][j] = abs(edge_x_img[i][j]) + abs(edge_y_img[i][j])


    #p(i,j)=sqrt(edge_x_img**2+edge_y_img**2)将平方和根作为梯度
    edge_img_sqrt=np.zeros([h,w],np.uint8)
    for i in range(h):
        for j in range(w):
            edge_img_sqrt[i][j]=np.sqrt((edge_x_img[i][j])**2+(edge_y_img[i][j])**2)


    type = [edge_img_max,edge_img_sum,edge_img_abs,edge_img_sqrt]
    return type[type_flags]


if __name__ == '__main__':
    img = 'colorful_lena.jpg'
    edge = sys_prewitt(img)
    edge1 = def_prewitt(img,3)
    cv2.imshow('',edge)
    cv2.imshow('1',edge1)
    cv2.waitKey(0)

结果

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第7张图片

3、Sobel

sobel和prewitt算子很像,都是检测水平和垂直边缘,但是其最终梯度的计算和算子的元素值不一样

不同方向的算子

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第8张图片

梯度计算

系统自带代码(opencv和skimage)

import cv2
from skimage import filters,color
import numpy as np

def cv2_sobel(img):
    '''
    利用cv2自带的函数进行sobel边缘检测
    :param img: 待检测的图像
    :return: 返回不同方向梯度的边缘图像矩阵,通过控制不同方向的导数为1或者0来选择卷积核
    '''
    img = cv2.imread(img)#读取图片
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#灰度化
    sobel_x = cv2.Sobel(gray, cv2.CV_8U, 1, 0)#通过控制dx,dy的值来控制梯度的方向,这里是求x方向的梯度
    sobel_y = cv2.Sobel(gray, cv2.CV_8U, 0, 1)#这里是求y方向的梯度
    sobel = cv2.Sobel(gray, cv2.CV_8U, 1, 1)#这里是求x,y方向综合的梯度
    return [sobel_x,sobel_y,sobel]

def skimage_sobel(img):
    '''
    利用skimage模块自带的函数进行图像的边缘检测
    :param img: 待检测图像
    :return: 返回的是边缘图像矩阵
    '''
    gray = cv2.imread(img,0)
    edge_img = filters.sobel(gray)#这里求的是x,y方向两个梯度的综合
    return edge_img

自定义函数代码

def sobel_cal(img, kernal):
    '''
    自定义sobel卷积函数
    :param img: 已经补零了的图像
    :param kernal: 指定方向的梯度算子,核
    :return: 返回梯度矩阵
    '''
    edge_img = cv2.filter2D(img, -1, kernal)
    # h, w = img.shape
    # img_filter = np.zeros([h, w])
    # #机芯卷积操作
    # for i in range(h - 2):
    #     for j in range(w - 2):
    #         img_filter[i][j] = img[i][j] *kernal[0][0] + img[i][j + 1] * kernal[0][1] + img[i][j + 2] * kernal[0][2] + \
    #                                img[i + 1][j] * kernal[1][0] + img[i + 1][j + 1] *kernal[1][1] + img[i + 1][j + 2] * kernal[1][2] + \
    #                                img[i + 2][j] * kernal[2][0] + img[i + 2][j + 1] * kernal[2][1] + img[i + 2][j + 2] * kernal[2][2]
    return edge_img

def def_sobel(img):
    #定义不同方向的梯度算子
    x_sobel = np.array([[-1, 0, 1],
                        [-2, 0, 2],
                        [-1, 0, 1]])
    y_sobel = np.array([[-1, -2, -1],
                        [0, 0, 0],
                        [1, 2, 1]])
    gray_img = cv2.imread(img,0)
    h, w = gray_img.shape
    img = np.zeros([h + 2, w + 2],np.uint8)
    img[2:h + 2, 2:w + 2] = gray_img[0:h, 0:w]
    x_edge_img = sobel_cal(img, x_sobel)
    y_edge_img = sobel_cal(img, y_sobel)
    edge_img = np.zeros([h, w])
    for i in range(h):
        for j in range(w):
            edge_img[i][j] = np.sqrt(x_edge_img[i][j] ** 2 + y_edge_img[i][j] ** 2) / (np.sqrt(2))
    return edge_img

if __name__ == '__main__':
    img = "test1.png"
    sobel_x,sobel_y,sobel = cv2_sobel(img)
    sk_sobel = skimage_sobel(img)
    def_sobelimg = def_sobel(img)
    cv2.imshow("src", cv2.imread(img))
    cv2.imshow("Sobel_x", sobel_x)
    cv2.imshow("Sobel_y", sobel_y)
    cv2.imshow("Sobel", sobel)
    cv2.imshow("sk_Sobel", sk_sobel)
    cv2.imshow("def_Sobel", def_sobelimg)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

结果

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第9张图片

4、 Kirsch

不同方向的算子

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第10张图片

梯度计算

Kirsch边缘算子由八个方向的卷积核构成,这8个模板代表8个方向,对图像上的8个特定边缘方向作出最大响应,运算中取最大值作为图像的边缘输出

自定义函数代码

from skimage import data,color
import matplotlib.pyplot as plt
import numpy as np
import cv2

def conv_cal(img,kernal):
    edge_img = cv2.filter2D(img, -1, kernal)
    # h,w=img.shape
    # img_filter=np.zeros([h,w])
    # for i in range(h-2):
    #     for j in range(w-2):
    #         img_filter[i][j]=img[i][j]*filter[0][0]+img[i][j+1]*filter[0][1]+img[i][j+2]*filter[0][2]+\
    #                 img[i+1][j]*filter[1][0]+img[i+1][j+1]*filter[1][1]+img[i+1][j+2]*filter[1][2]+\
    #                 img[i+2][j]*filter[2][0]+img[i+2][j+1]*filter[2][1]+img[i+2][j+2]*filter[2][2]
    return edge_img

def def_krisch(img):
    #定义算子
    krisch1=np.array([[5,5,5],
                      [-3,0,-3],
                      [-3,-3,-3]])
    krisch2=np.array([[-3,-3,-3],
                      [-3,0,-3],
                      [5,5,5]])
    krisch3=np.array([[5,-3,-3],
                      [5,0,-3],
                      [5,-3,-3]])
    krisch4=np.array([[-3,-3,5],
                      [-3,0,5],
                      [-3,-3,5]])
    krisch5=np.array([[-3,-3,-3],
                      [-3,0,5],
                      [-3,5,5]])
    krisch6=np.array([[-3,-3,-3],
                      [5,0,-3],
                      [5,5,-3]])
    krisch7=np.array([[-3,5,5],
                      [-3,0,5],
                      [-3,-3,-3]])
    krisch8=np.array([[5,5,-3],
                      [5,0,-3],
                      [-3,-3,-3]])
    gray_img = cv2.imread(img,0)
    w,h=gray_img.shape
    img=np.zeros([w+2,h+2])
    img[2:w+2,2:h+2]=gray_img[0:w,0:h]
    edge1=conv_cal(img,krisch1)
    edge2=conv_cal(img,krisch2)
    edge3=conv_cal(img,krisch3)
    edge4=conv_cal(img,krisch4)
    edge5=conv_cal(img,krisch5)
    edge6=conv_cal(img,krisch6)
    edge7=conv_cal(img,krisch7)
    edge8=conv_cal(img,krisch8)
    edge_img=np.zeros([w,h],np.uint8)
    for i in range(w):
        for j in range(h):
            edge_img[i][j]=max(list([edge1[i][j],edge2[i][j],edge3[i][j],edge4[i][j],edge5[i][j],edge6[i][j],edge7[i][j],edge8[i][j]]))
    return [edge1,edge2,edge3,edge4,edge5,edge6,edge7,edge8,edge_img]

if __name__ == '__main__':
    img = 'colorful_lena.jpg'
    edge1, edge2, edge3, edge4, edge5, edge6, edge7, edge8,edge_img = def_krisch(img)
    cv2.imshow('1',edge1)
    cv2.imshow('2',edge2)
    cv2.imshow('3',edge3)
    cv2.imshow('4',edge4)
    cv2.imshow('5',edge5)
    cv2.imshow('6',edge6)
    cv2.imshow('7',edge7)
    cv2.imshow('8',edge8)
    cv2.imshow('9',edge_img)
    cv2.waitKey(0)

结果

这个结果我也看不懂。。。。

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第11张图片

5、Canny

       可以说,Canny 边缘检测算法是被业界公认的性能最为优良的边缘检测算法之一。Canny算法不是像Roberts、Prewitt、Sobel等这样简单梯度算子或锐化模板,它是在梯度算子基础上,引入了一种能获得抗噪性能好、定位精度高的单像素边缘的计算策略。
       Canny把边缘检测问题转化为检测单位函数极大值问题。在高斯噪声假设中,一个典型的边缘代表一个阶跃的强度变化

       根据这个模型,一个好的边缘检测算子应满足以下3个指标:

(1) 低失误概率;(2) 高位置精度;(3) 对每个边缘有唯一的响应。
换句话说边缘检测问题就是要做到: 抑制噪声,精确定位边缘

canny主要计算步骤:

Canny 边缘检测算法的主要计算步骤包括四个方面:
(1) 利用高斯滤波器对原始图像进行平滑滤波,以提高算法的抗噪性。
(2) 用一阶有限差分近似代替偏导数计算图像梯度强度和方向。计算梯度可以利用先前的Roberts、 Prewitt、Sobel等算子(文中用的是Prewitt 算子)。其中,方向信息是为了下一步计算需要。
(3) 利用第(2)步梯度方向划分(划分为四个方向 0\ -45\ 90 \45 )进行梯度强度的非极大抑制,获取单像素边缘点。

个人认为比较难懂的就是第三步,实际上做的就是检测梯度方向是否垂直于切线方向,那么该怎么来确定这个切线方向呢?因为每个像素点的梯度方向在第二部中已经确定了,因此我们只需要取与梯度方向垂直的三个像素点即可,若该点的像素值是这三个中最大的,则说明这是梯度的切线方向处的极大值点,这就说明了这就是我们要找的边缘(边缘的灰度值突变性大,因此梯度大),下面用图表示了一下,可以结合代码进行查看


(4) 双(或滞后)阈值进行边缘的二值化

基本思路:通过梯度值和梯度方向划分后,得到一个只含有邻域为最大梯度的像素矩阵,这些点设为0,其余为255,然后再根据阈值对前面的像素矩阵进行修正设置,这样就得到了比较好的边缘图像

参考:https://blog.csdn.net/weixin_44403952/article/details/90375013

不同方向的算子

这里的算子是使用的是prewitt算子,也可以自定义一个合理的算子进行使用梯度计算

梯度计算

canny算法中的梯度在计算过程中会变化好多次:一个是由计算公式得到的梯度,类似于krewitt算子;一种是通过对第一种梯度进行梯度方向划分以及非极大值梯度抑制后得到的梯度,另一种就是梯度方向

系统自带代码

# coding=utf-8
import cv2
import numpy as np


def cv2_canny(img):
    '''
    由于Canny只能处理灰度图,所以将读取的图像转成灰度图。
    用高斯平滑处理原图像降噪。
    调用Canny函数,指定最大和最小阈值,其中apertureSize默认为3。
    :param img: 
    :return: 
    '''
    img = cv2.imread(img, 0)
    img = cv2.GaussianBlur(img, (3, 3), 2)
    edge_img = cv2.Canny(img, 50,100)
    return edge_img

自定义函数代码

def def_canny(img,top=1,buttom=1,left=1,right=1):
    '''
    自定义canny算子,这里可以修改的参数有填充的方式和值、高斯滤波是核的大小以及sigmaX的值
    :param img: 待测图片
    :param top: 图像上方填充的像素行数
    :param buttom: 图像下方填充的像素行数
    :param left: 图像左方填充的像素行数
    :param right: 图像右方填充的像素行数
    :return: [img1,img2,img3,theta]
    img1:梯度幅值图;img2:非极大值抑制梯度灰度图;img3:最终边缘图像;theta:梯度方向灰度图
    '''
    #算子,这里的梯度算子可以使用sobel、sobel等算子,这里用的是prewitt梯度算子
    m1 = np.array([[-1,0,1],[-1,0,1],[-1,0,1]])
    m2 = np.array([[-1,-1,-1],[0,0,0],[1,1,1]])

    # 第一步:完成高斯平滑滤波
    img = cv2.imread(img,0)#将彩色读成灰色图片
    img = cv2.GaussianBlur(img,(3,3),2)#高斯滤波

    # 第二步:完成一阶有限差分计算,计算每一点的梯度幅值与方向
    img1 = np.zeros(img.shape,dtype="uint8") # 与原图大小相同,用于存放梯度值
    theta = np.zeros(img.shape,dtype="float")  # 方向矩阵原图像大小,用于存放梯度方向
    img = cv2.copyMakeBorder(img,top,buttom,left,right,borderType=cv2.BORDER_CONSTANT,value=0)#表示增加的像素数,类似于补零操作,这里上下左右均增加了一行填充方式为cv2.BORDER_REPLICATE
    rows,cols = img.shape

    #以下是进行了卷积,以核与图像的卷积来代替核中间对应的那个值
    #dst = cv2.filter2D(img, -1, kernel)这个函数虽然可以直接进行卷积,但是无法返回梯度方向
    for i in range(1,rows-1):
        for j in range(1,cols-1):
            # Gy,对应元素相乘再累加
            Gy = (np.dot(np.array([1, 1, 1]), (m1 * img[i - 1:i + 2, j - 1:j + 2]))).dot(np.array([[1], [1], [1]]))
            # Gx,对应元素相乘再累加
            Gx = (np.dot(np.array([1, 1, 1]), (m2 * img[i - 1:i + 2, j - 1:j + 2]))).dot(np.array([[1], [1], [1]]))
            #将所有的角度转换到-180到180之间
            if Gx[0] == 0:
                theta[i-1,j-1] = 90
                continue
            else:
                temp = (np.arctan(Gy[0] / Gx[0]) ) * 180 / np.pi#求梯度方向
            if Gx[0]*Gy[0] > 0:
                if Gx[0] > 0:
                    theta[i-1,j-1] = np.abs(temp)
                else:
                    theta[i-1,j-1] = (np.abs(temp) - 180)
            if Gx[0] * Gy[0] < 0:
                if Gx[0] > 0:
                    theta[i-1,j-1] = (-1) * np.abs(temp)
                else:
                    theta[i-1,j-1] = 180 - np.abs(temp)
            img1[i-1,j-1] = (np.sqrt(Gx**2 + Gy**2))#求两个方向梯度的平方和根

    #以下对梯度方向进行了划分,将梯度全部划分为0,90,45,-45四个角度
    for i in range(1,rows - 2):
        for j in range(1, cols - 2):
            if (    ( (theta[i,j] >= -22.5) and (theta[i,j]< 22.5) ) or
                    ( (theta[i,j] <= -157.5) and (theta[i,j] >= -180) ) or
                    ( (theta[i,j] >= 157.5) and (theta[i,j] < 180) ) ):
                theta[i,j] = 0.0
            elif (    ( (theta[i,j] >= 22.5) and (theta[i,j]< 67.5) ) or
                    ( (theta[i,j] <= -112.5) and (theta[i,j] >= -157.5) ) ):
                theta[i,j] = 45.0
            elif (    ( (theta[i,j] >= 67.5) and (theta[i,j]< 112.5) ) or
                    ( (theta[i,j] <= -67.5) and (theta[i,j] >= -112.5) ) ):
                theta[i,j] = 90.0
            elif (    ( (theta[i,j] >= 112.5) and (theta[i,j]< 157.5) ) or
                    ( (theta[i,j] <= -22.5) and (theta[i,j] >= -67.5) ) ):
                theta[i,j] = -45.0

    # 第三步:进行 非极大值抑制计算
    #这里做的事情其实就是在寻找极值点,像素点的值是与该像素点的方向垂直方向上相邻三个像素最大的,则将该像素点定义为极值点,即边缘像素
    img2 = np.zeros(img1.shape) # 非极大值抑制图像矩阵
    for i in range(1,img2.shape[0]-1):
        for j in range(1,img2.shape[1]-1):
            if (theta[i,j] == 0.0) and (img1[i,j] == np.max([img1[i,j],img1[i+1,j],img1[i-1,j]]) ):
                    img2[i,j] = img1[i,j]

            if (theta[i,j] == -45.0) and img1[i,j] == np.max([img1[i,j],img1[i-1,j-1],img1[i+1,j+1]]):
                    img2[i,j] = img1[i,j]

            if (theta[i,j] == 90.0) and  img1[i,j] == np.max([img1[i,j],img1[i,j+1],img1[i,j-1]]):
                    img2[i,j] = img1[i,j]

            if (theta[i,j] == 45.0) and img1[i,j] == np.max([img1[i,j],img1[i-1,j+1],img1[i+1,j-1]]):
                    img2[i,j] = img1[i,j]

    # 第四步:双阈值检测和边缘连接
    img3 = np.zeros(img2.shape) #定义双阈值图像
    # TL = 0.4*np.max(img2)
    # TH = 0.5*np.max(img2)
    TL = 50#低阈值
    TH = 100#高阈值
    #关键在这两个阈值的选择
    #小于低阈值的则设置为0,大于高阈值的则设置为255
    for i in range(1,img3.shape[0]-1):
        for j in range(1,img3.shape[1]-1):
            #将比较明确的灰度级的像素点绘制出来
            if img2[i,j] < TL:
                img3[i,j] = 0
            elif img2[i,j] > TH:
                img3[i,j] = 255
            #如果一个像素点的值在二者之间,且这个像素点的邻近的八个像素点有一个的像素值是小于高阈值的,则将该点设置为255
            #实际上下面这段代码就是将剩余的像素点都设置成了255,这样就实现了边缘连接
            elif (( img2[i+1,j] < TH) or (img2[i-1,j] < TH )or( img2[i,j+1] < TH )or
                    (img2[i,j-1] < TH) or (img2[i-1, j-1] < TH )or ( img2[i-1, j+1] < TH) or
                       ( img2[i+1, j+1] < TH ) or ( img2[i+1, j-1] < TH) ):
                img3[i,j] = 255
    return [img1,img2,img3,theta]


if __name__ == '__main__':
    img = 'test1.png'
    img1, img2, img3, theta = def_canny(img)
    cv2.imshow("1", cv2.imread(img, 0))  # 原始图像
    cv2.imshow("2", img1)  # 梯度幅值图
    cv2.imshow("3", img2)  # 非极大值抑制灰度图
    cv2.imshow("4", img3)  # 最终效果图
    cv2.imshow("theta", theta)  # 角度值灰度图
    edge_img = cv2_canny(img)
    cv2.imshow("orign", cv2.imread(img))
    cv2.imshow('Canny', edge_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

结果

【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)_第12张图片

 

三、填充补零操作(究竟补多少行)

1、why&how

这里我们要和卷积神经网络的卷积区分开来,卷积神经网络卷积的主要目的是将图片的信息进行压缩,因此经过卷积图片的尺寸会变小,而在我们这里边缘检测中,我们要保证图像的大小不发生改变,这时候我们就需要进行补零操作,具体补几行几列,还得看情况而定,一般是和核的大小有关,一般情况下假设核是nXn的,那么就需要补充n-1行和n-1列

注意这里不一定是补零,也可以是其他的填充方法,如复制边缘的像素值进行填充,出来的效果就是拖拉的感觉,

2、cv2.copyMakeBorder()

在opencv中提供了函数cv2.copyMakeBorder()进行填充

cv2.copyMakeBorder(src, top, bottom, left, right, borderType, dst=None, value=None)
cv2.copyMakeBorder(img, 1, 1, 1, 1, cv2.BORDER_DEFAULT)

上面代码就表示按照默认的填充方式在上下左右各填充一行像素

可参考:https://blog.csdn.net/zfjBIT/article/details/86655182

3、创建零图像矩阵

记住一定要将其类型设置为dtype = np.uint8,否则不太好,多次创建0矩阵图像时,只需要将最后一个进行设置即可

img3 = np.zeros(img2.shape,np.uint8)

四、不同一阶算子的比较

        Roberts、Prewitt、Sobel等梯度算子不能获取单像素边缘,并进行必要的计算结果对比展示。实际上,以上简单的梯度算子与图像进行卷积滤波,仅仅是对图像的一个锐化过程,得到的仅是原始图像的梯度图,而不是最终的边缘结果,当然不会是单像素边缘。

       梯度算子Prewitt和Sobel它们的计算过程比较简单,所以计算出来的结果精度也就较低,利用它们进行检测出来的图像轮 廓比较简单,对于那些细的边缘以及裂纹缺陷就有可能检测不出来;相比于梯度算子,在检测效果方面,LOG滤波算子以及Canny算子会更好,对于图像的那些细节的边缘特征它们也可以检测得出来。研究表明,与LOG算子相比,采用Canny算子出来的边缘检测效果比较好,同时噪声干扰也较少

你可能感兴趣的:(图像处理)