写在前面的话: 方便以后查文档,且这篇文章会随着学习一直更(因为还有opencv还没怎么学,目前是一些基本的操作)。都是跟着学习资料巩固的,只供学习使用。这一篇分为俩部分—— 边缘提取 与 形态学处理。 其实这部分内容太多了,我也只记录了部分方法的代码实现,还有很多需要在应用中体现。
阈值分割、边缘分割、基于区域的分割、Hough变换
阈值分割 二值化
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def thresholding(im, threshold=128):
seg = np.zeros(im.shape)
seg[im > threshold] = 255
return seg
边缘分割
基本原理是检测图像中灰度变化较为显著的位置,即求图像在各个像素位置的梯度。
im = array(Image.open('G:/photo/创意/1.jpg').convert('L'))
#Sobel derivative filters
""" 1代表y方向梯度,0代表x方向梯度 """
imy = zeros(im.shape)
filters.sobel(im,1,imy) #
imx = zeros(im.shape)
filters.sobel(im,0,imx)
magnitude = sqrt(imx**2+imy**2)
plt.figure(figsize=(18,12))
plt.gray()
plt.subplot(131)
plt.imshow(imx)
plt.axis('off')
plt.subplot(132)
plt.imshow(imy)
plt.axis('off')
plt.subplot(133)
plt.imshow(magnitude)
plt.axis('off')
plt.show()
基于梯度近似计算的边缘检测方法得到的边缘位置不精确,对图像噪声较为敏感。图像在物体边缘处的一阶导数会产生一个极值,因此二阶导数会存在一个过零点。过零点比极值点更容易确定。那么我们应该怎么样可靠的计算二阶导数?
当标准差增大时所需卷积核的大小也相应增大。LoG算子可以用两个标准差不同的Gaussians算子卷积结果的差近似计算,也叫DoG(difference of Gaussians)算子。
def zero_cross(im):
# 过零点检测(背景中的明亮噪声点)
res = zeros(im.shape,dtype=float)
m,n = im.shape
for i in range(m-1):
for j in range(n-1):
if (im[i,j]*im[i+1,j]<0) or (im[i,j]*im[i,j+1]<0) or (im[i,j]*im[i+1,j+1]<0) or (im[i,j+1]*im[i+1,j]<0):
res[i,j]=1
return res
def post_procesing(im,edge,threshold=0.2):
# 用sobel算子计算一阶梯度
imy = zeros(im.shape)
filters.sobel(im,1,imy)
imx = zeros(im.shape)
filters.sobel(im,0,imx)
magnitude = sqrt(imx**2+imy**2)
# 去除一阶梯度角度的过零点
edge_res = zeros(im.shape)
edge_res[:] = edge
edge_res[magnitude<threshold]=0
return edge_res
im = array(Image.open('G:/photo/创意/1.jpg').convert('L'))/255.0
im1 = ndimage.filters.gaussian_filter(im,4)
im2 = ndimage.filters.gaussian_filter(im,6)
imd = im2 - im1 # DoG
sd = ndimage.laplace(im1) # LoG
edge = zero_cross(sd)
edge_f = post_procesing(im,edge)
plt.figure(figsize=(18,12))
plt.subplot(121)
plt.imshow(sd)
plt.title(r'LoG,$\sigma=4$')
plt.subplot(122)
plt.imshow(imd)
plt.title(r'DoG,$\sigma_1=4,\sigma_2=6$')
plt.show()
plt.figure(figsize=(18,12))
plt.gray()
plt.subplot(121)
plt.imshow(edge)
plt.title('zero crossing')
plt.subplot(122)
plt.imshow(edge_f)
plt.title('after post processing')
plt.show()
顺便介绍几个常用的算子
具体可看 常见边界提取算子
基于区域的分割
分裂-归并分割算法
Hough变换
图像空间中共线的点在参数空间中对应的直线相交于同一点
参数空间直线可以写为:
s = x c o s θ + y s i n θ s=xcos\theta+ysin\theta s=xcosθ+ysinθ
import cv2
new_path = 'G:/photo/innovation/1.jpg'
im = cv2.imread(new_path)
edges = cv2.Canny(im,5,100)
lines = cv2.HoughLines(edges,0.5,0.1,100)
x = np.array([0,im.shape[1]])
for line in lines:
y = (line[0,0] - x*np.cos(line[0,1]))/np.sin(line[0,1])
plt.plot(x,y)
plt.axis([0, im.shape[1], im.shape[0],0])
plt.figure()
plt.gray()
plt.imshow(edges)
plt.show()
Hough变换的特点:
连通域处理、腐蚀与膨胀
连通域处理
在二值图像中,如果两个像素点相邻且值相同(同为0或同为1),那么就认为这两个像素点在一个相互连通的区域内。而同一个连通区域的所有像素点,都用同一个数值来进行标记,这个过程就叫连通区域标记
skimage.measure.label(image,connectivity=None)
参数中的image表示需要处理的二值图像,connectivity表示连接的模式,1代表4邻接,2代表8邻接。
输出一个标记数组(labels), 从0开始标记
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import measurements,morphology
im = np.array(Image.open('G:/photo/创意/1.jpg').convert('L'))
im_s = (im > 30)
此时 im_s 返回的是布尔值,也可直接放入函数,显示出每个连通域的像素个数,可以对其进行后续处理
labels, nbr_objects = measurements.label(im_s)
for i in range(1,nbr_objects+1):
area = np.sum(labels == i)
print('No.{}: {}'.format(i,area))
if area < 100 or area >5000:
labels[labels==i]=0 # 去除不符合条件的区域
腐蚀与膨胀、开运算与闭运算
膨胀 binary_dilation,可以将不同区域不同的显示出来
im = (im>128)
im_d = morphology.binary_dilation(im,np.ones((50,50)))
labels, nbr = measurements.label(im_d)
labels = labels * im
plt.gray()
plt.imshow(labels)
plt.show()
进行一次开运算 binary_opening ,看一下连通域像素的变化
im = np.array(Image.open('G:/photo/创意/1.jpg').convert('L'))
im = (im<128)
labels, nbr_objects = measurements.label(im)
print( "Number of objects:", nbr_objects)
# morphology - opening to separate objects better
im_open = morphology.binary_opening(im,np.ones((9,5)),iterations=2)
labels_open, nbr_objects_open = measurements.label(im_open)
print( "Number of objects:", nbr_objects_open)