最近做验证码识别,原本用MATLAB已经实现的整个识别模型,不过代码要部署在Linux服务器上还是需要用另外的语言实现,于是决定用Python + OpenCV来实现。
bwlabel函数的作用是检测二值图像中连通域的个数及为每个连通域标记后的矩阵。
关于连通域检测算法我是参考的http://blog.sina.com.cn/s/blog_ad81d4310102vmll.html 这篇文章中的基于行程的标记 方法,以及传统的Two-Pass方法。

传统的Two-pass方法中关于连通域标记的规则如下:
1.逐行扫描图像上的点,检查每个点是否是前景点,如果不是,继续扫描;
2.检查该前景点的左边的点和上边的点
如果有一个为前景点,则为当前扫描点标记和此点相同编号;
如果两个都是前景点,则选择标号小的,为当前扫描点标记编号;这里注意如果两个都是前景点且标号不同,需要把标号小的点设为标号大的点的父节点,以方便第二遍扫描的时候用并查集算法(参考 利用不相交集实现等价元素的聚类 )合并;
如果都不是,则赋值目前标号label,label++(label初始值为零);
3.再次扫描图像,利用并查集算法合并联通的不同的label的区域。

第一二步就是一个二重循环遍历矩阵,对矩阵中每个元素的上邻元素和左邻元素进行判断来标记该元素,并扩充等价对列表。这两步都比较好理解。
经过标记后会得到一个标记后的矩阵和一个等价对列表。最后一步是将等价值列表合并成具有相同标记的标记集合,比如[(1,2),(1,5),(2,3),(4,6),(7,8)],那么需要把这幅等价值表转换成[(1,2,3,5),(4,6),(7,8)]这三个区域,最后通过这个新的表去重新遍历标记后的矩阵,将其他标记值修改为最小的标记值,这样最后就能得到一个标记了的连通域矩阵。

下面在仿照上面博客中的对等价值的处理。这篇博客中是用 C++ 来实现广度优先搜索,由于其中用到指针去改变栈的大小,这样能实现循环的时候边界长度随着栈的大小变化而动态的变化。
在java中这样在循环中动态改变列表的值数量是会报异常的,python中这样做的时候循环的边界大小不会随着列表的大小改变而改变。

参照博客中的算法,思路如下:

每个元素看做是无向图中的点,等价对看作是一条连通路径,所以先用等价对列表初始化一个连通矩阵 data。
创建一个列表 labelFlag 标记每个点是否被访问了。
对labelFlag进行遍历,如果labelFlag[i] == 0就表示i点没有被访问,!= 0就表示该点已经被访问了,则跳过该点继续遍历下一个点。
如果临时列表tempList为空,且遇到labelFlag[i]没有被访问,则说明需要创建一个新的连通域了。
如果tempList不为空,则将tempList列表的最后一个元素出栈得到元素index,然后判断index是否已经被访问了,如果没有被访问则继续对index为起点的路径访问;
   如果index所指的行的元素不为0且不为 0 的元素所在的列对应的labelFlag[j]没有被访问,就把j压入tempList列表,并把j标记压入到连通集合中。

经过上面的遍历就能获得结果。

为了解决这个问题,我的代码实现如下:

import numpy as np

data = [(1, 2), (2, 4), (1, 5), (3, 6), (7, 8)]
maxLabel = np.max(data)
eqTab = np.zeros((maxLabel, maxLabel))

for left, right in data:
    eqTab[left - 1, right - 1] = 1
    eqTab[right - 1, left - 1] = 1

labelFlag = np.zeros(maxLabel)

tempList = [1]
eqList = []
data_result = []

for i in xrange(maxLabel):
    if labelFlag[i]:
        continue
    eqList = [i + 1]
    tempList = [i + 1]
    for k in xrange(maxLabel):
        if not tempList:
            break
        index = tempList.pop()
        for j in xrange(maxLabel):
            if eqTab[index - 1, j] and not labelFlag[j]:
                tempList.append(j + 1)
                eqList.append(j + 1)

        labelFlag[index - 1] = 1

    data_result.append(eqList)

print data_result

输出结果为:[[1, 2, 5, 4], [3, 6], [7, 8]]

这个实现算法比较直白,完全是按照算法思路来写的程序,没有进一步考虑过优化什么的,读者可以自己琢磨琢磨。
等我把整个bwlabel算法写完再附上完整的源码。