图像连通域搜寻skimage和opencv方法

关于连通域的寻找和分割。在python中有两个库的函数可以使用,一个是scikit-image库中的measure文件,另一个是opencv中的connectedComponentsWithStats函数。

其中,第一个库比较好使,如果环境允许,可以尽量使用第一个库函数,因此比较方便,而且这个函数中有丰富的属性可以直接使用。但是在实际使用的时候,在Linux ARM64环境下安装scikit-image包总是失败,目前官方git也没有给出比较好的解决思路https://github.com/scikit-image/scikit-image/issues/4705。所以只能采用opencv中的库函数替换。

关于连通域的寻找和分割。在python中有两个库的函数可以使用,一个是scikit-image库中的measure文件,另一个是opencv中的connectedComponentsWithStats函数。

分别介绍两个库的连通域寻找的代码

一、 skimage.measure

skimage.measure.label和skimage.measure.regionprops是很好用的组合拳。

label_map = skimage.measure.label(image)可以将二值图中,连通域区域的像素都标记为特定的数字。

regions= skimage.measure.regionprops(label_map) 这里输出的regions是一个列表,每个列表元素是一个Region对象,Region对象中保存了连通域的丰富的属性信息

下面的代码就可以图像化展示skimage.measure.label的输出结果:

labels=measure.label(data,connectivity=2)  #8连通区域标记
dst=color.label2rgb(labels)  #根据不同的标记显示不同的颜色
print('regions number:',labels.max()+1)  #显示连通区域块数(从0开始标记)

其中measure.label的第二个参数表示搜素连通域的范围,2表示搜索周围八个像素点;1表示搜素上下左右四个像素点。

1-connectivity     2-connectivity     diagonal connection close-up

     [ ]           [ ]  [ ]  [ ]             [ ]
      |               \  |  /                 |  <- hop 2
[ ]--[x]--[ ]      [ ]--[x]--[ ]        [x]--[ ]
      |               /  |  \             hop 1
     [ ]           [ ]  [ ]  [ ]
image.png

skimage.measure.regionprops函数可以得到上面的每一个连通域的属性信息。常用的属性如下:

属性名称 类型 描述
area int 区域内像素点总数
bbox tuple 边界外接框(min_row, min_col, max_row, max_col)
centroid array 质心坐标
convex_area int 凸包内像素点总数
convex_image ndarray 和边界外接框同大小的凸包
coords ndarray 区域内像素点坐标
Eccentricity float 离心率
equivalent_diameter float 和区域面积相同的圆的直径
euler_number int 区域欧拉数
extent float 区域面积和边界外接框面积的比率
filled_area int 区域和外接框之间填充的像素点总数
perimeter float 区域周长
label int 区域标记

area属性是该连通域像素点之和,
bbox得到外接矩形坐标(min_row, min_col, max_row, max_col),
bbox_are属性是外接矩形面积,
filled_image大小[max_row-min_row,max_col-min_col],元素为Boolean,是外接矩形bbox中像素点是否属于连通域的标记图。所有属于连通域的像素点为True。

实际案例

比如对于表格框线的检测来说,采用分割网络可以输出每个像素点是否是表格线的[0,1]score特征图。根据阈值0.3将feature map像素变为0,1二值图。对于表格边框,feature map像素点为0,对于单元格内区域以及外部区域,像素点值为1.那么找到连通域就可以找到单元格区域。然后通过minAreaRectBox函数获得每个单元格的外接矩形局域就可以得到单元格的坐标。

from skimage import measure
import cv2
def minAreaRectBox(coords):
    """
    多边形外接矩形
    """
    rect = cv2.minAreaRect(coords[:, ::-1])
    box = cv2.boxPoints(rect)
    box = box.reshape((8,)).tolist()
    box = sort_box(box)
    return box
...
def get_table_cells(score):
    # score是一个分割网络输出的单通道score图,0.3是阈值
    tabelLabels = measure.label(score<0.3, connectivity=2)
    regions = measure.regionprops(tabelLabels)

    for region in regions:
        # 判断这个连通域是否是整图或者近似于整图,这样的连通域是不需要的
        if region.bbox_area > 0.9 * h * w:
            # print("bbox_area太大了")
            continue
        # 过滤掉非常小的区域,这种区域可能是边界上的意外连接
        elif region.area < 100 or region.bbox[2] - region.bbox[0] < 10 or region.bbox[3] - region.bbox[1] < 10:
            # print("bbox-area太小了")
            continue
        else:
            rbox = minAreaRectBox(region.coords)
            rbox = list(rbox)
            bboxes.append(rbox)
    return bboxes

二、cv2.connectedComponentsWithStats

同样的图像连通域分割也可以通过这个函数得到cv2.connectedComponentsWithStats

num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats((tmp==0).astype(np.uint8),connectivity=8)

该函数的输入必须是np.uint8类型的,而不像skimage中可以是boolean类型。第二个参数connectivity=8表示会搜索每个像素点周围八个像素点的,和measure.label函数的connectivity=8是一个意思。
输出:
该函数输入为一个二值化图像,输出为一个长为4的tuple:
第一个是连通区域的个数,
第二个是一整张图的label,这里的label标签从0开始
第三个是(x, y, width, height, area),即每个区域的每个区域的左上角坐标,宽和高,面积。
第四个是每个连通区域的中心点。

在使用skimage.measure这个库和cv2这个库并不是一样的,不能直接替换函数。需要特别注意使用cv2.connectedComponentsWithStats获得的labels图上标签为0的像素构成的区域。这个区域是需要去除的。
比如同样需要第二章节中提到的功能,这里的函数需要这样写才能保证结果完全一致。主要的区别在于需要过滤掉label=0的区域,这个区域一般是由背景像素构成的。

import cv2
def minAreaRectBox(coords):
    """
    多边形外接矩形
    """
    rect = cv2.minAreaRect(coords[:, ::-1])
    box = cv2.boxPoints(rect)
    box = box.reshape((8,)).tolist()
    box = sort_box(box)
    return box
...
def get_table_cells(score):
    # score是一个分割网络输出的单通道score图,0.3是阈值
    num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats((score>0.3).astype(np.uint8),connectivity=8)
    for index, stat in enumerate(stats):
        if index==0:#这一句话很重要!!!!!!
            continue
        x,y,cw,ch,area = stat
        # 判断这个连通域是否是整图或者近似于整图,这样的连通域是不需要的
        if cw*ch > 0.9 * h * w:
            # print("bbox_area太大了")
            continue
        # 过滤掉非常小的区域,这种区域可能是边界上的意外连接
        elif area < 100 or cw < 10 or ch < 10:
            # print("bbox-area太小了")
            continue
        # 其余正常检测到表格单元的情况
        else:
            # print("regular boxes:{}".format((y,x,h,w)))
            clabel_num = index
            x_list,y_list = np.where(labels==clabel_num)
            coords = np.array([x_list,y_list]).T
            rbox = utils.minAreaRectBox(coords)
            rbox = list(rbox)
            bboxes.append(rbox)
    return bboxes

cv2.connectedComponentsWithStats函数输出相较于skimage.measure.region对象来说,属性更少。比如region对象可以直接通过region.coords得到连通域的所有坐标,但是cv2.connectedComponentsWithStats需要通过对labels这个图进行一些操作才能得到特定数字的连通域的坐标信息。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容