二值图像的连接域标记实现

本文是在借鉴了https://www.cnblogs.com/ronny/p/img_aly_01.html这篇文章之后,对连接域标记进行了实现,原作者是在cpp下完成的,为了方便,我用python实现一下并做一些记录。

一、实现原理

这里选择基于行程的标记,以下图为例,这里借用原文章中的图片:

1、首先,我们自左向右,自上向下的遍历图片,以[start, end]这种形式记录白色块的起始列,对于第一行我们有[2,6], [10,13]两块,第二行是[5,6],[8,9]两块。

2、标号,以及制作等价对,我们按序给各块进行标号,注意如果该块与其上一行的某块连通,那么他继承连通块的标号,如果与上一行的好多块都连通,那么继承最小的标号,与其他连通块就是等价对关系。具体来看一下,第一行两块,不和别人连通,所以[2,6]是1号,[10,13]是2号;下一行,[5,6]与[2,6]连通,[5,6]也是1号,同理[8,9]是2号;第三行,[1,3]与其他都不连接,[1,3]是3号,[6,7]比较特殊,他和1号,2号都连通,那么他是1号,而且添加等价对[1号,2号]。

3、等价对融合,假如我们有好多等价对:[[1号,2号],[2号,5号],[5号,7号],[6号,8号],[8号,9号]]

我们需要把他们整理成:[[1号,2号,5号,7号],[6号,8号,9号]]这样的样子,原作者采用了深度树来实现这个整理

4、将属于等价的标号统一再标号,对于上面的例子就是:

[2,6] , [10,13] , [5,6] , [8,9] , [6,7]这是一整块是1号

[1,3]是2号

二、实现

首先是把所有的块统计出来:

def findRun(image):
    row, col = image.shape
    # 一共有多少个块
    run_num = 0
    # 每个块在第几行
    rowRun = []
    # 每个块的开始结束列
    startRun = []
    endRun = []
    for i in range(row):
        image_row = image[i,:]
        # 开头就是白
        if image_row[0] >= 150:
            rowRun.append(i)
            startRun.append(0)
            run_num += 1
        for j in range(1,col):
            # 前面是白,到这变成了黑,则为一个run的结束点
            if image_row[j-1] >= 150 and image_row[j] <= 10:
                endRun.append(j)
            # 前面是黑,到这变成了白,则为一个run的起始点
            elif image_row[j-1] <= 10 and image_row[j] >= 150:
                rowRun.append(i)
                startRun.append(j)
                run_num += 1
        # 结尾还是白
        if image_row[col-1] >= 150:
            endRun.append(col-1)

    return run_num, startRun, endRun, rowRun

标号,以及统计其中的等价对:

def findEquivalences(run_num, startRun, endRun, rowRun, offset=0):
    rowCur = 0
    # 上一行的第一个run的序号
    preRowFirstRun = 0
    # 上一行的最后一个run的序号
    preRowLastRun = -1
    # 当前行的第一个run的序号
    curRowFirstRun = 0
    # 各run标记
    run_label = [0 for i in range(run_num)]
    # 等价表
    run_equivalences = []
    name_index = 1
    # 对每一个run
    for i in range(run_num):
        # 换行更新run序号
        if not rowRun[i] == rowCur:
            rowCur = rowRun[i]
            preRowFirstRun = curRowFirstRun
            preRowLastRun = i
            curRowFirstRun = i
        if preRowFirstRun < preRowLastRun:
            for j in range(preRowFirstRun, preRowLastRun):
                # 若和上一层的run有连通
                # 开头在其他块结尾前且结尾在其他块开头后,且是当前块的上一行中的块
                if(startRun[i] <= (endRun[j] + offset) and endRun[i] >= (startRun[j] - offset) and rowRun[j] == rowRun[i]-1):
                    # 没赋标号则继承
                    if run_label[i] == 0:
                        run_label[i] = run_label[j]
                    # 已经赋标号了但是标号不一样则置为等价对
                    elif not run_label[i] == run_label[j]:
                        run_equivalences.append([run_label[i], run_label[j]])
        # 没有赋值且没有连通域,则为新的块
        if run_label[i] == 0:
            run_label[i] = name_index
            name_index += 1

    return run_equivalences, run_label

这里的offset参数是增加了一定的余量,不用严格的要求必须4连或者8连才是连通,有一定量的错开也是连通。

通过深度树节点迭代,将等价的标号重新标号为统一标号:

def emergeEquivalences(run_equivalences, run_label):
    equivalences_set = []
    # 对每一个等价对
    for equivalences in run_equivalences:
        # 如果已经被纳入某个等价集合中,则pass
        has_set = False
        for set in equivalences_set:
            if (equivalences[0] in set) and (equivalences[1] in set):
                has_set = True
                break
        if has_set:
            continue
        # 若还没有,开始二叉搜索
        # already_set:当前搜索进展
        # equivalences:当前搜索节点
        # run_equivalences:总集
        def search(already_set, equivalences, run_equivalences):
            # 对每一个等价对
            lift_search =[]
            right_search = []
            for equivalence in run_equivalences:
                # 如果已经被纳入某个当前集合中,则pass
                has_set = False
                if (equivalence[0] in already_set) and (equivalence[1] in already_set):
                    has_set = True
                if has_set:
                    continue
                # 若没有,判断是否与当前节点等价
                # 左
                if equivalences[0] in equivalence:
                    lift_search.append(equivalence)
                if equivalences[1] in equivalence:
                    right_search.append(equivalence)
            # 如果左右都没了,返回当前集合
            if len(lift_search)== 0 and len(right_search) == 0:
                return already_set
            # 若还可以延申
            else:
                # 先扩充already_set
                for set in lift_search:
                    already_set += set
                for set in right_search:
                    already_set += set
                # 此处应该对already_set去重
                # 迭代寻找接下来的节点
                for set in lift_search:
                    already_set += search(already_set, set, run_equivalences)
                for set in right_search:
                    already_set += search(already_set, set, run_equivalences)
                return already_set
        one_set = []
        one_set = search(one_set, equivalences, run_equivalences)
        equivalences_set.append(one_set)
    # 根据equivalences_set重新标号
    name_index = 1
    for equivalences in equivalences_set:
        for index in range(len(run_label)):
            if run_label[index] in equivalences:
                run_label[index] = name_index
        name_index += 1
    return run_label

把同标号的色素进行上色:

def drawConnectedDomain(img, run_label, startRun, endRun, rowRun):
    max_index = max(run_label)
    min_index = min(run_label)
    for run_index in range(len(run_label)):
        row = rowRun[run_index]
        for col in range(startRun[run_index], endRun[run_index]+1):
            img[row,col] = (run_label[run_index] / max_index) * 255
    cv2.imshow('result', img)
    cv2.waitKey()

 

 这是原图和处理后的一个图,简单的把10个块,按25.5的色差进行赋值上色,还算okk。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值