Python计算机视觉:图像边缘检测

如果需要检测到图像里面的边缘 , 首先我们需要知道边缘处具有什么特征 。
对于一幅灰度图像来说 , 边缘两边的灰度值肯定不相同 , 这样我们才能分辨出哪里是边缘 , 哪里不是 。
因此 , 如果我们需要检测一个灰度图像的边缘 , 我们需要找出哪里的灰度变化最大 。 显然 , 灰度变化越大 , 对比度越强 , 边缘就越明显 。
那么问题来了 , 我们怎么知道哪里灰度变化大 , 哪里灰度变化小呢?
导数 , 梯度 , 边缘信息
在数学中 , 与变化率有关的就是导数 。
如果灰度图像的像素是连续的(实际不是) , 那么我们可以分别原图像G对x方向和y方向求导数

如果要在同一图像上包含两个方向的边缘信息 , 我们可以用到梯度 。 (梯度是一个向量)
原图像的梯度向量Gxy为(Gx,Gy),梯度向量的大小和方向可以用下面两个式子计算
梯度向量大小就包含了x方向和y方向的边缘信息 。
图像导数
实际上 , 图像矩阵是离散的 。
连续函数求变化率用的是导数 , 而离散函数求变化率用的是差分 。
差分的概念很容易理解 , 就是用相邻两个数的差来表示变化率 。
下面公式是向后差分
x方向的差分:Gx(n,y)=G(n,y)-G(n-1,y)
y方向的差分:Gy(x,n)=G(x,n)-G(x,n-1)
实际计算图像导数时 , 我们是通过原图像和一个算子进行卷积来完成的(这种方法是求图像的近似导数) 。
最简单的求图像导数的算子是Prewitt算子:
x方向的Prewitt算子为
如果图像矩阵中一块区域为
x5处的x方向导数为x3+x6+x9-x1-x4-x7
对矩阵中所有元素进行上述计算 , 就是卷积的过程 。
因此 , 利用原图像和x方向Prewitt算子进行卷积就可以得到图像的x方向导数矩阵Gx ,
利用原图像和y方向Prewitt算子进行卷积就可以得到图像的y方向导数矩阵Gy 。
利用公式
Python实现卷积及Prewitt算子的边缘检测
首先我们把图像卷积函数封装在一个名为imconv的函数中(实际上 , scipy库中的signal模块含有一个二维卷积的方法convolve2d())
复制代码
importnumpyasnp
defimconv(image_array,suanzi):
【Python计算机视觉:图像边缘检测】'''计算卷积
参数
image_array原灰度图像矩阵
suanzi算子
返回
原图像与算子卷积后的结果矩阵
'''
image=image_array.copy()#原图像矩阵的深拷贝
dim1,dim2=image.shape
#对每个元素与算子进行乘积再求和(忽略最外圈边框像素)
foriinrange(1,dim1-1):
forjinrange(1,dim2-1):
image[i,j]=(image_array[(i-1):(i+2),(j-1):(j+2)]*suanzi).sum()
#由于卷积后灰度值不一定在0-255之间 , 统一化成0-255
image=image*(255.0/image.max())
#返回结果矩阵
returnimage
然后我们利用Prewitt算子计算x方向导数矩阵Gx , y方向导数矩阵Gy , 和梯度矩阵Gxy 。
importnumpyasnp
#x方向的Prewitt算子
suanzi_x=np.array([[-1,0,1],
[-1,0,1],
[-1,0,1]])
#y方向的Prewitt算子
suanzi_y=np.array([[-1,-1,-1],
[0,0,0],
[1,1,1]])
#打开图像并转化成灰度图像
image=Image.open("pika.jpg").convert("L")
#转化成图像矩阵
image_array=np.array(image)
#得到x方向矩阵
image_x=imconv(image_array,suanzi_x)
#得到y方向矩阵
image_y=imconv(image_array,suanzi_y)
#得到梯度矩阵
image_xy=np.sqrt(image_x**2+image_y**2)
#梯度矩阵统一到0-255
image_xy=(255.0/image_xy.max())*image_xy
#绘出图像
plt.subplot(2,2,1)
plt.imshow(image_array,cmap=cm.gray)
plt.axis("off")
plt.subplot(2,2,2)
plt.imshow(image_x,cmap=cm.gray)
plt.axis("off")
plt.subplot(2,2,3)
plt.imshow(image_y,cmap=cm.gray)
plt.axis("off")
plt.subplot(2,2,4)
plt.imshow(image_xy,cmap=cm.gray)
plt.axis("off")
plt.show()
Prewitt算子的结果如下图所示
上方:左图为原图像 , 右图为x方向导数图像
下方:左图为y方向导数图像 , 右图为梯度图像
近似导数的Sobel算子
Sobel算子与Prewitt比较类似 , 但是它比Prewitt算子要好一些 。
x方向的Sobel算子为
#x方向的Sobel算子
suanzi_x=np.array([[-1,0,1],
[-2,0,2],
[-1,0,1]])
#y方向的Sobel算子
suanzi_y=np.array([[-1,-2,-1],
[0,0,0],
[1,2,1]])
Sobel算子的结果如下图所示
上方:左图为原图像 , 右图为x方向导数图像
下方:左图为y方向导数图像 , 右图为梯度图像
近似二阶导数的Laplace算子
Laplace算子是一个二阶导数的算子 , 它实际上是一个x方向二阶导数和y方向二阶导数的和的近似求导算子 。
实际上 , Laplace算子是通过Sobel算子推导出来的 。
Laplace算子为
convolve()的第一个参数是原图像矩阵 , 第二个参数为卷积算子 , 然后指定关键字参数mode="same"(输出矩阵大小和原图像矩阵相同) 。
importnumpyasnp
fromPILimportImage
importmatplotlib.pyplotasplt
importmatplotlib.cmascm
importscipy.signalassignal#导入sicpy的signal模块
#Laplace算子
suanzi1=np.array([[0,1,0],
[1,-4,1],
[0,1,0]])
#Laplace扩展算子
suanzi2=np.array([[1,1,1],
[1,-8,1],
[1,1,1]])
#打开图像并转化成灰度图像
image=Image.open("pika.jpg").convert("L")
image_array=np.array(image)
#利用signal的convolve计算卷积
image_suanzi1=signal.convolve2d(image_array,suanzi1,mode="same")
image_suanzi2=signal.convolve2d(image_array,suanzi2,mode="same")
#将卷积结果转化成0~255
image_suanzi1=(image_suanzi1/float(image_suanzi1.max()))*255
image_suanzi2=(image_suanzi2/float(image_suanzi2.max()))*255
#为了使看清边缘检测结果 , 将大于灰度平均值的灰度变成255(白色)
image_suanzi1[image_suanzi1>image_suanzi1.mean()]=255
image_suanzi2[image_suanzi2>image_suanzi2.mean()]=255
#显示图像
plt.subplot(2,1,1)
plt.imshow(image_array,cmap=cm.gray)
plt.axis("off")
plt.subplot(2,2,3)
plt.imshow(image_suanzi1,cmap=cm.gray)
plt.axis("off")
plt.subplot(2,2,4)
plt.imshow(image_suanzi2,cmap=cm.gray)
plt.axis("off")
plt.show()
结果如下图
其中上方为原图像
下方:左边为Laplace算子结果 , 右边为Laplace扩展算子结果
而Laplace扩展算子的结果看上去比Laplace的结果少一些噪声 。
降噪后进行边缘检测
为了获得更好的边缘检测效果 , 可以先对图像进行模糊平滑处理 , 目的是去除图像中的高频噪声 。
python程序如下
首先用标准差为5的5*5高斯算子对图像进行平滑处理 , 然后利用Laplace的扩展算子对图像进行边缘检测 。
importnumpyasnp
fromPILimportImage
importmatplotlib.pyplotasplt
importmatplotlib.cmascm
importscipy.signalassignal
#生成高斯算子的函数
deffunc(x,y,sigma=1):
return100*(1/(2*np.pi*sigma))*np.exp(-((x-2)**2+(y-2)**2)/(2.0*sigma**2))
#生成标准差为5的5*5高斯算子
suanzi1=np.fromfunction(func,(5,5),sigma=5)
#Laplace扩展算子
suanzi2=np.array([[1,1,1],
[1,-8,1],
[1,1,1]])
#打开图像并转化成灰度图像
image=Image.open("pika.jpg").convert("L")
image_array=np.array(image)
#利用生成的高斯算子与原图像进行卷积对图像进行平滑处理
image_blur=signal.convolve2d(image_array,suanzi1,mode="same")
#对平滑后的图像进行边缘检测
image2=signal.convolve2d(image_blur,suanzi2,mode="same")
#结果转化到0-255
image2=(image2/float(image2.max()))*255
#将大于灰度平均值的灰度值变成255(白色) , 便于观察边缘
image2[image2>image2.mean()]=255
#显示图像
plt.subplot(2,1,1)
plt.imshow(image_array,cmap=cm.gray)
plt.axis("off")
plt.subplot(2,1,2)
plt.imshow(image2,cmap=cm.gray)
plt.axis("off")
plt.show()
结果如下图


    推荐阅读