搜尋此網誌

2020年4月7日 星期二

pytorch object detection

緣起:


    我現在每個禮拜一都會跟老師用 room 報告這個禮拜學 pytorch 的情況,好讓老師了解我的進度。這個禮拜一呢,我碰上了一點事情,無法參與討論,所以我想說之後以書面報告的形式交給老師。後來想到,我好像也可以寫到 Blog 上,當個記錄。




參考資源:


TORCHVISION OBJECT DETECTION FINETUNING TUTORIAL


    目前只理解它訓練用的資料要怎麼處理,至於怎麼建造訓練網路的部份,我是看不懂,所以只能把它的 code 整個 co 下來,試跑看看,不過跑的時候一直出錯,少東少西的,所以我之後可能會換在 Linux 上面跑。


Defining the Datase:


    要給模型訓練的資料,你要先有圖片集,還有,每張圖片都要有個對應的 "mask" 圖片,用來框出要辨識的物件。

    那個網站上有提供一個資料夾叫PennFudanPed,裡面有我說的那些訓練用的圖片,它的檔案結構如下。


    我各拿 PedMasks 跟 PNGImages 裡的第一張圖片來看


    PNGImages 裡的圖片很容易看懂,就是景物+人


    可是,PedMasks裡圖片就都很奇怪,全部都是黑壓壓的一片,我一開始很不懂它是要幹什麼的。我後來試了試用 PhotoCap  打開,用一些工具看了看,有看出點端倪。它裡面像素的RGB值並不是完全相同的,有 (0, 0, 0) 、 (1, 1, 1)  跟 (2, 2, 2),我們把 (0, 0, 0) 的部份去掉,就得到下面這張圖片。


    哦,原來,(0, 0, 0) 是代表背景,(1, 1, 1) 跟 (2, 2, 2) 是用來分別不同的實體。

    再來是關於資料集,程式的部份怎麼實作。我們要建立一個 class ,那個 class 要繼承 torch.utils.data.Dataset ,並且要實作 __getitem__ 跟 __len__ 方法,其中又以 __getitem__ 為重。__getitem__ 方法要回傳一個 image 物件還有 target 物件,那個 image 物件是屬於 PIL 這個函式庫的東西,而 target 是一個字典。

    target 字典包含下面這些東西

boxes (FloatTensor[N, 4])

    那個 N 似乎是測試用圖片裡的人物總數,4 的話就是每個人物的外框的四個座標。

labels (Int64Tensor[N])

    每張圖片裡的實例都會有個標籤,同樣的數字代表它們是同個東西,0 是代表背景。

image_id (Int64Tensor[1])

    圖片的 id ,每張圖片不能重覆,訓練時會用到。

area (Tensor[N])

    不太清楚是做什麼用的,好像跟一種叫 COCO matrix 的東西有關。

iscrowd (UInt8Tensor[N])

    被標示為 True 的物件會在訓練時被忽略,當背景(吧。

 masks (UInt8Tensor[N, H, W])

    這東西可加可不加,看起來是用來記錄圖片裡的每個人物的長跟寛。

keypoints (FloatTensor[N, K, 3])

    這東西也是可加可不加,在它的範列裡,它也沒被加進去,所以我就沒特別看這個東西在做什麼了。


Dataset 程式碼部分:


    他網站有題供 Dataset 的完整範例,我有試著理解它的程式邏輯,所以,我就把我的理解寫成註解,與它的程式碼作對應

import os
import numpy as np
import torch 
from PIL import Image #處理圖片的類別


class PennFudanDataset(object):
    #我不知道它為什麼要繼承object,可能是寫錯了吧
    def __init__(self, root, transforms):
        self.root = root
        #那個 root 就是PennFudanPed資料夾的位置
        self.transforms = transforms
        #transforms我不知道是在幹啥,我的猜測是,它會對圖片做變型
        #然後那些變型的資料又可以拿來訓練一次神經網路
        self.imgs = list(sorted(os.listdir(os.path.join(root, "PNGImages"))))
        self.masks = list(sorted(os.listdir(os.path.join(root, "PedMasks"))))
        #把PNGImages跟PedMasks裡的所有圖片的檔名各存成一個list,並做排序,
        #好一一對應,一張圖片對應一個mask圖片
        
    def __getitem__(self, idx):
        img_path = os.path.join(self.root, "PNGImages", self.imgs[idx])
        mask_path = os.path.join(self.root, "PedMasks", self.masks[idx])
        #圖片與mask圖片的路徑
        img = Image.open(img_path).convert("RGB")
        #開啟彩色圖片部份
        mask = Image.open(mask_path)
        #mask圖片不需轉成RGB,因為每個顏色都對應到一個實例,0是背景
        mask = np.array(mask)
        #把mask圖片轉為 numpy
        obj_ids = np.unique(mask)
        #把重覆的值去掉,並做排序,假如那個mask圖片有兩個人,那麼
        #那個obj_ids就會變成 [0, 1, 2]
        obj_ids = obj_ids[1:]
        #0是背景,我們不需要,所以去掉

        masks = mask == obj_ids[:, None, None]
        #這東西有點難裡解,它最後會把圖片裡的實體拆開來,各別存成
        #一個二維陣列,實例在的位置會是True,其它地方則是False
        
        num_objs = len(obj_ids)
        boxes = []
        for i in range(num_objs):
        #這邊是得到每個實例的邊界
            pos = np.where(masks[i])
            xmin = np.min(pos[1])
            xmax = np.max(pos[1])
            ymin = np.min(pos[0])
            ymax = np.max(pos[0])
            boxes.append([xmin, ymin, xmax, ymax])
            #四個座標存到boxes裡

        boxes = torch.as_tensor(boxes, dtype=torch.float32)
        #把numpy轉成tensor
        labels = torch.ones((num_objs,), dtype=torch.int64)
        #我們的實例就只有人,所以都是 1
        masks = torch.as_tensor(masks, dtype=torch.uint8)
        #把numpy轉成tensor
        
        image_id = torch.tensor([idx])
        #id部份
        area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
        #每個實例的長跟寛
        iscrowd = torch.zeros((num_objs,), dtype=torch.int64)
        #每個實例都不是crowd
        target = {}
        target["boxes"] = boxes
        target["labels"] = labels
        target["masks"] = masks
        target["image_id"] = image_id
        target["area"] = area
        target["iscrowd"] = iscrowd
        #把所有的東西都存到字典裡

        if self.transforms is not None:
        #我不知道這在幹啥
            img, target = self.transforms(img, target)

        return img, target

    def __len__(self):
    #圖片數量
        return len(self.imgs)


測試部份:


    它網頁上還有付完整的程式碼,可惜的是,我在測試時一直出錯,最主要好像是少了一個叫 pycocotools 的東西,這東西好像在以前只有支援 Linux 。雖然有人有釋出 Windows 的版本,可是,我照著它的方法下載了之後,還是沒有成功,所以,我之後打算用 Linux 跑看看。

    我昨天有在實驗室的電腦上安裝 Virtual Box ,並且成功安裝 Ubuntu ,這幾天應該就會在上面測試了。

沒有留言:

張貼留言