OpenCV —— 边缘检测(Roberts、Prewitt、Sobel、Scharr、Kirsch、Robinson、Canny边缘检测)

2023-11-03

图像的边缘指的是灰度值发生急剧变化的位置。在图像形成过程中,由于亮度、纹理、颜色、阴影等物理因素的不同而导致图像灰度值发生突变,从而形成边缘。边缘是通过检查每个像素的邻域并对其灰度变化进行量化的,这种灰度变化的量化相当于微积分里连续函数中方向导数或者离散数列的差分。

边缘检测大多数是通过基于方向导数掩码(梯度方向导数)求卷积的方法。计算灰度变化的卷积算子包含Roberts算子、Prewitt算子、Sobel算子、Scharr算子、Kirsch算子、Robinson算子、Laplacian算子,常用的检测方法有Canny边缘检测、Marr-Hidreth边缘检测。

大多数边缘检测算子是基于方向差分卷积核求卷积的方法,在使用由两个或者多个卷积核组成的边缘检测算子时,假设有 n 个卷积核,记 c o n v 1 , c o n v 2 , . . . , c o n v n \mathbf{conv}_1, \mathbf{conv}_2,...,\mathbf{conv}_n conv1,conv2,...,convn 为图像分别与个卷积核做卷积的结果,通常有四种方式来衡量最后输出的边缘强度。

  1. 取对应位置绝对值的和: ∑ i = 1 n ∣ c o n v i ∣ \sum_{i=1}^{n} |\mathbf{conv}_i| i=1nconvi
  2. 取对应位置平方和的开方: ∑ i = 1 n c o n v i 2 \sqrt{\sum_{i=1}^{n} \mathbf{conv}_i^2} i=1nconvi2
  3. 取对应位置绝对值的最大值: max ⁡ { ∣ c o n v 1 ∣ , ∣ c o n v 2 ∣ , . . . , ∣ c o n v i ∣ } \max{\{|\mathbf{conv}_1|, |\mathbf{conv}_2|, ..., |\mathbf{conv}_i|\}} max{conv1,conv2,...,convi}
  4. 插值法: ∑ i = 1 n a i ∣ c o n v i ∣ \sum_{i=1}^n a_i |\mathbf{conv}_i| i=1naiconvi,其中 a i > = 0 , 且 ∑ i = 1 n a i = 1 a_i >= 0, 且 \sum_{i=1}^n a_i = 1 ai>=0,i=1nai=1

其中取绝对值的最大值的方式,对边缘的走向有些敏感,而其他几种方式可以获得性能更一致的全方位响应。取平方和的开方的方式效果一般是最好的,但是同时会更加耗时。

Roberts 算子

Roberts 边缘检测是图像矩阵与以下两个卷积核分别做卷积(对于图像卷积的计算可查看这篇文章)。
R o b e r t s 135 = ( 1 0 0 − 1 ) ,        R o b e r t s 45 = ( 0 1 − 1 0 ) , \mathbf{Roberts}_{135} = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}, \;\;\; \mathbf{Roberts}_{45} = \begin{pmatrix} 0 & 1 \\ -1 & 0 \end{pmatrix}, Roberts135=(1001),Roberts45=(0110),
与 Roberts 核卷积,本质上是两个对角方向上的差分,与 R o b e r t s 135 \mathbf{Roberts}_{135} Roberts135 卷积后的结果取绝对值,反应的是 4 5 ∘ 45^{\circ} 45 方向上的灰度变化率;而与 R o b e r t s 45 \mathbf{Roberts}_{45} Roberts45 卷积后的结果取绝对值,反应的是 13 5 ∘ 135^{\circ} 135 方向上的灰度变化率,利用变化率对边缘强度进行数字衡量。对 Roberts 算子进行改进便可以反响在垂直方向和水平方向上的边缘。
R o b e r t s 135 = ( 1 − 1 ) ,        R o b e r t s 45 = ( 1 − 1 ) , \mathbf{Roberts}_{135} = \begin{pmatrix} 1 & -1 \end{pmatrix}, \;\;\; \mathbf{Roberts}_{45} = \begin{pmatrix} 1 \\ -1 \end{pmatrix}, Roberts135=(11),Roberts45=(11),
Python 示例

使用函数 convolve2d 实现图像矩阵分别与两个 Roberts 核的卷积。

import cv2 as cv
import numpy as np
from scipy import signal


def roberts(img, boundary='fill', fillvalue=0):
    h, w = img.shape[:2]
    h_k, w_k = 2, 2
    # 卷积核1及锚点的位置
    r1 = np.array([[1,0],[0,-1]], np.float32)
    kr1, kc1 = 0, 0
    # 计算full卷积
    img_conv_r1 = signal.convolve2d(img, r1, mode='full', boundary=boundary, fillvalue=fillvalue)
    # 根据锚点的位置截取 full 卷积,获得 same 卷积
    img_conv_r1 = img_conv_r1[h_k-kr1-1:h+h_k-kr1-1, w_k-kc1-1:w+w_k-kc1-1]
    # 卷积核2及锚点的位置
    r2 = np.array([[0, 1], [-1, 0]], np.float32)
    kr2, kc2 = 0, 1
    # 计算full卷积
    img_conv_r2 = signal.convolve2d(img, r2, mode='full', boundary=boundary, fillvalue=fillvalue)
    # 根据锚点的位置截取 full 卷积,获得 same 卷积
    img_conv_r2 = img_conv_r2[h_k - kr2 - 1:h + h_k - kr2 - 1, w_k - kc2 - 1:w + w_k - kc2 - 1]
    return img_conv_r1, img_conv_r2

if __name__ == '__main__':
    img = cv.imread("/img3.png", 0)
    cv.imshow('src', img)
    img_conv_r1, img_conv_r2 = roberts2(img)
    # 45 方向上的边缘强度的灰度级显示
    img_conv_r1 = np.abs(img_conv_r1)
    edge_45 = img_conv_r1.astype(np.uint8)
    cv.imshow('edge_45', edge_45)
    # 135 方向上的边缘强度
    img_conv_r2 = np.abs(img_conv_r2)
    edge_135 = img_conv_r2.astype(np.uint8)
    cv.imshow('edge_135', edge_135)
    # 用平方和的开方来衡量最后输出的边缘
    edge = np.sqrt(np.power(img_conv_r1, 2.0) + np.power(img_conv_r2, 2.0))
    edge = np.round(edge)
    edge[edge>255] = 255
    edge = edge.astype(np.uint8)
    # 显示边缘
    cv.imshow('edge', edge)
    cv.waitKey(0)
    cv.destroyAllWindows()

在这里插入图片描述

Roberts 边缘检测因为使用了很少的邻域像素来近似边缘强度,因此对图像中的噪声具有高度敏感性。因此,先对图像做平滑处理再进行Roberts边缘检测效果会更好。

Prewitt 边缘检测

标准的 Prewitt 边缘检测算子由以下两个卷积核组成。
p r e w i t t x = ( 1 0 − 1 1 0 − 1 1 0 − 1 ) ,        p r e w i t t y = ( 1 1 − 1 0 0 0 − 1 − 1 − 1 ) \mathbf{prewitt}_{x} = \begin{pmatrix} 1 & 0 & -1\\ 1 & 0 & -1\\ 1 & 0 & -1 \end{pmatrix}, \;\;\; \mathbf{prewitt}_{y} = \begin{pmatrix} 1 & 1 & -1\\ 0 & 0 & 0\\ -1 & -1 & -1 \end{pmatrix} prewittx=111000111,prewitty=101101101
图像与 p r e w i t t x \mathbf{prewitt}_{x} prewittx 卷积后可以反映图像垂直方向上的边缘,与 p r e w i t t y \mathbf{prewitt}_{y} prewitty 卷积后可以反映图像水平方向上的边缘。而且,这两个卷积核均是可分离的,其中
p r e w i t t x = ( 1 1 1 ) ★ ( 1 0 − 1 ) ,        p r e w i t t x = ( 1 1 1 ) ★ ( 1 0 − 1 ) \mathbf{prewitt}_{x} = \begin{pmatrix} 1 \\ 1 \\ 1 \end{pmatrix} \bigstar \begin{pmatrix} 1 & 0 & -1 \end{pmatrix}, \;\;\; \mathbf{prewitt}_{x} = \begin{pmatrix} 1 & 1 & 1 \end{pmatrix} \bigstar \begin{pmatrix} 1 \\ 0 \\ -1 \end{pmatrix} prewittx=111(101),prewittx=(111)101
从分离结果可以看出, p r e w i t t x \mathbf{prewitt}_{x} prewittx 算子实际上先对图像进行垂直方向上的非归一化的均值平滑,然后进行水平方向的差分;而 p r e w i t t y \mathbf{prewitt}_{y} prewitty 算子实际上先对图像进行水平方向上的非归一化的均值平滑,然后进行垂直方向上的差分。

由于对图像进行了平滑操作,所以对噪声较多的图像进行 Prewitt 边缘检测得到的边缘比 Roberts 要好。可以对标准的 Prewitt 算子进行改进,比如以下两个卷积核反映的是在 4 5 ∘ 45^{\circ} 45 13 5 ∘ 135^{\circ} 135 方向上的边缘。这两个卷积核是不可分离的。
p r e w i t t 135 = ( 1 1 0 1 0 − 1 0 − 1 − 1 ) ,        p r e w i t t y = ( 0 1 1 − 1 0 1 − 1 − 1 0 ) \mathbf{prewitt}_{135} = \begin{pmatrix} 1 & 1 & 0\\ 1 & 0 & -1\\ 0 & -1 & -1 \end{pmatrix}, \;\;\; \mathbf{prewitt}_{y} = \begin{pmatrix} 0 & 1 & 1\\ -1 & 0 & 1\\ -1 & -1 & 0 \end{pmatrix} prewitt135=110101011,prewitty=011101110
Python 示例

因为 Prewitt 算子是可分离的,所以为了减少耗时,在代码实现中,利用卷积运算的结合律先进性水平方向上的平滑,在进行垂直方向上的差分,或者先进行垂直方向上的平滑,再进行水平方向上的差分。

import cv2 as cv
import numpy as np
from scipy import signal


def prewitt(img, boundary='symm'):
    # 垂直方向上的均值平滑
    ones_y = np.array([[1], [1], [1]], np.float32)
    i_conv_pre_x = signal.convolve2d(img, ones_y, mode='same', boundary=boundary)
    # 水平方向的差分
    diff_x = np.array([[1, 0, -1]], np.float32)
    i_conv_pre_x = signal.convolve2d(i_conv_pre_x, diff_x, mode='same', boundary=boundary)

    # 水平方向上的均值平滑
    ones_x = np.array([[1,1,1]], np.float32)
    i_conv_pre_y = signal.convolve2d(img, ones_x, mode='same', boundary=boundary)
    # 垂直方向的差分
    diff_y = np.array([[1], [0], [-1]], np.float32)
    i_conv_pre_y = signal.convolve2d(i_conv_pre_y, diff_y, mode='same', boundary=boundary)
    return i_conv_pre_x, i_conv_pre_y


if __name__ == '__main__':
    img = cv.imread("/img7.jpg", 0)
    cv.imshow('src', img)
    i_conv_pre_x, i_conv_pre_y = prewitt(img)
    # 取绝对值,分别得到水平方向和垂直方向上的边缘强度
    abs_i_conv_pre_x = np.abs(i_conv_pre_x)
    abs_i_conv_pre_y = np.abs(i_conv_pre_y)
    # 水平方向和垂直方向上的边缘强度的灰度级显示
    edge_x = abs_i_conv_pre_x.copy()
    edge_y = abs_i_conv_pre_y.copy()
    edge_x[edge_x>255] = 255
    edge_y[edge_y>255] = 255
    edge_x = edge_x.astype(np.uint8)
    edge_y = edge_y.astype(np.uint8)
    cv.imshow('edge_x', edge_x)
    cv.imshow('edge_y', edge_y)
    # 利用 abs_i_conv_pre_x 和 abs_i_conv_pre_y 求最终的边缘强度
    # 求边缘强度,此处使用插值法
    edge = 0.5 * abs_i_conv_pre_x + 0.5 * abs_i_conv_pre_y
    edge[edge>255] = 255
    edge = edge.astype(np.uint8)
    cv.imshow('edge', edge)
    cv.waitKey(0)
    cv.destroyAllWindows()

在这里插入图片描述

从 Roberts 和 Prewitt 边缘检测的效果图可以清晰地理解差分方向(或称梯度方向)与得到的边缘是垂直的,如水平差分方向上的卷积放映的是垂直方向上的边缘。

图像平滑处理中 ,高斯平滑的效果往往比均值平滑要好,因此把 Prewitt 算子的非归一化的均值卷积核替换成非归一化的高斯卷积核,就可以构建 3 阶的 Sobel 边缘检测算子。

Sobel 边缘检测

3 阶的 Sobel 边缘检测算子
s o b e l x = ( 1 2 1 ) ★ ( 1 0 − 1 ) = ( 1 0 − 1 2 0 − 2 1 0 − 1 ) \mathbf{sobel}_{x} = \begin{pmatrix} 1 \\ 2 \\ 1 \end{pmatrix} \bigstar \begin{pmatrix} 1 & 0 & -1 \end{pmatrix} = \begin{pmatrix} 1 & 0 & -1 \\ 2 & 0 & -2\\ 1 & 0 & -1 \end{pmatrix} sobelx=121(101)=121000121

s o b e l y = ( 1 2 1 ) ★ ( 1 0 − 1 ) = ( 1 2 1 0 0 0 − 1 2 − 1 ) \mathbf{sobel}_{y} = \begin{pmatrix} 1 & 2 & 1 \end{pmatrix} \bigstar \begin{pmatrix} 1 \\ 0 \\ -1 \end{pmatrix} = \begin{pmatrix} 1 & 2 & 1\\ 0 & 0 & 0\\ -1 & 2 & -1 \end{pmatrix} sobely=(121)101=101202101

Sobel 的算子是可分离的,这是 Sobel 算子的标准形式,可以利用二项式展开式的系数构建窗口更大的 Sobel 算子,如 5x5、7x7等,窗口大小为奇数。

构建高阶的 Sobel 算子

Sobel 算子是在一个坐标轴方向上进行非归一化的高斯平滑,在另一个坐标轴方向上进行差分处理。 nxn 的 Sobel 算子是由平滑算子和差分算子 full 卷积而得到的,对于窗口大小为 n 的非归一化的高斯平滑算子等于 n-1 阶的二项式展开式的系数。窗口大小为 n 的差分算子是在 n-2 阶的二项式展开式的系数两侧补零,然后后向差分得到的。举例:构建 5 阶的非归一化的高斯平滑算子,取二项式的指数 n=4,然后计算展开式的系数,即

在这里插入图片描述

对于构建 5 阶的差分算子,令二项式的指数 n=5-2=3 ,然后计算展开式的系数,即

在这里插入图片描述

两侧补零,接着向后差分,得到差分后的结果即为 5 阶的差分算子,然后和 5 阶的平滑算子 full 卷积,即可得到 5x5 的 Sobel,Sobel平滑算子和差分算子的总结如下所示

n 窗口大小 平滑算子 差分算子
1 2 1 1 1 -1
2 3 1 2 1 1 0 -1
3 4 1 3 3 1 1 1 -1 -1
4 5 1 4 6 4 1 1 2 0 -2 -1

上表中的平滑算子就是帕斯卡三角形。Sobel 边缘检测算子是通过窗口大小为 k 的平滑算子和差分算子与图像卷积而得到的。高阶的 Sobel 边缘检测算子是可分离的。

Python 示例

import math
import cv2 as cv
import numpy as np
from scipy import signal


def pascal_smooth(n):
  	# 返回 n 阶的非归一化的高斯平滑算子
    pascal_smooth = np.zeros([1, n], np.float32)
    for i in range(n):
        pascal_smooth[0][i] = math.factorial(n-1) / (math.factorial(i) * math.factorial(n-1-i))
    return pascal_smooth

def pascal_diff(n):
  	# 返回 n 阶的差分算子
    pascal_diff = np.zeros([1, n], np.float32)
    pascal_smooth_previous = pascal_smooth(n-1)
    for i in range(n):
        if i == 0:
            # 恒等于 1
            pascal_diff[0][i] = pascal_smooth_previous[0][i]
        elif i == n-1:
            # 恒等于 -1
            pascal_diff[0][i] = - pascal_smooth_previous[0][i-1]
        else:
            pascal_diff[0][i] = pascal_smooth_previous[0][i] - pascal_smooth_previous[0][i-1]
    return pascal_diff


def get_sobel_kernel(n):
    pascal_smooth_kernel = pascal_smooth(n)
    pascal_diff_kernel = pascal_diff(n)
    # 水平方向的卷积核
    sobel_kerenl_x = signal.convolve2d(pascal_smooth_kernel.transpose(), pascal_diff_kernel, mode='full')
    # 垂直方向的卷积核
    sobel_kerenl_y = signal.convolve2d(pascal_smooth_kernel, pascal_diff_kernel.transpose(), mode='full')
    return sobel_kerenl_x, sobel_kerenl_y

def sobel(img, n):
    rows, cols = img.shape[:2]
    # 平滑算子
    pascal_smooth_kernel = pascal_smooth(n)
    # 差分算子
    pascal_diff_kernel = pascal_diff(n)
    # 水平方向上的 sobel 核卷积
    # 先进行垂直方向的平滑
    img_sobel_x = signal.convolve2d(img, pascal_smooth_kernel.transpose(), mode='same')
    # 再进行水平方向上的差分
    img_sobel_x = signal.convolve2d(img_sobel_x, pascal_diff_kernel, mode='same')
    # 垂直方向上的 sobel 核卷积
    img_sobel_y = signal.convolve2d(img, pascal_smooth_kernel, mode='same')
    img_sobel_y = signal.convolve2d(img_sobel_y, pascal_diff_kernel.transpose(), mode='same')

    return img_sobel_x, img_sobel_y


if __name__ == '__main__':
    img = cv.imread('img7.jpg', 0)
    cv.imshow('src', img)
    img_sobel_x, img_sobel_y = sobel(img, 3)

    img_sobel_x_c, img_sobel_y_c = img_sobel_x.copy(), img_sobel_y.copy()
    img_sobel_x_c, img_sobel_y_c = abs(img_sobel_x_c), abs(img_sobel_y_c)
    img_sobel_x_c[img_sobel_x_c>255] = 255
    img_sobel_y_c[img_sobel_y_c>255] = 255
    img_sobel_x_c = img_sobel_x_c.astype(np.uint8)
    img_sobel_y_c = img_sobel_y_c.astype(np.uint8)
    cv.imshow('sobel x', img_sobel_x_c)
    cv.imshow('sobel y', img_sobel_y_c)
    # 平方和开方的方式
    edge = np.sqrt(np.power(img_sobel_x, 2.0) + np.power(img_sobel_y, 2.0))

    # 直接截断显示
    edge_c = edge.copy()
    edge_c[edge_c > 255] = 255
    edge_c = edge_c.astype(np.uint8)
    cv.imshow('sobel edge 1', edge_c)
    # 归一化后显示,边缘强度的灰度级显示
    edge = edge/np.max(edge)
    edge = np.power(edge, 1)
    edge *= 255
    edge = edge.astype(np.uint8)
    cv.imshow('sobel edge 2', edge)

    cv.waitKey(0)
    cv.destroyAllWindows()

在这里插入图片描述

在这里插入图片描述

使用不同尺寸的 Sobel 核边缘检测效果,可以看出,使用高阶的 Sobel 核得到的边缘信息比低阶的更加丰富。

Opencv 函数

Sobel 函数官方地址

void cv::Sobel(InputArray 	src,
				OutputArray dst,
				int 		ddepth,
                int 		dx,
                int 		dy,
                int 		ksize = 3,
                double 	scale = 1,
                double 	delta = 0,
                int 	borderType = BORDER_DEFAULT 
				)		
//Python:
dst	= cv.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
参数 解释
ddepth 输出矩阵的深度
dx 当 dx ≠ \neq = 0 时,src 与差分方向为水平方向上的 Sobel 核卷积
dy 当 dx=0,dy$\neq$0时,src与差分方向为垂直方向上的 Sobel 核卷积
ksize sobel 核尺寸,当ksize=-1时,使用的是 Scharr 算子
scale 比例系数
delta 平移系数
borderType 边界扩充类型

对于 ksize,当 ksize=1 时,代表 Sobel 核没有平滑算子,只有差分算子,即如果设置参数 dx=1, dy=0,那么 src 只与 1x3 的水平方向上的差分算子 (1 0 -1) 卷积,没有平滑算子。通常,调用该函数时,( dx = 1,dy = 0,ksize = 3)或( dx = 0,dy = 1,ksize = 3)来分别获得与水平方向或垂直方向差分算子的卷积。

if __name__ == '__main__':
    img = cv.imread('img8.jpg', 0)
    cv.imshow('src', img)
    
    img_sobel_x = cv.Sobel(img, -1, dx=0, dy=1, ksize=5) # y方向差分
    img_sobel_y = cv.Sobel(img, -1, dx=1, dy=0, ksize=5) # x方向差分
    # 使用平方和开方计算边缘强度(也可以使用其他方法)
    img_edge = np.sqrt(np.power(img_sobel_x, 2.0) + np.power(img_sobel_y, 2.0))
    img_edge = edge / np.max(img_edge)
    img_edge *= 255
    img_edge = img_edge.astype(np.uint8)
    cv.imshow('cv sobel', img_edge)
    cv.waitKey(0)
    cv.destroyAllWindows()

Scharr算子

标准的 Scharr 边缘检测算子与 Prewitt 边缘检测算子和 3 阶的 Sobel 边缘检测算子类似,由以下两个卷积核组成,不同的是,这两个卷积核均是不可分离的。图像与水平方向上的 s c h a r r x \mathbf{scharr}_{x} scharrx 卷积结果反响的是垂直方向上的边缘强度,与垂直方向上的 s c h a r r y \mathbf{scharr}_{y} scharry 卷积结果反映的是水平方向上的边缘强度。
s c h a r r x = ( 3 0 − 3 10 0 − 10 3 0 − 3 ) ,        s c h a r r y = ( 3 10 3 0 0 0 − 3 − 10 − 3 ) \mathbf{scharr}_{x} = \begin{pmatrix} 3 & 0 & -3\\ 10 & 0 & -10\\ 3 & 0 & -3 \end{pmatrix}, \;\;\; \mathbf{scharr}_{y} = \begin{pmatrix} 3 & 10 & 3\\ 0 & 0 & 0\\ -3 & -10 & -3 \end{pmatrix} scharrx=31030003103,scharry=30310010303
同样,Scharr 边缘检测算子也可以扩展到其他方向,比如以下两个反映的是 13 5 ∘ 135^{\circ} 135 4 5 ∘ 45^{\circ} 45 方向上的边缘。
s c h a r r 45 = ( 0 3 10 − 3 0 3 − 10 − 3 0 ) ,        s c h a r r 135 = ( 10 3 0 3 0 − 3 0 − 3 − 10 ) \mathbf{scharr}_{45} = \begin{pmatrix} 0 & 3 & 10\\ -3 & 0 & 3\\ -10 & -3 & 0 \end{pmatrix}, \;\;\; \mathbf{scharr}_{135} = \begin{pmatrix} 10 & 3 & 0\\ 3 & 0 & -3\\ 0 & -3 & -10 \end{pmatrix} scharr45=03103031030,scharr135=10303030310
Opencv 函数

Scharr 函数官方地址

void cv::Scharr(InputArray 	src,
				OutputArray dst,
				int 		ddepth,
                int 		dx,
                int 		dy,
                double 		scale = 1,
                double 		delta = 0,
                int 		borderType = BORDER_DEFAULT 
                )		
//Python:
dst	= cv.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]

参数与 Sobel 相同,等同于

Sobel(src, dst, ddepth, dx, dy, FILTER_SCHARR, scale, delta, borderType)

与 Prewitt 边缘检测相比,因为 Scharr 卷积核中系数的增大,所以灰度变化较为敏感,即是灰度变化较小的区域,也会得到较强的边缘强度,所以得到的边缘图比 Prewitt 得到的边缘图显得丰富,但是不够细化。

Krisch 算子和Robinson 算子

Krisch算子由以下 8 个卷积核组成。图像与每一个核进行卷积,然后取绝对值作为对应方向上的边缘强度的量化。对 8 个卷积结果取绝对值,然后在对应值位置取最大值作为最后输出的边缘强度。
k 1 = ( 5 5 5 − 3 0 − 3 − 3 − 3 − 3 ) , k 2 = ( − 3 − 3 − 3 − 3 0 − 3 5 5 5 ) , k 3 = ( − 3 5 5 − 3 0 5 − 3 − 3 − 3 ) , k 4 = ( − 3 − 3 − 3 5 0 − 3 5 5 − 3 ) , \mathbf{k}_{1} = \begin{pmatrix} 5 & 5 & 5\\ -3 & 0 & -3\\ -3 & -3 & -3 \end{pmatrix}, \mathbf{k}_{2} = \begin{pmatrix} -3 & -3 & -3\\ -3 & 0 & -3\\ 5 & 5 & 5\\ \end{pmatrix}, \mathbf{k}_{3} = \begin{pmatrix} -3 & 5 & 5\\ -3 & 0 & 5\\ -3 & -3 & -3 \end{pmatrix}, \mathbf{k}_{4} = \begin{pmatrix} -3 & -3 & -3\\ 5 & 0 & -3 \\ 5 & 5 & -3 \end{pmatrix}, k1=533503533,k2=335305335,k3=333503553,k4=355305333,

k 5 = ( − 3 − 3 5 − 3 0 5 − 3 − 3 5 ) , k 6 = ( 5 − 3 − 3 5 0 − 3 5 − 3 − 3 ) , k 7 = ( − 3 − 3 − 3 − 3 0 5 − 3 5 5 ) , k 8 = ( 5 5 − 3 5 0 − 3 − 3 − 3 − 3 ) , \mathbf{k}_{5} = \begin{pmatrix} -3 & -3 & 5\\ -3 & 0 & 5\\ -3 & -3 & 5 \end{pmatrix}, \mathbf{k}_{6} = \begin{pmatrix} 5 & -3 & -3\\ 5 & 0 & -3\\ 5 & -3 & -3\\ \end{pmatrix}, \mathbf{k}_{7} = \begin{pmatrix} -3 & -3 & -3\\ -3 & 0 & 5\\ -3 & 5 & 5 \end{pmatrix}, \mathbf{k}_{8} = \begin{pmatrix} 5 & 5 & -3\\ 5 & 0 & -3 \\ -3 & -3 & -3 \end{pmatrix}, k5=333303555,k6=555303333,k7=333305355,k8=553503333,

Robinson 算子也由 8 个卷积核组成。
r 1 = ( 1 1 1 1 − 2 1 − 1 − 1 − 1 ) , r 2 = ( 1 1 1 − 1 − 2 1 − 1 − 1 1 ) , r 3 = ( − 1 1 1 − 1 − 2 1 − 1 1 1 ) , r 4 = ( − 1 − 1 1 − 1 0 1 1 1 1 ) , \mathbf{r}_{1} = \begin{pmatrix} 1 & 1 & 1\\ 1 & -2 & 1\\ -1 & -1 & -1 \end{pmatrix}, \mathbf{r}_{2} = \begin{pmatrix} 1 & 1 & 1\\ -1 & -2 & 1\\ -1 & -1 & 1\\ \end{pmatrix}, \mathbf{r}_{3} = \begin{pmatrix} -1 & 1 & 1\\ -1 & -2 & 1\\ -1 & 1 & 1 \end{pmatrix}, \mathbf{r}_{4} = \begin{pmatrix} -1 & -1 & 1\\ -1 & 0 & 1 \\ 1 & 1 & 1 \end{pmatrix}, r1=111121111,r2=111121111,r3=111121111,r4=111101111,

r 5 = ( − 1 − 1 − 1 1 − 2 1 1 1 1 ) , r 6 = ( 1 − 1 − 1 1 − 2 − 1 1 1 1 ) , r 7 = ( 1 1 − 1 1 − 2 − 1 1 1 − 1 ) , r 8 = ( 1 1 1 1 0 − 1 1 − 1 − 1 ) , \mathbf{r}_{5} = \begin{pmatrix} -1 & -1 & -1\\ 1 & -2 & 1\\ 1 & 1 & 1 \end{pmatrix}, \mathbf{r}_{6} = \begin{pmatrix} 1 & -1 & -1\\ 1 & -2 & -1\\ 1 & 1 & 1\\ \end{pmatrix}, \mathbf{r}_{7} = \begin{pmatrix} 1 & 1 & -1\\ 1 & -2 & -1\\ 1 & 1 & -1 \end{pmatrix}, \mathbf{r}_{8} = \begin{pmatrix} 1 & 1 & 1\\ 1 & 0 & -1 \\ 1 & -1 & -1 \end{pmatrix}, r5=111121111,r6=111121111,r7=111121111,r8=111101111,

Canny 边缘检测

基于卷积运算的边缘检测算法,比如 Sobel、Prewitt 等,有如下两个缺点:

  1. 没有充分利用边缘的梯度方向
  2. 最后输出的边缘二值图,只是简单地利用阈值进行处理,显然如果阈值过大,则会损失很多边缘信息;如果阈值过小,则会有很多噪声

而 Canny 边缘检测基于这两点做了改进,提出了:

  1. 基于边缘梯度方向的非极大值抑制
  2. 双阈值的滞后阈值处理

Canny 边缘检测近似算法的步骤如下:

  1. 图像矩阵分别与水平方向上的卷积核 s o b e l x \mathbf{sobel}_{x} sobelx 和垂直方向上的卷积核 s o b e l y \mathbf{sobel}_{y} sobely 卷积得到 d x \mathbf{dx} dx d y \mathbf{dy} dy ,然后利用平方和的开方 m a g n i t u d e = d x 2 + d y 2 \mathbf{magnitude} = \sqrt{\mathbf{dx}^2+\mathbf{dy}^2} magnitude=dx2+dy2 得到边缘强度。这一步的过程和 Sobel 边缘检测一样,这里也可以将卷积核换为 Prewitt 核。

  2. 利用第一步计算出的 d x \mathbf{dx} dx d y \mathbf{dy} dy ,计算出梯度方向 a n g l e = arctan ⁡ 2 ( d y , d x ) \mathbf{angle}=\arctan2(\mathbf{dy,dx}) angle=arctan2(dy,dx) ,即对每一个位置 ( r , c ) (r,c) (r,c) a n g l e = arctan ⁡ 2 ( d y ( r , c ) , d x ( r , c ) ) \mathbf{angle}=\arctan2(\mathbf{dy}(r,c),\mathbf{dx}(r,c)) angle=arctan2(dy(r,c),dx(r,c)) 代表该位置的梯度方向,一般用角度表示 a n g l e ( r , c ) ∈ [ 0 , 180 ] ∪ [ − 180 , 0 ] \mathbf{angle}(r,c) \in [0, 180]\cup[-180,0] angle(r,c)[0,180][180,0]

  3. 对每一个位置进行非极大值抑制的处理,非极大值抑制操作返回的仍然是一个矩阵,假设为 n o n M a x S u p \mathbf{nonMaxSup} nonMaxSup 。如果 m a g n i t u d e ( r , c ) \mathbf{magnitude}(r,c) magnitude(r,c) 在沿着梯度方向 a n g l e ( r , c ) \mathbf{angle}(r,c) angle(r,c) 上的邻域内是最大的则为极大值;否则设置为0。

    非极大值抑制的第一种方式:对于非极大值抑制的实现,将梯度方向一般离散化为以下四种情况:

    • a n g l e ( r , c ) ∈ [ 0 , 22.5 ) ∪ ( − 22.5 , 0 ] ∪ ( 157.5 , 180 ] ∪ ( − 180 , 157.5 ] \mathbf{angle}(r,c) \in [0, 22.5) \cup (-22.5, 0] \cup (157.5, 180] \cup (-180, 157.5] angle(r,c)[0,22.5)(22.5,0](157.5,180](180,157.5]
    • a n g l e ( r , c ) ∈ [ 22.5 , 67.5 ) ∪ ( − 157.5 , − 112.5 ] \mathbf{angle}(r,c) \in [22.5, 67.5) \cup (-157.5, -112.5] angle(r,c)[22.5,67.5)(157.5,112.5]
    • a n g l e ( r , c ) ∈ [ 67.5 , 112.5 ) ∪ ( − 112.5 , − 67.5 ] \mathbf{angle}(r,c) \in [67.5, 112.5) \cup (-112.5, -67.5] angle(r,c)[67.5,112.5)(112.5,67.5]
    • a n g l e ( r , c ) ∈ ( 112.5 , 157.5 ] ∪ [ − 67.5 , − 22.5 ] \mathbf{angle}(r,c) \in (112.5, 157.5] \cup [-67.5, -22.5] angle(r,c)(112.5,157.5][67.5,22.5]

    邻域定义为梯度方向所在的直线更多的穿过的部分,这四种情况依次对应的邻域如下图:

    在这里插入图片描述

    非极大值抑制的第二种方式:使用梯度方向所在的直线穿过的所有部分进行插值法,来拟合梯度方向上的边缘强度,可以更加准确的衡量梯度方向上的边缘强度。可以将梯度方向离散化为以下四种情况:

    • a n g l e ( r , c ) ∈ ( 45 , 90 ] ∪ ( − 135 , − 90 ] \mathbf{angle}(r,c) \in (45, 90] \cup (-135, -90] angle(r,c)(45,90](135,90]
    • a n g l e ( r , c ) ∈ ( 90 , 135 ] ∪ ( − 90 , − 45 ] \mathbf{angle}(r,c) \in (90, 135] \cup (-90, -45] angle(r,c)(90,135](90,45]
    • a n g l e ( r , c ) ∈ [ 0 , 45 ] ∪ [ − 180 , − 135 ] \mathbf{angle}(r,c) \in [0, 45] \cup [-180, -135] angle(r,c)[0,45][180,135]
    • a n g l e ( r , c ) ∈ ( 135 , 180 ] ∪ ( − 45 , 0 ) \mathbf{angle}(r,c) \in (135, 180] \cup (-45, 0) angle(r,c)(135,180](45,0)

    这四种情况一次对应的邻域如下图:

    在这里插入图片描述
    第一种情况时需要计算左上方 ( r − 1 , c − 1 ) (r-1, c-1) (r1,c1) 和上方 ( r − 1 , c ) (r-1, c) (r1,c) 的插值,右下方 ( r + 1 , c + 1 ) (r+1, c+1) (r+1,c+1) 和下方 ( r + 1 , c ) (r+1, c) (r+1,c) 的插值,在这种情况下, ∣ d y ( r , c ) > d x ( r , c ) ∣ |\mathbf{dy}(r,c) > \mathbf{dx}(r,c)| dy(r,c)>dx(r,c) 则比例系数为 ∣ d x ( r , c ) d y ( r , c ) ∣ : ( 1 − ∣ d x ( r , c ) d y ( r , c ) ∣ ) |\frac{\mathbf{dx}(r,c)}{\mathbf{dy}(r,c)}|:(1-|\frac{\mathbf{dx}(r,c)}{\mathbf{dy}(r,c)}|) dy(r,c)dx(r,c):(1dy(r,c)dx(r,c)) ,那么两个插值分别为:
    ∣ d x ( r , c ) d y ( r , c ) ∣ ∗ m a g n i t u d e ( r − 1 , c − 1 ) + ( 1 − ∣ d x ( r , c ) d y ( r , c ) ∣ ) ∗ m a g n i t u d e ( r − 1 , c ) |\frac{\mathbf{dx}(r,c)}{\mathbf{dy}(r,c)}| * \mathbf{magnitude}(r-1, c-1)+(1-|\frac{\mathbf{dx}(r,c)}{\mathbf{dy}(r,c)}|) * \mathbf{magnitude}(r-1, c) dy(r,c)dx(r,c)magnitude(r1,c1)+(1dy(r,c)dx(r,c))magnitude(r1,c)

    ∣ d x ( r , c ) d y ( r , c ) ∣ ∗ m a g n i t u d e ( r + 1 , c + 1 ) + ( 1 − ∣ d x ( r , c ) d y ( r , c ) ∣ ) ∗ m a g n i t u d e ( r + 1 , c ) |\frac{\mathbf{dx}(r,c)}{\mathbf{dy}(r,c)}| * \mathbf{magnitude}(r+1, c+1)+(1-|\frac{\mathbf{dx}(r,c)}{\mathbf{dy}(r,c)}|) * \mathbf{magnitude}(r+1, c) dy(r,c)dx(r,c)magnitude(r+1,c+1)+(1dy(r,c)dx(r,c))magnitude(r+1,c)

    其他情况类似。

    非极大值抑制因为只保留了极大值,抑制了非极大值,所以该步骤其实是对 Sobel 边缘强度图进行了细化。

  4. 双阈值的滞后阈值处理。经过非极大值抑制处理后的边缘强度图,一般需要阈值化处理,常用的方法是全局阈值分割和局部自适应阈值分割。此处使用的是滞后阈值处理,它使用两个阈值:高阈值和低阈值,按照以下三个规则进行边缘的阈值化处理

    • 边缘强度大于高阈值的那些点作为确定边缘
    • 边缘强度小于低阈值的那些点立即被剔除
    • 边缘强度在低阈值和高阈值之间的那些点,按照以下原则进行处理:只有这些点能按某一路径与确定边缘点相连时,才可以作为边缘点被接受。组成这一路径的所有点的边缘强度都比低阈值要大。

    对这一过程可以理解为,首先选定边缘强度大于高阈值的所有确定边缘点,然后在边缘强度大于低阈值的情况下尽可能延长边缘。

Python 示例

import math
import cv2 as cv
import numpy as np
from scipy import signal

def nms_default(dx, dy):
    # 边缘强度
    edge_map = np.sqrt(np.power(dx, 2.0) + np.power(dy, 2.0))
    rows, cols = dx.shape
    # 梯度方向
    gradient_direction = np.zeros(dx.shape)
    # 边缘强度非极大值抑制
    edge_map_nms = np.zeros(dx.shape)
    for r in range(1, rows-1):
        for c in range(1, cols-1):
            # angle 的范围 [0, 180] [-180, 0]
            angle = math.atan2(dy[r][c], dx[r][c]) / math.pi * 180
            gradient_direction[r][c] = angle
            # 左/右方向
            if abs(angle)<22.5 or abs(angle)>157.5:
                if edge_map[r][c]>edge_map[r][c - 1] and edge_map[r][c]>edge_map[r][c + 1]:
                    edge_map_nms[r][c] = edge_map[r][c]
            # 左上/右下
            if 22.5 <= angle < 67.5 or -157.5 < angle -112.5:
                if edge_map[r][c] > edge_map[r-1][c - 1] and edge_map[r][c] > edge_map[r+1][c + 1]:
                    edge_map_nms[r][c] = edge_map[r][c]
            # 上/下
            if 67.5 <= angle < 112.5 or -112.5 < angle < -67.5:
                if edge_map[r][c] > edge_map[r - 1][c] and edge_map[r][c] > edge_map[r + 1][c]:
                    edge_map_nms[r][c] = edge_map[r][c]
            # 右上/左下
            if 112.5 <= angle < 157.5 or -67.5 < angle < -22.5:
                if edge_map[r][c] > edge_map[r + 1][c+1] and edge_map[r][c] > edge_map[r + 1][c+1]:
                    edge_map_nms[r][c] = edge_map[r][c]
    return edge_map_nms

def nms_inter(dx, dy):
    # 边缘强度
    edge_map = np.sqrt(np.power(dx, 2.0) + np.power(dy, 2.0))
    rows, cols = dx.shape
    # 梯度方向
    gradient_direction = np.zeros(dx.shape)
    # 边缘强度非极大值抑制
    edge_map_nms = np.zeros(dx.shape)
    for r in range(1, rows-1):
        for c in range(1, cols-1):
            if dy[r][c] == 0 and dx[r][c] == 0: continue
            angle = math.atan2(dy[r][c], dx[r][c]) / math.pi * 180
            gradient_direction[r][c] = angle
            # 左上方和上方的插值,右下方和下方的插值
            if 45 < angle <= 90 or -135 < angle <= -90:
                ratio = dx[r][c] / dy[r][c]
                left_top_top = ratio * edge_map[r-1][c-1] + (1-ratio) * edge_map[r-1][c]
                right_bottom_bottom = ratio * edge_map[r + 1][c] + (1 - ratio) * edge_map[r+1][c+1]
                if edge_map[r][c] > left_top_top and edge_map[r][c] > right_bottom_bottom:
                    edge_map_nms[r][c] = edge_map[r][c]
            # 上方和右上方的插值,左下方和下方的插值
            if 90 < angle <= 135 or -90 < angle <= -45:
                ratio = dx[r][c] / dy[r][c]
                right_top_top = ratio * edge_map[r-1][c+1] + (1-ratio) * edge_map[r-1][c]
                left_bottom_bottom = ratio * edge_map[r + 1][c-1] + (1 - ratio) * edge_map[r+1][c]
                if edge_map[r][c] > right_top_top and edge_map[r][c] > left_bottom_bottom:
                    edge_map_nms[r][c] = edge_map[r][c]
            # 左上方和左方的插值,右下方和右方的插值
            if 0 < angle <= 45 or -180 < angle <= -135:
                ratio = dx[r][c] / dy[r][c]
                right_bottom_right = ratio * edge_map[r+1][c+1] + (1-ratio) * edge_map[r][c+1]
                left_top_left = ratio * edge_map[r-1][c-1] + (1 - ratio) * edge_map[r][c-1]
                if edge_map[r][c] > right_bottom_right and edge_map[r][c] > left_top_left:
                    edge_map_nms[r][c] = edge_map[r][c]
            # 右上方和右方的插值,左下方和左方的插值
            if 135 < angle <= 180 or -45 < angle < 0:
                ratio = dx[r][c] / dy[r][c]
                right_top_right = ratio * edge_map[r - 1][c + 1] + (1 - ratio) * edge_map[r][c + 1]
                left_bottom_left = ratio * edge_map[r + 1][c - 1] + (1 - ratio) * edge_map[r][c - 1]
                if edge_map[r][c] > right_top_right and edge_map[r][c] > left_bottom_left:
                    edge_map_nms[r][c] = edge_map[r][c]
    return edge_map_nms

# 判断一个点的做笔哦啊是否在图像范围内
def check_in_range(r, c, rows, cols):
    if 0 <= r < rows and 0 <= c < cols:
        return True
    return False

def trace(edge_map_nms, edge, low_thresh, r, c, rows, cols):
    # 大于高阈值的点为确定边缘点
    if edge[r][c] == 0:
        edge[r][c] = 255
        for i in range(-1, 2):
            for j in range(-1, 2):
                if check_in_range(r+i, c+j, rows, cols) and edge_map_nms[r+i][c+j] >= low_thresh:
                    trace(edge_map_nms, edge, low_thresh, r+i, c+j, rows, cols)

# 滞后阈值处理
def hysteresis_threshold(edge_nms, low_thresh, upper_thresh):
    rows, cols = edge_nms.shape
    edge = np.zeros(edge_nms.shape, np.uint8)
    for r in range(1, rows-1):
        for c in range(1, cols-1):
            # 大于高阈值的点被设置为确定边缘点,而且以该点为起点延长边缘
            if edge_nms[r][c] >= upper_thresh:
                trace(edge_nms, edge, low_thresh, r, c, rows, cols)
            # 小于低阈值的点被剔除
            if edge_nms[r][c] < low_thresh:
                edge[r][c] = 0
    return edge


if __name__ == '__main__':
    image = cv.imread("img9.jpg", 0)
    # Canny 边缘检测
    # 1.基于 sobel 核的卷积
    image_sobel_x, image_sobel_y = sobel(image, 3)
    # 2.边缘强度
    edge = np.sqrt(np.power(image_sobel_x, 2.0) + np.power(image_sobel_y, 2.0))
    edge[edge>255] = 255
    edge = edge.astype(np.uint8)
    cv.imwrite('./images/img9_sobel.jpg', edge)
    cv.imshow("sobel edge",edge)

    # 3.非极大值抑制
    edge_map_nms = nms_default(image_sobel_x, image_sobel_y)
    edge_map_nms[edge_map_nms>255] = 255
    edge_map_nms = edge_map_nms.astype(np.uint8)
    cv.imwrite('./images/img9_nms.jpg', edge_map_nms)
    cv.imshow("edgeMag_nonMaxSup",edge_map_nms)
    # 4. 双阈值滞后阈值处理
    edge = hysteresis_threshold(edge_map_nms, 60, 180)
    cv.imwrite('./images/img9_canny.jpg', edge)
    cv.imshow('canny', edge)

    # 单阈值
    lowEdge = np.copy(edge_map_nms)
    lowEdge[lowEdge > 60] = 255
    lowEdge[lowEdge < 60] = 0
    cv.imwrite('./images/img9_low.jpg', lowEdge)
    cv.imshow("lowEdge", lowEdge)
    upperEdge = np.copy(edge_map_nms)
    upperEdge[upperEdge > 180] = 255
    upperEdge[upperEdge <= 180] = 0
    cv.imwrite('./images/img9_upper.jpg', upperEdge)
    cv.imshow("upperEdge", upperEdge)
    cv.waitKey(0)
    cv.destroyAllWindows()

在这里插入图片描述

非极大值抑制的图像比Sobel边缘强度图显得细化,使用滞后阈值处理的图像与单阈值图像相比,去除了低阈值图像的细小边缘,比高阈值图像边缘更加完整。

OpenCV 函数

Canny函数官方地址

void cv::Canny(InputArray 	image,
              OutputArray 	edges,
              double 	threshold1,
              double 	threshold2,
              int 	apertureSize = 3,
              bool 	L2gradient = false 
              )		
//Python:
edges	=	cv.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]	)
参数 解释
threshold1 低阈值
threshold2 高阈值
apertureSize Sobel 核大小,默认 3x3
L2gradient 计算边缘强度的方式,true表示平方和开方,false表示绝对值和的方式

下边的函数是使用带有自定义图像梯度的Canny算法在图像中寻找边缘。


void cv::Canny(InputArray 	dx,
              InputArray 	dy,
              OutputArray 	edges,
              double 	threshold1,
              double 	threshold2,
              bool 	L2gradient = false 
              )		
// Python:
edges = cv.Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]])

参数 解释
dx 输入图像在 x 方向上导数
dy 输入图像在 y 方向上导数


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OpenCV —— 边缘检测(Roberts、Prewitt、Sobel、Scharr、Kirsch、Robinson、Canny边缘检测) 的相关文章

  • 在加载“cv2”二进制扩展期间检测到递归

    我有一个小程序 在 pyinstaller 编译后返回 opencv 错误 但无需编译即可工作 我在 Windows 10 上使用 Python 3 8 10 Program 导入 pyautogui将 numpy 导入为 np导入CV2
  • 如何使用 Python 将我的 GoPro Hero 4 相机直播连接到 openCV?

    我在尝试从我的新 GoPro Hero 4 相机捕获实时流并使用 openCV 对其进行一些图像处理时遇到麻烦 这是我的试用 创建的窗口上没有显示任何内容 import cv2 import argparse import time imp
  • 在 QtCreator 中将 OpenCV 2.3 与 Qt 结合使用

    随着 OpenCV 2 3 版本终于发布 我想在我的系统上编译并安装这个最新版本 由于我经常使用 Qt 和 QtCreator 我当然希望能够在我的 Qt 项目中使用它 我已经尝试了几种方法几个小时 但总是出现错误 第一次尝试 使用WITH
  • 我可以使用 openCV 比较两张不同图像上的两张脸吗?

    我对 openCV 很陌生 我看到它可以计算出脸部并返回一个矩形来指示脸部 我想知道 openCV 是否可以访问两张包含一张脸的图像 并且我希望 openCV 返回这两个人是否相同的可能性 Thanks OpenCV 不提供完整的人脸识别引
  • 无法在 Windows 7 机器中使用 OpenCV 2.4.3、Python 2.7 打开“.mp4”视频文件

    我目前正在进行一个涉及读取 mp4 视频文件的项目 我遇到的问题是它在Windows 7机器上使用Python 2 7 32位 OpenCV 2 4 3 cv2 pyd 代码片段如下 try video cv2 VideoCapture v
  • opencv 2.3.* 读取不工作

    我无法让 imread 工作 与这个人有同样的问题 OpenCV imwrite 2 2 在 Windows 7 上导致异常 并显示消息 OpenCV 错误 未指定错误 无法找到指定扩展名的编写器 https stackoverflow c
  • 当我将鼠标移到 Mat 关键字上时,Visual Studio 2017 冻结(OpenCv 3.4.1)

    我想在 Visual Studio 2017 中开发 openCv 项目 我下载了 opencv 预构建库并进行了必要的设置 那是 1 我添加了系统路径 build x64 vc14 bin 2 在 Visual Studio 中的项目属性
  • 检查图像中是否有太薄的区域

    我正在尝试验证雕刻机的黑白图像 更多的是剪贴画图像 不是照片 我需要考虑的主要事情之一是区域的大小 或线条的宽度 因为机器无法处理太细的线条 所以我需要找到比给定阈值更细的区域 以此图为例 竖琴的琴弦可能太细而无法雕刻 我正在阅读有关 Ma
  • OpenCV 2.4.3 中的阴影去除

    我正在使用 OpenCV 2 4 3 最新版本 使用内置的视频流检测前景GMG http docs opencv org modules gpu doc video html highlight gmg gpu 3a 3aGMG GPU算法
  • 使用 OpenCV 改进特征点匹配

    我想匹配立体图像中的特征点 我已经用不同的算法找到并提取了特征点 现在我需要一个良好的匹配 在本例中 我使用 FAST 算法进行检测和提取 BruteForceMatcher用于匹配特征点 匹配代码 vector lt vector
  • opencv人脸检测示例

    当我在设备上运行应用程序时 应用程序崩溃并显示以下按摩 java lang UnsatisfiedLinkError 无法加载 detector based tracker findLibrary 返回 null 我正在使用 OpenCV
  • 如何使用 opencv python 计算乐高积木上的孔数?

    我正在开发我的 python 项目 我需要计算每个乐高积木组件中有多少个孔 我将从输入 json 文件中获取有关需要计算哪个程序集的信息 如下所示 img 001 red 0 blue 2 white 1 grey 1 yellow 1 r
  • cv2.drawContours() - 取消填充字符内的圆圈(Python,OpenCV)

    根据 Silencer的建议 我使用了他发布的代码here https stackoverflow com questions 48244328 copy shape to blank canvas opencv python 482465
  • “没有名为‘cv2’的模块”,但已安装

    我已经安装了包含 opencv 贡献的 whl 文件 因为我想使用 SIFT 算法 我在 conda 环境中使用 pip 安装了它 所以当我在 conda list 中提示时 它会向我显示 opencv python 3 4 5 contr
  • OpenCV 2.2 和多 CPU - opencv_haartraining.exe 是多线程的吗?

    我在 VS 2010 上构建了 OpenCV 2 2 启用了 TBB 3 支持 我确保所有项目都有正确的 tbb lib 目录 并将 tbb lib 列为依赖项 通过隐藏 tbb dll 进行验证 果然 haartraining exe 抱
  • Python:Urllib2 和 OpenCV

    我有一个程序 可以将图像保存在本地目录中 然后从该目录中读取图像 但我不想保存图像 我想直接从url读取它 这是我的代码 import cv2 cv as cv import urllib2 url http cache2 allposte
  • 开放简历fisherfaces

    我有这个问题 当我使用 vs2010 调试 opencv 2 4 0 facetec demo c 运行时 程序出现此错误 OpenCV错误 未知函数中图像步长错误 矩阵不连续 因此其行数无法更改 文件 src opencv modul e
  • 如何使用 AdaBoost 进行特征选择?

    我想使用 AdaBoost 从大量 100k 中选择一组好的特征 AdaBoost 的工作原理是迭代功能集并根据功能的执行情况添加功能 它选择对现有特征集错误分类的样本表现良好的特征 我目前正在 Open CV 中使用CvBoost 我得到
  • 使用卡尔曼滤波器跟踪位置和速度

    我正在使用卡尔曼滤波器 恒定速度模型 来跟踪物体的位置和速度 我测量对象的 x y 并跟踪 x y vx vy 这是有效的 但是如果在传感器读数 x y vx vy 上添加 20 mm 的高斯噪声 即使该点没有移动 只是噪声也会发生波动 对
  • 在骨架图像中查找线 OpenCV python

    我有以下图片 我想找到一些线来进行一些计算 平均长度等 我尝试使用HoughLinesP 但它找不到线 我能怎么做 这是我的代码 sk skeleton mask rows cols sk shape imgOut np zeros row

随机推荐

  • keil错误 FATAL ERROR L250: CODE SIZE LIMIT IN RESTRICTED VERSION EXCEEDED 全部解决方法

    今天我用keil5调试C51的程序 编译都编译不了 出现以下 错误信息 FATAL ERROR L250 CODE SIZE LIMIT IN RESTRICTED VERSION EXCEEDED 问题分析 说明程序大小受到了版本的限制
  • Python读取csv文件的三种方式

    一 前期准备 Python版本 3 7 3 制作一个不包含头文件的csv文件 为了方便文件内容是纯数字 字符集为utf 8 并命名为test csv 放到程序的根目录下 使用PyCharm创建一个Python工程 并安装Numpy和Pand
  • HarmonyOS创作激励计划启动:助力技术创作突破边界

    即日起推出HarmonyOS创作激励计划 成功投稿并入选的文章将在HarmonyOS开发者公众号上线 9大技术社区同步宣发 不仅有丰厚稿酬 还有机会赢取创作奖品 活动时间 即日起 2024年12月31日 每季度按照活动规则评审奖项 活动面向
  • 国内下载VSCode速度太慢解决问题

    国内下载VSCode速度太慢解决问题 首先要去官网找到相应的下载版本 点击下载 此为官网地址 https code visualstudio com 建议下载64位的压缩包 以上为官网下载地址 可以看到下载速度非常慢 解决方法 右键选中该下
  • 1070 结绳(25 分)

    1070 结绳 25 分 给定一段一段的绳子 你需要把它们串成一条绳 每次串连的时候 是把两段绳子对折 再如下图所示套接在一起 这样得到的绳子又被当成是另一段绳子 可以再次对折去跟另一段绳子串连 每次串连后 原来两段绳子的长度就会减半 给定
  • gin框架34--重定向

    gin框架34 重定向 介绍 案例 说明 介绍 本文主要介绍gin框架中的重定向 HTTP 重定向很容易 内部 外部重定向均支持 案例 源码 package main import github com gin gonic gin net
  • 线程的生命周期及五种基本状态

    一 线程的生命周期及五种基本状态 关于Java中线程的生命周期 首先看一下下面这张较为经典的图 上图中基本上囊括了Java中多线程各重要知识点 掌握了上图中的各知识点 Java中的多线程也就基本上掌握了 主要包括 Java线程具有五中基本状
  • 怎么用css设置字体小于12px

    因为浏览器的限制 网页上的字体最小只能设置12px 因为小于12px就会影响浏览效果 但是有时候我们需要将字体设置的很小这就需要用到特殊的手段 用 transform属性设置 用 transform的scale缩放属性将字体缩放 实际上并没
  • 百度编辑器取消高度自动拉长

    首先去下载百度编辑器 引入百度编辑器
  • 在GIT中创建一个空分支

    问题描述 有时候我们需要在GIT里面创建一个空分支 该分支不继承任何提交 没有父节点 完全是一个干净的分支 例如我们需要在某个分支里存放项目文档 使用传统的git checkout命令创建的分支是有父节点的 意味着新branch包含了历史提
  • 二叉树之遍历

    文章目录 一 二叉树的基本概念及实现 1 根结点 2 父结点 3 子结点 4 二叉树数据结构的实现 二 二叉树的遍历方法 1 前序遍历 2 中序遍历 3 后序遍历 4 层序遍历 三 几种遍历的实现 1 递归方法实现 2 迭代方法实现 3 M
  • 浏览器获取当前位置

    p window navigator geolocation对象存在3个方法 p p 1 getCurrentPosition 获取当前地理位置 p p 2 watchPosition 监视位置信息 p p 3 clearWatch 停止获
  • shell脚本监控Tomcat并重启发送短信

    bin sh TomcatID ps ef grep tomcat grep w tomcat grep v grep awk print 2 StartTomcat tomcat startup path WebUrl www xxx c
  • 固高运动控制卡跟随运动(Follow 运动模式)

    固高运动控制卡跟随运动 Follow 运动模式 一 Follow 运动模式指令列表 固高运动控制卡开发资料 关注我免费下载 GT PrfFollow 设置指定轴为 Follow 运动模式 GT SetFollowMaster 设置 Foll
  • 基于Vshare插件实现vue分享功能

    Vue中引入分享功能插件 一度为快 实现 引入插件 npm config set strict ssl false npm install vshare S 页面实现
  • go语言有哪些web框架

    前言 由于工作需要 这些年来也接触了不少的开发框架 Golang的开发框架比较多 不过基本都是Web 框架 为主 这里稍微打了个引号 因为大部分 框架 从设计和功能定位上来讲 充其量都只能算是一个组件 需要项目使用的话得自己四处再去找找其他
  • python基础练习题(二) --分支结构练习题

    一 单选题 1 哪个选项是实现多路分支的最佳控制结构 5 0分 A if B if elif else C try D if else 2 关于程序的控制结构 哪个选项的描述是错误的 5 0分 A 流程图可以用来展示程序结构 B 顺序结构有
  • Echarts散点图筛选新玩法dataZoom

    目录 前言 一 引入Echarts5 4 3 二 新建index html 三 绑定Echarts展示元素 四 初始数据绑定 五 option设置 六 效果展示 七 参数说明 总结 前言 如果您在日常的工作当中也会遇到如下场景 需要在线对已
  • 也谈系统设计的一些原则

    在进行系统设计时 不仅要考虑软件的功能性需求 还要考虑非功能性需求 比如软件的性能 Performance 可扩展性 Scalability 系统的稳定性 Reliability 部署 Deployment 和更新 Upgrade 可维护性
  • OpenCV —— 边缘检测(Roberts、Prewitt、Sobel、Scharr、Kirsch、Robinson、Canny边缘检测)

    边缘检测 Roberts 算子 Prewitt 边缘检测 Sobel 边缘检测 Scharr算子 Krisch 算子和Robinson 算子 Canny 边缘检测 图像的边缘指的是灰度值发生急剧变化的位置 在图像形成过程中 由于亮度 纹理