圖形識別 - 利用高斯混合模型(Gaussian Mixture Model)找出鴨子
===
github:https://github.com/Hank860809/Bayes_Classification_for_Multivariate_Gaussian_Distrubution
## 一、高斯混合模型的背景建立
高斯混合模型經常應用於影像處裡的分類
原因有以下兩點
1. 移動所造成的改變:例如鴨子在水面划水、費風吹起的草皮和樹葉、漂浮的雲或是因為攝影機移動而造成向素質的變化
2. 亮度的改變:在靜態的影像中一樣會受到外界環境的影響、例如陰影的遮蔽、太陽的位置變化、雲朵的移動等等
這些改變會造成像素些微的變化、而造成像素值不固定、所以很適合用高斯分布來做為分類依據

用以上圖片可以清楚看到r g值的分布 並沒有一個明顯的分布範圍
我所取的特徵向量為(R,G,B)三個顏色,此三個顏色為各自獨立,因此全共變異矩陣是不需要的、對角共變異矩陣的高斯分布線性組合就具有描述特徵向量的能力,因此在計算過程中,我會採用對角共變異矩陣作為計算,其優點有兩點原因,第一點為可以降低計算時的複雜度,第二點為可以降低計算時所付出的時間成本。

上圖左邊為全共變異矩陣算出來的結果 右邊為對角共變異矩陣算出來的結果
可以看出使用對角共變異矩陣算出來的結果相對全共變異矩陣結果雜點較少
## 二、高斯混合模型描述
高斯混合模型一共具有三個參數,分別是混合加權值(mixture weights)、平均向量值(mean vector)以及共變異矩陣(covariance matrix)
### 混合加權值(mixture weights)
混合加權值是其他隨機變量的集合中導出的隨機變量的概率分佈
且=1
### 平均向量值(mean vector)
針對觀察到的隨機變數取様後得到的算術平均數

因為這邊取樣的是像素值所以為(R,G,B)向量
### 共變異矩陣(covariance matrix)
衡量兩個隨機變數的聯合變化程度。
共變異數(Covariance) 是度量兩個變數的變動的同步程度,也就是度量兩個變數線性相關性程度。

### 高斯混合模型

1. p(x|ωi):為後驗概率
2. x:為特徵向量 在這裡代表的是單一像素的(r,g,b)值
3. *µi*:為平均向量
4. Σ:為共變異矩陣
### 貝式分類器模型

1. Likelihood of an observed feature vector: the conditional probability that some feature vector x is observed in samples of a class c, denoted as P(x|c)
2. class Prior Probability:the probabiity of a class under the situation that no feature vector is given, denoted as P(c)
3. Predictor Prior Probability: is the prior probability of predictor, denoted as P(x)
4. Posterior Probaility:the conditional probability of a class c given an input feature vector, denoted as P(c|x)
## 三、套件使用
Labelme 4.6.0
opencv 4.5.4
numpy 1.21.4
matplotlib 3.2.2
PyQt5 5.15.6
Labelme Github:https://github.com/wkentaro/labelme
### Labelme introduction
LabelMe 是個可以繪製多邊形、矩形、圓形、直線、點的一套標記工具,可用於分類、目標檢測、語義分割、實例分割任務上的數據標記。
安裝Labelme
:::info
進入Anaconda Prompt命令視窗
1.創立labelme環境
2.進入labelme環境
3.安裝pyqt5
4.安裝labelme
5.下載完成後輸入labelme即可開啟labelme使用
```
1.輸入conda create --name=labelme python=3.7
2.activate labelme
3.pip install pyqt5
4.pip install labelme
5.labelme
```
:::


標記完成後會產生一個json檔案 紀錄我們標記的結果

### 將json轉換成voc格式
進入 labelme\examples\semantic_segmentation 資料夾
建立一個duck的資料夾
放上剛剛用來標記的圖片及json檔
建立一個txt檔用來記錄我們標記的類型

然後在 Anaconda Prompt 執行以下指令進行轉換,<data> 是標記圖片的資料夾路徑、<data_output> 是轉換標記格式的資料夾路徑、<label.txt path> 是剛剛建立在圖片資料夾外的 label.txt 的路徑
```
$ python labelme2voc.py <data> <data_output> --labels <label.txt path>
```
```
cd C:\Users\Hank Wu\PycharmProjects\labelme\examples\instance_segmentation\duck
python labelme2voc.py full_duck.jpg duck_voc_json --labels label_names.txt
```
執行完成後即可看到多出以下資料夾

資料夾內即有標記過後的PNG檔

## 四、模型參數的初始化
### 混合加權值(mixture weights)
鴨子像素點比整張圖像大約為1:2000000
### 平均向量值(mean vector)
透過標記綠色區塊為非鴨子的像素值,紅色為鴨子的像素值
取出每一個像素點做平均
鴨子平均像素:(242,236,225)
非鴨子平均像素:(91,95,79)
### 共變異矩陣(covariance matrix)
鴨子的對角共變異矩陣:
[622.47539743,0,0],
[0,685.20464295,0],
[0,0,691.06081252]
非鴨子的對角共變異矩陣:
[3105.79471358,0,0],
[0,1756.22959208,0],
[0,0,1891.14783615]
## 五、程式碼
### 1.得到鴨子及非鴨子的範圍
```python=
'''
function get_pixel
將對應到指定顏色的pixel範圍存成list回傳
img:圖片
'''
def get_pixel(img):
duck_pixels = []
noe_duck_pixel = []
for x in range(0,img.shape[0]):
for y in range(0, img.shape[1]):
if(img[x][y] == [128,0,0]).all(): # 紅色鴨子像素
duck_pixels.append([x,y])
if(img[x][y] == [0,128,0]).all(): # 綠色非鴨子像素
noe_duck_pixel.append([x,y])
return duck_pixels,noe_duck_pixel
```
### 2.計算範圍像素的平均RGB值
```python=
'''
function mean 用來計算像素平均的RGB值
回傳pixel_array 內RGB的平均值
pixel_array:pixel list
img:鴨子原圖
mean_pixels_RGB:平均的RGB值
'''
def mean(pixel_array,img):
RGB = [0, 0, 0]
for i in range(0,len(pixel_array)):
pixel_RGB = img[pixel_array[i][0]][pixel_array[i][1]]
RGB += pixel_RGB
# print(RGB)
mean_pixels_RGB = RGB // len(pixel_array)
mean_pixels_RGB = np.array([mean_pixels_RGB]).T
'''
因為原本mean_pixels_RGB是一維陣列 要轉置的話必須多增加一維變成
[[232.92149533]
[227.56448598]
[220.79314642]]
否則無法轉置
'''
return mean_pixels_RGB
```
### 3.計算共變異矩陣
```python=
'''
function sigma 共變異矩陣
衡量兩個像素值的聯合變化程度
x_RGB_pixel_list:像素範圍的list
mean_RGB:平均像素值
img:鴨子原圖
'''
def sigma(x_RGB_pixel_list,mean_RGB,img):
sigma = [[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]
for i in range(len(x_RGB_pixel_list)):
x_RGB = img[x_RGB_pixel_list[i][0]][x_RGB_pixel_list[i][1]]
x_RGB = np.array([x_RGB]).T
sigma += (x_RGB - mean_RGB) * ((x_RGB - mean_RGB).T)
sigma = sigma/(len(x_RGB_pixel_list)-1)
return sigma
```
### 4.高斯混合模型
```python=
'''
function norm_pdf_multivariate 高斯混合模型
x : 單位RGB特徵值
mu: 平均RGB值
sigma : 共變異矩陣
'''
def norm_pdf_multivariate(x, mu, sigma):
size = len(x)
sigma = np.matrix(sigma)
if size == len(mu) and (size, size) == sigma.shape:
det = np.linalg.det(sigma)
if det == 0:
raise NameError("The covariance matrix can't be singular")
part1 = 1 / ( ((2* np.pi)**(len(mu)/2)) * (np.linalg.det(sigma)**(1/2)) )
x_mu = (x - mu)
part2 = np.exp((-0.5) * (x_mu.T.dot(np.linalg.inv(sigma.T))).dot(x_mu))
return part1 * part2
else:
raise NameError("The dimensions of the input don't match")
```
part1:
part2:
### 主程式
```python=
import model
import cv2
import numpy as np
import matplotlib.pyplot as plt
import math
if __name__ == '__main__':
duck_vocimg_url = 'ducks/voc_img.png' # 經過labelme轉換過的PNG圖檔路徑
duck_img_url = 'ducks/img.png' # 原圖路徑
voc_img = cv2.imread(duck_vocimg_url) # 讀取PNG圖檔
voc_img = cv2.cvtColor(voc_img, cv2.COLOR_BGR2RGB)
img = cv2.imread(duck_img_url) # 讀取原圖圖檔
# voc_img = voc_img[500:800,:]
# img = img[500:800,:]
predict_img = np.zeros((img.shape[0], img.shape[1], 3),dtype='uint8') # 預設一個和圖片大小一樣的陣列
duck_pixel,noe_duck_pixel = model.get_pixel(voc_img) # 得到鴨子&非鴨子的範圍
totel_pixel = len(duck_pixel)+len(noe_duck_pixel)
# 算出鴨子區域的平均RGB值
duck_pixel_RGB_mean = model.mean(duck_pixel,img)
# 算出非鴨子區域的平均RGB值
noe_duck_pixel_RGB_mean = model.mean(noe_duck_pixel,img)
# 算出鴨子的sigma
duck_pixel_RGB_sigma = model.sigma(duck_pixel,duck_pixel_RGB_mean,img)
# 算出非鴨子的sigma
noe_duck_pixel_RGB_sigma = model.sigma(noe_duck_pixel,noe_duck_pixel_RGB_mean,img)
# 轉換為對角矩陣
duck_pixel_RGB_sigma = np.diag(np.diag(duck_pixel_RGB_sigma))
noe_duck_pixel_RGB_sigma = np.diag(np.diag(noe_duck_pixel_RGB_sigma))
# 全圖數值
# duck_pixel_RGB_mean = np.array([[242,236,225]]).T
# noe_duck_pixel_RGB_mean = np.array([[91,95,79]]).T
# duck_pixel_RGB_sigma =np.array([[622.47539743,0 ,0],
# [0,685.20464295,0],
# [0,0,691.06081252]]).T
# noe_duck_pixel_RGB_sigma =np.array([[3105.79471358,0,0],
# [0,1756.22959208,0],
# [0,0,1891.14783615]]).T
for i in range(0, img.shape[0]):
print('Predicting : {:d}/{:d}'.format(i, img.shape[0]))
for j in range(0, img.shape[1]):
x_array = np.array([img[i][j]])
x_array = x_array.T
possibility_of_duck = model.norm_pdf_multivariate(x_array, duck_pixel_RGB_mean, duck_pixel_RGB_sigma)
possibility_of_non_duck = model.norm_pdf_multivariate(x_array, noe_duck_pixel_RGB_mean, noe_duck_pixel_RGB_sigma)
a = possibility_of_duck[0] * (1/2000000)
b = possibility_of_non_duck[0] * (1999999/2000000)
if possibility_of_duck[0] >= possibility_of_non_duck[0]:
predict_img[i][j] = [255, 255, 255]
else:
predict_img[i][j] = [0, 0, 0]
duck_vocimg_url = 'result/voc_img.png' # 經過labelme轉換過的PNG圖檔路徑
duck_img_url = 'result/img.png' # 原圖路徑
duck_predict_img_url = 'result/predict_img.png' # 結果圖路徑
voc_img = cv2.cvtColor(voc_img, cv2.COLOR_BGR2RGB)
predict_img = cv2.imread(duck_img_url) # 讀取結果圖圖檔
cv2.imwrite("result/10/img.jpg", img)
cv2.imwrite("result/10/voc_img.png", voc_img)
cv2.imwrite("result/10/predict_img.png", predict_img)
img_url = "ducks/full_duck.jpg"
voc_img_url = "result/10/voc_img.png"
predict_img_url = "result/10/predict_img.png"
#
img = cv2.imread(img_url)
voc_img = cv2.imread(voc_img_url)
predict_img = cv2.imread(predict_img_url)
#
plt.subplot(1, 3, 1)
plt.imshow(img)
plt.subplot(1, 3, 2)
plt.imshow(voc_img)
plt.subplot(1, 3, 3)
plt.imshow(predict_img)
plt.show()
```
## 六、訓練結果

白色像素代表鴨子
## 七、結論
### 可以加強的地方
1. 記憶體容量資源消耗過大:
在訓練時有時會出現記憶體爆炸的問題,這部分我選擇將計算參數與訓練資料分開,防止記憶體爆炸
2. 訓練時間過久:
每一次訓練時間大約會耗費六小時,我想是因為我用了過多迴圈,導致時間複雜度為指數成長
3. 標記種類過少:
我只用了兩種標記 第一種是鴨子類 第二種為非鴨子類
這對於訓練的難度大上不少,對於取樣範圍選擇要更加謹慎
我認為增加其他種類的標記能更有效的判斷是否是鴨子的像素
### 我獲得了什麼
1. 在一開始訓練的結果相當不理想,後來透過不同取樣範圍情況才逐見好轉,讓我了解到取樣的重要性
2. 對於高斯混合模型有更深入的了解