《Python 计算机视觉编程》学习笔记(九)

文章目录

  • 第 9 章 图像分割
    • 引言
    • 9.1 图割( Graph Cut)
      • 从图像创建图
      • 用户交互式分割
    • 9.2 利用聚类进行分割
    • 9.3 变分法
    • 9.4小结

第 9 章 图像分割

引言

图像分割是将一幅图像分割成有意义区域的过程。区域可以是图像的前景与背景或图像中一些单独的对象。这些区域可以利用一些诸如颜色、边界或近邻相似性等特征进行构建。

9.1 图割( Graph Cut)

图论中的图( graph)是由若干节点(有时也称顶点)和连接节点的边构成的集合。图 9-1 给出了一个示例 1。边可以是有向的(图 9-1 中用箭头示出)或无向的,并且这些可能有与它们相关联的权重。

《Python 计算机视觉编程》学习笔记(九)_第1张图片
图割是将一个有向图分割成两个互不相交的集合,可以用来解决很多计算机视觉方面的问题,诸如立体深度重建、图像拼接和图像分割等计算机视觉方面的不同问题。从图像像素和像素的近邻创建一个图并引入一个能量或“代价”函数,我们有可能利用图割方法将图像分割成两个或多个区域。图割的基本思想是,相似且彼此相近的像素应该划分到同一区域。

图割 C( C 是图中所有边的集合)的“代价”函数定义为所有割的边的权重求合相加:
E c u t = ∑ ( i , j ) ∈ C w i j E_{c u t}=\sum_{(i, j) \in C} w_{i j} Ecut=(i,j)Cwij

w i j w_{ij} wij是图中节点 i 到节点 j 的边( i, j)的权重,并且是对割 C 所有的边进行求和。

图割图像分割的思想是用图来表示图像,**并对图进行划分以使割代价 E c u t E_{cut} Ecut 最小。**在用图表示图像时,增加两个额外的节点,即源点和汇点;并仅考虑那些将源点和汇点分开的割。

寻找最小割( minimum cut 或 min cut)等同于在源点和汇点间寻找最大流( maximum flow 或 max flow)。此外,很多有效的算法都可以解决这些最大流 /最小割问题。

我们在图割例子中将采用 python-graph 工具包,该工具包包含了许多非常有用的图算法。随后的例子里,我们要用到 maximum_flow() 函数,该函数用 Edmonds-Karp 算法计算最大流 / 最小割。采用
一个完全用 Python 写成工具包的好处是安装容易且兼容性良好;不足是速度较慢。不过,对于小尺寸图像,该工具包的性能足以满足我们的需求,对于较大的图像,需要一个更快的实现。

这里给出一个用 python-graph 工具包计算一幅较小的图 1 的最大流 / 最小割的简单例子:

from pygraph.classes.digraph import digraph
from pygraph.algorithms.minmax import maximum_flow

gr = digraph()
gr.add_nodes([0,1,2,3])

gr.add_edge((0,1), wt=4)
gr.add_edge((1,2), wt=3)
gr.add_edge((2,3), wt=5)
gr.add_edge((0,2), wt=3)
gr.add_edge((1,3), wt=4)
flows,cuts = maximum_flow(gr, 0, 3)
print ('flow is:' , flows)
print ('cut is:' , cuts)

上来就有坑:安装pygraph有问题,看这里——>[pygraph安装问题解决] 这个过去时间太久了(https://blog.csdn.net/frostime/article/details/104620220)
安装这个就好,无意发现。

pip install python-graph-core

首先,创建有 4 个节点的有向图, 4 个节点的索引分别 0…3,然后用 add_edge() 增添边并为每条边指定特定的权重。边的权重用来衡量边的最大流容量。以节点 0 为源点、 3 为汇点,计算最大流。打印出流和割结果:
在这里插入图片描述
上面两个 python 字典包含了流穿过每条边和每个节点的标记: 0 是包含图源点的部分, 1 是与汇点相连的节点。

从图像创建图

给定一个邻域结构,我们可以利用图像像素作为节点定义一个图。这里我们将集中讨论最简单的像素四邻域和两个图像区域(前景和背景)情况。一个四邻域( 4-neighborhood)指一个像素与其正上方、正下方、左边、右边的像素直接相连。

除了像素节点外,我们还需要两个特定的节点——“源”点和“汇”点,来分别代表图像的前景和背景。我们将利用一个简单的模型将所有像素与源点、汇点连接起来。

下面给出创建这样一个图的步骤:

  • 每个像素节点都有一个从源点的传入边;
  • 每个像素节点都有一个到汇点的传出边;
  • 每个像素节点都有一条传入边和传出边连接到它的近邻。

为确定边的权重,需要一个能够确定这些像素点之间,像素点与源点、汇点之间边的权重(表示那条边的最大流)的分割模型。与前面一样,像素 i 与像素 j 之间的边的权重记为 w i j w_{ij} wij,源点到像素 i 的权重记为 w s i w_{si} wsi,像素 i 到汇点的权重记为 w i t w_{it} wit

让我们先回顾一下 8.2 节中在像素颜色值上用朴素贝叶斯分类器进行分类的知识。假定我们已经在前景和背景像素(从同一图像或从其他的图像)上训练出了一个贝叶斯分类器,我们就可以为前景和背景计算概率 p F ( I i ) 和 p B ( I i ) p_F(I_i) 和 p_B(I_i) pF(Ii)pB(Ii)。这里, I i I_i Ii 是像素 i 的颜色向量。

现在我们可以为边的权重建立如下模型:
w s i = p F ( I i ) p F ( I i ) + p B ( I i ) w i t = p B ( I i ) p F ( I i ) + p B ( I i ) w i j = κ e − ∣ I i − I j ∣ 2 2 \begin{aligned} w_{s i} &=\frac{p_{F}\left(I_{i}\right)}{p_{F}\left(I_{i}\right)+p_{B}\left(I_{i}\right)} \\ w_{i t} &=\frac{p_{B}\left(I_{i}\right)}{p_{F}\left(I_{i}\right)+p_{B}\left(I_{i}\right)} \\ w_{i j} &=\kappa \mathrm{e}^{-\left|I_{i}-I_{j}\right|^{2}{ }^{2}} \end{aligned} wsiwitwij=pF(Ii)+pB(Ii)pF(Ii)=pF(Ii)+pB(Ii)pB(Ii)=κeIiIj22

利用该模型,可以将每个像素和前景及背景(源点和汇点)连接起来,权重等于上面归一化后的概率。 w i j w_{ij} wij 描述了近邻间像素的相似性,相似像素权重趋近于 κ,不相似的趋近于 0。参数 σ 表征了随着不相似性的增加,指数次幂衰减到 0 的快慢。

创建一个名为 graphcut.py 的文件,将下面从一幅图像创建图的函数写入该文件中:

from pygraph.classes.digraph import digraph
from pygraph.algorithms.minmax import maximum_flow
import bayes

def build_bayes_graph(im,labels,sigma=1e2,kappa=2):
    """从像素四邻域建立一个图,前景和背景(前景用1标记,背景用-1标记,
    其他的用0标记)由labels决定,并用朴素贝叶斯分类器建模"""
    
    m,n = im.shape[:2]
    
    #每行是一个像素的RGB向量
    vim = im.reshape((-1,3))
    
    #前景和背景(RGB)
    foreground = im[labels==1].reshape((-1,3))
    background = im[labels==-1].reshape((-1,3))
    train_data = [foreground,background]
    
    #训练朴素贝叶斯分类器
    bc = bayes.BayesClassifier()
    bc.train(train_data)
    
    #获取所有像素的概率
    bc_labels,prob = bc.classify(vim)
    prob_fg = prob[0]
    prob_bg = prob[1]
    
    #用m*n+2个节点创建图
    gr = digraph()
    gr.add_node(range(m*n+2))
    source = m*n #倒数第二个是源点
    sink = m*n+1 #最后一个节点是汇点
    
    #归一化
    for i in range(vim.shape[0]):
        vim[i] = vim[i]/linalg.norm(vim[i])
        
    #遍历所有的节点,并添加边
    for i in range(m*n):
        #从源点添加边
        gr.add_edge((source,i),wt=(prob_fg[i]/(prob_fg[i]+prob_bg[i])))
        
    #向汇点添加边
    gr.add_edge((i,sink),wt=(prob_bg[i]/(prob_fg[i]+prob_bg[i])))
    
    #向相邻节点添加边
    if i%n !=0 : #左边存在
        edge_wt = kappa*exp(-1.0*sum((vim[i]-vim[i-1])**2)/sigma)
        gr.add_edge((i,i-1),wt=edge_wt)
    if (i+1)%n !=0 : #右边存在
        edge_wt = kappa*exp(-1.0*sum((vim[i]-vim[i+1])**2)/sigma)
        gr.add_edge((i,i+1),wt=edge_wt)
    if i//n !=0 : #上边存在
        edge_wt = kappa*exp(-1.0*sum((vim[i]-vim[i-n])**2)/sigma)
        gr.add_edge((i,i-n),wt=edge_wt)
    if i//n !=m-1 : #下左边存在
        edge_wt = kappa*exp(-1.0*sum((vim[i]-vim[i+n])**2)/sigma)
        gr.add_edge((i,i+n),wt=edge_wt)
        
    return gr

这里我们用到了用 1 标记前景训练数据、用 -1 标记背景训练数据的一幅标记图像。基于这种标记,在 RGB 值上可以训练出一个朴素贝叶斯分类器,然后计算每一像素的分类概率,这些计算出的分类概率便是从源点出来和到汇点去的边的权重;由
此可以创建一个节点为 n× m+2 的图。注意源点和汇点的索引;为了简化像素的索引,我们将最后的两个索引作为源点和汇点的索引。

为了在图像上可视化覆盖的标记区域,我们可以利用 contourf() 函数填充图像(这里指带标记图像)等高线间的区域,参数 alpha 用于设置透明度。将下面函数增加到 graphcut.py 中:

def show_labeling(im, labels):
    """显示图像的前景和背景区域。前景labels=1,背景labels=-1,其他labels=0"""

    imshow(im)
    contour(labels, [-0.5, 0.5])
    contourf(labels, [-1, -0.5], colors='b', alpha=0.25)
    contourf(labels, [0.5, 1], colors='r', alpha=0.25)
    axis('off')

图建立起来后便需要在最优位置对图进行分割。下面这个函数可以计算最小割并将输出结果重新格式化为一个带像素标记的二值图像:


def cut_graph(gr, imsize):
    """用最大流对图gr进行分割,并返回分割结果的二值标记"""

    m, n = imsize
    source = m * n  # 倒数第二个节点是源点
    sink = m * n + 1  # 倒数第一个是汇点

    # 对图进行分割
    flows, cuts = maximum_flow(gr, source, sink)

    # 将图转为带有标记的图像
    res = zeros(m * n)
    for pos, label in cuts.items()[:-2]:  # 不要添加源点/汇点
        res[pos] = label

    return res.reshape((m, n))

需要再次注意源点和汇点的索引;我们需要将图像的尺寸作为输入去计算这些索引,在返回分割结果之前要对输出结果进行 reshape() 操作。割以字典返回,需要将它复制到分割标记图像中,这通过返回列表(键,值)的 .item() 方法完成。这里我
们再一次略过了列表中最后两个元素。

现在让我们看看怎样利用这些函数来分割一幅图像。下面这个例子会读取一幅图像,从图像的两个矩形区域估算出类概率,然后创建一个图:

im = array(Image.open("C:/hqq/document/python/computervision/ch09/picture/empire.jpg"))

im = cv.resize(im, None, fx = 0.01, fy = 0.01, interpolation = cv.INTER_NEAREST)

size = im.shape[:2]
# add two rectangular training regions
labels = zeros(size)
labels[3:18, 3:18] = -1
labels[-18:-3, -18:-3] = 1

figure()
imshow(labels),gray(),title('labels')

# create graph
g = graphcut.build_bayes_graph(im, labels, kappa=2)  # k决定领进像素间的相对权重

# cut the graph
res = graphcut.cut_graph(g, size)
figure()
title('cut picture')

graphcut.show_labeling(im, labels)
figure()
imshow(res)
gray()
axis('off')
show()

原代码中的imresize不能使用了,要改一下。

#im = imresize(im, 0.07, interp='bilinear')
im = cv.resize(im, None, fx = 0.01, fy = 0.01, interpolation = cv.INTER_NEAREST)

还有graphcut.py中的

 for pos,label in islice(cuts.items(), 0,m*n):
    # for pos,label in cuts.items()[:-2]: #don't add source/sink
        res[pos] = label

注释掉的那一句。换成上面那句。

输出图像如下:

《Python 计算机视觉编程》学习笔记(九)_第2张图片标记图像
《Python 计算机视觉编程》学习笔记(九)_第3张图片 训练区域
《Python 计算机视觉编程》学习笔记(九)_第4张图片 结果
用于模型训练的标记图像(左);在待分割图像上显示训练区域(中);分割的结果(右) 可以看到标记图像过大,所以训练效果很差。试着修改图像缩小比例,得到如下结果:

用户交互式分割

利用一些方法可以将图割分割与用户交互结合起来。例如,用户可以在一幅图像上为前景和背景提供一些标记。另一种方法是利用边界框( bounding box)或“ lasso”工具选择一个包含前景的区域。

这里给出一个完整的示例代码,它会载入一幅图像及对应的标注信息,然后将其传递到我们的图割分割路径中:


def create_msr_labels(m, lasso=False):
    """ Create label matrix for training from
    user annotations. """
    labels = zeros(im.shape[:2])
    # background
    # print('m == 0',m == 0)
    labels[where(m == 0)] = -1
    labels[where(m == 64)] = -1
    # foreground

    if lasso:
        labels[where(m == 255)] = 1
    else:
        labels[where(m == 128)] = 1
    return labels

# load image and annotation map
im = array(Image.open('./picture/banana.jpg'))
m = Image.open('./picture/banana.bmp')
# resize
scale = 0.1
im = cv.resize(im, None, fx = 0.05, fy = 0.05, interpolation = cv.INTER_LINEAR)
# im = imresize(im, scale, interp='bilinear')
# m = imresize(m, scale, interp='nearest')
m = cv.resize(im, None, fx = 0.05, fy = 0.05, interpolation = cv.INTER_NEAREST)

m = cv.cvtColor(m, cv.COLOR_BGR2GRAY)

# im = imresize(im, scale, interp='bilinear')
# create training labels
labels = create_msr_labels(m, False)
# build graph using annotations
g = graphcut.build_bayes_graph(im, labels, kappa=2)

# cut graph
res = graphcut.cut_graph(g, im.shape[:2])
# remove parts in background
res[where(m == 0)] = 1
res[where(m == 64)] = 1

# plot the result
figure()
imshow(res)
gray()
xticks([])
yticks([])
savefig('labelplot.pdf')
show()

首先,我们定义一个辅助函数用以读取这些标注图像,格式化这些标注图像便于将其传递给背景和前景训练模型函数,矩形框中只包含背景标记。在本例中,我们设置前景训练区域为整个“未知的”区域(矩形内部)。下一步我们创建图并分割。由于有用户输入,所以我们移除那些在标记背景区域里有任何前景的结果。最后,我们绘制出分割结果,并通过设置这些勾选标记到一个空列表来移去这些勾选标记。这样我们就可以得到一个很好的边框(否则,图像中的边界在黑白图中很难看到)。

有坑啊!首先python版本有差别,代码要改一部分。还有图片压缩特别小时输出是黑框,不压缩,就会在处理过程中报矩阵过大,数据溢出的问题。所以输出一直是黑框。

《Python 计算机视觉编程》学习笔记(九)_第5张图片
后面回来再调试。

9.2 利用聚类进行分割

上一节的图割问题通过在图像的图上利用最大流 / 最小割找到了一种离散解决方案。在本节,我们将看到另外一种分割图像图的方法,即基于谱图理论的归一化分割算法,它将像素相似和空间近似结合起来对图像进行分割。

该方法来自定义一个分割损失函数,该损失函数不仅考虑了组的大小而且还用划分的大小对该损失函数进行“归一化”。该归一化后的分割公式将方程中分割损失函数修改为: E ncut  = E c u t ∑ i ∈ A w i x + E cut  ∑ j ∈ B w j x E_{\text {ncut }}=\frac{E_{c u t}}{\sum_{i \in A} w_{i x}}+\frac{E_{\text {cut }}}{\sum_{j \in B} w_{j x}} Encut =iAwixEcut+jBwjxEcut 

A 和 B 表示两个割集,并在图中分别对 A 和 B 中所有其他节点(函数进行“归一化”这里指图像像素)的权重求和相加,相加求和项称为 association。对于那些像素与其他像素具有相同连接数的图像,它是对划分大小的一种粗糙度量方式。该算法是针对两类分割问题衍生出来的,将在下面进行讲解。

定义 W 为边的权重矩阵,矩阵中的元素 wij 为连接像素 i 和像素 j 边的权重。 D 为对W 每行元素求和后构成的对角矩阵,即 D = diag ⁡ ( d i ) , d i = ∑ j w i j \boldsymbol{D}=\operatorname{diag}\left(d_{i}\right), \quad d_{i}=\sum_{j} w_{i j} D=diag(di),di=jwij。归一化分割可以通过最小化下面的优化问题而求得:
min ⁡ y y T ( D − W ) y y T D y \min _{\mathbf{y}} \frac{\mathbf{y}^{T}(\boldsymbol{D}-\boldsymbol{W}) \mathbf{y}}{\mathbf{y}^{T} \boldsymbol{D} \mathbf{y}} yminyTDyyT(DW)y

向量 y 包含的是离散标记,这些离散标记满足对于 b 为常数 y i ∈ { 1 , − b } y_{i} \in\{1,-b\} yi{1,b}(即 y 只可以取这两个值)的约束, y T D y^TD yTD求和为 0。由于这些约束条件,该问题不太容易求解。

然而,通过松弛约束条件并让 y 取任意实数,该问题可以变为一个容易求解的特征分解问题。这样求解的缺点是你需要对输出设定阈值或进行聚类,使它重新成为一个离散分割。

松弛该问题后,该问题便成为求解拉普拉斯矩阵特征向量问题:
L = D − 1 / 2 W D − 1 / 2 \boldsymbol{L}=\boldsymbol{D}^{-1 / 2} \boldsymbol{W} \boldsymbol{D}^{-1 / 2} L=D1/2WD1/2
正如谱聚类情形一样,现在的难点是如何定义像素间边的权重 w i j w_{ij} wij。归一化割与谱聚类有很多相似之处,并且基础理论有所重叠,具体解释及细节参阅文献[32]Jianbo Shi and Jitendra Malik. Normalized cuts and image segmentation. IEEE Trans. Pattern Anal. Mach. Intell., 22:888-905, August 2000.

我们利用原始归一化割论文 [32] 中的边的权重,通过下式给出连接像素 i 和像素 j的边的权重:
w i j = e − ∣ I i − I j ∣ 2 / σ g e − ∣ x i − x j ∣ 2 / σ d w_{i j}=\mathrm{e}^{-\left|I_{i}-I_{j}\right|^{2} / \sigma_{g}} \mathrm{e}^{-\left|x_{i}-x_{j}\right|^{2} / \sigma_{d}} wij=eIiIj2/σgexixj2/σd

式中第一部分度量像素 I i 和 I j I_i 和 I_j IiIj 之间的像素相似性, I i 和 ( I j ) I_i 和( I_j) Ii(Ij) 定义为 RGB 向量或灰度值;第二部分度量图像中 xi 和 xj 的接近程度, x i ( x j ) x_i(xj_) xi(xj) 定义为每个像素的坐标矢量,缩放因子 σ g 和 σ d σ_g 和 σ_d σgσd决定了相对尺度和每一部件趋近 0 的快慢。

让我们看看这在代码中如何体现,将下面的函数添加到名为 ncut.py 的文件中:

# -*- coding: utf-8 -*-

from pygraph.classes.digraph import digraph
from pygraph.algorithms.minmax import maximum_flow
import bayes
import numpy
import scipy

def ncut_graph_matrix(im,sigma_d=1e2,sigma_g=1e-2):
    """创建用于归一化割的矩阵,其中sigma_d和sigma_g是像素距离和像素相似性的权重参数"""
    
    m,n = im.shape[:2]
    N = m*n
    
    #归一化,并创建RGB或灰度特征向量
    if len(im.shape)==3:
        for i in range(3):
            im[:,:,i] = im[:,:,i] / im[:,:,i].max()
        vim = im.reshape((-1,3))
    else:
        im = im / im.max()
        vim = im.flatten()
        
    #x,y坐标用于距离计算
    xx,yy = meshgrid(range(n),range(m))
    x,y = xx.flatten(),yy.flatten()
    
    #创建边线权重矩阵
    W = zeros((N,N),'f')
    for i in range(N):
        for j in range(i,N):
            d = (x[i]-x[j])**2+(y[i]-y[j])**2
            W[i,j] = W[j,i] = exp(-1.0*sum((vim[i]-vim[j])**2)/sigma_g)*exp(-d/sigma_d)
    return W

该函数获取图像数组,并利用输入的彩色图像 RGB 值或灰度图像的灰度值创建一个特征向量。由于边的权重包含了距离部件,对于每个像素的特征向量,我们利用meshgrid() 函数来获取 x 和 y 值,然后该函数会在 N 个像素上循环,并在 N× N 归
一化割矩阵 W 中填充值。我们可以顺序分割每个特征向量或获取一些特征向量对它们进行聚类来计算分割结果。这里选择第二种方法,它不需要修改任意分割数也能正常工作。将拉普拉斯矩阵进行特征分解后的前 ndim 个特征向量合并在一起构成矩阵 W,并对这些像素进行聚类。下面函数实现了该聚类过程,可以看到,它和 6.3 节的谱聚类例子几乎是一样的:

from scipy.cluster.vq import *
def cluster(S,k,ndim):
    """ 从相似性矩阵进行谱聚类"""
    
    # 检查对称性
    if sum(abs(S-S.T)) > 1e-10:
        print ('not symmetric')
    
    # 创建拉普拉斯矩阵
    rowsum = sum(abs(S),axis=0)
    D = diag(1 / sqrt(rowsum + 1e-6))
    L = dot(D,dot(S,D))
    
    # 计算L的特征向量
    U,sigma,V = linalg.svd(L,full_matrices=False)
    
    # 从前ndim个特征向量创建特征向量
    # 堆叠特征向量作为矩阵的列
    features = array(V[:ndim]).T
 
    # k-means
    features = whiten(features)
    centroids,distortion = kmeans(features,k)
    code,distance = vq(features,centroids)
        
    return code,V

这里我们采用基于特征向量图像值的 K-means 聚类算法(细节参见 6.1 节)对像素进行分组。如果你想对该结果进行实验,可以采用任一聚类算法或分组准则。现在我们可以利用该算法在一些样本图像上进行测试。下面的脚本展示了一个完整的例子:

from PIL import Image
from PCV.tools import ncut
from pylab import *
from numpy import *
 
def scipy_misc_imresize(arr, size, interp='bilinear', mode=None):
   im = Image.fromarray(arr, mode=mode)
   ts = type(size)
   if np.issubdtype(ts, np.signedinteger):
      percent = size / 100.0
      size = tuple((np.array(im.size)*percent).astype(int))
   elif np.issubdtype(type(size), np.floating):
      size = tuple((np.array(im.size)*size).astype(int))
   else:
      size = (size[1], size[0])
   func = {'nearest': 0, 'lanczos': 1, 'bilinear': 2, 'bicubic': 3, 'cubic': 3}
   imnew = im.resize(size, resample=func[interp]) # 调用PIL库中的resize函数
   return np.array(imnew)
 
 
im = array(Image.open("C:/hqq/document/python/computervision/ch09/picture/C-uniform01.ppm"))
m,n = im.shape[:2]
 
#调整图像的尺寸大小为(wid,wid)
wid = 50
rim = scipy_misc_imresize(im, (wid,wid), interp='bilinear')
rim = array(rim, 'f')
 
#创建归一化割矩阵
A = ncut.ncut_graph_matrix(rim,sigma_d=1,sigma_g=1e-2)
 
#聚类
code,V = ncut.cluster(A,k=3,ndim=3)
 
#变换到原来的图像大小
codeim = scipy_misc_imresize(code.reshape(wid,wid), (m,n), interp='nearest')
 
#绘制分割结果
figure()
subplot(121)
imshow(im)
title('before')
subplot(122)
imshow(codeim)
title('after')
gray()
show()

因为 Numpy 的 linanlg.svd() 函数在处理大型矩阵时的计算速度并不够快(有时对于太大的矩阵甚至会给出不准确的结果),所以这里我们重新设定图像为一固定尺寸(在该例中为 50× 50),以便更快地计算特征向量。在重新设定图像大小的时候
我们采用了双线性插值法;因为不想插入类标记,所以在重新调整分割结果标记图像的尺寸时我们采用近邻插值法。注意,重新调整到原图像尺寸大小后第一次利用reshape 方法将一维矩阵变换为( wid, wid)二维数组。在该例中,我们用到了静态手势( Static Hand Posture)数据库(详见 8.1 节)的某幅手势图像,并且聚类数 k 设置为 3。分割结果如下图所示,取前 4 个特征向量。

《Python 计算机视觉编程》学习笔记(九)_第6张图片原始图像和三类分割结果
《Python 计算机视觉编程》学习笔记(九)_第7张图片图相似矩阵的前 4 个特征向量
特征向量展示方法如下:
 
    sequence = ['first','second','third','fourth']
    figure()
    for i in range(4):
        subplot(1,4,i+1)
        imshow( scipy_misc_imresize(V[i].reshape(wid,wid),(m,n),interp='bilinear')),title(sequence[i])
        gray()
    show()

9.3 变分法

在本书中有很多利用最小化代价函数或能量函数来求解计算机视觉问题的例子,如前面章节中在图中最小化割;我们同样可以看到诸如 ROF 降噪、 K-means 和 SVM的例子,这些都是优化问题。

当优化的对象是函数时,该问题称为变分问题,解决这类问题的算法称为变分法。我们看一个简单而有效的变分模型。

Chan-Vese 分割模型对于待分割图像区域假定一个分片常数图像模型。这里我们集中关注两个区域的情形,比如前景和背景,不过这个模型也可以拓展到多区域,比如文献 [38] 中的例子。我们接下来对该模型进行描述。

如果我们用一组曲线 Γ \Gamma Γ 将图像分离成两个区域 Ω 1 和 Ω 2 Ω_1 和 Ω_2 Ω1Ω2,如图 9-7 所示,分割是通过最小化 Chan-Vese 模型能量函数给出的: E ( Γ ) = λ  length  ( Γ ) + ∫ Ω 1 ( I − c 1 ) 2   d x + ∫ Ω 2 ( I − c 2 ) 2   d x E(\Gamma)=\lambda \text { length }(\Gamma)+\int_{\Omega_{1}}\left(I-c_{1}\right)^{2} \mathrm{~d} \mathbf{x}+\int_{\Omega_{2}}\left(I-c_{2}\right)^{2} \mathrm{~d} \mathbf{x} E(Γ)=λ length (Γ)+Ω1(Ic1)2 dx+Ω2(Ic2)2 dx
该能量函数用来度量与内部平均灰度常数 c1 和外部平均灰度常数 c2 的偏差。这里这两个积分是对各自区域的积分,分离曲线 Γ \Gamma Γ的长度用以选择更平滑的方案。
《Python 计算机视觉编程》学习笔记(九)_第8张图片

由分片常数图像 U = χ 1 c 1 + χ 2 c 2 U=\chi_{1} c_{1}+\chi_{2} c_{2} U=χ1c1+χ2c2,我们可以将上式重写为: E ( Γ ) = λ λ ∣ c 1 − c 2 ∣ 2 ∫ ∣ ∇ U ∣ d x + ∥ I − U ∥ 2 E(\Gamma)=\lambda \lambda \frac{\left|c_{1}-c_{2}\right|}{2} \int|\nabla U| \mathrm{d} \mathbf{x}+\|I-U\|^{2} E(Γ)=λλ2c1c2∣∇Udx+IU2
χ 1  和  χ 2 \chi_{1} \text { 和 } \chi_{2} χ1  χ2是两区域 Ω1、 Ω2 的特征(指示)函数。该变换意义重大,要求一些并不需要理解的复杂数学运算,并且已超出本书的讲述范围。

如果用 λ ∣ c 1 − c 2 ∣ \lambda\left|c_{1}-c_{2}\right| λc1c2替换 ROF 方程 (1.1) 中的 λ,则该方程与 ROF 方程 (1.1) 的形式一致;唯一的区别在于,我们 Chan-Vese 模型中在寻找一幅具有分片常数的图像 U。

最小化 Chan-Vese 模型现在转变成为设定阈值的 ROF 降噪问题:

from PIL import Image
from PCV.tools import rof
from pylab import *
import imageio
import scipy.misc

im = array(Image.open("C:\hqq\document\python\computervision\ch09\picture\picture.jpg").convert('L'))
U, T = rof.denoise(im,im,tolerance=0.01)
t = 0.4  #阈值
figure()
title('Original Image')
# imshow(im)
figure1 = U < t*U.max()
imshow(figure1)
gray()
savefig('result.pdf'),axis('off')

# imageio.imwrite('result.pdf', U < t*U.max())

# scipy.misc.imsave('result.pdf',U
show()

函数scipy.misc.imsave('result.pdf',U需要回退到版本1.2.1 退不回去了。。。

在该示例中,为确保得到足够的迭代次数,我们调低 ROF 迭代终止时的容忍阈值。
下图显示了两幅图像的分割结果:

《Python 计算机视觉编程》学习笔记(九)_第9张图片原始图像
《Python 计算机视觉编程》学习笔记(九)_第10张图片分割结果
《Python 计算机视觉编程》学习笔记(九)_第11张图片原始图像
《Python 计算机视觉编程》学习笔记(九)_第12张图片分割结果

分割效果可能不是那么好,但是还是能看出颜色深浅对图片进行了分割。

9.4小结

本章主要的内容是对于图像分割,由于程序直接对一幅图片进行分割时做不到的,所以提出了一个数据结构 ‘图’ ,之后就是如何根据我们的图像去创建一个图,利用像素的四领域,建立起简单的连接,之后通过计算对应的权重,就能离散的分割一幅图像。介绍完了离散分割的方法,就开始介绍连续分割的相关办法。即基于谱图理论的归一化分割算法,它将像素相似和空间近似结合起来对图像进行分割。

你可能感兴趣的:(计算机视觉,python,学习)