文章目录
引言
这一章,我们的重点转到了图像提取出来的属性的图像处理方法,分割是该方向的一个主要步骤。
分割将图像细化分为构成它的子区域和物体,当感兴趣的物体或区域被检测出来时,就停止分割,分割的精度决定了计算分析过程中的最终成败,我们应该在减少图像无关细节的影响的同时,增强感兴趣物体。
10.1 基础知识
令R表示图像占据的整个空间区域,把R分为n个子区域 R 1 , R 2 . . . R n R_1,R_2...R_n R1,R2...Rn,需要满足以下的条件。
- ⋃ i = 1 n R i = R \bigcup ^{n} _{i=1} R{i}=R i=1⋃nRi=R
- R i 为 4 连 接 或 8 连 接 R_{i}为4连接或8连接 Ri为4连接或8连接4领域:(x+1,y),(x-1,y),(x,y+1),(x,y-1) ∈ N 4 ( p ) \in N_4(p) ∈N4(p);8领域:(x-1,y-1),(x-1,y+1),(x+1,y+1),(x+1,y-1) ∈ N 8 ( p ) \in N_8(p) ∈N8(p),注意均为4个像素。
邻接性:考虑二值图像,灰度值为0或1,V为邻接性灰度值的集合,假设令V={1},4邻接的定义是p=q(灰度值),且q在N_4(p)中,8邻接类似。
- R i ∩ R j = ∅ R_{i}\cap R_{j}=\emptyset Ri∩Rj=∅
- Q ( R i ) = T r u e : R i 中 所 有 的 像 素 具 有 相 同 的 灰 度 值 Q(R_{i})=True:R_{i}中所有的像素具有相同的灰度值 Q(Ri)=True:Ri中所有的像素具有相同的灰度值
- Q ( R j ∪ R i = F a l s e ) : 分 割 后 区 域 的 特 性 不 同 Q(R_{j}\cup R_{i}=False):分割后区域的特性不同 Q(Rj∪Ri=False):分割后区域的特性不同
像素值的标准差可以用于区分纹理区域和恒定区域的一个属性,标准差是针对总体而言,感兴趣的区域标准差为正,我们也可以计算区域的边界。
10.2 点、线和边缘检测
也叫做不连续检测,我们可将孤立点视作一条线,边缘视作相连边缘像素的集合。
检测方法:一阶/二阶微分
一阶:
α
f
α
x
=
f
′
(
x
)
=
f
(
x
+
1
)
−
f
(
x
)
\frac{\alpha f}{\alpha x}=f'(x)=f(x+1)-f(x)
αxαf=f′(x)=f(x+1)−f(x)
二阶:
α
2
f
α
x
2
=
f
(
x
+
2
)
−
2
f
(
x
+
1
)
+
f
(
x
)
=
f
(
x
+
1
)
+
f
(
x
−
1
)
−
2
f
(
x
)
\frac{\alpha ^ {2}f }{\alpha x^{2}}=f(x+2)-2f(x+1)+f(x)=f(x+1)+f(x-1)-2f(x)
αx2α2f=f(x+2)−2f(x+1)+f(x)=f(x+1)+f(x−1)−2f(x)
用剖面线的一阶/二阶微分值来检测点、线和边缘,一阶导数会产生粗的边缘(灰度过渡),二阶导数会产生细的边缘,这是很好理解的,只需明白一阶/二阶导数在斜坡的值就能明白,二阶导数只有在不同线段斜率的突变点处值才不为零,会产生双边缘响应。二阶导数的符号可用于确定边缘的过渡是从亮到暗还是从暗到亮,<0时,变亮。
且通过观察,二阶导数在遇到孤立的噪声,精细细节时,响应幅度远大于一阶响应,所以,无论时增强细节还是噪声,二阶导数远强于一阶导数。
计算图像的一阶导数和二阶导数的方法是使用空间滤波器。
R
=
∑
k
=
1
9
w
k
z
k
,
z
k
是
像
素
的
灰
度
,
w
k
是
系
数
R=\sum ^{9} _{k=1} w_{k}z_{k},z_{k}是像素的灰度,w_{k}是系数
R=k=1∑9wkzk,zk是像素的灰度,wk是系数空间滤波器也是掩膜,用于卷积。
10.2.1 孤立点的检测
孤立点的检测,基于二阶导数,使用拉式算子,我们可以在四邻域各向同性的基础上,将点扩展至掩膜的四个对角,如果在某掩膜的中心点上,响应的绝对值超过了某个指定的阈值,那么该店就被检测到,输出图像中,这样的点被标注为1,非检测出的点被标记为0,从而产生二值图像。
掩膜:
表达式:
g ( x , y ) = { 1 , ∣ R ( x , y ) ∣ ≥ T 0 , 其 他 g(x,y)= \begin{cases} 1,\quad |R(x,y)|\geq T\\ 0, \ 其他 \end{cases} g(x,y)={1,∣R(x,y)∣≥T0, 其他
matlab:g=abs(imfilter((double(f),w))) ≥ T \geq T ≥T
10.2.2 线检测
线检测的复杂度比孤立点的检测更高,我们检测线时,结果中可能同时存在正像素和负像素,而绝对值的处理会使线的宽度加倍,更合适的方法是仅使用正值,这就需要调整比例。当线的宽度比拉普拉斯模板尺寸宽时,这些线就被零值“山谷”分开。
在线检测中,我们的兴趣在于检测特定方向的线,我们有4个检测模板,分别是
0
0
,
+
(
−
)
4
5
0
,
9
0
0
0^0,+(-)45^{0},90^{0}
00,+(−)450,900,若某个方向的响应更大,说明越接近某个方向的线。对于感兴趣的方向,选择用一个比其他方向更大的系数加权,且每个模板中的系数之和为零,这表明恒定灰度区域中的响应为零。
10.2.3 边缘模型
边缘模型按照灰度剖面来分类。
- 台阶模型:存在一像素宽
- 斜坡模型:不存在一像素宽,属于边缘模糊
- 屋顶模型(一条穿过图像区域的一像素宽的线),屋顶的宽度由区域的线的宽度和尖锐度决定
几个关于导数的结论:
- 一阶导数的幅度可用于检测图像的点是否存在一个边缘
- 二阶导数的符号可确定一个边缘像素是位于边缘的暗侧还是亮侧
- 二阶导数的零交叉点可用于定位粗边缘的中心
边缘检测的3 steps
- 平滑处理
- 边缘点的检测
- 边缘定位
10.2.4 基本边缘检测
我们要在图像中寻找边缘的强度和方向,我们所用的工具是梯度,定义如下:
▽
f
=
g
r
a
d
(
f
)
=
[
g
x
g
y
]
=
[
α
f
α
x
α
f
α
y
]
\bigtriangledown f=grad(f)=\left[\begin{matrix}g_{x}\\g_{y}\\\end{matrix} \right]=\left[ \begin{matrix} \frac{\alpha f}{\alpha x}\\ \frac{\alpha f}{\alpha y}\\ \end{matrix} \right]
▽f=grad(f)=[gxgy]=[αxαfαyαf]
梯度指出了变化最大率的方向,向量的大小为两个偏导数模平方的根号,近似为:
M
(
x
,
y
)
≈
∣
g
x
∣
+
∣
g
y
∣
M(x,y)\approx |g_{x}|+|g_{y}|
M(x,y)≈∣gx∣+∣gy∣
方向:
α
(
x
,
y
)
=
a
r
c
t
a
n
[
g
x
g
y
]
\alpha(x,y)=arctan\left[\begin{matrix}g_{x}\\g_{y}\\\end{matrix} \right]
α(x,y)=arctan[gxgy]注意x轴正方向向下,y轴正方向向右。
几种算子与其计算方法:
- Roberts算子(对角线边缘感兴趣):
- Sobel算子(有中心权重,可以平滑图像,抑制噪声):
- 拉普拉斯算子:
拉普拉斯算子是有缺点的,由于各向同性,所以不能辨别方向,噪声也更大;优点是可以进行边缘定位及判断像素是暗的一边还是亮的一边。
同时,我们也可以采用阈值处理的方法使得图像的边缘更少,且边缘要清晰得多,相当于平滑处理。
现在更为先进的边缘处理技术是Marr-Hidreth边缘检测器和坎农边缘检测器。
Marr算子也被称为高斯拉普拉斯算子,定义如下:
▽
2
G
(
x
,
y
)
=
[
x
2
+
y
2
−
2
σ
2
σ
4
e
−
(
x
2
+
y
2
)
2
σ
2
]
\bigtriangledown ^{2}G(x,y)=[\frac{x^2+y^2-2\sigma ^2}{\sigma ^4}e^{\frac{-(x^2+y^2)}{2\sigma ^2}}]
▽2G(x,y)=[σ4x2+y2−2σ2e2σ2−(x2+y2)]零交叉出现在
x
2
+
y
2
=
2
σ
2
x^2+y^2=2\sigma^2
x2+y2=2σ2处,定义了一个中心位于原点,半径为根号2
σ
\sigma
σ的圆,模板正的中心项由紧邻的负区域包围,该区域的值随着到原点距离函数而递减,外层区域值为零,系数之和为零,以便模板的响应在恒定区域为0。
优点:
- 能计算每一点处的一阶导数或是二阶导数
- 可以被调整,使得大算子用于检测边缘模糊,小算子用于检测锐度集中的精细细节
算法定义如下:
g
(
x
,
y
)
=
▽
2
G
(
x
,
y
)
★
f
(
x
,
y
)
g(x,y)=\bigtriangledown ^{2}G(x,y)★f(x,y)
g(x,y)=▽2G(x,y)★f(x,y)
寻找g(x,y)的零交叉来寻找边缘的位置。
寻找零交叉的方法:零交叉意味着至少有两个相对的邻域像素符号的不同,左/右,上/下及两个对角。
10.3 边缘连接和边界检测
因噪声等产生的边缘像素断裂无法形成边缘,我们要引入边缘的连接使得这些断裂的点聚合。
10.3.1 局部处理
分析边缘点邻域内像素,根据相似准则进行连接,相似点性质:
∣
M
(
s
,
t
)
−
M
(
x
,
y
)
≤
E
∣
(1)
|M(s,t)-M(x,y)\leq E| \tag{1}
∣M(s,t)−M(x,y)≤E∣(1)
∣
α
(
s
,
t
)
−
α
(
x
,
y
)
∣
≤
A
(2)
|\alpha(s,t)-\alpha(x,y)|\leq A \tag{2}
∣α(s,t)−α(x,y)∣≤A(2)条件都要得到满足,(1)式为梯度大小,(2)式为角度方向。
10.3.2 区域处理
区域处理对于多边形尤其具有吸引力,要拟合二维曲线,对边界的基本特性产生一个近似,例如端点和凹点。
10.3.3 霍夫变换全局处理
在图像中,一条线(或是边缘)都应该满足 y i = a x i + b y_i=ax_i+b yi=axi+b,我们也画出ab平面。
基本思想是若 ( x 1 , y 1 ) 与 ( x 2 , y 2 ) (x_1,y_1)与(x_2,y_2) (x1,y1)与(x2,y2)共线,这两点在ab平面上的直线将有一个交点,相交直线最多的点,对应xy平面上的直线就是我们的解,即共线,边缘连接。
10.4 阈值处理
阈值关系如下: T ( x , y ) = T [ x , y , p ( x , y ) , f ( x , y ) ] T(x,y)=T[x,y,p(x,y),f(x,y)] T(x,y)=T[x,y,p(x,y),f(x,y)]p(x,y)是局部性质,f(x,y)是全局性质。
阈值处理步骤:
-
用选定的阈值分割图像
-
G 1 ∈ ( T 1 > T ) , G 2 ∈ ( T 2 < T ) G_1 \in(T_1>T),G_2 \in(T_2<T) G1∈(T1>T),G2∈(T2<T),分别计算灰度平均值 m 1 , m 2 m_1,m_ 2 m1,m2
-
计算新的 T , T = 1 2 ( m 1 + m 2 ) T,T=\frac{1}{2}(m_1+m_ 2) T,T=21(m1+m2)
-
迭代,直到T值小于预定义的参数 △ T \triangle T △T
改善全局阈值处理
- 处理之前图像平滑,改善直方图的形状
- 边缘改进(平滑不可制造波谷时)
- 使用梯度,拉普拉斯等工具
10.5 基于区域的分割
区域生长
一致性准则:
- 邻近点的灰度值与物体的平均灰度值的差<2
- 该像素最少和区域中的一像素8连接
分裂和聚合
当 Q ( R i ) = F a l s e 时 Q(R_i)=False时 Q(Ri)=False时,分裂4个不相交的象限,不能再分裂时,需满足条件 Q ( R j ∪ R i ) = T R U E Q(R_j \cup R_i)=TRUE Q(Rj∪Ri)=TRUE
形态学分水岭分割
关键是找到“汇水盆地”和“脊线”,使用距离变换/梯度/控制标记符。
10.6 实验部分
- 线检测
img=cv.imread('pic/xinhanjiepan.tif',0)
cv.imshow('yuan tu',img)
kernel1=np.array([[2,-1,-1],[-1,2,-1],[-1,-1,2]])
#kernel2=np.array([[-1,-1,2],[-1,2,-1],[2,-1,-1]])
img01=cv.filter2D(img,-1,kernel1)
cv.imshow('45',img01)
img02=img01[0:120,0:120]
img04=img01[360:486,360:486]
img03=cv.resize(img02,None,fx=4,fy=4)
img05=cv.resize(img04,None,fx=4,fy=4)
cv.imshow('zuoshangfang',img03)
cv.imshow('youxiafang',img05)
cv.waitKey(0)
cv.destroyAllWindows()
实验分析: 我们使用加上对角线元素的拉普拉斯模板对线进行检测,但是不能使用绝对值来处理负值,否则将导致加倍线的宽度,对于线检测来说,重点是特定方向的检测,注意左上方的线检测比右下方的线来得亮,说明在+45度存在着更加相关的线段。
- 点检测
img=cv.imread('pic/dot.tif')
cv.imshow('yuan tu',img)
kernel=np.array([[1,1,1],[1,-8,1],[1,1,1]])
img01=cv.filter2D(img,-1,kernel)
cv.imshow('juan ji tu',img01)
img02=np.uint8(img01)
ret,thresh = cv.threshold(img02, 225, 255, cv.THRESH_BINARY)
cv.imshow('er zhi tu xiang',thresh)
cv.waitKey(0)
cv.destroyAllWindows()
实验分析: 图三中的孤立点由于经拉普拉斯模板检测后响应不为0,且超过了设置的阈值而被标为1,从而在图像中显示为白点。
- 边缘检测
import cv2
import numpy as np
import matplotlib.pyplot as plt
#读取图像
img = cv2.imread('pic/building.tif')
img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #转成RGB 方便后面显示
#灰度化处理图像
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#高斯滤波 平滑操作
gaussianBlur = cv2.GaussianBlur(grayImage, (3,3), 0)
#阈值处理
ret, binary = cv2.threshold(gaussianBlur, 127, 255, cv2.THRESH_BINARY)
#Roberts算子
# gx分量
kernelx = np.array([[-1,0],[0,1]], dtype=int)
# gy分量
kernely = np.array([[0,-1],[1,0]], dtype=int)
x = cv2.filter2D(binary, cv2.CV_16S, kernelx)
y = cv2.filter2D(binary, cv2.CV_16S, kernely)
# 绝对值
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
# 融合
Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
#Prewitt算子
kernelx = np.array([[1,1,1],[0,0,0],[-1,-1,-1]], dtype=int)
kernely = np.array([[-1,0,1],[-1,0,1],[-1,0,1]], dtype=int)
x = cv2.filter2D(binary, cv2.CV_16S, kernelx)
y = cv2.filter2D(binary, cv2.CV_16S, kernely)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Prewitt = cv2.addWeighted(absX,0.5,absY,0.5,0)
#Sobel算子
x = cv2.Sobel(binary, cv2.CV_16S, 1, 0)
y = cv2.Sobel(binary, cv2.CV_16S, 0, 1)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Sobel = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
#Laplacian算子
dst = cv2.Laplacian(binary, cv2.CV_16S, ksize = 3)
Laplacian = cv2.convertScaleAbs(dst)
# #效果图
# titles = ['Source Image', 'Binary Image', 'Roberts Image',
# 'Prewitt Image','Sobel Image', 'Laplacian Image']
# images = [原图, binary, Roberts, Prewitt, Sobel, Laplacian]
# for i in np.arange(6):
# plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
# plt.title(titles[i])
# plt.xticks([]),plt.yticks([])
# plt.show()
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
# # 显示图形
plt.subplot(231),plt.imshow(img_RGB),plt.title('原始图像'), plt.axis('off') #坐标轴关闭
plt.subplot(232),plt.imshow(binary, cmap=plt.cm.gray ),plt.title('二值图'), plt.axis('off')
plt.subplot(233),plt.imshow(Roberts, cmap=plt.cm.gray ),plt.title('Roberts算子'), plt.axis('off')
plt.subplot(234),plt.imshow(Prewitt, cmap=plt.cm.gray ),plt.title('Prewitt算子'), plt.axis('off')
plt.subplot(235),plt.imshow(Sobel, cmap=plt.cm.gray ),plt.title('Sobel算子'), plt.axis('off')
plt.subplot(236),plt.imshow(Laplacian, cmap=plt.cm.gray ),plt.title('Laplacian算子'), plt.axis('off')
plt.show()
实验分析: Roberts算子边缘定位精度较高,定位的准确度较差,边缘较不清晰,不具备抑制噪声的能力。适用于边缘正负45度较多的图像;
Sober算子对抑制噪声有一定的作用(中心权重为2),可以达到平滑的效果,适用于对建筑边缘的提取;
Prewitt算子适用于灰度渐变的图像边缘,比如墙砖部分,但抑制噪声的效果比不上S算子;
Laplacian算子适用于阶跃型的边缘点,由于对噪声相当敏感,对于非阶跃型的边缘点,可能会导致双边缘现象,并丢失掉一些边缘信息,图中边缘的宽度明显宽于其他算子图。
- 全局阈值处理
#计算新阈值
img=cv.imread('pic/new-zhiwen.tif',0)
def threshold(img,T):
G1=G2=0
count1=count2=0
h,w=img.shape
for i in range(h):
for j in range(w):
if img[i,j]>T:
G1+=img[i,j]
count1+=1
else:
G2+=img[i,j]
count2+=1
m1=G1/count1
m2=G2/count2
T=0.5*(m1+m2)
#一个新阈值
return T
print(threshold(img,150))
#迭代
def best_threshold(img,T):
h,w=img.shape
# 迭代
for k in range(50):
T1=threshold(img,T)
if abs(T-T1)<=0.01:
break
else:
T=threshold(img,T1)
T1=0.5*(T+T1)
for i in range(h):
for j in range(w):
if img[i,j]>T:
img[i,j]=255
else:
img[i,j]=0
print(k+1)
print(T)
return img
img01=best_threshold(img,127)
show(img01)
实验分析:根据全局阈值处理的算法,第一次计算的新阈值为127.61,经过两次迭代,满足了相对阈值的要求,最终的阈值是125.38,目标和背景之间存在着相当清晰的界限,这个算法非常适用于直方图清晰的图像中(存在波谷)。
- 自适应阈值处理
def adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None):
自适应方法:
OpenCV给我们提供了两种:cv2.ADAPTIVE_THRESH_MEAN_C与cv2.ADAPTIVE_THRESH_GAUSSINA_C。它们都是逐个像素的计算自适应阈值,自适应阈值等于每个像素由参数blockSize所指定的邻域的加权平均值减去常量C。
其中,两种不同的方法计算邻域的加权平均值不同:
(1)cv2.ADAPTIVE_THRESH_MEAN_C:邻域所有像素点的权重值是一致的
(2)cv2.ADAPTIVE_THRESH_GAUSSINA_C:与邻域各个像素点到中心点的距离有关,通过高斯方程得到各个点的权重值
阈值类型:
cv2.THRESH_BINARY或者cv2.THRESH_BINARY_INV中的一个
- 最佳阈值处理函数Otsu
需要注意的是,在使用Otsu方法时,要把阈值设为0。此时的cv2.threshold()会自动寻找最优阈值,并将该阈值返回。
img=cv.imread('pic/kobe_mamba.jpg',0)
t1,binary_img=cv.threshold(img,150,255,cv.THRESH_BINARY)
t2,otsu_img=cv.threshold(img,0,255,cv.THRESH_OTSU)
print(t2)
image=[binary_img,otsu_img]
title=['binary img','otsu img']
for i in range(2):
plt.subplot(1,2,(i+1))
plt.imshow(image[i],plt.cm.gray)
plt.title(title[i])
plt.axis('off')
plt.show()
实验分析: 基本全局阈值处理时的分割效果不佳,背景和目标之间发生重叠,是因为背景和目标的灰度级比较相近而不能够完全分离开,使用otsu’s(最佳阈值处理函数)算法进行分割时,可以完全将目标和背景分割开来,86为上图分离的最佳阈值。
- 分割和聚合
import numpy as np
import cv2
import matplotlib.pyplot as plt
#判断方框是否需要再次拆分为四个,选取的天鹅座(566,566),选取左上角(0,0)
def judge(w0, h0, w, h):
a = img[h0: h0 + h, w0: w0 + w]
ave = np.mean(a)
# ddof=1,无偏差的标准差
std = np.std(a, ddof=1)
count = 0
total = 0
for i in range(w0, w0 + w):
for j in range(h0, h0 + h):
# 根据背景和感兴趣的区域设定判定值
if abs(img[j, i] - ave) <5 and std>10:
# 只控制count
count += 1
total += 1
if (count / total) < 0.95:
# 继续执行
return True
else:
# 终止符
return False
##将图像将根据阈值二值化处理,在此默认125
def draw(w0, h0, w, h):
for i in range(w0, w0 + w):
for j in range(h0, h0 + h):
if img[j, i] > 125:
img[j, i] = 255
else:
img[j, i] = 0
#这个函数懵懵的,else难道不是执行一次之后就结束吗,似乎是把区域给遍历。
def function(w0, h0, w, h):
# 判断 judge函数 且确定最小的(w,h)
if judge(w0, h0, w, h) and (min(w, h) > 5):
# 拆分成四个板块,满足则继续拆分,且同时执行4个语句
function(w0, h0, int(w / 2), int(h / 2))
function(w0 + int(w / 2), h0, int(w / 2), int(h / 2))
function(w0, h0 + int(h / 2), int(w / 2), int(h / 2))
function(w0 + int(w / 2), h0 + int(h / 2), int(w / 2), int(h / 2))
else:
draw(w0, h0, w, h)
img = cv2.imread('pic/tianezuo.tif', 0)
img_input = cv2.imread('pic/tianezuo.tif', 0)
height, width = img.shape
function(0, 0, width, height)
cv2.imshow('input',img_input)
cv2.imshow('output',img)
cv2.waitKey()
cv2.destroyAllWindows()
总结
本章中多数分割算法均基于灰度值的两个基本性质,不连续性和相似性。不连续性(即不连续的灰度),包括点、线、边缘的灰度突变。相似性包括阈值处理、区域生长、区域分割和聚合,在图像识别中,图像分割是一种基本的预处理步骤,选择何种分割方法要考虑具体的情形。