草庐IT

超像素(superpixel)——SLIC和深度学习法

追影子的蛇 2023-07-05 原文

定义

可以理解成在图像上做的聚类问题。超像素的做法是将感知上相似的像素组在一起,称为一个超像素,以此来提供图像数据的紧凑表示。然后在后续的处理,处理单位就变成了超像素,而不是我们常用的像素。

一般超像素的结果可以为下游任务提供帮助,比如说语义分割、对象检测等。

SLIC

Simple Linear Iterative Clustering,简单的线性迭代聚类

论文2011-PAMI-SLIC Superpixels Compared to State-of-the-art Superpixel Methods

前提:这个算法实在CIELAB这个颜色空间上做的,即每个pixel的值用来表示,其中表示亮度(取值范围0-100,数值越大越亮);表示红色到绿色之间的色域(负值表示绿色,正值表示红色);表示黄色和蓝色之间的色域(负值表示蓝色,正值表示黄色)。

符号表示:

假设一张图片有N个像素,需要我们人为去设定的超参数有且仅有一个,就是超像素的个数k。

那么,每个超像素的平均面积是,每个超像素中心的平均间隔是

算法实现:

关于初始化:

  1. 初始化各个超像素中心的位置。比如一张图片的大小为,将其均分个grid,然后设置每个grid的中心为一个超像素的中心。
  2. 微调这些超像素的中心,在以它们为中心的邻域里进行计算,将超像素中心更换成其中梯度值最小的。这样做的目的是为了防止超像素中心落在边缘/噪点上。
  3. 标签表示像素i属于哪一个superpixel,距离表示像素i与其所属的superpixel的中心的距离。初始化每一个像素i的

进行下面迭代直到收敛,收敛的前提是误差满足一定要求:

对于每个聚类中心

        对于以其为中心的邻域内的每个像素i: 

                计算与i之间的距离D

                如果,那么更新

                [这表明将i分到以为中心的超像素中]

重新计算聚类中心

关于点与点之间距离D的定义:

由两个距离加权而得,分别是颜色域之间的距离和空间域之间的距离。

颜色域距离: 

空间域距离:

距离D的计算:

,其中指的是maximum spatial distance,超像素中包含的平均像素个数,即为S;指的是maximum color distance,在实际应用中通常用一个人为设定的常数m来表示即可。

那么,上式可以被重写成

我们实际生活中用的距离公式与上式等价

m越大,距离D受空间域距离影响越大,产生的超像素将更紧凑;m越小,距离D受颜色域影响越大,产生的超像素将更加紧密地附着在图像边界(edge)上。

 代码实现

源码地址:GitHub - aleenaniklaus/SLIC_superpixels: SLIC Superpixels* implementation was my final computer vision project. Superpixels are instrumental in segmentation. This implementation is a proof of concept as taken from SLICsuperpixels paper mentioned in README.SLIC Superpixels* implementation was my final computer vision project. Superpixels are instrumental in segmentation. This implementation is a proof of concept as taken from SLICsuperpixels paper mentioned in README. - GitHub - aleenaniklaus/SLIC_superpixels: SLIC Superpixels* implementation was my final computer vision project. Superpixels are instrumental in segmentation. This implementation is a proof of concept as taken from SLICsuperpixels paper mentioned in README.https://github.com/aleenaniklaus/SLIC_superpixels

稍微修改了一下代码细节,让大家能更加直观地看到超像素分割和原图信息的对应关系。另外,我们还能从超像素图导出一张超像素掩码(superpixel mask),也就是将超像素的边缘设置成0,超像素内部设置为1的mask。这个掩码能在一定程度上反映图片的结构信息。

import numpy
import cv2
import tqdm
import argparse

# 将原作者的sys转换成params
parser = argparse.ArgumentParser(description='SLIC-python')
parser.add_argument('--img_path', default='lena.png', type=str, help="单张图片的路径")
parser.add_argument('--k', default=500, type=int, help="超像素个数")
parser.add_argument('--SLIC_ITERATIONS', default=4, type=int, help="SLIC计算过程中的迭代次数")
parser.add_argument('--m', default=40, type=int, help="权衡颜色和位置对距离影响的权重参数")
args = parser.parse_args()


def generate_pixels():
    indnp = numpy.mgrid[0:SLIC_height, 0:SLIC_width].swapaxes(0, 2).swapaxes(0, 1)
    # 迭代SLIC_ITERATIONS次
    for i in tqdm.tqdm(range(SLIC_ITERATIONS)):
        SLIC_distances = 1 * numpy.ones(img.shape[:2])
        # 按次序取出聚类中心SLIC_centers[j]
        for j in range(SLIC_centers.shape[0]):
            # 框出该聚类中心的搜索范围
            x_low, x_high = int(SLIC_centers[j][3] - step), int(SLIC_centers[j][3] + step)
            y_low, y_high = int(SLIC_centers[j][4] - step), int(SLIC_centers[j][4] + step)

            # 防止搜索范围超出图像边界[保证搜索范围有效性]
            if x_low <= 0:
                x_low = 0
            if x_high > SLIC_width:
                x_high = SLIC_width
            if y_low <= 0:
                y_low = 0
            if y_high > SLIC_height:
                y_high = SLIC_height

            # cropimg是该聚类中心对应的2S\times2S内的有效邻域
            cropimg = SLIC_labimg[y_low: y_high, x_low: x_high]
            # 挨个像素算出颜色差
            color_diff = cropimg - SLIC_labimg[int(SLIC_centers[j][4]), int(SLIC_centers[j][3])]
            # 算出颜色距离
            color_distance = numpy.sqrt(numpy.sum(numpy.square(color_diff), axis=2))

            yy, xx = numpy.ogrid[y_low: y_high, x_low: x_high]
            # 算出空间距离
            pixdist = ((yy - SLIC_centers[j][4]) ** 2 + (xx - SLIC_centers[j][3]) ** 2) ** 0.5

            # 运用论文中的(2)式计算邻域内pixel与该邻域中心的聚类中心的距离(加权求和)
            # SLIC_m is "m" in the paper, (m/S)*dxy
            dist = ((color_distance / SLIC_m) ** 2 + (pixdist / step) ** 2) ** 0.5

            # 更新距离,更新了距离的pixel也更新聚类中心为SLIC_centers[j]
            distance_crop = SLIC_distances[y_low: y_high, x_low: x_high]
            idx = dist < distance_crop
            distance_crop[idx] = dist[idx]
            SLIC_distances[y_low: y_high, x_low: x_high] = distance_crop
            SLIC_clusters[y_low: y_high, x_low: x_high][idx] = j

        for k in range(len(SLIC_centers)):
            # 对于第k个聚类,找到聚类中心为SLIC_centers[k]的pixel
            idx = (SLIC_clusters == k)
            # 分别取出他们的颜色和位置索引
            colornp = SLIC_labimg[idx]
            distnp = indnp[idx]

            # 重新计算聚类中心的颜色和位置坐标(这个聚类中心和k-means中的一样,不一定是已有的点)
            SLIC_centers[k][0:3] = numpy.sum(colornp, axis=0)
            sumy, sumx = numpy.sum(distnp, axis=0)
            SLIC_centers[k][3:] = sumx, sumy
            ### 注:numpy.sum(idx)是该聚类pixel数目
            SLIC_centers[k] /= numpy.sum(idx)


# At the end of the process, some stray labels may remain meaning some pixels
# may end up having the same label as a larger pixel but not be connected to it
# In the SLIC paper, it notes that these cases are rare, however this
# implementation seems to have a lot of strays depending on the inputs given

def create_connectivity():
    """
        按照论文的说法,总有那么些点和它对应的超像素是分离的(比较零散的碎点)
        运用connected components algorithm来将这些零散的点分配给最近的聚类中心
    """
    label = 0
    adj_label = 0
    lims = int(SLIC_width * SLIC_height / SLIC_centers.shape[0])

    new_clusters = -1 * numpy.ones(img.shape[:2]).astype(numpy.int64)
    elements = []
    for i in range(SLIC_width):
        for j in range(SLIC_height):
            if new_clusters[j, i] == -1:
                elements = []
                elements.append((j, i))
                for dx, dy in [(-1, 0), (0, -1), (1, 0), (0, 1)]:
                    x = elements[0][1] + dx
                    y = elements[0][0] + dy
                    if (x >= 0 and x < SLIC_width and
                            y >= 0 and y < SLIC_height and
                            new_clusters[y, x] >= 0):
                        adj_label = new_clusters[y, x]
                    # end
                # end
            # end

            count = 1
            counter = 0
            while counter < count:
                for dx, dy in [(-1, 0), (0, -1), (1, 0), (0, 1)]:
                    x = elements[counter][1] + dx
                    y = elements[counter][0] + dy

                    if (x >= 0 and x < SLIC_width and y >= 0 and y < SLIC_height):
                        if new_clusters[y, x] == -1 and SLIC_clusters[j, i] == SLIC_clusters[y, x]:
                            elements.append((y, x))
                            new_clusters[y, x] = label
                            count += 1
                        # end
                    # end
                # end

                counter += 1
            # end
            if (count <= lims >> 2):
                for counter in range(count):
                    new_clusters[elements[counter]] = adj_label
                # end

                label -= 1
            # end

            label += 1
        # end
    # end

    SLIC_new_clusters = new_clusters


# end

def display_contours(color):
    is_taken = numpy.zeros(img.shape[:2], numpy.bool)  # 标志哪些点是聚类与聚类之间的edge
    contours = []

    for i in range(SLIC_width):
        for j in range(SLIC_height):
            nr_p = 0
            for dx, dy in [(-1, 0), (-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1)]:
                x = i + dx
                y = j + dy
                if x >= 0 and x < SLIC_width and y >= 0 and y < SLIC_height:
                    if is_taken[y, x] == False and SLIC_clusters[j, i] != SLIC_clusters[y, x]:
                        nr_p += 1
                    # end
                # end
            # end

            if nr_p >= 2:
                is_taken[j, i] = True
                contours.append([j, i])

    # 将这些edge-pixel全用黑色来表示
    for i in range(len(contours)):
        img[contours[i][0], contours[i][1]] = color
        mask[contours[i][0], contours[i][1]] = color
    # end


# end

def find_local_minimum(center):
    """
        微调
        在3\times3领域内找梯度最小的点作为初始聚类中心
    """
    min_grad = 1
    loc_min = center
    for i in range(center[0] - 1, center[0] + 2):
        for j in range(center[1] - 1, center[1] + 2):
            c1 = SLIC_labimg[j + 1, i]
            c2 = SLIC_labimg[j, i + 1]
            c3 = SLIC_labimg[j, i]
            if ((c1[0] - c3[0]) ** 2) ** 0.5 + ((c2[0] - c3[0]) ** 2) ** 0.5 < min_grad:
                min_grad = abs(c1[0] - c3[0]) + abs(c2[0] - c3[0])
                loc_min = [i, j]
    return loc_min


def calculate_centers():
    """
        按照grid_cell初始化聚类中心
    """
    centers = []
    for i in range(step, SLIC_width - int(step / 2), step):
        for j in range(step, SLIC_height - int(step / 2), step):
            nc = find_local_minimum(center=(i, j))  # 微调
            color = SLIC_labimg[nc[1], nc[0]]
            center = [color[0], color[1], color[2], nc[0], nc[1]]  # LAB+XY
            centers.append(center)
    return centers  # 储存聚类中心的信息


# 样例命令是slic.py Lenna.png 1000 40
# sys.argv[1]是放图片路径
# sys.argv[2]这个参数指示划分的superpixel的个数
# sys.argv[3]这个参数是论文中的m与论文中的m对应,是计算点与点间的距离时用于衡量颜色距离和空间距离所占权重的重要参数

# global variables
img = cv2.imread(args.img_path)
mask = 255 * numpy.ones(img.shape).astype('uint8')
step = int((img.shape[0] * img.shape[1] / args.k) ** 0.5)  # 每个superpixel中心之间的平均距离
SLIC_m = args.m
SLIC_ITERATIONS = args.SLIC_ITERATIONS  # 迭代次数
SLIC_height, SLIC_width = img.shape[:2]
SLIC_labimg = cv2.cvtColor(img, cv2.COLOR_BGR2LAB).astype(numpy.float64)  # BGR转LAB

# 初始化距离和每个点所属聚类中心
SLIC_distances = 1 * numpy.ones(img.shape[:2])
SLIC_clusters = -1 * SLIC_distances  ### 我们应该是依靠这个搞出mask ###

# 聚类中心初始化
SLIC_center_counts = numpy.zeros(len(calculate_centers()))
SLIC_centers = numpy.array(calculate_centers())

# main
generate_pixels()  # 迭代SLIC_ITERATIONS次,聚好各组点,算出他们的聚类中心位置和类颜色
create_connectivity()  # 后处理,对一些比较零散的点重新分配给邻近的聚类
calculate_centers()
display_contours([0.0, 0.0, 0.0])
img2 = numpy.hstack((img, mask))
cv2.imwrite(args.img_path.replace(".png","_{}_SLIC.png".format(args.k)), img2)

结果展示

我们用非常经典的lena图片来做展示:

设置超像素为500:

 

 设置超像素为100:

 

深度学习学超像素的方法我后续再补充上来

有关超像素(superpixel)——SLIC和深度学习法的更多相关文章

  1. ruby-on-rails - 如何在 Rails Controller Action 上触发 Facebook 像素 - 2

    我有一个ruby​​onrails应用程序。我按照facebook的说明添加了一个像素。但是,要跟踪转化,Facebook要求您将页面置于达到预期结果时出现的转化中。即,如果我想显示客户已注册,我会将您注册后转到的页面作为成功对象进行跟踪。我的问题是,当客户注册时,在我的应用程序中没有登陆页面。该应用程序将用户带回主页。它在主页上显示了一条消息,所以我想看看是否有一种方法可以跟踪来自Controller操作而不是实际页面的转化。我需要计数的Action没有页面,它们是ControllerAction。是否有任何人都知道的关于如何执行此操作的gem、文档或最佳实践?这是进入布局文件的像素

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  4. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  5. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  6. ruby - 我如何学习 ruby​​ 的正则表达式? - 2

    如何学习ruby​​的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby​​的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/

  7. 深度学习12. CNN经典网络 VGG16 - 2

    深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG

  8. 机器学习——时间序列ARIMA模型(四):自相关函数ACF和偏自相关函数PACF用于判断ARIMA模型中p、q参数取值 - 2

    文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk​=Var(yt​)Cov(yt​,yt−k​)​其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞

  9. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板 - 2

    写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c

  10. ruby-on-rails - 这个 C 和 PHP 程序员如何学习 Ruby 和 Rails? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我来自C、php和bash背景,很容易学习,因为它们都有相同的C结构,我可以将其与我已经知道的联系起来。然后2年前我学了Python并且学得很好,Python对我来说比Ruby更容易学。然后从去年开始,我一直在尝试学习Ruby,然后是Rails,我承认,直到现在我还是学不会,讽刺的是那些打着简单易学的烙印,但是对于我这样一个老练的程序员来说,我只是无法将它

随机推荐