角检测算法为倾斜边缘提供非常高的值?

Corner detection algorithm gives very high value for slanted edges?

我已经尝试实现 shi-tomasi 角点检测算法的基本版本。该算法适用于角落,但我遇到了一个奇怪的问题,该算法还为倾斜(标题)边缘提供了高值。

这是我所做的

我预计它会为 dx 和 dy 都很高的角提供最大值,但我发现它甚至为带标题的边提供高值。

这是我收到的图像的一些输出:

结果:

到目前为止一切顺利,角点值很高。

另一张图片:

结果:

问题就出在这里。边缘具有算法不期望的高值。我无法理解边缘如何对 x 和 y 梯度都具有高值(sobel 是梯度的近似值)。

我想请求你的帮助,如果你能帮我解决边缘的这个问题。我愿意接受任何建议和想法。

这是我的代码(如果有帮助的话):

def st(image, w_size):
    v = []
    dy, dx = sy(image), sx(image)

    dy = dy**2
    dx = dx**2
    dxdy = dx*dy

    dx = cv2.GaussianBlur(dx, (3,3), cv2.BORDER_DEFAULT)
    dy = cv2.GaussianBlur(dy, (3,3), cv2.BORDER_DEFAULT)
    dxdy = cv2.GaussianBlur(dxdy, (3,3), cv2.BORDER_DEFAULT)

    ofset = int(w_size/2)
    for y in range(ofset, image.shape[0]-ofset):
        for x in range(ofset, image.shape[1]-ofset):

            s_y = y - ofset
            e_y = y + ofset + 1

            s_x = x - ofset
            e_x = x + ofset + 1

            w_Ixx = dx[s_y: e_y, s_x: e_x]
            w_Iyy = dy[s_y: e_y, s_x: e_x]
            w_Ixy = dxdy[s_y: e_y, s_x: e_x]

            sum_xx = w_Ixx.sum()
            sum_yy = w_Iyy.sum()
            sum_xy = w_Ixy.sum()
            #sum_r = w_r.sum()

            m = np.matrix([[sum_xx, sum_xy],
                         [sum_xy, sum_yy]])

            eg = np.linalg.eigvals(m)

            v.append((min(eg[0], eg[1]), y, x))
    return v

def sy(img):
    t = cv2.Sobel(img,cv2.CV_8U,0,1,ksize=3)
    return t
def sx(img):
    t = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=3)
    return t

如果你在寻找角点,为什么不看看 Harris 角点检测?
该检测器不仅查看一阶导数,还查看二阶导数以及它们在 loca 邻域中的排列方式。

ans = cv2.cornerHarris(np.float32(gray)/255., 2, 3, 0.03)

查看像素 ans > 0.001:

如您所见,检测到大部分角,但未检测到倾斜边缘。 您可能需要稍微调整一下 Harris 检测器的参数以获得更好的结果。

我强烈建议您阅读此检测器背后的解释和基本原理,以及它如何能够可靠地区分拐角和倾斜边缘。确保您了解这与您发布的方法有何不同。

你误解了Shi-Tomasi method. You are computing the two derivatives dx and dy, locally averaging them (the sum is different from the local average by a constant factor which we can ignore), and then taking the lowest value. The Shi-Tomasi equation refers to the Structure Tensor,它使用了这个矩阵的两个特征值中的最小值。

Structure张量是梯度与其自身外积,然后平滑后形成的矩阵:

[ smooth(dx*dx)  smooth(dx*dy) ]
[ smooth(dx*dy)  smooth(dy*dy) ]

也就是我们取x导数dx和y导数dy,形成三个图像dx*dxdy*dydx*dy,并对这三幅图像进行平滑处理。现在对于每个像素,我们有三个值一起形成一个对称矩阵。这叫做结构张量。

这个结构张量的特征值说明了局部边缘。如果两者都很小,则邻域中没有边。如果一个很大,那么在局部邻域中只有一个边缘方向。如果两者都很大,则说明发生了更复杂的事情,可能是一个角落。平滑度越大 window,我们检查的局部邻域就越大。 select 与我们正在查看的结构大小相匹配的邻域大小很重要。

结构张量的特征向量说明了局部结构的方向。如果有一条边(一个特征值很大),那么对应的特征向量就是这条边的法线。

Shi-Tomasi 使用两个特征值中的最小值。如果两个特征值中最小的一个很大,那么在局部邻域中发生的事情比边缘更复杂。

Harris corner detector同样使用了Structure tensor,但是它结合了行列式和trace,以更少的计算成本获得了相似的结果。 Shi-Tomasi 更好但计算成本更高,因为特征值计算需要计算平方根。 Harris 检测器是 Shi-Tomasi 检测器的近似值。

这是对 Shi-Tomasi(上)和 Harris(下)的比较。我将两者都削减了最大值的一半,因为最大值出现在文本区域,这让我们可以更好地看到对相关角的较弱响应。正如您所看到的,Shi-Tomasi 对图像中的所有角落都有更均匀的响应。

对于这两个,我使用了 sigma=2 的高斯 window 进行局部平均(使用 3 sigma 的截止值,导致 13x13 平均 window) .


查看您更新后的代码,我发现了几个问题。我在这里用评论注释了这些:

def st(image, w_size):
    v = []
    dy, dx = sy(image), sx(image)

    dy = dy**2
    dx = dx**2
    dxdy = dx*dy
    # Here you have dxdy=dx**2 * dy**2, because dx and dy were changed
    # in the lines above.

    dx = cv2.GaussianBlur(dx, (3,3), cv2.BORDER_DEFAULT)
    dy = cv2.GaussianBlur(dy, (3,3), cv2.BORDER_DEFAULT)
    dxdy = cv2.GaussianBlur(dxdy, (3,3), cv2.BORDER_DEFAULT)
    # Gaussian blur size should be indicated with the sigma of the Gaussian,
    # not with the size of the kernel. A 3x3 kernel corresponds, in OpenCV,
    # to a Gaussian with sigma = 0.8, which is way too small. Use sigma=2.

    ofset = int(w_size/2)
    for y in range(ofset, image.shape[0]-ofset):
        for x in range(ofset, image.shape[1]-ofset):

            s_y = y - ofset
            e_y = y + ofset + 1

            s_x = x - ofset
            e_x = x + ofset + 1

            w_Ixx = dx[s_y: e_y, s_x: e_x]
            w_Iyy = dy[s_y: e_y, s_x: e_x]
            w_Ixy = dxdy[s_y: e_y, s_x: e_x]

            sum_xx = w_Ixx.sum()
            sum_yy = w_Iyy.sum()
            sum_xy = w_Ixy.sum()
            # We've already done the local averaging using GaussianBlur,
            # this summing is now no longer necessary.

            m = np.matrix([[sum_xx, sum_xy],
                         [sum_xy, sum_yy]])

            eg = np.linalg.eigvals(m)

            v.append((min(eg[0], eg[1]), y, x))
    return v

def sy(img):
    t = cv2.Sobel(img,cv2.CV_8U,0,1,ksize=3)
    # The output of Sobel has positive and negative values. By writing it
    # into a 8-bit unsigned integer array, you lose all these negative
    # values, they become 0. This is half your edges that you lose!
    return t
def sx(img):
    t = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=3)
    return t

我是这样修改你的代码的:

import cv2
import numpy as np

def st(image):
    dy, dx = sy(image), sx(image)
    dxdx = cv2.GaussianBlur(dx**2, ksize = None, sigmaX=2)
    dydy = cv2.GaussianBlur(dy**2, ksize = None, sigmaX=2)
    dxdy = cv2.GaussianBlur(dx*dy, ksize = None, sigmaX=2)
    for y in range(image.shape[0]):
        for x in range(image.shape[1]):
            m = np.matrix([[dxdx[y,x], dxdy[y,x]],
                           [dxdy[y,x], dydy[y,x]]])
            eg = np.linalg.eigvals(m)
            image[y,x] = min(eg[0], eg[1])  # Write into the input image.
                                            # Better would be to create a new
                                            # array as output. Make sure it is
                                            # a floating-point type!

def sy(img):
    t = cv2.Sobel(img,cv2.CV_32F,0,1,ksize=3)
    return t

def sx(img):
    t = cv2.Sobel(img,cv2.CV_32F,1,0,ksize=3)
    return t

image = cv2.imread('fu4r5.png', 0)
output = image.astype(np.float32)  # I'm writing the result of the detector in here
st(output)
pp.imshow(output); pp.show()