- 注: 内容仅供参考, 不保证正确性, 如有误欢迎交流指正.
- 鸣谢: 感谢 🐰🐔🧄💯💯 小组的各位同学为内容整理提供的帮助
七. 边缘检测
1. Canny算子
请写出Canny算子检测边缘的详细步骤。
Canny边缘检测算法可以分为一下五个步骤:
- 使用高斯滤波器,以平滑图像,滤除噪声。
- 计算图像中每个像素点的梯度强度和方向。
- 根据梯度方向,对梯度幅值应用非极大值抑制(non-maximum suppression)。
- 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
- 通过抑制孤立的弱边缘最终完成边缘检测
1. 使用高斯滤波器,以平滑图像,滤除噪声。
高斯滤波使用的高斯核是具有x和y两个维度的高斯函数,且两个维度上标准差一般取相同,形式为:
G
(
X
,
Y
)
=
∑
x
−
m
x
+
m
∑
y
−
m
y
+
m
e
x
p
∣
−
x
2
+
y
2
2
σ
2
∣
G(X,Y)=\sum\limits^{x+m}\limits_{x-m}\sum\limits^{y+m}\limits_{y-m}exp\vert-\frac{x^2+y^2}{2\sigma^2}\vert
G(X,Y)=x−m∑x+my−m∑y+mexp∣−2σ2x2+y2∣ ,
m
=
n
−
1
2
m=\frac{n-1}{2}
m=2n−1,
n
n
n表示高斯滤波器窗口大小
高斯滤波其实就是将所指像素用周围的像素的某种均值代替(即卷积核),卷积核尺寸越大,去噪能力越强,因此噪声越少,但图片越模糊,canny检测算法抗噪声能力越强,但模糊的副作用也会导致定位精度不高。
2. 计算图像中每个像素点的梯度强度和方向。
图像中的边缘可以指向各个方向,因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。边缘检测的算子(如Roberts,Prewitt,Sobel等)返回水平
G
x
G_x
Gx和垂直
G
y
G_y
Gy方向的一阶导数值,由此便可以确定像素点的梯度
G
G
G和方向
θ
\theta
θ。下面以Sobel算子为例讲述如何计算梯度强度和方向。
Sobel算子是两个 3×3 的矩阵,分别为
S
x
S_x
Sx和
S
y
S_y
Sy和前者用于计算图像
X
X
X方向像素梯度矩阵
G
x
G_x
Gx,后者用于计算图像
Y
Y
Y方向像素梯度矩阵
G
y
G_y
Gy。具体形式为:
G
x
=
S
x
∗
I
=
[
−
1
0
+
1
−
2
0
+
2
−
1
0
+
1
]
∗
I
G_x=S_x*I=\begin{bmatrix}-1&0&+1 \\-2&0&+2\\-1&0&+1\end{bmatrix}*I
Gx=Sx∗I=⎣
⎡−1−2−1000+1+2+1⎦
⎤∗I,
G
y
=
S
y
∗
I
=
[
−
1
−
2
−
1
0
0
0
+
1
+
2
+
1
]
∗
I
G_y=S_y*I=\begin{bmatrix}-1&-2&-1\\0&0&0\\+1&+2&+1 \end{bmatrix}*I
Gy=Sy∗I=⎣
⎡−10+1−20+2−10+1⎦
⎤∗I,
I
I
I为灰度图像矩阵,由此可得梯度强度矩阵和方向
G
=
G
x
2
+
G
y
2
G=\sqrt{G_x^2+G^2_y}
G=Gx2+Gy2
θ
=
arctan
(
G
y
G
x
)
\theta=\arctan(\frac{G_y}{G_x})
θ=arctan(GxGy)
其中
G
G
G为梯度强度,
θ
\theta
θ表示梯度方向,
arctan
\arctan
arctan为反正切函数。
3. 根据梯度方向,对梯度幅值应用非极大值抑制(non-maximum suppression)。
非极大值抑制的做法为:其基本方法是将当前像素梯度强度与沿正负梯度方向上的相邻像素的梯度强度进行比较,若其最大(即为极值),则保留该像素为边缘点,若不是最大,则对其进行抑制,不将其作为边缘点。如图所示,可将像素的邻接情况划分为4个区域,其中每个区域包含上下两部分。沿梯度方向检测模值的极大值点,即边缘点,遍历8个方向图像像素,把每个像素偏导值与相邻像素的模值比较,取其MAX值为边缘点,置像素灰度值为0.
算法过程如下:
tan
(
θ
)
=
G
y
G
x
\tan(\theta)=\frac{G_y}{G_x}
tan(θ)=GxGy
G
p
1
=
(
1
−
tan
(
θ
)
)
×
G
E
+
tan
(
θ
)
×
G
N
E
G_{p_1}=(1-\tan(\theta))\times G_E+\tan(\theta)\times G_{NE}
Gp1=(1−tan(θ))×GE+tan(θ)×GNE
G
p
2
=
(
1
−
tan
(
θ
)
)
×
G
W
+
tan
(
θ
)
×
G
S
W
G_{p_2}=(1-\tan(\theta))\times G_W+\tan(\theta)\times G_{SW}
Gp2=(1−tan(θ))×GW+tan(θ)×GSW
如果
G
p
≥
G
p
1
G_p\ge G_{p_1}
Gp≥Gp1 且
G
p
≥
G
p
2
G_p \ge G_{p_2}
Gp≥Gp2,
G
p
G_p
Gp 应该成为边缘点否则应该对
G
P
G_P
GP进行抑制。剩余的三个区域计算方法类似。
4. 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
施加非极大值抑制后,剩余像素可以更准确地表示图像中的实际边缘。但仍然存在由于噪声和颜色变化引起的一些边缘像素。为了解决这些杂散响应,必须用弱梯度值过滤边缘像素,并保留具有高梯度值的边缘像素,可以通过选择高低阈值来实现。如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;如果边缘像素的梯度值小于低阈值,则会被抑制。
算法描述:
if
G
p
≥
H
i
g
h
T
h
r
e
s
h
o
l
d
G_p\ge HighThreshold
Gp≥HighThreshold
G
p
G_p
Gp is an strong edge
else if
G
p
≥
L
o
w
T
h
r
e
s
h
o
l
d
G_p \ge LowThreshold
Gp≥LowThreshold
G
p
G_p
Gp is an weak edge
else
G
p
G_p
Gp should be suppressed
- 选取系数TH和TL,比率为2:1或3:1。(一般取TH=0.3或0.2,TL=0.1);将小于低阈值的点抛弃,赋0;
- 将大于高阈值的点立即标记(这些点为确定边缘点),赋1或255
- 将小于高阈值,大于低阈值的点即为弱边缘点。
对于弱边缘点,采用八连通区域确定。
5. 通过抑制孤立的弱边缘最终完成边缘检测
到目前为止,被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取出来的。然而,对于弱边缘像素,将会有一些争论,因为这些像素可以从真实边缘提取也可以是因噪声或颜色变化引起的。为了获得准确的结果,应该抑制由后者引起的弱边缘。通常,由真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声响应未连接。为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素,则该弱边缘点就可以保留为真实的边缘。
算法描述:
if
G
p
=
=
L
o
w
T
h
r
e
s
h
o
l
d
G_p == LowThreshold
Gp==LowThreshold and
G
p
G_p
Gp connected to a strong edge pixel
G
p
G_p
Gp is an strong edge
else
G
p
G_p
Gp should be suppressd
2. LoG算子
请写出LoG(Laplacian Of Gaussian)算子的计算方法。
高斯拉普拉斯算子(
L
o
G
LoG
LoG):
对于图像
I
(
x
,
y
)
I(x,y)
I(x,y), 首先通过尺度为
σ
\sigma
σ 的高斯平滑:
G
σ
(
x
,
y
)
=
1
2
π
σ
2
exp
(
−
x
2
+
y
2
2
σ
2
)
G_\sigma (x,y)=\frac{1}{\sqrt{2\pi \sigma ^2}}\exp(-\frac{x^2+y^2}{2\sigma ^2})
Gσ(x,y)=2πσ21exp(−2σ2x2+y2)
接着使用拉普拉斯算子检测边缘:
∇
2
∣
G
σ
(
x
,
y
)
∗
I
(
x
,
y
)
∣
=
[
∇
2
G
σ
(
x
,
y
)
]
∗
I
(
x
,
y
)
\nabla ^2 |G_\sigma (x,y) * I(x,y)|=[\nabla ^2G_\sigma (x,y)] * I(x,y)
∇2∣Gσ(x,y)∗I(x,y)∣=[∇2Gσ(x,y)]∗I(x,y)
该式证明如下:
d
d
t
2
[
h
(
t
)
∗
f
(
t
)
]
=
d
d
t
∫
f
(
τ
)
h
(
t
−
τ
)
d
τ
=
∫
f
(
τ
)
d
d
t
2
h
(
t
−
τ
)
d
τ
=
f
(
t
)
∗
d
d
t
2
h
(
t
)
\begin{aligned} \frac{d}{dt^2}[h(t)*f(t)] &=\frac{d}{dt}\int f(\tau )h(t-\tau )d\tau \\ &=\int f(\tau )\frac{d}{dt^2}h(t-\tau )d\tau \\ &= f(t) * \frac{d}{dt^2}h(t) \end{aligned}
dt2d[h(t)∗f(t)]=dtd∫f(τ)h(t−τ)dτ=∫f(τ)dt2dh(t−τ)dτ=f(t)∗dt2dh(t)
所以高斯拉普拉斯算子等价于先对高斯函数求二阶导,再与原图进行卷积,将LoG算子展开即为:
L
o
G
=
∇
2
G
σ
(
x
,
y
)
=
∂
2
G
σ
(
x
,
y
)
∂
x
2
+
∂
2
G
σ
(
x
,
y
)
∂
y
2
=
x
2
+
y
2
−
2
σ
2
2
π
σ
6
exp
(
−
x
2
+
y
2
2
σ
2
)
LoG=\nabla^2 G_\sigma(x,y)=\frac{\partial^2 G_\sigma(x,y)}{\partial x^2}+\frac{\partial^2 G_\sigma(x,y)}{\partial y^2}=\frac{x^2+y^2-2\sigma^2}{2\pi\sigma^6}\exp(-\frac{x^2+y^2}{2\sigma ^2})
LoG=∇2Gσ(x,y)=∂x2∂2Gσ(x,y)+∂y2∂2Gσ(x,y)=2πσ6x2+y2−2σ2exp(−2σ2x2+y2)
写出DoG (Difference Of Gaussian)算子的计算方法。
高斯函数差分算子(
D
o
G
DoG
DoG):
D
o
G
DoG
DoG即对不同尺度下的高斯函数的差分。DoG算子的表达式如下:
D
o
G
=
G
σ
1
−
G
σ
2
=
1
2
π
[
1
σ
1
e
−
(
x
2
+
y
2
)
/
(
2
σ
1
2
)
−
1
σ
2
e
−
(
x
2
+
y
2
)
/
(
2
σ
2
2
)
]
DoG=G_{\sigma _1}-G_{\sigma _2}=\frac{1}{\sqrt{2\pi }}[\frac{1}{\sigma _1}e^{-(x^2+y^2)/(2\sigma _1^2)}-\frac{1}{\sigma _2}e^{-(x^2+y^2)/({2\sigma _2^2})}]
DoG=Gσ1−Gσ2=2π1[σ11e−(x2+y2)/(2σ12)−σ21e−(x2+y2)/(2σ22)]
DoG与LoG之间有何关系?
DoG算子可以用来近似LoG算子。DoG算子和LoG算子具有类似的波形,仅仅是幅度不同,不影响极值点的检测,而DoG算子的计算复杂度显然低于LoG,因此一般使用DoG代替LoG算子。
DoG算子是高斯函数的差分,具体到图像中,就是将图像在不同参数下的高斯滤波结果相减,得到差分图。LoG先对高斯核函数求取二阶导数,再与原图像进行卷积操作。LoG算子和DoG算子既可以用于检测图像边缘,也可用于检测局部极值点或极值区域。
相似性证明如下
因为,
∂
G
∂
σ
=
x
2
+
y
2
−
2
σ
2
2
π
σ
5
e
x
2
+
y
2
2
σ
2
\frac{\partial G}{\partial \sigma }=\frac{x^2+y^2-2\sigma ^2}{2\pi \sigma ^5}e^{\frac{x^2+y^2}{2\sigma ^2}}
∂σ∂G=2πσ5x2+y2−2σ2e2σ2x2+y2
由上述的定义得
∂
G
∂
σ
=
σ
∇
2
G
\frac{\partial G}{\partial \sigma }=\sigma \nabla ^2G
∂σ∂G=σ∇2G
由导数的定义得:
∂
G
∂
σ
=
lim
Δ
σ
→
0
G
(
x
,
y
,
σ
+
Δ
σ
)
−
G
(
x
,
y
,
σ
)
Δ
σ
≈
G
(
x
,
y
,
σ
+
k
σ
)
−
G
(
x
,
y
,
σ
)
k
σ
−
σ
\frac{\partial G}{\partial \sigma }=\lim_{\Delta \sigma \to 0}\frac{G(x,y,\sigma +\Delta \sigma )-G(x,y,\sigma )}{\Delta \sigma }\approx \frac{G(x,y,\sigma +k\sigma )-G(x,y,\sigma )}{k\sigma -\sigma }
∂σ∂G=Δσ→0limΔσG(x,y,σ+Δσ)−G(x,y,σ)≈kσ−σG(x,y,σ+kσ)−G(x,y,σ)
则
σ
∇
2
G
≈
G
(
x
,
y
,
σ
+
k
σ
)
−
G
(
x
,
y
,
σ
)
k
σ
−
σ
\sigma \nabla ^2G \approx \frac{G(x,y,\sigma +k\sigma )-G(x,y,\sigma )}{k\sigma -\sigma }
σ∇2G≈kσ−σG(x,y,σ+kσ)−G(x,y,σ)
变形得
G
(
x
,
y
,
σ
+
k
σ
)
−
G
(
x
,
y
,
σ
)
≈
(
k
−
1
)
σ
2
∇
2
G
G(x,y,\sigma +k\sigma )-G(x,y,\sigma ) \approx (k-1)\sigma ^2\nabla ^2G
G(x,y,σ+kσ)−G(x,y,σ)≈(k−1)σ2∇2G
利用LoG算子对图像进行处理,可以得到何种信息?
LoG算子就是先对图像进行高斯模糊,然后再求二阶导数,二阶导数等于0处对应的像素就是图像的边缘,即得到图像的边缘信息。
3. 边界跟踪
设一幅二值图像中,只有一个白色区域,试给出求该区域外围轮廓线的方法(要求按顺时针的顺序给出各点的坐标,即行/列号)。
- 令起点 b 0 b_0 b0为图像中左上角标记为1的点,用 c 0 c_0 c0表示 b 0 b_0 b0西侧的点,很显然, c 0 c_0 c0总是背景点。从 c 0 c_0 c0开始按顺时针方向考察 b 0 b_0 b0的8个邻接点,令 b b b表示所遇到的值为1的第一个邻接点,并直接令 c 1 c_1 c1是序列 b 1 b_1 b1之前的点,存储 b 0 b_0 b0和的 b 1 b_1 b1位置,以便在步骤5中使用。
- 令 b b b= b 1 b_1 b1和 c c c= c 1 c_1 c1
- 从 c c c 开始按顺时针方向行进,令 b b b 的8个邻接点为 n 1 n_1 n1, n 2 n_2 n2,…, n 8 n_8 n8。找到标为 1 的第一个 n k n_k nk
- 令 b b b= b k b_k bk 和 c c c= c k c_k ck
- 重复步骤 3 和 4,直到 b b b= b 0 b_0 b0 就找到了下一个边界点为 b 1 b_1 b1.
- 当算法停止时,所找到的 b b b 点的序列就构成了排列后的边界点的集合。
- 拓展: 按逆时针方向进行边界跟踪:
搜索顺序表
3 | 2 | 1 |
---|---|---|
4 | x | 0 |
5 | 6 | 7 |
-
对图像进行逐行查找(从上到下,从左到右),找到第一个值为1的点,用 P 0 P_0 P0表示。
P 0 P_0 P0:边界跟踪的起始点。
定义变量 dir:搜索方向(1,…,7)dir=7; -
按逆时针方向顺序依次判断当前点(一开始为 P 0 P_0 P0 点)8个 3×3 邻居是否为 1,开始的邻居号为:
- (dir+7)mod 8 如果当前的dir为偶数;
- (dir+6)mod 8 如果当前的dir为奇数;
按上述的逆时针方向找到的第一个不为 0 的点为找到的新的边界,记为 P n P_n Pn,并更新 dir 值(以当前找到为1的点为最后更新的 dir 方向)。
-
如果当前的边界点 P n P_n Pn 的坐标等于找到的第二个边界点 P 1 P_1 P1 的坐标,而且它前一个边界点 P n − 1 P_{n-1} Pn−1 的坐标又与起始点 P 0 P_0 P0 坐标相同,则算法结束。否则,重复步骤 2
4. 图像分割与求最小代价路径
设在一幅图像中分割出一个区域,如何将目标分割问题转换为一个求最小代价路径的问题?
- 识别地标(Landmark)。对每个地标点
P
i
P_i
Pi,图像中个
m
m
m最可能的位置只根据它的密度形式来选择。为了选择这些位置,我们使用密度损失函数
h
i
(
p
,
I
)
h_i(p,I)
hi(p,I)扫描整个图像或图像中一个最相关的部分。点
P
i
,
1
,
P
i
,
2
,
P
i
,
m
P_{i,1},P_{i,2},P_{i,m}
Pi,1,Pi,2,Pi,m对应
m
m
m个最小的灰度损失将成为每
i
i
i个地标的候选。每个地标的
m
m
m个最好的位置被选中后,算法将构造一个
n
×
m
n\times m
n×m的灰度损失矩阵,
C
C
C中的第
i
i
i行包含了地标
i
i
i的
m
m
m个最可能位置的损失:
- 最小代价路径。找到分割一个目标的轮廓被转化为找到C中的一条最优路径。图像分割问题因此转化成了找到一条路径
(
k
1
∗
,
k
2
∗
,
…
k
n
∗
)
(k_1^*,k_2^*,\dots k_n^*)
(k1∗,k2∗,…kn∗)使得下面的某个损失函数最小化:
( k 1 ∗ , k 2 ∗ , … k n ∗ ) = arg min k 1 ∗ , … k n ∗ J ( k 1 ∗ , … k n ∗ ) (k_1^*,k_2^*,\dots k_n^*)=\mathop{\arg\min}\limits_{k_1^*,\dots k_n^*}\mathcal{J}(k_1^*,\dots k_n^*) (k1∗,k2∗,…kn∗)=k1∗,…kn∗argminJ(k1∗,…kn∗)
损失(代价)有两种:- 分配给每条路径结点i的地标候选的密度损失 h i h_i hi
- 从结点i转移到结点i+1的形状损失 f i f_i fi
从而,目标函数可以为:
J
(
k
1
∗
,
…
k
n
∗
)
=
∑
i
=
1
b
h
i
+
γ
∑
i
=
1
n
f
i
\mathcal{J}(k_1^*,\dots k_n^*)=\sum\limits_{i=1}\limits^bh_i+\gamma\sum\limits^n\limits_{i=1}f_i
J(k1∗,…kn∗)=i=1∑bhi+γi=1∑nfi
h
i
=
h
i
(
P
i
,
k
i
)
h_i=h_i(P_i,k_i)
hi=hi(Pi,ki)
f
i
=
f
i
(
P
i
+
1
,
k
i
+
1
−
P
i
,
k
i
)
f_i=f_i(P_{i+1,k_i+1}-P_{i,k_i})
fi=fi(Pi+1,ki+1−Pi,ki)
或者
f
i
=
f
i
(
P
i
+
1
,
k
i
+
1
−
P
i
,
k
i
,
P
i
,
k
i
−
P
i
−
1
,
k
i
−
1
)
f_i=f_i(P_{i+1,k_i+1}-P_{i,k_i},P_{i,k_i}-P_{i-1,k_i-1})
fi=fi(Pi+1,ki+1−Pi,ki,Pi,ki−Pi−1,ki−1)
基于图论的分割方法就是把要进行分割的图像看成是一个带权无向图。原图像中的各像素点就是带权无向图中的结点。边是在各结点之间形成的。边的权值
W
(
i
,
j
)
W(i,j)
W(i,j)可以反正出顶点i与顶点j之间的相似程度,其可以由空间关系(如顶点i到顶点j的距离)与灰度测试(如纹理、颜色、灰度值)形成。我们可以将原带权无向图按照每各个像素之间的相似程度切割成若干个子集区域。每个子集区域内的像素相似度比较高,不同的子集区域的像素相似性较低。切割的过程实际上就是去除相似度低的结点之间的边。
代价可以包括哪些因素?
- 梯度代价: 平面拟合、径向梯度、归一化
- 灰度代价: 边界灰度估计
- 结点间距离代价
八. 形态运算
1. 图像腐蚀
设有一幅二值图像,采用 3×3的结构元(每个元素均为1)对其进行腐蚀操作,试写出得到结果图像的方法。
腐蚀处理的结果是使原来的二值图像减小一圈, 原图A被结构B腐蚀的定义如下,z代表B的中心位置:
A
⊙
B
=
{
z
∣
(
B
)
z
⊆
A
}
A\odot B=\{z|(B)_z \subseteq A \}
A⊙B={z∣(B)z⊆A}
可以理解为,移动结构B,如果结构B完全属于原图A的区域内,则保存该位置点,所有满足条件的点构成结构A被结构B腐蚀的结果。
用结构体B对原图A进行腐蚀的整个过程如下:
- 将 3*3 的结构元B在A中移动,即让B的中心循环扫描图像A的每一个像素。
- 用结构元素B的每个位置与其覆盖的二值图像对应的位置做“与”操作
- 如果步骤 2 结果中所有像素结果都为 1,也即结构体B完全包含在A中,则此时目标图像对应B的中心位置像素为1; 否则为0。
示例结果如下, 有颜色代表像素为1,空白代表像素为0 :
2. 孔洞填充
试写出孔洞填充的算法。对二值图像中所有被白色区域包围(封闭)的黑色像素即为孔洞。
孔洞填充的公式为:
X
k
=
(
X
k
−
1
⊕
B
)
∩
A
c
k
=
1
,
2
,
3
,
.
.
.
X_k=(X_{k-1} \oplus B) \cap A^c \qquad k=1,2,3,...
Xk=(Xk−1⊕B)∩Ack=1,2,3,...
设原包含孔洞的图像为A,原图补集为
A
c
A^c
Ac,用于填充膨胀的结构体为B,则孔洞填充算法流程:
- 将原图像 A 每个像素取反,获得补集原图像的补集记作 A c A^c Ac,用来限制膨胀结果在孔洞内,防止膨胀超出孔洞区域范围;
- 在原图孔洞中选择一个初始点,使用结构体B膨胀该点;
- 将膨胀后的结果与原图补集 A c A^c Ac 相交,得到该轮次的结果 X k X_k Xk。
- 在原图孔洞中移动到下一个点,重复步骤 2~4,直到第 K 步和第 K+1 步结果相同,填充完毕。
上述算法步骤中使用了膨胀操作,下面简述膨胀操作过程:
膨胀处理的结果是使原来的二值图像扩大一圈, 原图A被结构B膨胀的定义如下,z代表B的中心位置:
A
⊕
B
=
{
z
∣
(
B
^
)
z
⋂
A
≠
∅
}
A\oplus B=\{z|(\hat{B})_z \bigcap A \neq \varnothing\}
A⊕B={z∣(B^)z⋂A=∅}
上式中
(
B
^
)
z
(\hat{B})_z
(B^)z是结构体关于中心圆点反转的结构,膨胀可以理解为,移动结构B的反转结构
(
B
^
)
z
(\hat{B})_z
(B^)z,如果与原图A存在重叠区域,也即交集不为空,则此时结构体中心位置对应在目标图结果中处像素为1,反之没有交集则为0。
3. 粘连区域断开
设有两个白色区域,被一条细小的白线所连接,试设计一种算法,消除两个区域之间的细线,使两个区域分开。
使用开运算即可,开运算等于对原图先腐蚀后膨胀,可以用来消除小物体、在纤细点处分离物体、消除物体周围的毛刺等。设原图为A,结构体为B开运算的公式为:
A
∘
B
=
(
A
⊙
B
)
⊕
B
A \circ B=(A\odot B) \oplus B
A∘B=(A⊙B)⊕B
分割两个细小白线连接的白色区域的步骤如下:
- 设置开运算结构B为N*N, N的大小根据细线的宽度而更改,要比细线宽度大。
- 先对原图A使用结构体B进行腐蚀运算。
- 对步骤 2 的结果再次使用结构体B进行膨胀运算。
- 注意: 上述开运算步骤中使用了腐蚀和膨胀运算,这两种算法见前两题图像腐蚀和孔洞填充中介绍,这里不再赘述。
4. 计算凸壳
计算包围给定点集的最小凸多变形。
采用安德鲁算法计算最小凸包,流程简述如下:
- 将给定的点集合按照x坐标升序排列。x相同的话,按照y坐标升序排列
- 创建凸包的上部U点序列:默认将x最小的两个点加入上凸包U,再将排序后的点按照x坐标从小到大的顺序依次加入凸包U,并检查U是否仍然为凸包。如果新加入的点使得U不再是凸多边形,那么就逆序依次删除已经插入U的点,直到U为凸多边形。
- 创建凸包的下部L点序列:默认将x最大的两个点加入下凸包L, 再将排序后的点按照x坐标从大到小的顺序加入凸包L,并检查L是否仍然为凸包。如果新加入的点使得L不再是凸多边形,那么就逆序删除已经插入L的点,直到L为凸多边形。
- 连接凸包的上部和下部: 最终外接凸多边形点集为凸包上部的点序列+凸包下部逆置后的点序列(下部形成是按照x从大到小,要逆序形成从小到大的序列),每两个点之间连线,最后一个点和第一个点连线即是最终外接凸多边形。
检查是否为凸包的方法如下:
设将要加入点
P
2
P_2
P2,凸包集中上一轮加入的节点为
P
1
P_1
P1, 上上一轮加入的节点为
P
0
P_0
P0 , 则形成两个向量:
a
⃗
=
P
0
→
P
1
\vec{a}=P_0 \rightarrow P_1
a=P0→P1和
b
⃗
=
P
0
→
P
2
\vec{b}=P_0 \rightarrow P_2
b=P0→P2。如果
a
⃗
×
b
⃗
<
0
\vec{a} \times \vec{b} < 0
a×b<0, 则说明第二个向量
b
⃗
\vec{b}
b位于第一个向量
a
⃗
\vec{a}
a的逆时针处, 此时新加入节点
P
2
P_2
P2会使结果不再是凸边形,需要逆序删除已经插入的点,直到加入
P
2
P_2
P2后,保持凸包。
上述判断方法示意图如下, 此时加入P2会使第二个向量
b
⃗
\vec{b}
b位于第一个向量
a
⃗
\vec{a}
a的逆时针处,凸包被破坏,需要逆序删除P1点。