使用Python/scikit-image实现图像分割

原标题 |  Image Segmentation using Python’s scikit-image module.

者 |  Parul Pandey

译者 | 李美丽(华南师范大学)、汪鹏(重庆邮电大学)、Mr丶Lonely(软件工程师)、申请成为白菜(西安电子科技大学)

注:本文的相关链接请访问文末【阅读原文】

所有的东西本质上都是数字,包括图像.

曾今看过The Terminator的人肯定会同意这是那个时代最伟大的科幻电影。在电影中,詹姆斯卡梅隆推出了一个有趣的视觉效果概念,让观众可以看到被称为终结者的机器人的眼睛。这种效应后来被称为终结者视觉,在某种程度上,它将人类从背景中分割出来。它可能听起来完全不合适,但图像分割是当今许多图像处理技术的重要组成部分。

    图像分割

我们都非常清楚Photoshop或类似图形编辑器提供的无限可能性,它们将一个人从一个图像中取出并放入另一个图像中。但是,这样做的第一步是确定该人在源图像中的位置,这就是图像分割发挥作用的地方。为图像分析目的编写了许多库。在本文中,我们将详细讨论scikit-image,这是一个基于Python的图像处理库。也可以从与本文相关的Github存储库访问整个代码。

    Scikit-image

scikit-image.org

Scikit-image是Python一个专门用于图像处理的包.

安装

scikit-image安装方法如下:


 

pip install -U scikit-image(Linux and OSX)

pip install scikit-image(Windows)


# For Conda-based distributions

conda install scikit-image

Python中图像预览

在开始图像分割前, 我们很有必要先熟悉一下scikit image以及它对图片的操作原理.

  • 从skimage库导入灰度图片

skimage库中的data模组,包含一些内置的格式为jpeg或png的样例数据集.


 

from skimage import data

import numpy as np

import matplotlib.pyplot as plt


image = data.binary_blobs()

plt.imshow(image, cmap='gray')

  • 从skimage库导入彩色图片


 

from skimage import data

import numpy as np

import matplotlib.pyplot as plt


image = data.astronaut()


plt.imshow(image)

  • 从外部资源导入图片


 

# The I/O module is used for importing the image

from skimage import data

import numpy as np

import matplotlib.pyplot as plt

from skimage import io


image = io.imread('skimage_logo.png')


plt.imshow(image);

  • 加载多张图片


 

images = io.ImageCollection('../images/*.png:../images/*.jpg')


print('Type:', type(images))

images.files

Out[]: Type: <class ‘skimage.io.collection.ImageCollection’>

  • 保存图片


 

#Saving file as ‘logo.png’

io.imsave('logo.png', logo)

    深入了解图像分割

现在我们大概了解scikit-image,让我们再深入认识一下图像分割。图像分割本质是一个将数字图像划分多个区域(块段)的过程,通过这个过程来简化和改变图像,使其更有意义以及更容易分析。

在本文中, 我们会结合监督算法和无监督算法来学习分割处理。

scikit-image库里有一些能用的分割算法

监督分割:一般要一些由人输入的先验知识,用于引导算法。

无监督分割:无需先验知识。这些算法想要自动把图像分成有意义的区域。使用者可能还要调整确切参数来获得想要的输出。

让我们先从一个最简单的名叫阈值的方法开始。

阈值

这是图像分割里最简单的方法,原理是从背景通过选取像素在某个确切阈值之上或之下来分出物体。当我们试着从背景分出物体,这通常很有用。你可以点击here了解更多。

让我们在教科书的图像试一下这个方法,这张图在scikit-image数据集里事先载入了。

导入基本库


 

import numpy as np

import matplotlib.pypot as plt


import skimage.data as data

import skimage.segmentation as seg

import skimage.filters as filters

import skimage.draw as draw

import skimage.color as color

一个绘制图像的样例


 

def image_show(image, nrows=1, ncols=1, cmap='gray'):

fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(14, 14))

ax.imshow(image, cmap='gray')

ax.axis('off')

return fig, ax

图像


 

text = data.page()


image_show(text)

这幅图像有点暗,但也许我们仍然可以选择一个值,这样我们就可以在没有任何高级算法的情况下进行合理的分割。现在,为了帮助我们找出这个值,我们将使用直方图(Histogram)。

直方图是一种图表,显示图像中以不同强度值显示的像素的个数。简单地说,直方图是一个图,在x轴中显示图像中的所有值(像素级),而y轴显示这些值的频率(或者出现次数)。


 

fig, ax = plt.subplots(1, 1)

ax.hist(text.ravel(), bins=32, range=[0, 256])

ax.set_xlim(0, 256);

我们的例子恰好是一个8-bit的图像,所以我们在x轴上总共有256个可能的像素取值。我们观察到原图比较亮,即它的像素值是比较大(0:黑色,255:白色)。那是,图片中文本背景比较亮,但其余部分比较暗。一个理想的分割直方图应该是具有双峰而且是可分离的,以便我们可以在中间(双峰的中间)选择一个正确的数字(阈值)。现在,让我们尝试做一些基于简单阈值的分割图像。

有监督的阈值

由于我们要自己选择阈值,所以我们称之为有监督的阈值。


 

text_segmented = text > (value concluded from histogram i.e 50,70,120 )


image_show(text_segmented)

左边: text>50 | 中间: text > 70 | 右边: text >120

因为左边的阴影的影响,我们没有得到任何理想的结果。让我们来试试无监督的阈值法。

无监督的阈值

Scikit-image有许多自动的阈值处理方法,在选择最佳阈值时无需输入。比如otsu,li,local。


 

text_threshold = filters.threshold_ # Hit tab with the cursor after the underscore to get all the methods.


image_show(text < text_threshold);

左: otsu || 右: li

在local的情况下,我们还需要指定block_size。偏移有助于调整图像以获得更好的效果。


 

text_threshold = filters.threshold_local(text,block_size=51, offset=10)

image_show(text > text_threshold);

local 阈值

这是非常好的,并在很大程度上消去了噪声的区域。

    监督分割

阈值处理是一个非常基本的分割过程,在高对比度图像中无法正常工作,我们需要更高级的工具。

对于本节,我们将使用免费提供的示例图像,并尝试使用监督分割技术对头部进行分割。


 

from skimage import io

image = io.imread('girl.jpg')

plt.imshow(image);

在对图像进行任何分割之前,最好使用一些滤镜对其进行去噪。

但是,在我们的例子中,图像噪声不是很多,所以我们会照原样。下一步是使用rgb2gray将图像转换为灰度。


 

image_gray = color.rgb2gray(image)

image_show(image_gray);

我们将使用两种分割方法,它们将根据完全不同的规则。

主动轮廓分割

主动轮廓分割也称为Snakes,并且在感兴趣的区域周围使用用户定义的轮廓或线进行初始化,然后该轮廓慢慢收缩并被光和边缘吸引或排斥。

对于我们的示例图像,让我们在人的头部周围画一个圆来初始化snake。


 

def circle_points(resolution, center, radius):


"""

Generate points which define a circle on an image.Centre refers to the centre of the circle

"""

radians = np.linspace(0, 2*np.pi, resolution)c = center[1] + radius*np.cos(radians)#polar co-ordinates

r = center[0] + radius*np.sin(radians)

return np.array([c, r]).T

# Exclude last point because a closed path should not have duplicate points

points = circle_points(200, [80, 250], 80)[:-1]

上述计算计算圆周边上的点的x和y坐标。由于我们将分辨率设为200,因此它将计算200个这样的分数。


 

fig, ax = image_show(image)

ax.plot(points[:, 0], points[:, 1], '--r', lw=3)

然后,算法通过将闭合曲线拟合到脸部的边缘来从人的图像的其余部分分割人的面部。


 

snake = seg.active_contour(image_gray, points)

fig, ax = image_show(image)

ax.plot(points[:, 0], points[:, 1], '--r', lw=3)

ax.plot(snake[:, 0], snake[:, 1], '-b', lw=3);

我们可以调整名为alpha和beta的参数。较高的alpha值会使snake收缩得更快,而beta会让snake变得更加平滑。


 

snake = seg.active_contour(image_gray, points,alpha=0.06,beta=0.3)


fig, ax = image_show(image)

ax.plot(points[:, 0], points[:, 1], '--r', lw=3)

ax.plot(snake[:, 0], snake[:, 1], '-b', lw=3);

随机游走分割

在该方法中,用户交互地标记少量像素点,这些像素称为标签。然后想象每个未标记的像素释放随机游走者,然后可以确定随机游走者在每个未标记的像素处开始并到达预先标记的像素之一的概率。通过将每个像素分配给计算出最大概率的标签,可以获得高质量的图像分割。 请阅读参考文档。

我们将在此处重复使用上一个示例中的种子值。我们可以有不同的初始化,但为了简单起见,我们坚持使用圆。

image_labels = np.zeros(image_gray.shape, dtype=np.uint8)

随机游走算法期望标签图像作为输入。所以我们将有一个更大的圆圈,包括人的整个脸部和另一个靠近脸部中间的小圆圈。


 

indices = draw.circle_perimeter(80, 250,20)#from here


image_labels[indices] = 1

image_labels[points[:, 1].astype(np.int), points[:, 0].astype(np.int)] = 2


image_show(image_labels);

现在,我们可以使用随机游走分割并看看它的效果。


 

image_segmented = seg.random_walker(image_gray, image_labels)


# Check our results

fig, ax = image_show(image_gray)

ax.imshow(image_segmented == 1, alpha=0.3);

它看起来并不像我们想要的那样抓住边缘。要解决这种情况,我们可以调整beta参数,直到获得所需的结果。经过多次尝试,当值取3000时表现得相当好。


 

image_segmented = seg.random_walker(image_gray, image_labels, beta = 3000)


# Check our results

fig, ax = image_show(image_gray)

ax.imshow(image_segmented == 1, alpha=0.3);

这就是监督分割的全部,我们必须提供某些输入并且还必须调整某些参数。然而,并不总是让人看着图像,然后决定给出什么输入或从哪里开始。幸运的是,对于这些情况,我们还有无监督的分割技术。

    无监督分割

无监督分割不需要先验知识。考虑一个如此大的图像,以至于不可能同时考虑所有像素。因此,在这种情况下,无监督分割可以将图像分解为多个子区域,因此您需要数十到数百个区域而不是数百万个像素。我们来看看两个这样的算法:

SLIC(简单线性迭代聚类)

SLIC算法实际上使用了称为K-Means的机器学习算法。它接收图像的所有像素值并尝试将它们分离到给定数量的子区域中。请阅读参考文档。

SLIC是需要RGB图来工作的,因此我们将使用原始图像。

image_slic = seg.slic(image,n_segments=155)

我们所做的只是将我们找到的每个子图像或子区域设置为该区域的平均值,使其看起来不像是随机分配的颜色拼凑而成,更像是已经分解为区域的图像有点类似。

image_show(color.label2rgb(image_slic, image, kind='avg'));

我们已将此图像从512 * 512 = 262,000像素缩小到了155个区域。

Felzenszwalb

该算法还使用了一种称为最小生成树聚类的机器学习算法。Felzenszwaib没有告诉我们图像将被分割成的集群的确切数量。它将运行并生成尽可能多的簇,因为它认为适合于图像上的给定比例或缩放因子,请参考文档。


 

image_felzenszwalb = seg.felzenszwalb(image)

image_show(image_felzenszwalb);

这些是很多的区域。让我们计算一下唯一区域的数量。


 

np.unique(image_felzenszwalb).size

3368

现在让我们使用区域平均值重新着色它们,就像我们在SLIC算法中所做的那样。


 

image_felzenszwalb_colored = color.label2rgb(image_felzenszwalb, image, kind='avg')


image_show(image_felzenszwalb_colored);

现在我们得到了合理的小区域。如果我们想要更少的区域,我们可以更改比例参数或从这里开始并组合它们。这种方法有时被称为过度分割。

这看起来更像是一张图片,它基本上只是减少了颜色数量。要再次组合它们,您可以使用区域邻接图(RAG),但这已经超出了本文的范围。

    总结

图像分割是一个非常重要的图像处理步骤。它是一个活跃的研究领域,应用范围从计算机视觉到医学图像,再到交通和视频监控。Python中以scikit-image的形式提供了一个强大的库,它具有大量的图像处理算法。它是免费提供的,没有任何限制,背后有一个活跃的社区。查看他们的文档以了解更多有关库及其用例的信息。

参考资料:

  • Scikit image文档

本文编辑:王立鱼

英语原文:https://towardsdatascience.com/image-segmentation-using-pythons-scikit-image-module-533a61ecc980

想要继续查看该篇文章相关链接和参考文献?

点击底部 【阅读原文】 即可访问:

https://ai.yanxishe.com/page/TextTranslation/1487

你可能还想看

  点击 阅读原文 ,查看本文更多内容

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章