选中内容(绿色)时除了会搜索文章名,还会搜索文章内容
点击结果中的文章名进入文章界面后可以按Ctrl+F在页面内搜索
  • 版权:CC BY-SA 4.0
  • 创建:2021-07-26
  • 更新:2021-08-03
Pytorch 使用模型(卷积/conv)实现 sobel(索贝尔) 边缘检测实现源码


边缘检测效果

sobel_edge2

final

sobel_edge

sobel_v831

源码在末尾

边缘检测原理

边缘就是值变化剧烈的地方, 如果对值的变化求导, 则边缘部分就是导数局部最大.
但是在图像处理时没有具体的函数让我们求导, 使用卷积运算则可以很好的近似替代

如下图, 假设左上为坐标原点, 横轴为 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. [-1, 0, 1],
  2. [-2, 0, 2],
  3. [-1, 0, 1]

对图像进行卷积运算, 如图中的计算方法, 像素点左右权值取2, 角上的也参与计算,但是权值为1,没有左右边的权值高. 这样我们就得到了横轴的变化率图, 即边缘检测图.

注意, 这里是对横轴计算了, 比较的点左右的值变化, 所以实际看到的图像会出现明显的纵轴边缘, 如下图左边

vertical_horizontal

同理, 上图右边的图使用卷积核

  1. [1,2,1],
  2. [0,0,0],
  3. [-1, -2, -1]

得到的纵轴的边缘图.

注意这里用右边减左边, 如果右边的值比左边的小会是负数, 如果我们希望只检测颜色值变大(变白)则可以直接使用, 如果两个变化方向都要检测, 则可以取绝对值. 比如下图左边是没有取绝对值, 右边取了绝对值

without_with_abs

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

final

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

sobel_edge2

不同的原因是使用水平和垂直的图平方和开根后, 直接用 plt.imshow 显示, 和 GIMP 的处理方式不同

  1. out = np.sqrt(np.square(out_v) + np.square(out_h))
  2. plt.imshow(out)

简单地直接将值规范到[0, 255] 就和 GIMP 的图相似了(但不完全一样)

  1. out = np.sqrt(np.square(out_v) + np.square(out_h))
  2. out = out * 255.0 / out.max()
  3. plt.imshow(out.astype(np.uint8))

sobel_v_h

自定义卷积核来实现边缘检测

除了上面说了使用两次卷积计算, 也可以用只计算一次的卷积核, 比如:

  1. [-1, -1, -1],
  2. [ -1, 8, -1],
  3. [ -1, -1, -1]

这是对于一个通道(灰度图)来说, 如果要扩充到三个通道(RGB), 卷积核参数就是如下形式

  1. conv_rgb_core_sobel = [
  2. [[-1,-1,-1],[-1,8,-1], [-1, -1, -1],
  3. [0,0,0],[0,0,0], [0,0,0],
  4. [0,0,0],[0,0,0], [0,0,0]
  5. ],
  6. [[0,0,0],[0,0,0], [0,0,0],
  7. [-1,-1,-1],[-1,8,-1], [-1, -1, -1],
  8. [0,0,0],[0,0,0], [0,0,0]
  9. ],
  10. [[0,0,0],[0,0,0], [0,0,0],
  11. [0,0,0],[0,0,0], [0,0,0],
  12. [-1,-1,-1],[-1,8,-1], [-1, -1, -1],
  13. ]]

经过卷积运算后, 前后图如下:

sobel_edge

注意, 输入值范围如果为[0, 255], 输出值则范围会变化, 以图片形式查看时需要注意加以处理, 这里使用了plt.imshow(out)来显示, 这个函数会自动对图像做简单的处理, 才会看起来是黑色背景

导出成模型使用

可以将 Net 导出成 onnx 即可在其它平台使用, 就是一个简单的卷积层

部署到 V831 后的样子(使用了卷积核[-1,-1,-1],[-1,8,-1], [-1, -1, -1],):

sobel_v831

源码

  1. '''
  2. simple sobel edge demo
  3. visit: https://neucrack.com/p/377
  4. @author neucrack
  5. @license MIT
  6. '''
  7. import torch
  8. import torch.nn as nn
  9. import numpy as np
  10. import cv2
  11. from PIL import Image
  12. import matplotlib.pyplot as plt
  13. class Net(nn.Module):
  14. def __init__(self):
  15. super(Net, self).__init__()
  16. self.conv1 = nn.Conv2d(3, 3, 3, padding=(0, 0), bias=False)
  17. def forward(self, x):
  18. x = self.conv1(x)
  19. return x
  20. net = Net()
  21. conv_rgb_core_original = [
  22. [[0,0,0],[0,1,0], [0,0,0],
  23. [0,0,0],[0,0,0], [0,0,0],
  24. [0,0,0],[0,0,0], [0,0,0]
  25. ],
  26. [[0,0,0],[0,0,0], [0,0,0],
  27. [0,0,0],[0,1,0], [0,0,0],
  28. [0,0,0],[0,0,0], [0,0,0]
  29. ],
  30. [[0,0,0],[0,0,0], [0,0,0],
  31. [0,0,0],[0,0,0], [0,0,0],
  32. [0,0,0],[0,1,0], [0,0,0]
  33. ]]
  34. conv_rgb_core_sobel = [
  35. [[-1,-1,-1],[-1,8,-1], [-1, -1, -1],
  36. [0,0,0],[0,0,0], [0,0,0],
  37. [0,0,0],[0,0,0], [0,0,0]
  38. ],
  39. [[0,0,0],[0,0,0], [0,0,0],
  40. [-1,-1,-1],[-1,8,-1], [-1, -1, -1],
  41. [0,0,0],[0,0,0], [0,0,0]
  42. ],
  43. [[0,0,0],[0,0,0], [0,0,0],
  44. [0,0,0],[0,0,0], [0,0,0],
  45. [-1,-1,-1],[-1,8,-1], [-1, -1, -1],
  46. ]]
  47. conv_rgb_core_sobel_vertical = [
  48. [[-1,0,1],[-2,0,2], [-1, 0, 1],
  49. [0,0,0],[0,0,0], [0,0,0],
  50. [0,0,0],[0,0,0], [0,0,0]
  51. ],
  52. [[0,0,0],[0,0,0], [0,0,0],
  53. [-1,0,1],[-2,0,2], [-1, 0, 1],
  54. [0,0,0],[0,0,0], [0,0,0]
  55. ],
  56. [[0,0,0],[0,0,0], [0,0,0],
  57. [0,0,0],[0,0,0], [0,0,0],
  58. [-1,0,1],[-2,0,2], [-1, 0, 1],
  59. ]]
  60. conv_rgb_core_sobel_horizontal = [
  61. [[1,2,1],[0,0,0], [-1, -2, -1],
  62. [0,0,0],[0,0,0], [0,0,0],
  63. [0,0,0],[0,0,0], [0,0,0]
  64. ],
  65. [[0,0,0],[0,0,0], [0,0,0],
  66. [1,2,1],[0,0,0], [-1, -2, -1],
  67. [0,0,0],[0,0,0], [0,0,0]
  68. ],
  69. [[0,0,0],[0,0,0], [0,0,0],
  70. [0,0,0],[0,0,0], [0,0,0],
  71. [1,2,1],[0,0,0], [-1, -2, -1],
  72. ]]
  73. def sobel(net, kernel):
  74. sobel_kernel = np.array(kernel, dtype='float32')
  75. sobel_kernel = sobel_kernel.reshape((3, 3, 3, 3))
  76. net.conv1.weight.data = torch.from_numpy(sobel_kernel)
  77. params = list(net.parameters())
  78. img = cv2.imread("out/test.jpg")
  79. input_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  80. input_tensor = (input_img.astype(np.float32) - 127.5) / 128 # to [-1, 1]
  81. input_tensor = torch.Tensor(input_tensor).permute((2, 0, 1))
  82. print(input_tensor.shape)
  83. input_tensor = input_tensor.unsqueeze(0)
  84. print("input shape:", input_tensor.shape)
  85. sobel(net, conv_rgb_core_sobel)
  86. out = net(input_tensor).detach().numpy()[0].transpose([1,2,0])
  87. sobel(net, conv_rgb_core_sobel_vertical)
  88. out_v = net(input_tensor).detach().numpy()[0].transpose([1,2,0])
  89. sobel(net, conv_rgb_core_sobel_horizontal)
  90. out_h = net(input_tensor).detach().numpy()[0].transpose([1,2,0])
  91. print("out shape: {}, tensor:{}".format(out.shape, out))
  92. print(out.shape, out.max(), out.min())
  93. plt.figure()
  94. plt.figure()
  95. plt.subplot(1, 5, 1)
  96. input = input_tensor.numpy()[0].transpose((1,2,0))
  97. print(input.max(), input.min())
  98. plt.imshow(input_img)
  99. plt.subplot(1, 5, 2)
  100. print(out.max(), out.min())
  101. # out = np.sqrt(np.square(out))
  102. # out = out * 255.0 / out.max()
  103. # out = out.astype(np.uint8)
  104. # print(out.max(), out.min())
  105. plt.imshow(out)
  106. plt.subplot(1, 5, 3)
  107. out = np.abs(out_v)
  108. # out = out * 255.0 / out.max()
  109. # plt.imshow(out.astype(np.uint8))
  110. plt.imshow(out)
  111. plt.subplot(1, 5, 4)
  112. out = np.abs(out_h)
  113. # out = out * 255.0 / out.max()
  114. # plt.imshow(out.astype(np.uint8))
  115. plt.imshow(out)
  116. plt.subplot(1, 5, 5)
  117. out = np.sqrt(np.square(out_v) + np.square(out_h))
  118. # out = out * 255.0 / out.max()
  119. # plt.imshow(out.astype(np.uint8))
  120. plt.imshow(out)
  121. plt.show()

更多

如何部署到 V831/ V833 上可以参考: 在V831上(awnn)跑 pytorch resnet18 模型

参考

文章有误?有想法想讨论?查看或者发起勘误/讨论 主题
(发起评论需要先登录 github)

/wallpaper/wallhaven-28el1y.jpg