- 作者:
- 分类:知识&开发->AI->视觉
- 阅读:7898
- 点赞:78
- 版权:CC BY-SA 4.0
- 创建:2021-07-26
- 更新:2021-08-03
原文链接(持续更新):https://neucrack.com/p/377
边缘检测效果





源码在末尾
边缘检测原理
边缘就是值变化剧烈的地方, 如果对值的变化求导, 则边缘部分就是导数局部最大.
但是在图像处理时没有具体的函数让我们求导, 使用卷积运算则可以很好的近似替代
如下图, 假设左上为坐标原点, 横轴为 x, 纵轴为y, 如下图左上角9个像素点, P(x, y)表示坐标(x, y)的点, 要求P(1, 1)处在x轴的变化率, 则只需将P(2, 1) - P(0, 1) 得到值为0, P(1, 0)处为1-3 = -2, 这个差值即变化率, 类比成导数, 我们就能知道横轴在哪些地方变化率更大.

上面这种方法我们可以得到横轴的变化率, 这里使用卷积核
[-1, 0, 1],[-2, 0, 2],[-1, 0, 1]
对图像进行卷积运算, 如图中的计算方法, 像素点左右权值取2, 角上的也参与计算,但是权值为1,没有左右边的权值高. 这样我们就得到了横轴的变化率图, 即边缘检测图.
注意, 这里是对横轴计算了, 比较的点左右的值变化, 所以实际看到的图像会出现明显的纵轴边缘, 如下图左边

同理, 上图右边的图使用卷积核
[1,2,1],[0,0,0],[-1, -2, -1]
得到的纵轴的边缘图.
注意这里用右边减左边, 如果右边的值比左边的小会是负数, 如果我们希望只检测颜色值变大(变白)则可以直接使用, 如果两个变化方向都要检测, 则可以取绝对值. 比如下图左边是没有取绝对值, 右边取了绝对值

得到两个方向的图后, 对其进行合并, 对每个像素平方和开方即可

这张图左边是使用 GIMP 的 sobel 边缘检测(垂直+水平)的效果, 略微有点不同:

不同的原因是使用水平和垂直的图平方和开根后, 直接用 plt.imshow 显示, 和 GIMP 的处理方式不同
out = np.sqrt(np.square(out_v) + np.square(out_h))plt.imshow(out)
简单地直接将值规范到[0, 255] 就和 GIMP 的图相似了(但不完全一样)
out = np.sqrt(np.square(out_v) + np.square(out_h))out = out * 255.0 / out.max()plt.imshow(out.astype(np.uint8))

自定义卷积核来实现边缘检测
除了上面说了使用两次卷积计算, 也可以用只计算一次的卷积核, 比如:
[-1, -1, -1],[ -1, 8, -1],[ -1, -1, -1]
这是对于一个通道(灰度图)来说, 如果要扩充到三个通道(RGB), 卷积核参数就是如下形式
conv_rgb_core_sobel = [[[-1,-1,-1],[-1,8,-1], [-1, -1, -1],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[-1,-1,-1],[-1,8,-1], [-1, -1, -1],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[-1,-1,-1],[-1,8,-1], [-1, -1, -1],]]
经过卷积运算后, 前后图如下:

注意, 输入值范围如果为[0, 255], 输出值则范围会变化, 以图片形式查看时需要注意加以处理, 这里使用了plt.imshow(out)来显示, 这个函数会自动对图像做简单的处理, 才会看起来是黑色背景
导出成模型使用
可以将 Net 导出成 onnx 即可在其它平台使用, 就是一个简单的卷积层
部署到 V831 后的样子(使用了卷积核[-1,-1,-1],[-1,8,-1], [-1, -1, -1],):

源码
'''simple sobel edge demovisit: https://neucrack.com/p/377@author neucrack@license MIT'''import torchimport torch.nn as nnimport numpy as npimport cv2from PIL import Imageimport matplotlib.pyplot as pltclass Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(3, 3, 3, padding=(0, 0), bias=False)def forward(self, x):x = self.conv1(x)return xnet = Net()conv_rgb_core_original = [[[0,0,0],[0,1,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,1,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,1,0], [0,0,0]]]conv_rgb_core_sobel = [[[-1,-1,-1],[-1,8,-1], [-1, -1, -1],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[-1,-1,-1],[-1,8,-1], [-1, -1, -1],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[-1,-1,-1],[-1,8,-1], [-1, -1, -1],]]conv_rgb_core_sobel_vertical = [[[-1,0,1],[-2,0,2], [-1, 0, 1],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[-1,0,1],[-2,0,2], [-1, 0, 1],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[-1,0,1],[-2,0,2], [-1, 0, 1],]]conv_rgb_core_sobel_horizontal = [[[1,2,1],[0,0,0], [-1, -2, -1],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[1,2,1],[0,0,0], [-1, -2, -1],[0,0,0],[0,0,0], [0,0,0]],[[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[1,2,1],[0,0,0], [-1, -2, -1],]]def sobel(net, kernel):sobel_kernel = np.array(kernel, dtype='float32')sobel_kernel = sobel_kernel.reshape((3, 3, 3, 3))net.conv1.weight.data = torch.from_numpy(sobel_kernel)params = list(net.parameters())img = cv2.imread("out/test.jpg")input_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)input_tensor = (input_img.astype(np.float32) - 127.5) / 128 # to [-1, 1]input_tensor = torch.Tensor(input_tensor).permute((2, 0, 1))print(input_tensor.shape)input_tensor = input_tensor.unsqueeze(0)print("input shape:", input_tensor.shape)sobel(net, conv_rgb_core_sobel)out = net(input_tensor).detach().numpy()[0].transpose([1,2,0])sobel(net, conv_rgb_core_sobel_vertical)out_v = net(input_tensor).detach().numpy()[0].transpose([1,2,0])sobel(net, conv_rgb_core_sobel_horizontal)out_h = net(input_tensor).detach().numpy()[0].transpose([1,2,0])print("out shape: {}, tensor:{}".format(out.shape, out))print(out.shape, out.max(), out.min())plt.figure()plt.figure()plt.subplot(1, 5, 1)input = input_tensor.numpy()[0].transpose((1,2,0))print(input.max(), input.min())plt.imshow(input_img)plt.subplot(1, 5, 2)print(out.max(), out.min())# out = np.sqrt(np.square(out))# out = out * 255.0 / out.max()# out = out.astype(np.uint8)# print(out.max(), out.min())plt.imshow(out)plt.subplot(1, 5, 3)out = np.abs(out_v)# out = out * 255.0 / out.max()# plt.imshow(out.astype(np.uint8))plt.imshow(out)plt.subplot(1, 5, 4)out = np.abs(out_h)# out = out * 255.0 / out.max()# plt.imshow(out.astype(np.uint8))plt.imshow(out)plt.subplot(1, 5, 5)out = np.sqrt(np.square(out_v) + np.square(out_h))# out = out * 255.0 / out.max()# plt.imshow(out.astype(np.uint8))plt.imshow(out)plt.show()
上述代码效果:
更多
如何部署到 V831/ V833 上可以参考: 在V831上(awnn)跑 pytorch resnet18 模型

