天天看點

[煉丹術]UNet圖像分割模型相關總結

UNet圖像分割模型相關總結

1.制作圖像分割資料集

1.1使用labelme進行标注

(注:labelme與labelImg類似,都屬于對圖像資料集進行标注的軟體。但不同的是,labelme更關心對象的邊緣和輪廓細節,也即通過生成和訓練圖像對應的mask來實作圖像分割的目的。這裡的分割一般使用的是閉合多邊形折線來進行标注,每張圖檔标注完成後,按下Ctrl+S來進行儲存,此時存儲的檔案是與圖檔同名的.json格式檔案。)

[煉丹術]UNet圖像分割模型相關總結

我們要得到的結果是mask,儲存生成的.json檔案還需要通過轉換得到對應的mask圖像。

(這裡的轉換有兩種方式,一種是找到目前python環境下的labelme_json_to_dataset.py進行修改,二是直接在指令行中調用對應的接口labelme_json_to_dataset {file}生成mask,由于單指令行直接執行一個檔案的生成,是以這裡考慮編寫對應的腳本,對目前目錄下的.json進行批量處理。)

1.2生成mask檔案

使用第二種方式,步驟如下:

1.建立.sh腳本檔案

touch json2mask.sh
           

2.編輯.sh腳本檔案

将下列内容複制進.sh腳本檔案中

gedit json2mask.sh
           
#!/bin/bash
let i=1
path=./
cd ${path}
for file in *.json
do
     labelme_json_to_dataset ${file}
     let i=i+1
 done
           

3.執行腳本

bash json2mask.sh
           

對.json檔案進行轉換生成之後,會得到對應名稱的檔案夾

如圖所示

[煉丹術]UNet圖像分割模型相關總結

檢視檔案夾,發現存在四個檔案:

[煉丹術]UNet圖像分割模型相關總結

分别為以下:

  • img.png,源檔案圖像
  • label.png,标簽圖像
  • label_names.txt,标簽中的各個類别的名稱
  • label_viz.png,源檔案與标簽融合檔案

其中的label.png即是我們要的想要的标簽檔案。如果本來的源檔案圖像為jpg格式,我們會發現生成的png格式源檔案圖像大小會大很多,不必驚慌。JPG品質不會有變化,但大小通常會增加幾倍左右,這是因為JPG是有損壓縮,而PNG是無損壓縮。

1.3 轉換二值圖像并批量整理

  • 得到以上這些結果是不是意味着結束了呢?
    事實上,到這裡才僅僅完成的一半,我們還需要對label.png圖檔進行轉換為二值圖檔,最後我們可以周遊檔案夾内所有小檔案夾,分别對其中的img和轉換後的label進行重命名存儲到對應的imgs和masks檔案目錄下,到這一步整個資料集制作才算全部完成。

通過執行下面代碼可以批量的對各個小檔案夾下的圖檔進行重命名和整理:

'''
@author: linxu
@contact: [email protected]
@time: 2021-08-21 上午11:54
@desc: 将多通道mask圖像批量轉換為單通道二值化圖像并存放到指定位置
'''
import cv2
import numpy as np
import os

import os
def os_mkdir(path):
    # 去除首位空格
    path = path.strip()
    # 去除尾部/符号
    path = path.rstrip("/")
    # 判斷路徑是否存在
    isExists = os.path.exists(path)
    # 判斷結果
    if not isExists:
        # 如果不存在則建立目錄
        # 建立目錄操作函數
        os.makedirs(path)
        print(path + ' 建立成功')
        return True
    else:
        # 如果目錄存在則不建立,并提示目錄已存在
        print(path + ' 目錄已存在')
        return False
        
def mask2binimg(path,show=False):
    for root, dirs, files in os.walk(path):
        print('################################################################')
        for name in files:
            # 周遊label生成的{x}_json目錄
            if len(dirs) == 0:
                # print('root', root)
                # 字元分割,得到label排序序号
                filepath = os.path.split(root)[0]
                numname = os.path.split(root)[1]
                n_name = numname.replace('_json','')
           
           # 處理原圖img
            if name == 'img.png':
                fname = os.path.join(root, name)
                print('INFO[img]', fname)
                img = cv2.imread(fname)
                img_dst = cv2.resize(img, (640, 480))
                # img = cv2.resize(img, (0, 0), fx=0.3, fy=0.3, interpolation=cv2.INTER_NEAREST)
                if show:
                    cv2.imshow('img', img_dst)
                    cv2.waitKey()
                # 根據指定路徑存取二值化圖檔
                img_path = filepath + '/imgs/'
                os_mkdir(img_path)
                cv2.imwrite(img_path + str(n_name) + '.png', img_dst)

            # 處理label标簽圖
            if name == 'label.png':
                fname = os.path.join(root, name)
                print('INFO[label]', fname)
                label = cv2.imread(fname)
                label = cv2.resize(label, (640, 480))
                gray = cv2.cvtColor(label, cv2.COLOR_BGR2GRAY)
                retVal, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
                # 顯示圖檔
                if show:
                    cv2.imshow('label', label)
                    cv2.imshow('dst', dst)
                    if cv2.waitKey(1) & 0xff == ord("q"):
                        break
                # 根據指定路徑存取二值化圖檔
                mask_path = filepath + '/masks/'
                os_mkdir(mask_path)
                cv2.imwrite(mask_path + str(n_name) + '.png', dst)

    print('目前圖檔轉換完成...')
pass

if __name__ == '__main__':
    path = '/home/linxu/下載下傳/flow_dataset/image/'
    mask2binimg(path,False)
           

運作結束後,會發現目錄下多了兩個檔案夾,一個是imgs,用來存放原圖;另外一個是masks,用來存放二值化标注圖像。

[煉丹術]UNet圖像分割模型相關總結

檔案目錄imgs下内容如下圖所示:

[煉丹術]UNet圖像分割模型相關總結

檔案目錄masks下内容如下圖所示:

[煉丹術]UNet圖像分割模型相關總結

确認imgs與masks内容無誤之後,将這兩個檔案夾拷貝到UNet模型源碼目錄下的data路徑,如下圖所示:

[煉丹術]UNet圖像分割模型相關總結

至此,資料集制作完畢并放置到指定訓練路徑下。

2.Train訓練

制作完資料集之後,下一步就是對資料集進行訓練

python train.py -h
           

2.1 用法

train.py [-h] [-e E] [-b [B]] [-l [LR]] [-f LOAD] [-s SCALE] [-v VAL]
           

在圖像和目标掩碼上訓練 UNet

可選參數:

  • -h , --help 顯示此幫助資訊并退出
  • -e E, --epochs E 時期數(預設值:5)
  • -b [B], --batch-size [B]

    批量大小(預設值:1)

  • -l [LR], --learning-rate [LR]

    學習率(預設:0.1)

  • -f LOAD, --load LOAD 從 .pth 檔案加載模型(預設:False)
  • -s SCALE, --scale SCALE

    圖像的縮小因子(預設值:0.5)

  • -v VAL, --validation VAL

    用作驗證的資料百分比 (0-100)

    (預設值:10.0)

預設情況下,該

scale

值為 0.5,是以如果您希望獲得更好的結果(但使用更多記憶體),請将其設定為 1。

輸入圖像和目标掩碼應分别位于

data/imgs

data/masks

檔案夾中。

2.2 調用示例

python train.py -e 200 -b 1 -l 0.1 -s 0.5 -v 15.0
           

3.Predict預測

python predict.py -h
           

3.1 用法

predict.py [-h] [--model FILE] --input INPUT [INPUT ...] 
                  [--output INPUT [INPUT ...]] [--viz] [ --no-save] 
                  [--mask-threshold MASK_THRESHOLD] [--scale SCALE]
           

可選參數:-h , --help 顯示此幫助消息并退出

  • --model FILE, -m FILE

    指定檔案在該模型被存儲(預設值:MODEL.pth)

  • --input INPUT [INPUT ...],-i INPUT [INPUT ...]

    的輸入圖像的檔案名(預設值:無)

  • --output INPUT [INPUT ...], -o INPUT [INPUT ...]

    輸出圖像的檔案名(預設值:無)--

    viz,-v 在處理圖像時可視化(預設值:False)

  • -- no -save, -n 不儲存輸出掩碼 (預設: False)
  • --mask-threshold MASK_THRESHOLD, -t MASK_THRESHOLD

    考慮掩碼像素 白色的最小機率值(預設: 0.5)

  • --scale SCALE, -s SCALE 比例因子對于輸入圖像(預設值:0.5)

3.2 調用示例

  • 要預測單個圖像并儲存它:
python predict.py -i image.jpg -o output.jpg
           
  • 要預測多個圖像并顯示它們而不儲存它們:
python predict.py -i image1.jpg image2.jpg --viz --no-save
           

3.3 融合預覽

為更加直覺地感受分割後得到的結果,下面采用圖像融合的方式進行預覽

(說明:其中img1為圖像原圖,img2為預測的二值圖像,image為兩者根據一定比例融合之後得到的結果。)

[煉丹術]UNet圖像分割模型相關總結
  • 下面一并附上圖像融合代碼
import cv2              
import numpy as np      
                        
src = "/home/linxu/下載下傳/flow_dataset/image/30.jpg"
mask = "/home/linxu/下載下傳/flow_dataset/output.png"
                        
# 使用opencv疊加圖檔          
img1 = cv2.imread(src)  
img2 = cv2.imread(mask) 
                        
alpha = 1               
meta = 0.4              
gamma = 0               
cv2.imshow('img1', img1)
cv2.imshow('img2', img2)
 image = cv2.addWeighted(img1,alpha,img2,meta,gamma)                     
 
 cv2.imshow('image', image)                                              
 cv2.waitKey(0)                                                          
           

參考:https://blog.csdn.net/jcfszxc/article/details/106289555

Talk is cheap. Show me the code