条件随机向量场CRF

1、什么是条件随机向量场CRF?

条件随机向量场CRF_第1张图片
一个普遍意义的条件随机向量场

上述模型表示,以表示观测序列,以表示隐含的状态序列,那么观察值与。

2、图像分割中的应用全连接CRF

  由于CRF具有考虑上下文关系的特性,因此一般的CRF可以用在图像去噪,平滑图像的过程。但是图像分割过程中产生了分类目标边界模糊的问题,为了得到更精确的最终分类结果,通常要进行一些图像后处理。考虑到图像像素之间的强相关性,因此提出了全连接CRF来解决这一问题。

条件随机向量场CRF_第2张图片
DeepLab V1

  全连接CRFs与稀疏CRFs的最大差别在于:每个像素点都与所有的像素点相连接构成连接边。为了简化计算,献《Efficient Inference in Fully Connected CRFs with Gaussian Edge Potentials》给出了快速推理算法。接着我们就简单讲解具体的求解算法:
首先,假设图像中的每一点都符合吉布斯分布

其中是观测值,是能量函数,也是我们优化的目标,是归一化因子。
  能量函数由一元势函数和二元势函数构成,如下公式所示:

其中的一元势函数用于衡量像素点的类别概率,自卷积神经网络网络的后端输出。二元势函数用于描述像素点和像素点之间的关系,鼓励相似像素分配相同的标签,而相差较大的像素分配不同的标签。这个相似的定义与颜色值rgb和实际相对距离xy相关,所以CRF能够使图片尽量在边界处分割。
二元势函数公式如下:

其中叫做标签兼容项(Label Compatibility),它约束了像素间传导的条件,只有相同标签(label)条件下,能量才可以相互传导。是特征函数,以特征的形式表示了不同像素之前的“亲密度”,第一项被称作表面核,第二项被称作平滑核,公式如下:

  在全连接CRFs进行影像后处理的实际操作中,一元势能为概率分布图,即由模型输出的特征图经过softmax函数运算得到的结果;二元势能中的位置信息和颜色信息由原始影像提供。当能量越小时,预测的类别标签X就越准确,我们通过迭代最小化能量函数,得到最终的后处理结果。

3、代码理解

import numpy as np
import pydensecrf.densecrf as dcrf

try:
    from cv2 import imread, imwrite
except ImportError:
    # 如果没有安装OpenCV,就是用skimage
    from skimage.io import imread, imsave
    imwrite = imsave

from pydensecrf.utils import unary_from_labels, create_pairwise_bilateral, create_pairwise_gaussian

"""
original_image_path  原始图像路径
predicted_image_path  之前用自己的模型预测的图像路径
CRF_image_path  即将进行CRF后处理得到的结果图像保存路径
"""
def CRFs(original_image_path,predicted_image_path,CRF_image_path):
    print("original_image_path: ",original_image_path)
    img = imread(original_image_path)
    
    # 将predicted_image的RGB颜色转换为uint32颜色 0xbbggrr
    anno_rgb = imread(predicted_image_path).astype(np.uint32) #shape为(240, 320, 3)
    anno_lbl = anno_rgb[:,:,0] + (anno_rgb[:,:,1] << 8) + (anno_rgb[:,:,2] << 16) #shape变为了(240, 320)
    
    # 将uint32颜色转换为1,2,...
    # 如果是分两类,colors会产生三种像素值,如:[0,16384,4227072];
   # labels为anno_lbl矩阵中像素值在colors中的索引,并以列表形式储存, 因此labels的值只有0,1,2三种;shape为(240*320,)
    colors, labels = np.unique(anno_lbl, return_inverse=True)
    
    # 如果你的predicted_image里的黑色(0值)不是待分类类别,表示不确定区域,即将分为其他类别
    # 那么就取消注释以下代码
    #HAS_UNK = 0 in colors
    #if HAS_UNK:
    #colors = colors[1:]
    
    # 创建从predicted_image到32位整数颜色的映射。
    colorize = np.empty((len(colors), 3), np.uint8)
    colorize[:,0] = (colors & 0x0000FF)
    colorize[:,1] = (colors & 0x00FF00) >> 8
    colorize[:,2] = (colors & 0xFF0000) >> 16
    
    # 计算predicted_image中的类数。
    n_labels = len(set(labels.flat))
    #n_labels = len(set(labels.flat)) - int(HAS_UNK) ##如果有不确定区域,用这一行代码替换上一行
    
    ###########################
    ###     设置CRF模型     ###
    ###########################
    use_2d = False               
#    use_2d = True   
    ###########################################################   
    ##不是很清楚什么情况用2D        
    ##作者说“对于图像,使用此库的最简单方法是使用DenseCRF2D类”
    ##作者还说“DenseCRF类可用于通用(非二维)密集CRF”
    ##但是根据我的测试结果一般情况用DenseCRF比较对
    #########################################################33
    if use_2d:                   
        # 使用densecrf2d类
        d = dcrf.DenseCRF2D(img.shape[1], img.shape[0], n_labels)
    
        # 得到一元势(负对数概率)
        # U.shape为(2, 76800),即(n_labels,len(labels))
        U = unary_from_labels(labels, n_labels, gt_prob=0.2, zero_unsure=None)
        #U = unary_from_labels(labels, n_labels, gt_prob=0.2, zero_unsure=HAS_UNK)## 如果有不确定区域,用这一行代码替换上一行
        d.setUnaryEnergy(U)
    
        # 增加了与颜色无关的术语,只是位置-----会惩罚空间上孤立的小块分割,即强制执行空间上更一致的分割
        d.addPairwiseGaussian(sxy=(3, 3), compat=3, kernel=dcrf.DIAG_KERNEL,
                              normalization=dcrf.NORMALIZE_SYMMETRIC)
    
        # 增加了颜色相关术语,即特征是(x,y,r,g,b)-----使用局部颜色特征来细化它们
        d.addPairwiseBilateral(sxy=(80, 80), srgb=(13, 13, 13), rgbim=img,compat=10,
                               kernel=dcrf.DIAG_KERNEL,
                               normalization=dcrf.NORMALIZE_SYMMETRIC)
        '''
        addPairwiseGaussian函数里的sxy为公式中的 $\theta_{\gamma}$, 
        addPairwiseBilateral函数里的sxy、srgb为$\theta_{\alpha}$ 和 $\theta_{\beta}$
        '''
    else:
        # 使用densecrf类
        d = dcrf.DenseCRF(img.shape[1] * img.shape[0], n_labels)
    
        # 得到一元势(负对数概率)
        U = unary_from_labels(labels, n_labels, gt_prob=0.5, zero_unsure=None)  
        #U = unary_from_labels(labels, n_labels, gt_prob=0.7, zero_unsure=HAS_UNK)## 如果有不确定区域,用这一行代码替换上一行
        d.setUnaryEnergy(U)
    
        # 这将创建与颜色无关的功能,然后将它们添加到CRF中
        feats = create_pairwise_gaussian(sdims=(3, 3), shape=img.shape[:2])
        d.addPairwiseEnergy(feats, compat=8,kernel=dcrf.DIAG_KERNEL,
                            normalization=dcrf.NORMALIZE_SYMMETRIC)
    
        # 这将创建与颜色相关的功能,然后将它们添加到CRF中
        feats = create_pairwise_bilateral(sdims=(80, 80), schan=(13, 13, 13),
                                          img=img, chdim=2)
        d.addPairwiseEnergy(feats, compat=10,
                            kernel=dcrf.DIAG_KERNEL,
                            normalization=dcrf.NORMALIZE_SYMMETRIC)
    
    ####################################
    ###         做推理和计算         ###
    ####################################
    
    # 进行5次推理
    Q = d.inference(10)
    
    # 找出每个像素最可能的类
    MAP = np.argmax(Q, axis=0)
    
    # 将predicted_image转换回相应的颜色并保存图像
    MAP = colorize[MAP,:]
    imwrite(CRF_image_path, MAP.reshape(img.shape))
    print("CRF图像保存在",CRF_image_path,"!")

4、一元势函数 Unary potential

一元势即网络预测得到的结果,进行-np.log(py)等操作

U = np.array(...)     # Get the unary in some way.
print(U.shape)        # -> (2, 240, 320)
print(U.dtype)        # -> dtype('float32')
U = U.reshape((5,-1)) # Needs to be flat.
d.setUnaryEnergy(U)
# Or alternatively: d.setUnary(ConstUnary(U))
#注意,nlabel维度是这里reshape之前的第一个维度;如果不是这样的话,你可能需要在reshape之前把nlabel移到前面,即U.shape的结果应该为(5, 480, 640),就像这样:
print(U.shape)  # -> (480, 640, 5)
U = U.transpose(2, 0, 1).reshape((5,-1))

得到 unary potentials有两种常见的方法:
1)由人类或其他过程产生的硬标签。该方法由from pydensecrf.utils import unary_from_labels实现
2)由概率分布计算得到,例如深度网络的softmax输出。即我们之前先对图片使用训练好的网络预测得到最终经过softmax函数得到的分类结果,这里需要将这个结果转成一元势
对此,请参阅from pydensecrf.utils import unary_from_softmax

1)unary_from_labels(labels, n_labels, gt_prob, zero_unsure=True)函数的使用
简单分类器,该分类器50%确定注释(即从训练好的网络预测img后得到的结果)是正确的。(与推理示例中相同)。

参数:
labels: numpy.array;标签label映射,即数据的形状的数组,其中每个唯一值对应于一个标签,一种像素值对应一种标签。
n_labels: int;标签的总数。如果zero_unsure参数为True(默认值),这个数字不应该包括' 0 '标签,因为' 0 '不是一个标签!
gt_prob: float;基本事实的确定性(必须在(0,1)之内)。
zero_unsure: bool;如果“True”,则将标签值“0”视为“可能是任何东西”,即具有此值的项将得到一致的一元概率,不将其当作标签。如果“False”,不要特别对待值“0”,而是像对待任何其他类一样对待它。

2)unary_from_softmax(sm, scale=None, clip=1e-5)函数的使用
将softmax类概率转换为一元势(每个节点的NLL),即我们之前先对图片使用训练好的网络预测得到最终经过softmax函数得到的分类结果,这里需要将这个结果转成一元势。

参数:
sm: numpy.array,第一个维度是类的softmax的输出,其他所有维度都是flattend。这意味着“sm.shape[0] == n_classes”。
scale: float,softmax输出的确定性(默认为None),需要值在(0,1]。如果不为None,则softmax输出被缩放到从[0,scale]概率的范围。
clip: float,将概率裁剪到的最小值。这是因为一元函数是概率的负对数,而log(0) = inf,所以我们需要把0概率裁剪成正的值。
在这里因为scale=None,clip=None,所以这个函数的作用其实只进行了下面的操作:
-np.log(sm).reshape([num_cls, -1]).astype(np.float32)
构建好一元势后需要调用, 将该一元势添加到CRF中:
d.setUnaryEnergy(U)

5、参考

  [1] 如何轻松愉快地理解条件随机场(CRF)?
  [2] 图像语义分割之FCN和CRF
  [3] https://hit-computer.github.io/2017/06/10/CRF/
  [4] DeepLearning-500-questions第九章 图像分割d
  [5] https://zhuanlan.zhihu.com/p/64854535
  [6] pydensecrf的使用

你可能感兴趣的:(条件随机向量场CRF)