边缘检测系列1:传统边缘检测算子
边缘检测系列1:使用 Paddle 实现一些传统边缘检测算子,如:Roberts、Prewitt、Sobel、Scharr、Kirsch、Robinson、Laplacian
引入
-
图像的边缘指的是灰度值发生急剧变化的位置。
-
在图像形成过程中,由于亮度、纹理、颜色、阴影等物理因素的不同而导致图像灰度值发生突变,从而形成边缘。
-
边缘是通过检查每个像素的邻域并对其灰度变化进行量化的,这种灰度变化的量化相当于微积分里连续函数中方向导数或者离散数列的差分。
算法原理
-
传统的边缘检测大多数是通过基于方向导数掩码(梯度方向导数)求卷积的方法。
-
计算灰度变化的卷积算子包含Roberts算子、Prewitt算子、Sobel算子、Scharr算子、Kirsch算子、Robinson算子、Laplacian算子。
-
大多数边缘检测算子是基于方向差分卷积核求卷积的方法,在使用由两个或者多个卷积核组成的边缘检测算子时假设有 n 个卷积核,记 C o n v 1 , C o n v 2 , . . . , C o n v n Conv_1, Conv_2, ..., Conv_n Conv1,Conv2,...,Convn,为图像分别与个卷积核做卷积的结果,通常有四种方式来衡量最后输出的边缘强度。
-
取对应位置绝对值的和: ∑ i = 1 n ∣ c o n v i ∣ \sum_{i=1}^{n} |\mathbf{conv}_i| ∑i=1n∣convi∣
-
取对应位置平方和的开方: ∑ i = 1 n c o n v i 2 \sqrt{\sum_{i=1}^{n} \mathbf{conv}_i^2} ∑i=1nconvi2
-
取对应位置绝对值的最大值: 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∣}
-
插值法: ∑ i = 1 n a i ∣ c o n v i ∣ \sum_{i=1}^n a_i |\mathbf{conv}_i| ∑i=1nai∣convi∣,其中 a i > = 0 a_i >= 0 ai>=0,且 ∑ i = 1 n a i = 1 \sum_{i=1}^n a_i = 1 ∑i=1nai=1
-
代码实现
构建通用的边缘检测算子
- 因为上述的这些算子在本质上都是通过卷积计算实现的,只是所使用到的卷积核参数有所不同
- 所以可以构建一个通用的计算算子,只需要传入对应的卷积核参数即可实现不同的边缘检测
- 并且在后处理时集成了上述的四种计算最终边缘强度的方式
import numpy as np
import paddle
import paddle.nn as nn
class EdgeOP(nn.Layer):
def __init__(self, kernel):
'''
kernel: shape(out_channels, in_channels, h, w)
'''
super(EdgeOP, self).__init__()
out_channels, in_channels, h, w = kernel.shape
self.filter = nn.Conv2D(in_channels=in_channels, out_channels=out_channels, kernel_size=(h, w), padding='SAME', bias_attr=False)
self.filter.weight.set_value(kernel.astype('float32'))
@staticmethod
def postprocess(outputs, mode=0, weight=None):
'''
Input: NCHW
Output: NHW(mode==1-3) or NCHW(mode==4)
Params:
mode: switch output mode(0-4)
weight: weight when mode==3
'''
if mode==0:
results = paddle.sum(paddle.abs(outputs), axis=1)
elif mode==1:
results = paddle.sqrt(paddle.sum(paddle.pow(outputs, 2), axis=1))
elif mode==2:
results = paddle.max(paddle.abs(outputs), axis=1)
elif mode==3:
if weight is None:
C = outputs.shape[1]
weight = paddle.to_tensor([1/C] * C, dtype='float32')
else:
weight = paddle.to_tensor(weight, dtype='float32')
results = paddle.einsum('nchw, c -> nhw', paddle.abs(outputs), weight)
elif mode==4:
results = paddle.abs(outputs)
return paddle.clip(results, 0, 255).cast('uint8')
@paddle.no_grad()
def forward(self, images, mode=0, weight=None):
outputs = self.filter(images)
return self.postprocess(outputs, mode, weight)
图像边缘检测测试函数
- 为了方便测试就构建了如下的测试函数,测试同一张图片不同算子/不同边缘强度计算方法的边缘检测效果
import os
import cv2
from PIL import Image
def test_edge_det(kernel, img_path='test.jpg'):
img = cv2.imread(img_path, 0)
img_tensor = paddle.to_tensor(img, dtype='float32')[None, None, ...]
op = EdgeOP(kernel)
all_results = []
for mode in range(4):
results = op(img_tensor, mode=mode)
all_results.append(results.numpy()[0])
results = op(img_tensor, mode=4)
for result in results.numpy()[0]:
all_results.append(result)
return all_results, np.concatenate(all_results, 1)
Roberts 算子
roberts_kernel = np.array([
[[
[1, 0],
[0, -1]
]],
[[
[0, -1],
[1, 0]
]]
])
_, concat_res = test_edge_det(roberts_kernel)
Image.fromarray(concat_res)
Prewitt 算子
prewitt_kernel = np.array([
[[
[-1, -1, -1],
[ 0, 0, 0],
[ 1, 1, 1]
]],
[[
[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]
]],
[[
[ 0, 1, 1],
[-1, 0, 1],
[-1, -1, 0]
]],
[[
[ -1, -1, 0],
[ -1, 0, 1],
[ 0, 1, 1]
]]
])
_, concat_res = test_edge_det(prewitt_kernel)
Image.fromarray(concat_res)
Sobel 算子
sobel_kernel = np.array([
[[
[-1, -2, -1],
[ 0, 0, 0],
[ 1, 2, 1]
]],
[[
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
]],
[[
[ 0, 1, 2],
[-1, 0, 1],
[-2, -1, 0]
]],
[[
[ -2, -1, 0],
[ -1, 0, 1],
[ 0, 1, 2]
]]
])
_, concat_res = test_edge_det(sobel_kernel)
Image.fromarray(concat_res)
Scharr 算子
scharr_kernel = np.array([
[[
[-3, -10, -3],
[ 0, 0, 0],
[ 3, 10, 3]
]],
[[
[-3, 0, 3],
[-10, 0, 10],
[-3, 0, 3]
]],
[[
[ 0, 3, 10],
[-3, 0, 3],
[-10, -3, 0]
]],
[[
[ -10, -3, 0],
[ -3, 0, 3],
[ 0, 3, 10]
]]
])
_, concat_res = test_edge_det(scharr_kernel)
Image.fromarray(concat_res)
Krisch 算子
Krisch_kernel = np.array([
[[
[5, 5, 5],
[-3,0,-3],
[-3,-3,-3]
]],
[[
[-3, 5,5],
[-3,0,5],
[-3,-3,-3]
]],
[[
[-3,-3,5],
[-3,0,5],
[-3,-3,5]
]],
[[
[-3,-3,-3],
[-3,0,5],
[-3,5,5]
]],
[[
[-3, -3, -3],
[-3,0,-3],
[5,5,5]
]],
[[
[-3, -3, -3],
[5,0,-3],
[5,5,-3]
]],
[[
[5, -3, -3],
[5,0,-3],
[5,-3,-3]
]],
[[
[5, 5, -3],
[5,0,-3],
[-3,-3,-3]
]],
])
_, concat_res = test_edge_det(Krisch_kernel)
Image.fromarray(concat_res)
Robinson算子
robinson_kernel = np.array([
[[
[1, 2, 1],
[0, 0, 0],
[-1, -2, -1]
]],
[[
[0, 1, 2],
[-1, 0, 1],
[-2, -1, 0]
]],
[[
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
]],
[[
[-2, -1, 0],
[-1, 0, 1],
[0, 1, 2]
]],
[[
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]
]],
[[
[0, -1, -2],
[1, 0, -1],
[2, 1, 0]
]],
[[
[1, 0, -1],
[2, 0, -2],
[1, 0, -1]
]],
[[
[2, 1, 0],
[1, 0, -1],
[0, -1, -2]
]],
])
_, concat_res = test_edge_det(robinson_kernel)
Image.fromarray(concat_res)
Laplacian 算子
laplacian_kernel = np.array([
[[
[1, 1, 1],
[1, -8, 1],
[1, 1, 1]
]],
[[
[0, 1, 0],
[1, -4, 1],
[0, 1, 0]
]]
])
_, concat_res = test_edge_det(laplacian_kernel)
[1, 1, 1],
[1, -8, 1],
[1, 1, 1]
]],
[[
[0, 1, 0],
[1, -4, 1],
[0, 1, 0]
]]
])
_, concat_res = test_edge_det(laplacian_kernel)
Image.fromarray(concat_res)
总结
- 简单介绍并实现了几种常用的传统边缘检测算子
更多推荐
所有评论(0)