图像处理边缘检测之梯度的理解和应用

在边缘处理中需要通过计算梯度来检测定位边缘,在Opencv中可以使用sobel算子来分别计算出水平和垂直方向的梯度,再通过梯度来计算出梯度幅值和方向。

sobel算子计算梯度原理:

sobel算子使用两个3*3的卷积核来计算水平和垂直方向的梯度,卷积核如下:

水平方向卷积核:

垂直方向卷积核:

计算过程如下:

对于给定的图像块 I:

水平方向梯度Gx计算:

垂直方向梯度Gy计算:

在计算完图像的X和Y方向的梯度值之后再根据公式计算梯度幅值

计算公式如下:

计算梯度方向:

梯度的应用:

梯度应用最常见的地方就在于对图像边缘的提取,下面我用opnecv来模拟实现canny的边缘轮廓提取。

import cv2
import numpy as np

def test_canny():
    # 读取图像
    image = cv2.imread(r'D:\wangtianqing\code\script\iou\data\front\front_1_1_1565.png')
    # 转换为灰度图像
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # 使用高斯滤波器平滑图像
    blurred = cv2.GaussianBlur(gray, (3, 3), 1.4)

    # 使用Canny边缘检测
    edges = cv2.Canny(blurred, threshold1=60, threshold2=130)

    # 显示结果
    cv2.imshow('Edges', edges)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    cv2.imwrite("canny_edges.jpg", edges)


def test_reproduce_canny():
    # 读取图像
    image = cv2.imread(r'D:\wangtianqing\code\script\iou\data\front\front_1_1_1565.png')

    # 转换为灰度图像
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # 使用高斯滤波器平滑图像
    blurred = cv2.GaussianBlur(gray, (3, 3), 1.4)
    # cv2.imshow("gray", gray)
    # cv2.imshow("blurred", blurred)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()

    # 使用Sobel算子计算梯度
    grad_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
    grad_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
    # cv2.imshow("sobel-grad_x", grad_x)
    # cv2.imshow("sobel-grad_y", grad_y)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()

    # # 计算梯度幅值和方向
    magnitude = np.sqrt(grad_x ** 2 + grad_y ** 2)
    direction = np.arctan2(grad_y, grad_x)
    # print(f"magnitude: {magnitude}, direction: {direction}")

    # 获取梯度方向的角度(0-180度)
    angle = np.degrees(direction)
    # angle = direction * 180.0 / np.pi
    angle[angle < 0] += 180

    # 创建一个空的非极大值抑制后的图像
    nms = np.zeros_like(magnitude, dtype=np.uint8)

    for i in range(1, magnitude.shape[0] - 1):
        for j in range(1, magnitude.shape[1] - 1):
            q = 255
            r = 255
            ang = angle[i, j]
            # Angle 0
            if (0 <= ang < 22.5) or (157.5 <= ang <= 180):
                q = magnitude[i, j + 1]
                r = magnitude[i, j - 1]
            # Angle 45
            elif 22.5 <= ang < 67.5:
                # q = magnitude[i + 1, j - 1]
                # r = magnitude[i - 1, j + 1]
                q = magnitude[i - 1, j - 1]
                r = magnitude[i + 1, j + 1]
            # Angle 90
            elif 67.5 <= ang < 112.5:
                q = magnitude[i + 1, j]
                r = magnitude[i - 1, j]
            # Angle 135
            elif 112.5 <= ang < 157.5:
                # q = magnitude[i - 1, j - 1]
                # r = magnitude[i + 1, j + 1]
                q = magnitude[i - 1, j + 1]
                r = magnitude[i + 1, j - 1]
            magt = magnitude[i, j]
            if magt >= q and magt >= r:
                nms[i, j] = magt
            else:
                nms[i, j] = 0

    cv2.imshow('nms', nms)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    # 设置双阈值
    low_threshold = 60
    high_threshold = 130

    # 创建一个空的双阈值图像
    thresholded = np.zeros_like(nms, dtype=np.uint8)

    # 强边缘
    strong_i, strong_j = np.where(nms >= high_threshold)
    # 弱边缘
    weak_i, weak_j = np.where((nms <= high_threshold) & (nms >= low_threshold))

    # 标记强边缘和弱边缘
    thresholded[strong_i, strong_j] = 255
    thresholded[weak_i, weak_j] = 50
    cv2.imshow("StrongAndWeak", thresholded)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    # 获取图像大小
    M, N = thresholded.shape

    # extend_num = 1
    # 连接边缘
    for i in range(1, M - 1):
        for j in range(1, N - 1):
            if thresholded[i, j] == 50:
                if (255 in thresholded[i - 1:i + 2, j - 1:j + 2]):
                    thresholded[i, j] = 255
                else:
                    thresholded[i, j] = 0

    # 最终边缘图像
    edges = thresholded

    # 显示结果
    cv2.imshow('Edges', edges)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    cv2.imwrite("Edges-0.jpg", edges)


if __name__ == '__main__':
    # test_canny()
    test_reproduce_canny()

运行结果:

一样的滤波值以及双阈值的canny算子运行结果:

以上与canny运行的结果来看效果差不少,canny的结果更加贴合实际。经调查自写的算法与canny算子实现上有以下差距:

  • 实现细节的差异

OpenCV的Canny函数在内部有一些优化和细节处理,比如更精确的边缘连接和更高效的算法实现,这些细节可能在手动实现时容易被忽略或实现不当。

  • 参数选择和调整

OpenCV的Canny函数在内部可能会对参数进行一些自动调整或优化,而手动实现时,参数选择不当可能导致结果不佳。特别是高斯滤波器的标准差、Sobel算子的核大小、以及高低阈值的选择,都需要仔细调节。

  • 边缘连接的实现

双阈值处理阶段的弱边缘连接实现起来比较复杂,OpenCV在这一阶段可能有更智能的处理方法,从而获得更连贯的边缘。

  • 非极大值抑制的精度

非极大值抑制阶段的实现也需要很高的精度,OpenCV可能在这一阶段有一些优化策略,而手动实现时可能会在梯度方向的计算或比较过程中引入误差。

改进方向:

  1. 调整参数

调节高斯滤波的参数,使得噪音更少,并且边缘轮廓保持清晰

调节双阈值参数,使得强和弱两种边缘轮廓更合理

调节sobel核大小,使得提取的梯度信息尽可能准确

2. 优化非极大值抑制

确保梯度方向的计算和比较过程准确无误,避免误差积累。

优化非极大值抑制代码,保留更多的局部最大值

3. 优化边缘连接策略

优化强弱边缘的连接策略。

优化后代码:

import cv2
import numpy as np

def non_max_suppression(magnitude, angle):
    rows, cols = magnitude.shape
    nms = np.zeros((rows, cols), dtype=np.float32)

    angle = angle * 180.0 / np.pi  # 将弧度转换为角度
    angle[angle < 0] += 180

    for i in range(1, rows - 1):
        for j in range(1, cols - 1):
            q = 255
            r = 255

            if (0 <= angle[i, j] < 22.5) or (157.5 <= angle[i, j] <= 180):
                q = magnitude[i, j + 1]
                r = magnitude[i, j - 1]
            elif 22.5 <= angle[i, j] < 67.5:
                q = magnitude[i + 1, j - 1]
                r = magnitude[i - 1, j + 1]
            elif 67.5 <= angle[i, j] < 112.5:
                q = magnitude[i + 1, j]
                r = magnitude[i - 1, j]
            elif 112.5 <= angle[i, j] < 157.5:
                q = magnitude[i - 1, j - 1]
                r = magnitude[i + 1, j + 1]

            if (magnitude[i, j] >= q) and (magnitude[i, j] >= r):
                nms[i, j] = magnitude[i, j]
            else:
                nms[i, j] = 0

    return nms


def double_threshold(nms, low_threshold, high_threshold):
    strong_edges = (nms >= high_threshold).astype(np.uint8)
    weak_edges = ((nms >= low_threshold) & (nms < high_threshold)).astype(np.uint8)
    edges = np.zeros_like(nms, dtype=np.uint8)

    edges[strong_edges == 1] = 255
    edges[weak_edges == 1] = 75

    rows, cols = edges.shape
    for i in range(1, rows - 1):
        for j in range(1, cols - 1):
            if edges[i, j] == 75:
                if (edges[i + 1, j - 1] == 255 or edges[i + 1, j] == 255 or edges[i + 1, j + 1] == 255
                        or edges[i, j - 1] == 255 or edges[i, j + 1] == 255
                        or edges[i - 1, j - 1] == 255 or edges[i - 1, j] == 255 or edges[i - 1, j + 1] == 255):
                    edges[i, j] = 255
                else:
                    edges[i, j] = 0
    return edges

# 读取图像并转换为灰度图像
image = cv2.imread(r'D:\wangtianqing\code\script\iou\data\front\front_1_1_1565.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 使用高斯滤波器平滑图像
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)

# 使用Sobel算子计算x方向和y方向上的梯度
grad_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
grad_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)

# 计算梯度幅值和方向
magnitude = np.sqrt(grad_x**2 + grad_y**2)
angle = np.arctan2(grad_y, grad_x)

# 非极大值抑制
nms = non_max_suppression(magnitude, angle)

# 双阈值化
low_threshold = 50
high_threshold = 150
edges = double_threshold(nms, low_threshold, high_threshold)

# 使用OpenCV的Canny函数进行对比
opencv_edges = cv2.Canny(blurred, low_threshold, high_threshold)

# 显示结果
# cv2.imshow('Original Image', image)
# cv2.imshow('Gray Image', gray)
# cv2.imshow('Blurred Image', blurred)
cv2.imshow('Custom Canny Edges', edges)
cv2.imshow('OpenCV Canny Edges', opencv_edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite("CustomEdges.jpg", edges)
cv2.imwrite("CannyEdges.jpg", opencv_edges)

对比优化前,轮廓的连接要好一些,整体上更贴近实际轮廓,但仍旧跟canny的轮廓提取有不小差距。

这里贴上测试用的原图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值