《计算机视觉》:图像滤波与边缘检测

文章目录

  • 任务一:基本处理
    • 高斯滤波
      • 原理
      • 代码
      • 结果
      • 分析
    • 高斯拉普拉斯边缘检测
      • 原理
      • 代码
      • 结果
      • 分析
  • 任务二:Canny检测子
    • 原理
    • 代码
    • 结果
    • 分析
  • 任务三(拓展-可选):

代码:matlab或python(可调用库函数,任务二需要自己实现)

拿anaconda navigator搜索opencv,然后勾选opencv安装即可
《计算机视觉》:图像滤波与边缘检测_第1张图片
lena灰度图
《计算机视觉》:图像滤波与边缘检测_第2张图片
lena彩色图
《计算机视觉》:图像滤波与边缘检测_第3张图片

任务一:基本处理

数据:lena灰度图和彩色图
要求:分别对lena灰度图和彩色图进行不同尺度和不同窗口大小的滤波和边缘检测
滤波器:高斯滤波核
边缘检测子:高斯拉普拉斯检测子
窗口大小:从3*3到29*29,以2为间隔
尺度因子:从1像素到11像素,以1为间隔

高斯滤波

原理

高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个窗口(或称卷积、掩模)扫描图像中的每一个像素,用窗口确定的邻域内像素的加权平均灰度值去替代窗口中心像素点的值。⾼斯滤波模板是通过⼆维⾼斯函数计算出来的。
在这里插入图片描述

代码

import cv2 as cv

# 原图
lena512color = cv.imread('./lena512color.tiff')
cv.imshow('src', lena512color)

# 滤波后的图
for kernel_size in range(3, 30, 2):
    for sigma in range(1, 12, 1):
        cv.imshow('kernel_size: ' + str(kernel_size) + '*' + str(kernel_size) +
                  ' ' + 'sigmaX: ' + str(sigma) + ' ' + 'sigmaY: ' + str(sigma),
                  cv.GaussianBlur(lena512color, (kernel_size, kernel_size), sigma, sigma))
        # 按q退出
        key = cv.waitKey(0)
        if key == ord('q'):
            exit()

结果

分析

  1. σ值越大,滤波效果越好,图像越模糊。因为调整σ实际是在调整周围像素对当前像素的影响程度,调大σ即提高了远处像素对中心像素的影响程度,滤波结果也就越平滑。
  2. 窗口取得越大,滤波效果越好,同样图像也会变得越模糊,因为如果窗口越⼤,像素的邻域就越⼤,和邻域内其他像素点的值经过加权平均的结果也就和附近的像素差的越小,从⽽显⽰的更加模糊。

高斯拉普拉斯边缘检测

过程为先进行高斯滤波后进行拉普拉斯边缘检测

原理

拉普拉斯算⼦(Laplance operator) 是⼆阶微分线性算⼦,在图像边缘处理中,⼆阶微分的边缘定位能力更强,锐化效果更好,因此在进⾏图像边缘处理时,直接采⽤⼆阶微分算⼦⽽不使⽤⼀阶微分。 在边缘部分,像素值出现”跳跃“或者较⼤的变化。此边缘部分求取⼀阶导数,会看到极值的出现;此边 缘部分求取⼆阶导数,会看到⼀阶导数的极值位置,⼆阶导数为0。所以我们也可以⽤这个特点来作为检 测图像边缘的⽅法。 但是, ⼆阶导数的0值不仅仅出现在边缘,它们也可能出现在⽆意义的位置,但是 我们可以过滤掉这些点。
《计算机视觉》:图像滤波与边缘检测_第4张图片
拉普拉斯算⼦是x和y⽅向的向量和,是通过⼆元参数的⼆阶偏微分和和得来的。
在这里插入图片描述

代码

在高斯滤波代码的基础上加一个拉普拉斯函数就可以了

import cv2 as cv

# 原图
lena512color = cv.imread('./lena512color.tiff')
lena512 = cv.imread('./lena512.bmp')
cv.imshow('lena512color', lena512color)
cv.imshow('lena512', lena512)


# 处理图像并显示
def GBL(src, name, kernel_size, sigma):
    cv.imshow(name + ' ' + 'kernel_size: ' + str(kernel_size) + '*' + str(kernel_size) +
              ' ' + 'sigmaX: ' + str(sigma) + ' ' + 'sigmaY: ' + str(sigma),
              cv.Laplacian(cv.GaussianBlur(src, (kernel_size, kernel_size), sigma, sigma), -1, ksize=3))


# 滤波后的图
for kernel_size in range(3, 30, 2):
    for sigma in range(1, 12, 1):
        GBL(lena512color, 'lena512color', kernel_size, sigma)
        GBL(lena512, 'lena512', kernel_size, sigma)
        # 按q退出
        key = cv.waitKey(0)
        if key == ord('q'):
            exit()

结果

分析

二阶微分算子检测的边缘更加准确,对边缘的定位能力更强。但是相较于一阶微分算子,不能保留梯度的方向信息,并且对噪声更为敏感。

任务二:Canny检测子

数据:lena灰度图
要求:按照步骤实现lena灰度图的Canny边缘检测算法,与标准算法结果进行对比

原理

  1. canny 算子简介
           Canny边缘检测算子是John F.Canny于1986年开发出来的一个多级边缘检测算法。更为重要的是,Canny创立了边缘检测计算理论(Computational theoryofedge detection),解释了这项技术是如何工作的。Canny边缘检测算法以Canny的名字命名,被很多人推崇为当今最优的边缘检测的算法。
           其中,Canny的目标是找到一个最优的边缘检测算法,让我们看一下最优边缘检测的三个主要评价标准。
  • 低错误率:标识出尽可能多的实际边缘,同时尽可能地减少噪声产生的误报。
  • 高定位性:标识出的边缘要与图像中的实际边缘尽可能接近。
  • 最小响应:图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。

       为了满足这些要求,Canny使用了变分法,这是一种寻找满足特定功能的函数的方法。最优检测用4个指数函数项的和表示,但是它非常近似于高斯函数的一阶导数。

  1. Canny边缘检测的步骤
    (1)【第一步】消除噪声
    一般情况下,使用高斯平滑滤波器卷积降噪。以下显示了一个size = 5 的高斯内核示例:
    《计算机视觉》:图像滤波与边缘检测_第5张图片
    (2)【第二步】计算梯度幅值和方向此处,按照 Sobel滤波器的步骤来操作。

    1. 运用一对卷积阵列(分别作用于x和 y方向)
      在这里插入图片描述

    2. 使用下列公式计算梯度幅值和方向
      在这里插入图片描述
      而梯度方向一般取这4个可能的角度之一——0度,45度,90度,135度。

    (3)【第三步】非极大值抑制
    这一步排除非边缘像素,仅仅保留了一些细线条(候选边缘)。
    (4)【第四步】滞后阈值
    这是最后一步,Canny使用了滞后阈阙值,滞后阈值需要两个阈值(高阈值和低阀值):
    ①若某一像素位置的幅值超过高阈值,该像素被保留为边缘像素。
    ②若某一像素位置的幅值小于低阈值,该像素被排除。
    若某一像素位置的幅值在两个阀值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留。
    对于Canny函数的使用,推荐的高低阈值比在2:1到3:1之间。

代码

import numpy as np
import cv2 as cv


# 使用高斯检测子计算
def myGaussBlur(image, ksize=5, sigma=1.5):
    """
    高斯滤波实现,高斯滤波内核的计算公式如下所示:
        G[i, j] = (1/(2*pi*sigma**2))*exp(-((i-k-1)**2 + (j-k-1)**2)/2*sigma**2)
    :param image: 输入原始图片
    :param ksize: 高斯滤波核大小,这里高斯滤波核长宽是相等的
    :param sigma: 高斯滤波的sigma参数
    :return: 输出滤波后的图片
    """
    # 计算高斯滤波器
    k = ksize // 2
    gaussian = np.zeros([ksize, ksize])
    for i in range(ksize):
        for j in range(ksize):
            gaussian[i, j] = np.exp(-((i - k) ** 2 + (j - k) ** 2) / (2 * sigma ** 2))
    gaussian /= 2 * np.pi * sigma ** 2
    # 归一化处理
    gaussian = gaussian / np.sum(gaussian)

    # 对图像进行高斯滤波
    W, H = image.shape
    new_image = np.zeros([W - k * 2, H - k * 2])

    for i in range(W - 2 * k):
        for j in range(H - 2 * k):
            new_image[i, j] = np.sum(image[i:i + ksize, j:j + ksize] * gaussian)

    new_image = np.uint8(new_image)

    return new_image


# 确定梯度幅值和方向
def get_gradient_and_direction(image):
    """
    计算图像的梯度幅值和方向
    使用Sobel算子来进行计算,其中:
         -1 0 1        -1 -2 -1
    Gx = -2 0 2   Gy =  0  0  0
         -1 0 1         1  2  1
    :param image: 输入图像
    :return:每个像素的梯度幅值, 每个梯度幅值方向
    """
    # 定义Gx与Gy
    Gx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
    Gy = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])

    W, H = image.shape
    gradients = np.zeros([W - 2, H - 2])
    direction = np.zeros([W - 2, H - 2])

    # 计算梯度幅值和方向
    for i in range(W - 2):
        for j in range(H - 2):
            dx = np.sum(image[i:i + 3, j:j + 3] * Gx)
            dy = np.sum(image[i:i + 3, j:j + 3] * Gy)
            gradients[i, j] = np.sqrt(dx ** 2 + dy ** 2)
            if dx == 0:
                direction[i, j] = np.pi / 2
            else:
                direction[i, j] = np.arctan(dy / dx)

    gradients = np.uint8(gradients)

    return gradients, direction


# 非极大值抑制
def NMS(gradients, direction):
    """
    非极大值抑制
    :param gradients: 梯度幅值
    :param direction: 方向
    :return: 处理后的图像
    """
    W, H = gradients.shape
    nms = np.copy(gradients[1:-1, 1:-1])

    # 进行非极大值抑制
    for i in range(1, W - 1):
        for j in range(1, H - 1):
            theta = direction[i, j]
            weight = np.tan(theta)
            if theta > np.pi / 4:
                d1 = [0, 1]
                d2 = [1, 1]
                weight = 1 / weight
            elif theta >= 0:
                d1 = [1, 0]
                d2 = [1, 1]
            elif theta >= - np.pi / 4:
                d1 = [1, 0]
                d2 = [1, -1]
                weight *= -1
            else:
                d1 = [0, -1]
                d2 = [1, -1]
                weight = -1 / weight

            g1 = gradients[i + d1[0], j + d1[1]]
            g2 = gradients[i + d2[0], j + d2[1]]
            g3 = gradients[i - d1[0], j - d1[1]]
            g4 = gradients[i - d2[0], j - d2[1]]

            grade_count1 = g1 * weight + g2 * (1 - weight)
            grade_count2 = g3 * weight + g4 * (1 - weight)

            if grade_count1 > gradients[i, j] or grade_count2 > gradients[i, j]:
                nms[i - 1, j - 1] = 0

    return nms


# 边缘阈值和连接 (滞后法)
def double_threshold(nms, threshold1, threshold2):
    """
    双阈值选择与边缘连接
    通过假设两个阈值:低阈值TL和高阈值TH,并做如下的处理
    a.对于任意边缘像素低于TL的则丢弃
    b.对于任意边缘像素高于TH的则保留
    c.对于任意边缘像素值在TL与TH之间的,如果能通过边缘连接到一个像素大于TH而且边缘所有像素大于最小阈值TL的则保留,否则丢弃。
    :param nms: 输入的非最大化抑制图像
    :param threshold1: 低阈值
    :param threshold2: 高阈值
    :return: 二值图
    """
    visited = np.zeros_like(nms)
    output_image = nms.copy()
    W, H = output_image.shape

    # 内嵌一个深度搜索函数,处理情况b、c
    def dfs(i, j):
        if i >= W or i < 0 or j >= H or j < 0 or visited[i, j] == 1:
            return
        visited[i, j] = 1
        if output_image[i, j] > threshold1:
            output_image[i, j] = 255
            dfs(i - 1, j - 1)
            dfs(i - 1, j)
            dfs(i - 1, j + 1)
            dfs(i, j - 1)
            dfs(i, j + 1)
            dfs(i + 1, j - 1)
            dfs(i + 1, j)
            dfs(i + 1, j + 1)
        else:
            output_image[i, j] = 0

    for w in range(W):
        for h in range(H):
            if visited[w, h] == 1:
                continue
            if output_image[w, h] >= threshold2:  # 处理情况b、c
                dfs(w, h)
            elif output_image[w, h] <= threshold1:  # 处理情况a
                output_image[w, h] = 0
                visited[w, h] = 1

    # 处理没有被访问到的像素点
    for w in range(W):
        for h in range(H):
            if visited[w, h] == 0:
                output_image[w, h] = 0

    return output_image


if __name__ == "__main__":
    # 读取图片
    image = cv.imread("./lena512.bmp", 0)
    # 进行滤波,这里用的是手写的滤波方法
    smoothed_image = myGaussBlur(image)
    # smoothed_image = cv.GaussianBlur(image, (5, 5), 1.5)
    # 确定梯度幅值和方向
    gradients, direction = get_gradient_and_direction(smoothed_image)
    # 非极大值抑制
    nms = NMS(gradients, direction)
    # 边缘阈值和连接 (滞后法)
    output_image = double_threshold(nms, 30, 100)

    # 使用标准算法进行计算并且比较
    canny = cv.Canny(smoothed_image, 75, 150)
    # 两张图片进行与运算,计算不同的像素点数目和占比
    # 如何解决两张图片分辨率不对应的问题?(不可以resize!)
    # canny = cv.resize(canny, (500, 500))
    # output_image = cv.resize(output_image, (500, 500))
    canny = canny[4:504, 4:504]
    output_image = output_image[2:502, 2:502]
    result = canny ^ output_image
    total = np.count_nonzero(result)
    acc = total / np.count_nonzero(output_image)
    print("不同的像素点数:\t", total)
    print("边缘检测结果的非零像素点数:\t", np.count_nonzero(output_image))

    # 显示最后的结果
    cv.imshow("raw image", image)
    cv.imshow("Gauss Blur", smoothed_image)
    cv.imshow("gradients", gradients)
    cv.imshow("nms", nms)
    cv.imshow("my canny", output_image)
    cv.imshow("opencv canny", canny)
    cv.waitKey(0)

结果

分析

canny的边缘少且受噪声影响小,手写的canny和opencv自带的canny都比拉普拉斯的效果好

任务三(拓展-可选):

数据集:BSDS500数据集 https://www2.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/resources.html
要求:使用Canny边缘检测或某一种边缘检测算法对测试集进行边缘检测,与Ground-Truth进行对比,计算出精确率和召回率
具体:结果可参照网页中的结果图

你可能感兴趣的:(NWPU,计算机视觉,opencv,python)