緣起:
我現在每個禮拜一都會跟老師用 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 ,這幾天應該就會在上面測試了。
沒有留言:
張貼留言