Canny算法通常处理的图像为灰度图,因此如果摄像机获取的是彩色图像,那首先就得进行灰度化。对一幅彩色图进行灰度化,就是根据图像各个通道的采样值进行加权平均。以RGB格式的彩图为例,通常灰度化采用的方法主要有:
方法1:Gray=(R+G+B)/3;
方法2:Gray=0.299R+0.587G+0.114B;
注意1:至于其他格式的彩色图像,可以根据相应的转换关系转为RGB然后再进行灰度化;
注意2:在编程时要注意图像格式中RGB的顺序通常为BGR。
为了尽可能减少噪声对边缘检测结果的影响,所以必须滤除噪声以防止由噪声引起的错误检测。为了平滑图像,使用高斯滤波器与图像进行卷积,该步骤将平滑图像,以减少边缘检测器上明显的噪声影响。
其实在各个算法库如Matlab、OpenCV等,在实现的时候,就是采用一个矩阵模板进行加权运算,拿图像的八连通区域来说,中间点的像素值就等于八连通区的像素值的均值,这样达到平滑的效果,该模板我们常成为高斯核。
根据上述分析可知,高斯核是整个求解的关键。很显然,它是通过二维高斯函数计算得来的。这里给出离散高斯核矩阵的计算公式,
离散的高斯卷积核H: (2k+1)×(2k+1)维,其元素计算方法为:
其中Sigma为方差,k确定核矩阵的维数。高斯卷积核大小的选择将影响Canny检测器的性能。尺寸越大,检测器对噪声的敏感度越低,但是边缘检测的定位误差也将略有增加。一般5x5是一个比较不错的平衡
图像的边缘可以指向不同方向,因此经典Canny算法用了四个梯度算子来分别计算水平,垂直和对角线方向的梯度。但是通常都不用四个梯度算子来分别计算四个方向。常用的边缘差分算子(如Rober,Prewitt,Sobel)计算水平和垂直方向的差分Gx和Gy。这样就可以如下计算梯度模和方向:
梯度角度θ范围从弧度-π到π,然后把它近似到四个方向,分别代表水平,垂直和两个对角线方向(0°,45°,90°,135°)。可以以±iπ/8(i=1,3,5,7)分割,落在每个区域的梯度角给一个特定值,代表四个方向之一。
非极大值抑制是一种边缘稀疏技术,非极大值抑制的作用在于“瘦”边。对图像进行梯度计算后,仅仅基于梯度值提取的边缘仍然很模糊。对于标准3,对边缘有且应当只有一个准确的响应。而非极大值抑制则可以帮助将局部最大值之外的所有梯度值抑制为0,对梯度图像中每个像素进行非极大值抑制的算法是:
如图:假设P是中心点,W,SW,NE,E是旁边的四个像素点,P1,P2是虚拟的像素点(亚像素),P2-P1的红线是梯度,那么P1,P2的像素值怎么算?
这里用到线性差值的算法去近似算一个值,
def getSubPixel(self, weight, g1, g2, g3, g4):
dp1 = weight * g1 + (1 - weight) * g2
dp2 = weight * g3 + (1 - weight) * g4
return dp1, dp2
weight就是权重,或者比例,假设权重是0.6,那么P2=SW*0.6+W*0.4,P1=E*0.4+NE*0.6
然后比较P2,P,P1三个点最大值,如果P是最大,保留当前值,否则,置为0
import cv2
import matplotlib.pyplot as plt
import numpy as np
import random
# 1.对图像进行灰度化
# 2.对图像进行高斯滤波:
# 根据待滤波的像素点及其邻域点的灰度值按照一定的参数规则进行加权平均。这样可以有效滤去理想图像中叠加的高频噪声。
# 3.检测图像中的水平、垂直和对角边缘(如Prewitt,Sobel算子等),计算梯度。
# 4.对梯度幅值进行非极大值抑制:通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。
# 5.用双阈值算法检测和连接边缘
class Canny:
# Prewitt算子
kernelx = np.array(
[
[1, 1, 1],
[0, 0, 0],
[-1, -1, -1]
]
)
kernely = np.array(
[
[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]
]
)
# 灰度化图像
def grayscaleImage(self, filePath):
imageGrayscaleArray = cv2.imread(filePath)
gray_img = cv2.cvtColor(imageGrayscaleArray, cv2.COLOR_RGB2GRAY)
return gray_img
# 高斯滤波
def gaussFilterImage(self, filePath):
gaussFilterImage = cv2.GaussianBlur(self.grayscaleImage(self, filePath), (5, 5), 0, 0)
return gaussFilterImage
# 边缘检测&非极大值抑制
def nonmaxLimit(self, filePath):
image = self.gaussFilterImage(self, filePath)
# Sobel
x = cv2.Sobel(image, cv2.CV_16S, 1, 0)
y = cv2.Sobel(image, cv2.CV_16S, 0, 1)
# Prewitt
# x = cv2.filter2D(image, -1, self.kernelx)
# y = cv2.filter2D(image, -1, self.kernely)
# 转换数据 并 合成
absX = cv2.convertScaleAbs(x) # 格式转换函数
absY = cv2.convertScaleAbs(y)
# 图像混合后的数组
result = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
width, height = result.shape
tempImage = np.zeros_like(image)
g1 = 0
g2 = 0
g3 = 0
g4 = 0
for i in range(1, width - 1):
for j in range(1, height - 1):
# 梯度>1
if (result[i][j] > 1):
g1 = image[i - 1][j - 1]
g2 = image[i][j - 1]
g3 = image[i][j + 1]
g4 = image[i + 1][j + 1]
# 梯度<1
if (result[i][j] < 1):
g1 = image[i - 1][j - 1]
g2 = image[i][j - 1]
g3 = image[i][j + 1]
g4 = image[i + 1][j + 1]
# 梯度=1
if (result[i][j] == 1):
g1 = g2 = image[i - 1][j - 1]
g3 = g4 = image[i + 1][j + 1]
# 梯度=-1
if (result[i][j] == 1):
g1 = g2 = image[i + 1][j - 1]
g3 = g4 = image[i - 1][j + 1]
dp1, dp2 = self.getSubPixel(self, result[i][j], g1, g2, g3, g4)
if image[i, j] == max(image[i, j], dp1, dp2):
tempImage[i][j] = image[i][j]
# 基于梯度统计信息计算法实现自适应阈值
MAX = image.max()
MIN = image.min()
MED = np.median(image)
average = (MAX + MIN + MED) / 3
sigma = 0.33
# 低阈值
lowThreshold = max(0, (1 - sigma) * average)
# 高阈值
highThreshold = min(255, (1 + sigma) * average)
imageEdge = np.zeros_like(tempImage)
for i in range(1, width - 1):
for j in range(1, height - 1):
if tempImage[i][j] >= highThreshold:
imageEdge[i][j] = 255
elif tempImage[i][j] > lowThreshold:
# python切片 https://blog.csdn.net/Cai_Xu_Kun/article/details/114978189
around = tempImage[i - 1: i + 2, j - 1: j + 2]
if around.max() >= highThreshold:
imageEdge[i, j] = 255
fig, axes = plt.subplots(1, 3)
axes[0].imshow(image)
axes[1].imshow(result)
axes[2].imshow(imageEdge)
plt.show()
# 计算亚像素
def getSubPixel(self, weight, g1, g2, g3, g4):
dp1 = weight * g1 + (1 - weight) * g2
dp2 = weight * g3 + (1 - weight) * g4
return dp1, dp2
Canny.nonmaxLimit(Canny, "image/aaa.jpg")