Mythsman


乐极生悲,苦尽甘来。


二值形态学之击中击不中变换

击中击不中变换(Hit Miss Transform ,HMT),是通过同时探测图像的内部和外部,进而获取更多的内外标记,体现更多信息的一个方法。他的应用有很多,特别是在图像识别以及图像细化方面。

定义

既然既要有击中也要有击不中,那么显然我们需要两个结构基元E和F,我们把这两个结构基元记为一个结构元素对$B=(E,F$。其中一个用来探测图像内部,一个用来探测图像外部。

对于给定的图像A,我们定义用B对他进行的击中击不中变换为

$A*B=(A\Theta E)\bigcap (A^c\Theta F)$

即用E对A进行腐蚀,用F对A的补集进行腐蚀,并将得到的结果求交集。

这样说可能冠冕堂皇了一点,简单的讲其实就在判断图像中的某一个像素,将这个像素与结构基元的‘原点'对应,看看结构基元中的‘击中’基元是否能完全被图像覆盖、结构基元中的‘击不中部分’是否能与图像没有任何交集。如果答案均为是,那么这个点就可以得到保留,否则就舍弃。当然,这里用了腐蚀的方法使得图像更加容易被识别进去。

说白了一点其实就是相当于在用一个模子来测试这个图片,并将所有符合这个模子的中心点标记下而已。

应用

物体识别

这个很显而易见了,只要将击中基元设计成为需要识别的图像的样子,击不中基元设计为击中基元取反后的样子即可。然后用这两个基元对图像进行检测即可。

不过有时候图像会发生略微变形,为了提高识别率,我们通常会将两个结构基元都稍微腐蚀一下,给图像变形留下些余地。

思路比较清楚,具体代码依据实际情况编写即可,这里不做举例。

细化

定义

图像细化是一种最常见的使用击中击不中变换的形态学算法。他的目的就如他的字面意思,将图像变细,就像取到了图像的骨架。

其定义是,对于图像S,利用结构对$B=(E,f)$进行细化记为:

$S\bigotimes B=S\setminus (S*B)$

其中$S*B$表示用B对S进行细化得到的标记点,$\setminus$表示求差集。

下面介绍一种细化的方法。

方法

设置八个分别代表不同边界的击中击不中模板:

$\begin{bmatrix}\bullet&\bullet&\bullet\\&\circ&\\\circ&\circ&\circ\end{bmatrix}$$\begin{bmatrix}&\bullet&\bullet\\\circ&\circ&\bullet\\\circ&\circ&\end{bmatrix}$$\begin{bmatrix}\circ&&\bullet\\\circ&\circ&\bullet\\\circ&&\bullet\end{bmatrix}$$\begin{bmatrix}\circ&\circ&\\\circ&\circ&\bullet\\&\bullet&\bullet\end{bmatrix}$

$\begin{bmatrix}\circ&\circ&\circ\\&\circ&\\\bullet&\bullet&\bullet\end{bmatrix}$$\begin{bmatrix}&\circ&\circ\\\bullet&\circ&\circ\\\bullet&\bullet&\end{bmatrix}$$\begin{bmatrix}\bullet&&\circ\\\bullet&\circ&\circ\\\bullet&&\circ\end{bmatrix}$$\begin{bmatrix}\bullet&\bullet&\\\bullet&\circ&\circ\\&\circ&\circ\end{bmatrix}$

其中$\circ$表示击中模板,$\bullet$表示击不中模板,0表示不设置限制。每个矩阵的中心位置设为原点。

接下来我们要做的就是用着八个模板依次对待处理的图像做击中击不中变换,每轮操作都会使图像变细。经过多次的迭代之后图像保持不变,这个结果就是经过细化后的图像了。

由于OpenCV中没有直接进行HMT的模板,因此手敲了一个(大概意思差不多是这样,就是效率不敢恭维。。)

OpenCV实现

#coding:utf-8
import cv2
import numpy as np

im=cv2.imread('test.png',cv2.IMREAD_GRAYSCALE)
thresh,im=cv2.threshold(im,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imshow('binary.png',im)#控制背景为黑色
cv2.imwrite('binary.png',im)

arr=np.array(im)
mat=[]#HMT模板矩阵
mat.append(np.array([[-1,-1,-1],[0,1,0],[1,1,1]]))
mat.append(np.array([[0,-1,-1],[1,1,-1],[1,1,0]]))
mat.append(np.array([[1,0,-1],[1,1,-1],[1,0,-1]]))
mat.append(np.array([[-1,-1,-1],[0,1,0],[1,1,1]]))
mat.append(np.array([[1,1,0],[1,1,-1],[0,-1,-1]]))
mat.append(np.array([[1,1,1],[0,1,0],[-1,-1,-1]]))
mat.append(np.array([[-1,0,-1],[-1,1,1],[-1,0,1]]))
mat.append(np.array([[-1,-1,0],[-1,1,1],[0,1,1]]))

height,width=arr.shape

while True:#迭代直至无变化
    before=arr.copy()
    for m in mat:#使用八个模板进行变换
        mark=[]
        for i in xrange(height-2):#对每个非边界点进行测试
            for j in xrange(width-2):
                reg=True
                for im in xrange(3):
                    for jm in xrange(3):
                        if not arr[i+1][j+1]==255:
                            continue
                        if m[im][jm]==1 and arr[i+im][j+jm]==0:
                            reg=False
                        if m[im][jm]==-1 and arr[i+im][j+jm]==255:
                            reg=False
                if reg:#找到标记,删除
                    mark.append((i+1,j+1))
        for it in mark:
            x,y=it
            arr[x][y]=0
    if (before==arr).all():
        break

cv2.imshow('thin.png',arr)
cv2.imwrite('thin.png',arr)
cv2.waitKey(0)
cv2.destroyAllWindows()

就是用八个模板不停的求卷积,并将标记到的点删除,直至无法删除为止。

运行结果

binary
thin