基于Python实现图像分割算法

这篇具有很好参考价值的文章主要介绍了基于Python实现图像分割算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

资源下载地址:https://download.csdn.net/download/sheziqiong/86763995
资源下载地址:https://download.csdn.net/download/sheziqiong/86763995文章来源地址https://www.toymoban.com/news/detail-447689.html

1. 问题描述

结合“Lecture 7 Segmentation”内容及参考文献[1],实现基于 Graph-based image segmentation 方法(可以参考开源代码,建议自己实现) ,通过设定恰当的阈值将每张图分割为 50~70 个区域,同时修改算法要求任一分割区域的像素个数不能少于 50 个(即面积太小的区域需与周围相近区域合并) 。结合GT 中给定的前景 mask,将每一个分割区域标记为前景(区域 50%以上的像素
在 GT 中标为 255)或背景(50%以上的像素被标为 0) 。区域标记的意思为将该区域内所有像素置为 0 或 255。要求对测试图像子集生成相应处理图像的前景标注并计算生成的前景 mask 和 GT 前景 mask 的 IOU 比例。假设生成的前景区域为 R1, 该图像的 GT 前景区域为 R2, 则IOU = (𝑅1∩𝑅2)/(𝑅1∪𝑅2)

2. 求解过程与算法

假设有无向图 G = ( V , E ) G=(V,E) G=(V,E),每条边有权重 w ( v i , v j ) w(v_i,v_j) w(vi,vj)表示距离, S S S是原图的子图,有: S = G ′ = ( V , E ′ ) E ′ ⊂ E S=G'=(V,E')\quad E'\sub E S=G=(V,E)EE,且将 G G G分到成了不同的集群。使用谓词 D D D决定是否存在分段边界,也就是说,使用 D D D作为标准判断不同集群的相似性,如果两个集群的集群间相似性小于集群内的相似性,则进行合并:
M e r g e ( C 1 , C 2 ) = { T r u e , d i f ( C 1 , C 2 ) < i n ( C 1 , C 2 ) F a l s e , o t h e r w i s e d i f ( C 1 , C 2 ) = min ⁡ v i ∈ C 1 , v j ∈ C 2 , ( C 1 , C 2 ) ∈ E w ( v i , v j ) i n ( C 1 , C 2 ) = min ⁡ C ∈ { C 1 , C 2 } [ max ⁡ v i , v j ∈ C [ w ( v i , v j ) + k ∣ C ∣ ] ] Merge(C_1,C_2)=\left\{\begin{matrix} True,dif(C_1,C_2)<in(C_1,C_2)\\ False,otherwise \end{matrix}\right.\\ dif(C_1,C_2)=\min_{v_i\in C_1,v_j\in C_2,(C_1,C_2)\in E}w(v_i,v_j)\\ in(C_1,C_2)=\min_{C\in\{C_1,C_2\}}[\max_{v_i,v_j\in C}[w(v_i,v_j)+\frac k{|C|}]] Merge(C1,C2)={True,dif(C1,C2)<in(C1,C2)False,otherwisedif(C1,C2)=viC1,vjC2,(C1,C2)Eminw(vi,vj)in(C1,C2)=C{C1,C2}min[vi,vjCmax[w(vi,vj)+Ck]]
也就是说,如果两个集群中点的距离的最小值小于两个集群内的点的距离最大值更小的那一个则进行合并。其中 k / ∣ C ∣ k/|C| k/∣C设置了内部节点的差异性。如果 k k k较大,则集群规模往往更大,否则更小。

对于一张图像,我们可以将每个像素投影到特征空间 ( x , y , r , g , b ) (x,y,r,g,b) (x,y,r,g,b)上,也就是坐标和颜色通道组成的特征空间。每个像素在八领域内与八个像素相邻,即具有边。边的距离由特征向量计算得出,一般用欧氏距离。

由此,我实现基于图的图像分割方法流程如下:

  1. 将图片的每个像素按照坐标和颜色投影到 ( x , y , r , g , b ) (x,y,r,g,b) (x,y,r,g,b)特征空间上,初始时每个像素自身属于单独一个区域
  2. 计算所有边的权重大小,即八领域内像素的特征向量的欧式距离,并按照距离大小排序
  3. 按距离从小到大的顺序,依次将这些边两端的像素合并到同一个区域。能否合并依据上述的 M e r g e ( C 1 , C 2 ) Merge(C_1,C_2) Merge(C1,C2)的公式判断
  4. 重复第3步,直到剩余区域数到达设置的下界
  5. 遍历剩下的区域,将像素数少于50个的区域和相邻的区域直接合并

由此就能得到划分好区域的图片。我们只需要记录每个区域所含的全部像素的坐标,由这些坐标找到前景图的相应位置,统计前景像素数和背景像素数就能确定该区域应该被判定为前景还是背景。

3. 代码实现与说明

该部分的代码全部由自己实现。

3.1 一些功能模块的实现

在实现算法主体流程前,可以预见到,程序需要用多种方式刻画像素和像素、像素和区域之间的关系。为了之后代码的简洁性,我先实现了部分功能模块方便多次调用。首先需要两个全局变量:

# 用于查询某个片段内部的所有像素
SEG_GROUP = dict()
# 每个片段内的最大距离
INNER_WEIGHT = dict()

其中SEG_GROUP是字典,用于表示区域以及查询区域内的全部像素。如果像素坐标为[x,y]​,则表示某个区域时,使用该区域中所有像素的x值最小的那个作为“代表”表示该区域。如果有多个x相同的像素,则找y值最小的。因此,SEG_GROUP[(x1,y1)]=[(x1,y1),(x2,y2),...]表示以(x1,y1)这个像素点为代表的区域中包含着(x1,y1)(x2,y2)…这些像素点。该区域的键值只能用(x1,y1)表示,而不能用(x2,y2)等其他的像素点表示。因此SEG_GROUP的键值数量就表示了区域的数量。初始化时,该字典中包含所有像素坐标,且每个像素坐标对应的区域中只含有自己这一个像素。

而每个集合中需要计算集合内部的最大距离,即上述的 i n ( C 1 , C 2 ) in(C_1,C_2) in(C1,C2)中的 m a x ( w ( v i , v j ) ) max(w(v_i,v_j)) max(w(vi,vj)),该值用INNER_WEIGHT表示,其键值也和上述的一样,用该区域的坐标最小的像素表示。实际的 i n ( C 1 , C 2 ) in(C_1,C_2) in(C1,C2)通过下面的函数实现:

# 计算两个片段的片段内距离
def internalDif(coord1, coord2, img_array, segment_coord):
    global INNER_WEIGHT, SEG_GROUP
    coord1 = getSegment(coord1, segment_coord)
    coord2 = getSegment(coord2, segment_coord)
    k = 100
    dist1 = INNER_WEIGHT[(coord1[0],coord1[1])] + k/len(SEG_GROUP[(coord1[0],coord1[1])])
    dist2 = INNER_WEIGHT[(coord2[0],coord2[1])] + k/len(SEG_GROUP[(coord2[0],coord2[1])])
    return min(dist1, dist2)

上述关系描述了如何用一个“代表像素”找到其对应的区域,然后区域就能用SEG_GROUP来查询该区域中所有的像素了。然而这只能由区域查询到像素,并不能直接知道某个像素属于哪个区域,因此我们还需要另一个字典segment_coord表示像素的所属区域。segment_coord[(x1,y1)]=(x2,y2)表示像素(x1,y1)的区域和(x2,y2)相同。每个像素的segment_coord初始化为自己,当两个区域合并时,只需要将一个区域的代表像素的segment_coord指向另一个区域的任一像素即可。这样一来,对于合并的两个区域,总有一个区域的代表像素是不变的,因此其segment_coord值总是指向自身。如此一来,当一个像素满足segment_coord[(xi,yi)]=(xi,yi)时,我们就能知道:该像素为代表像素,从而找到了该区域。如果不是,则可以查询上一个像素的segment_coord,直到键值和所指向的值相同。通过上述的合并规则不难发现,像素查询其实是一个树状结构,我们总能通过segment_coord查询到一个像素所在区域的代表像素,即:

# 查询一个像素所属的区域
def getSegment(coord, segment_coord):
    coord = np.array(coord)
    tmp = coord.copy()
    # 一直向上查询,直到找到代表像素
    while (segment_coord[coord[0]][coord[1]] != coord).any():
        coord = segment_coord[coord[0]][coord[1]]
    # 直接将上一个像素指向代表像素以加速之后查找
    segment_coord[tmp[0]][tmp[1]] = coord   
    return np.array((coord[0], coord[1]))

合并的代码如下,需要提供合并的两个区域分别的任一像素。flag用于控制是否更新区域内最大距离。在合并不足50个像素的区域时,因为之后不需要用到区域内最大距离,因此不进行计算,可以进行一定的加速。

# 合并两个区域
def mergeSegment(coord1, coord2, img_array, segment_coord, flag=True):
    global INNER_WEIGHT, SEG_GROUP
    # 查询这两个像素所在区域的代表像素
    coord1 = getSegment(coord1, segment_coord)
    coord2 = getSegment(coord2, segment_coord)
    # 更新区域代表
    segment_coord[coord2[0]][coord2[1]] = coord1
    # 更新区域内部最大距离
    if flag:
        if (coord2[0],coord2[1]) in INNER_WEIGHT.keys():
            del INNER_WEIGHT[(coord2[0],coord2[1])]
        for vi in SEG_GROUP[(coord1[0],coord1[1])]:
            for vj in SEG_GROUP[(coord2[0],coord2[1])]:
                tmp = distance(vi,vj,img_array)
                if tmp > INNER_WEIGHT[(coord1[0],coord1[1])]:
                    INNER_WEIGHT[(coord1[0],coord1[1])] = tmp
    # 合并区域,删除原来的区域
    SEG_GROUP[(coord1[0],coord1[1])] += SEG_GROUP[(coord2[0],coord2[1])]
    del SEG_GROUP[(coord2[0],coord2[1])]
    return

有了上述操作后,我们可以通过查询两个像素所属区域的代表像素,通过二者的代表像素是否相同,来判断二者是否在同一个区域中。如果当前检索到的两个顶点属于同一个区域,则不需要进行合并操作。判断像素是否属于同一区域的代码如下:

# 判断两个像素是否属于同一个区域
def isSameSegment(coord1, coord2, segment_coord):
    coord1 = getSegment(coord1, segment_coord)
    coord2 = getSegment(coord2, segment_coord)
    return (coord1 == coord2).all()

除此之外,还需要计算任意两个像素距离的函数。依据这两个像素的坐标和RGB颜色值,将二者转换到 ( x , y , r , g , b ) (x,y,r,g,b) (x,y,r,g,b)空间,再计算欧氏距离,即特征向量各个分量相减后求平方和再开根即可,可以通过调用numpy模块中的范式功能实现,代码如下:

# 计算两个相邻像素在特征空间(x,y,r,g,b)的欧式距离
def distance(coord1,coord2,img_array):
    vec1 = np.array([coord1[0],coord1[1],img_array[coord1[0]][coord1[1]][0],
            img_array[coord1[0]][coord1[1]][1],img_array[coord1[0]][coord1[1]][2]])
    vec2 = np.array([coord2[0],coord2[1],img_array[coord2[0]][coord2[1]][0],
            img_array[coord2[0]][coord2[1]][1],img_array[coord2[0]][coord2[1]][2]])
    return np.linalg.norm(vec1-vec2)

3.2 分区算法实现的主体部分

算法实现的主体部分由函数segment实现。首先,对各个变量进行初始化操作,包括将区域数量初始化为总的像素数、将segment_coord字典初始化为各个像素自身的坐标、每个区域内部的距离为0:

# 初始时,区域数等于像素数
segment_num = img.size[0] * img.size[1]
# segment_coord[i][j]表示i,j位置的像素属于的区域,用一个统一的坐标表示
segment_coord = np.zeros((img_array.shape[0], img_array.shape[1],2)).astype(np.int16)
for i in range(img_array.shape[0]):
    for j in range(img_array.shape[1]):
        # 初始时,每个像素属于自己所在的区域
        segment_coord[i][j] = [i,j]
        SEG_GROUP[(i,j)] = [(i,j)]
        INNER_WEIGHT[(i,j)] = 0

每次合并时,我们需要找到权值最小的边,将边两边的像素所属的区域进行合并。为了防止繁琐的权重计算和排序,我考虑一开始将所有的边的权重都计算出来,并且进行从小到大的排序,从而就能由小到大的权值依次遍历各个边,对像素区域进行合并:

# 计算所有边的权重并按大小排序
weight = []
for i in range(img_array.shape[0]):
    for j in range(img_array.shape[1]):
        # 每个像素只计算四个方向的边,防止重复
        if j < img_array.shape[1]-1:    # 右边的邻居
            weight.append([(i,j),(i,j+1),distance((i,j),(i,j+1),img_array)])
            if i < img_array.shape[0]-1:# 右下的邻居
                weight.append([(i,j),(i+1,j+1),distance((i,j),(i+1,j+1),img_array)])
        if i < img_array.shape[0]-1:    # 下边的邻居
            weight.append([(i,j),(i+1,j),distance((i,j),(i+1,j),img_array)])
            if j > 0:
                weight.append([(i,j),(i+1,j-1),distance((i,j),(i+1,j-1),img_array)])
weight.sort(key = lambda x:x[2])

需要注意的是,所谓的“边”只能出现在某个像素的八邻域内,也就是说,如果不出界的话,每个像素只能和它左上、上、右上、左、右、左下、下、右下这八个相邻的像素连接。当遍历每个像素,将像素延伸出去的这八条边都考虑进去的话,每条边会计算两次,从而产生重复。考虑到每个条的表示方法为[(顶点1坐标),(顶点2坐标),边的权重],遍历每个像素时只考虑它右边的和下面一排的共四个像素产生的边,这样可以保证每条边只计入一次,且顶点1和顶点2的坐标是按照升序顺序排列的。当然,边界情况要另外讨论。

然后就能开始进行图片的区域合并了。因为事先实现了上面的功能模块,这里基本只需要进行简单的调用即可。从小到大依次遍历各个边,如果该边的两端的顶点像素不属于同一个区域,且满足在理论部分提及的 M e r g e ( C 1 , C 2 ) Merge(C_1,C_2) Merge(C1,C2)条件的话,就对二者进行合并。因为之后还要合并像素少于50个的区域,所以这里是对整个图片区域的初步划分,当区域数量小于一定值就退出循环。该值我对不同的图片进行了不同的设置,即代码中的 SEGMENT_NUM变量:

4. 结果展示与分析

本题的IOU结果可以运行IOUcalc.py得到,图片结果放在下面的文件夹中:

output\ImageSegmentation

结果展示如下,分别为原图、区域图、最终的前景图:

最终计算得到的IOU结果如下:

总的来说,结果有好有坏。对于较好的结果,如下图所示(编号657的图片),可以看出,前景部分,即图片中的主体,拥有大块的相同颜色的区域,且能够和背景区分开来。消防栓为大片的白色,两边的手套为天蓝色,而背景确实大片的绿地和后面的灰色地板:

而对于下面的效果较差的图片(编号957的图),图中的动物和所在的树枝颜色极为相近,很容易混淆。相对来说,右下的树叶和右上的墙体颜色区分更加明显,也就更容易被划分出来:

而对于下面的图(编号为57的图),还可以发现一点:图中左边的黑猫、两只鹅因为全身颜色相近,猫为黑,鹅为白,且与背景有明显区别,因此被区分出来,效果较好。而下方的花猫,身上的花纹颜色多变,容易在分区时被分成许多小块,然后在像素数少于50个的区域进行合并时,合并到背景所属的区域中,因此效果较差。

从上面三张图片的分析,不难看出,该基于图的图片分区算法的确能有效对图片内容进行划分,但总的来说,更加适合图片主体颜色较为单一、且主体与背景颜色有明显不同的情况。

资源下载地址:https://download.csdn.net/download/sheziqiong/86763995
资源下载地址:https://download.csdn.net/download/sheziqiong/86763995

到了这里,关于基于Python实现图像分割算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • python 图像处理——图像分割及经典案例篇之基于颜色的图像分割

    前言 作者在第一部分向大家介绍了图像处理的基础知识,第二部分介绍了图像运算和图像增强,接下来第三部分我们将详细讲解图像分割及图像处理经典案例,该部分属于高阶图像处理知识,能进一步加深我们的理解和实践能力。图像分割是将图像分成若干具有独特性质的区

    2024年02月11日
    浏览(50)
  • 【图像分割】基于浣熊优化算法COA的Otsu(大津法)多阈值电表数字图像分割 电表数字识别【Matlab代码#52】

    长鼻浣熊优化算法(Cоati Optimization Algorithm,COA)是一种启发式优化算法,灵感来源于长鼻浣熊(Coati)的行为策略。长鼻浣熊优化算法基于长鼻浣熊在觅食过程中的特性和行为模式。长鼻浣熊是一种树栖动物,具有长而灵活的鼻子,用于觅食和捕食。它们通过嗅觉感知周围环

    2024年02月16日
    浏览(36)
  • 图像分割Unet算法及其Pytorch实现

    UNet是一种用于图像分割的神经网络,由于这个算法前后两个部分在处理上比较对称,类似一个U形,如下图所示,故称之为Unet,论文链接:U-Net: Convolutional Networks for Biomedical Image Segmentation,全文仅8页。 从此图可以看出,左边的基础操作是两次 3 × 3 3times3 3 × 3 卷积后池化,

    2024年01月22日
    浏览(41)
  • 基于Matlab实现图像分割技术(附上源码+图像)

    Matlab是一种功能强大的编程语言和开发环境,被广泛应用于图像处理和计算机视觉领域。图像分割是图像处理中的重要技术之一,它将图像分割成若干个具有相似特征的区域,以便更好地理解和处理图像。 在Matlab中,实现图像分割可以使用多种方法和函数。下面将介绍几种常

    2024年02月07日
    浏览(44)
  • 基于python+MobileNetV2算法模型实现一个图像识别分类系统

    算法模型介绍 模型使用训练 模型评估 项目扩展 图像识别是计算机视觉领域的重要研究方向,它在人脸识别、物体检测、图像分类等领域有着广泛的应用。随着移动设备的普及和计算资源的限制,设计高效的图像识别算法变得尤为重要。MobileNetV2是谷歌(Google)团队在2018年提

    2024年02月12日
    浏览(38)
  • 基于U-Net网络实现图像分割

    黎长淼,男,西安工程大学电子信息学院,2022级研究生 研究方向:控制科学与工程 电子邮件:1043626870@qq.com 陈梦丹,女,西安工程大学电子信息学院,2022级硕士研究生,张宏伟人工智能课题组 研究方向:机器视觉与人工智能 电子邮件:1169738496@qq.com U-Net是2015年提出的一种

    2024年02月09日
    浏览(38)
  • 基于K-means的图像分割(python代码详解)

    本文主要借鉴了下面文章,对下文的一些代码在本文进行了详细解释 机器学习十大经典算法之K-means (图像分割,python)_Mr.Q的博客-CSDN博客_kmeans图像分割python 十大经典算法分别为:K-means K均值(无监督算法,聚类算法,随机算法)KNN(K Nearest Neighbor) K近邻(有监督算法,分类算

    2023年04月08日
    浏览(36)
  • VGAN实现视网膜图像血管分割(基于pytorch)

    VGAN(Retinal Vessel Segmentation in Fundoscopic Images with Generative Adversarial Networks)出自2018年的一篇论文,尝试使用生成性对抗网络实现视网膜血管分割的任务,原论文地址:https://arxiv.org/abs/1706.09318 在github上有相应的源码仓库,不过由于版本的原因也会出现一些bug,本篇博客在复现项

    2024年01月16日
    浏览(41)
  • 计算机视觉:聚类算法(K-Means)实现图像分割

    什么是K-means聚类? K-means聚类是一种无监督学习算法,用于将一组数据划分为K个不同的类别或簇。它基于数据点之间的相似性度量,将数据点分配到最接近的聚类中心。K-means算法的目标是最小化数据点与其所属聚类中心之间的平方距离和。 K-means聚类在图像分割中的应用 在

    2024年02月02日
    浏览(35)
  • 【OpenCV-Python】——单/多模板匹配&分水岭算法图像分割&图像金字塔&交互式前景提取

    目录 前言: 1、模板匹配 1.1 单目标匹配 1.2 多目标匹配 2、图像分割

    2024年02月07日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包