最近做驗證碼識別,原本用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算法寫完再附上完整的源碼。