機器學習-馬爾可夫隨機場(MRF)

一,介紹

首先,我們來交接幾個基本概念:

1)馬爾可夫隨機過程:是隨機過程的一種,其原始模型爲馬爾科夫鏈,其主要特徵是:在已知眼下狀態(如今)的條件下,它未來的變化(將來)不依賴於以往的變化,而只跟眼下所處的狀態有關。

2)隨機場:隨機場實際上是一種特殊的隨機過程,跟普通的隨機過程不同的是,其參數取值不再是實數值而有是多維的矢量值甚至是流行空間的點集。

3)馬爾可夫隨機場:馬爾科夫隨機場是具有馬爾科夫特性的隨機場,它每一個結點的取值只與周圍結點有關。

4)團:任意的兩結點,如果有連接線,稱爲團。

馬爾科夫隨機場(MRF)是由變量x1, ..., xn組成的概率分佈p,由無向圖G定義,其中節點對應於變量xi。 概率p形式爲:

                                                                       

其中C表示G的團集合(即完全連通子圖)。

                                                                      

是標準化常數,確保整個分佈和爲一。

一般情況下,爲了方便,通常只求對數形式:

                                                               

實例:

假設我們有兩張圖,第一張爲原圖,第二章爲加入噪聲的圖:

                                

我們定義:

  • yi:噪聲圖中的像素
  • xi:原圖中的像素,對應噪聲圖中的 yi

接着假設,原圖中,各像素只與相鄰像素相關,那麼我們就可以獲得一個具有馬爾可夫性質的圖模型,並且(xi,yi,xj)是一個極大團,接下來就可以用馬爾可夫隨機場進行處理。

因爲我們需要處理的是二值圖,首先我們定義 xi ∈ {-1, +1},假設這裏白色爲1,黑色爲-1。

對於原圖像素與噪聲圖像素構成的團 {xi, yi},我們定義一項 −ηxiyi,其中 η 爲一個非負的權重。當兩者同號時,這項的值爲−η,異號時爲 η。這樣,當噪聲圖像素與原圖像素較爲接近時,這一項能夠降低 energy(因爲爲負)。

對於噪聲圖中相鄰像素構成的團 {xi, xj},我們定義一項 −βxixj,其中 β 爲一個非負的權重。這樣,當處理過的圖像裏相鄰的像素較爲接近時,這一項能夠降低 energy(因爲爲負)。

最後,我們再定義一項 hxi,使處理過的圖像整體偏向某一個像素值。

對圖像中的每一個像素,將這三項加起來,就得到我們的 energy function:

                                                    

對應聯合概率:

                                                              

顯然 energy 越低,降噪過的圖像與原圖一致的概率越高。

得到了 energy function 的表示,接下來我們需要做的就是想辦法讓處理過後的圖像具備儘可能低的 energy,從而獲得更高的概率 P(X=x)。

注意如果我們固定 y 作爲先驗知識(假設噪聲圖不變),我們所求的概率就變成了 p(x|y),這種模型叫做 Ising Model,在統計物理中有廣泛的應用。這樣我們的問題就成了以 y 爲基準,想辦法不斷變動 x,然後找出最接近原圖的 x。

一種最簡單的辦法是我們先將 x 初始化爲 y,然後遍歷每一個元素,對每個元素分別嘗試 1 和 -1 兩種狀態,選擇能夠得到更小的 energy 的那個,實際上相當於一種貪心的策略,這種方法稱爲 Iterated Conditional Modes(ICM)。

另外,也可以嘗試使用模擬退火(Simulated annealing)或者爬山算法等,在有限的時間內找到儘可能好的解。

模擬退火算法流程如下:

                        1,創建一個隨機解;

                        2,創建一個隨機跳躍值及跳躍方向;

                        3,新解優於之前則替換,或者新解比之前要差,但是公式P值足夠大,也進行替換(可能下次可以找到更優的                             解):

                       

                       其中,newcost爲新解,nowcost爲之前的解,T爲初始溫度,一般初始值需要足夠大。

                       4,如果有進行降溫計算,則需要更新T值:T=r*T,r爲設定的降溫率(取值在0-1之間),r約接近1,則降溫慢,找 到最優解的概率高,但花費時間長。

                       5,單T值小於開始設定的的大小後,算法結束。

python代碼:

創建了兩個py文件:denoise.py、util.py

denoise.py代碼:

from random import random
import time
import os
import argparse

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

from util import *


def E_generator(beta, eta, h):
    # 求E = h * sum{xi} - beta * sum{xi xj} - eta * sum{xi yi}
    # h=0;eta=0.0021;beta=0.001
    def E(x, y):
        xxm = np.zeros_like(x)
        #sum{xi xj} 需要每個點都乘以它上下左右相鄰元素的和
        xxm[:-1, :] = x[1:, :]  # Xi下面的元素構成的矩陣
        xxm[1:, :] += x[:-1, :]  # Xi上面的元素構成的矩陣
        xxm[:, :-1] += x[:, 1:]  # Xi右面的元素構成的矩陣
        xxm[:, 1:] += x[:, :-1]  # Xi左面的元素構成的矩陣
        xx = np.sum(xxm * x)  # 計算sum{xi xj}
        xy = np.sum(x * y)    # 計算sum{xi yi}
        xsum = np.sum(x)      # 計算sum{xi}
        return h * xsum - beta * xx - eta * xy

    def is_valid(i, j, shape):
        """Check if coordinate i, j is valid in shape."""
        return i >= 0 and j >= 0 and i < shape[0] and j < shape[1]

    # 將矩陣中一個值取反,並計算新的energy
    # E1--當前energy;i,j矩陣元素位置,x--原圖像素矩陣,y--噪聲圖像素矩陣
    def localized_E(E1, i, j, x, y):
        oldval = x[i, j]
        newval = oldval * -1  # 將狀態取反
        # 計算新的energy,需要先減去原元素值,再加上現元素值
        E2 = E1 - (h * oldval) + (h * newval)
        E2 = E2 + (eta * y[i, j] * oldval) - (eta * y[i, j] * newval)
        adjacent = [(0, 1), (0, -1), (1, 0), (-1, 0)]
        neighbors = [x[i + di, j + dj] for di, dj in adjacent
                     if is_valid(i + di, j + dj, x.shape)]
        E2 = E2 + beta * sum(a * oldval for a in neighbors)
        E2 = E2 - beta * sum(a * newval for a in neighbors)
        return oldval, newval, E1, E2

    return E, localized_E

# 模擬退火算法當前溫度
def temperature(k, kmax):
    return 1.0 / 500 * (1.0 / k - 1.0 / kmax)

# 如果E1大,進行替換,否則用模擬退火公式計算P值
def prob(E1, E2, t):
    return 1 if E1 > E2 else np.exp((E1 - E2) / t)

# 模擬退火算法,y--噪聲圖像矩陣,E--energy計算公式,localized_E--計算函數,temp_dir--路徑
def simulated_annealing(y, kmax, E, localized_E, temp_dir):
    x = np.array(y)         #將x初始化爲y
    Ebest = Ecur = E(x, y)  # 獲取初始energy
    initial_time = time.time()  # 獲取當前時間戳
    energy_record = [[0.0, ], [Ebest, ]]

    for k in range(1, kmax + 1):
        start_time = time.time()
        t = temperature(k, kmax + 1)
        print ("k = %d, Temperature = %.4e" % (k, t))
        accept, reject = 0, 0
        for idx in np.ndindex(y.shape):  # 編譯矩陣每一個元素,先行後列
            old, new, E1, E2 = localized_E(Ecur, idx[0], idx[1], x, y)
            p, q = prob(E1, E2, t), random()   # 獲取p值和一個0-1的隨機數判斷是否計算
            if p > q:                         # E1大或者模擬退火算法計算的p值大則直接替換
                accept += 1
                Ecur, x[idx] = E2, new
                if (E2 < Ebest):
                    Ebest = E2  # 更新最佳energy
            else:
                reject += 1
                Ecur, x[idx] = E1, old

        end_time = time.time()
        energy_record[0].append(end_time - initial_time)
        energy_record[1].append(Ebest)

        print ("--- k = %d, accept = %d, reject = %d ---" % (k, accept, reject))
        print ("--- k = %d, %.1f seconds ---" % (k, end_time - start_time))

        temp = sign(x, {-1: 0, 1: 255})
        temp_path = os.path.join(temp_dir, 'temp-%d.png' % (k))
        Image.fromarray(temp).convert('1', dither=Image.NONE).save(temp_path)   # 畫圖
        print ("[Saved]", temp_path)

    return x, energy_record


def ICM(y, E, localized_E):
    """Greedy version of simulated_annealing()."""
    x = np.array(y)
    Ebest = Ecur = E(x, y)  # initial energy
    initial_time = time.time()
    energy_record = [[0.0, ], [Ebest, ]]

    for idx in np.ndindex(y.shape):  # for each pixel in the matrix
        old, new, E1, E2 = localized_E(Ecur, idx[0], idx[1], x, y)
        if (E2 < Ebest):               # 如果E2更小則更新
            Ecur, x[idx] = E2, new
            Ebest = E2
        else:
            Ecur, x[idx] = E1, old

        if idx[1] == y.shape[1] - 1:
            used_time = time.time() - initial_time
            energy_record[0].append(used_time)
            energy_record[1].append(Ebest)

    return x, energy_record

# 圖像降噪
def denoise_image(image, args, method='SA'):
    data = sign(image.getdata(), {0: -1, 255: 1})  # 白色映射到 -1, 黑色映射到1
    E, localized_E = E_generator(args.beta, args.eta, args.argh)
    temp_dir = os.path.dirname(os.path.realpath(args.output))
    y = data.reshape(image.size[::-1])  # 獲取圖像矩陣
    if method == 'SA':
        result, energy_record = simulated_annealing(
            y, args.kmax, E, localized_E, temp_dir)
    else:
        result, energy_record = ICM(y, E, localized_E)
    result = sign(result, {-1: 0, 1: 255})
    output_image = Image.fromarray(result).convert('1', dither=Image.NONE)
    return output_image, energy_record


def main():
    args = get_args(src="flipped.png", dest="best.png")

    # 降噪並保存
    image = Image.open(args.input)
    result, energy_record = denoise_image(image, args, args.method)
    result.save(args.output)
    print ("[Saved]", args.output)

    # plot time-energy relationship and save
    plt.plot(*energy_record)
    plt.xlabel('Time(s)')
    plt.ylabel('Energy')
    output_dir = os.path.dirname(os.path.realpath(args.output))
    plot_path = os.path.join(output_dir, args.method + '-energy-time.png')
    plt.savefig(plot_path)
    print ("[Saved]", plot_path)

if __name__ == "__main__":
    main()

util.py代碼:

import os
import argparse

import numpy as np

# 映射函數
def sign(data, translate):
    temp = np.array(data)
    return np.vectorize(lambda x: translate[x])(temp)

# 獲取參數
def get_args(src="in.png", dest="flipped.png"):
    # 定義默認參數
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", "--input", type=str, default=src)
    parser.add_argument("-o", "--output", type=str, default=dest)
    parser.add_argument("-d", "--density", type=float, default=0.1)
    parser.add_argument("-b", "--beta", type=float, default=1e-3)
    parser.add_argument("-e", "--eta", type=float, default=2.1e-3)
    parser.add_argument("-a", "--argh", type=float, default=0.0)
    parser.add_argument("-k", "--kmax", type=int, default=15)
    parser.add_argument("-m", "--method", type=str, default='BA')
    args = parser.parse_args()

    file_dir = os.path.dirname(os.path.realpath(__file__))
    parent_dir, _ = os.path.split(file_dir)
    args.input = os.path.join(parent_dir, 'img', args.input)
    args.output = os.path.join(parent_dir, 'img', args.output)
    return args

貪心算法得到結果:

模擬退火算法迭代15次結果:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章