天天看點

Few-shot Object Detection via Feature Reweighting跑自己的資料集說明

Few-shot Object Detection via Feature Reweighting跑自己的資料集

  • 說明
    • 配置環境

說明

    上篇文章記錄了我配置環境的過程,現在看來感覺有點啰嗦,是以打算寫一篇簡潔的配置環境的流程,這篇文章是few-shot learning運用于目标檢測的比較早的文章,最近貌似few-shot learning比較火,估計很多人都會以這篇文章作為切入點。

配置環境

很多人可能會用pycharm建立環境,但是在租用伺服器的時候有時候沒有圖形界面,比較麻煩,是以我寫一個黑框(終端)的比較通用(在pycharm的終端裡也可以使用)的版本,步驟如下:

(1)基礎電腦作業系統為ubantu(為了更好的操作,可以裝個ubantu,變為雙系統,如果條件允許,多分點記憶體給ubantu吧,不然後面記憶體不足太難受了),我目前隻在ubantu上配通。另外還需要裝個Anocaonda(去官網找對應ubantu的安裝包),常用的版本就行。租用伺服器的話一般都自帶有Anocaonda。我常租用的伺服器是 MistGPU,其他的沒怎麼用過。關于MistGPU的使用,我寫了一個教程: MistGPU雲伺服器的使用.

(2)下載下傳代碼: 代碼,并解壓。

(3)cd進入到主目錄下,或者在主目錄下打開終端。

(4)建立虛拟環境:

conda create -n xxx python=2.7   # xxx為你設定的虛拟環境的名字,假設為few
           

會提示下載下傳一些東西,輸入y,摁Enter。

(5)激活虛拟環境

conda activate few
           

(6)執行requirements.txt

pip install -r requirements.txt
           

    requirements.txt為代碼運作需要的一些包。

    如果顯示requirements.txt中opencv版本不比對,沒有3.4.2,我修改equirements.txt中opencv版本為3.4.2.16後,再執行pip install -r requirements.txt,沒有報錯,就是看你的報錯裡顯示支援哪個版本,選一個改一下再執行requirements.txt即可。

(7)執行requirements.txt成功後,配置pytorch和cuda,執行

pip install torch==1.4.0+cu92 torchvision==0.5.0+cu92 -f https://download.pytorch.org/whl/torch_stable.html
           

    記得驗證下cuda是否能用:import torch … 具體語句記不清了,網上都能查到的。

以上都沒有報錯的話,環境是沒多大問題了。接下來就是準備資料集,具體可以看上一篇文章連結: Few-shot Object Detection via Feature Reweighting論文學習以及複現,可能遇到的問題啥的基本都記錄了。後面有時間再寫下如何準備資料集吧,特别是對于新的資料集怎麼處理、怎們改使其符合代碼需要的格式。

(8)資料集格式

将整個代碼工程放在一個名為fewshot的檔案夾下,在裡面建立一個名為voc的檔案夾存放資料集。整個架構如下:

Few-shot Object Detection via Feature Reweighting跑自己的資料集說明

關于資料集類别的問題:

    代碼裡有關于voc、coco資料集的代碼,如果訓練這些資料集的話,可以直接選對應的參數既可,資料集的準備過程按照作者給的步驟操作就行,假如訓練新的資料集的話就可以用我的資料準備的方法,我也是模仿作者所給的步驟所得出的架構。

    訓練新的資料集,如果直接改類别,就要改一些代碼,而且還可能報錯,我嘗試了一下,報錯了,改起來貌似挺麻煩。其實可以這樣,把自己的類别與代碼裡的類别對應即可,就是用代碼裡的類别代替自己資料的類别,自己注意對應關系即可,訓練過程中使用的是class_id。

__C.voc_classes = ["aeroplane", "bicycle", "bird", "boat", "bottle",
           "bus", "car", "cat", "chair", "cow", "diningtable",
           "dog", "horse", "motorbike", "person", "pottedplant",
           "sheep", "sofa", "train", "tvmonitor"]
           

修改示例:

假如我有6個類,plane1、tank1、ship1、plane2、tank2、ship2,用voc class中的 [“aeroplane”, “bicycle”, “bird”, “boat”, “bottle”, “bus”]來表示即可(記得修改xml檔案對應),并修改标簽類别id分别為0、1、2、3、4、5、6(修改txt).

具體步驟:

(1).先把目标域和源域的資料集标簽格式轉為yolo格式:class_id x,y,w,h (網上代碼很多)

(2).把目标域和源域的資料集合起來,images和labels放在一起,資料集按類分出來,得到label_1c對應的檔案,代碼如下:

txtpath = r"path/labels"  // 定位到資料集标簽所在的檔案夾labels
txtnames = os.listdir(txtpath)
# 分類
for i in txtnames:
    with open(txtpath + '/' + i, "r") as f:
        l = []
        file = f.readlines()
        print(i)
        # flag = 0
        for line in file:
            line = line.strip("\n")
            dd = line.split()
            if dd[0] == '2':
                # flag = flag + 1
                l.append(dd)
        if len(l)>0:
            with open(r"xxx\labels_1c/xx/" + i, 'w') as q: // xx為類别名稱
                for j in l:
                    tem =j[0]+" "+j[1]+" "+j[2]+" "+j[3]+" "+j[4]
                    q.write(tem+'\n')
           

(3)制作xml檔案(所有圖檔的,如果自帶xml檔案,記得修改對應的類别,詳見修改示例),也可以重新生成txt,txt檔案轉xml,代碼如下:

from xml.dom.minidom import Document
import os
import cv2


# def makexml(txtPath, xmlPath, picPath):  # txt所在檔案夾路徑,xml檔案儲存路徑,圖檔所在檔案夾路徑
def makexml(picPath, txtPath, xmlPath):  # txt所在檔案夾路徑,xml檔案儲存路徑,圖檔所在檔案夾路徑
    """此函數用于将yolo格式txt标注檔案轉換為voc格式xml标注檔案
    """
    dic = {'0': "aeroplane",  # 建立字典用來對類型進行轉換
           '1': "bicycle",  # 此處的字典要與自己的classes.txt檔案中的類對應,且順序要一緻
           '2': "bird",
           '3': "boat",
           '4': "cow",
           '5': "bus",
           }
    files = os.listdir(txtPath)
    for i, name in enumerate(files):
        xmlBuilder = Document()
        annotation = xmlBuilder.createElement("annotation")  # 建立annotation标簽
        xmlBuilder.appendChild(annotation)
        txtFile = open(txtPath + name)
        txtList = txtFile.readlines()
        img = cv2.imread(picPath + name[0:-4] + ".tif")
        Pheight, Pwidth, Pdepth = img.shape

        folder = xmlBuilder.createElement("folder")  # folder标簽
        foldercontent = xmlBuilder.createTextNode("driving_annotation_dataset")
        folder.appendChild(foldercontent)
        annotation.appendChild(folder)  # folder标簽結束

        filename = xmlBuilder.createElement("filename")  # filename标簽
        filenamecontent = xmlBuilder.createTextNode(name[0:-4] + ".tif")
        filename.appendChild(filenamecontent)
        annotation.appendChild(filename)  # filename标簽結束

        size = xmlBuilder.createElement("size")  # size标簽
        width = xmlBuilder.createElement("width")  # size子标簽width
        widthcontent = xmlBuilder.createTextNode(str(Pwidth))
        width.appendChild(widthcontent)
        size.appendChild(width)  # size子标簽width結束

        height = xmlBuilder.createElement("height")  # size子标簽height
        heightcontent = xmlBuilder.createTextNode(str(Pheight))
        height.appendChild(heightcontent)
        size.appendChild(height)  # size子标簽height結束

        depth = xmlBuilder.createElement("depth")  # size子标簽depth
        depthcontent = xmlBuilder.createTextNode(str(Pdepth))
        depth.appendChild(depthcontent)
        size.appendChild(depth)  # size子标簽depth結束

        annotation.appendChild(size)  # size标簽結束

        for j in txtList:
            oneline = j.strip().split(" ")
            object = xmlBuilder.createElement("object")  # object 标簽
            picname = xmlBuilder.createElement("name")  # name标簽
            namecontent = xmlBuilder.createTextNode(dic[oneline[0]])
            picname.appendChild(namecontent)
            object.appendChild(picname)  # name标簽結束

            pose = xmlBuilder.createElement("pose")  # pose标簽
            posecontent = xmlBuilder.createTextNode("Unspecified")
            pose.appendChild(posecontent)
            object.appendChild(pose)  # pose标簽結束

            truncated = xmlBuilder.createElement("truncated")  # truncated标簽
            truncatedContent = xmlBuilder.createTextNode("0")
            truncated.appendChild(truncatedContent)
            object.appendChild(truncated)  # truncated标簽結束

            difficult = xmlBuilder.createElement("difficult")  # difficult标簽
            difficultcontent = xmlBuilder.createTextNode("0")
            difficult.appendChild(difficultcontent)
            object.appendChild(difficult)  # difficult标簽結束

            bndbox = xmlBuilder.createElement("bndbox")  # bndbox标簽
            xmin = xmlBuilder.createElement("xmin")  # xmin标簽
            mathData = int(((float(oneline[1])) * Pwidth + 1) - (float(oneline[3])) * 0.5 * Pwidth)
            xminContent = xmlBuilder.createTextNode(str(mathData))
            xmin.appendChild(xminContent)
            bndbox.appendChild(xmin)  # xmin标簽結束

            ymin = xmlBuilder.createElement("ymin")  # ymin标簽
            mathData = int(((float(oneline[2])) * Pheight + 1) - (float(oneline[4])) * 0.5 * Pheight)
            yminContent = xmlBuilder.createTextNode(str(mathData))
            ymin.appendChild(yminContent)
            bndbox.appendChild(ymin)  # ymin标簽結束

            xmax = xmlBuilder.createElement("xmax")  # xmax标簽
            mathData = int(((float(oneline[1])) * Pwidth + 1) + (float(oneline[3])) * 0.5 * Pwidth)
            xmaxContent = xmlBuilder.createTextNode(str(mathData))
            xmax.appendChild(xmaxContent)
            bndbox.appendChild(xmax)  # xmax标簽結束

            ymax = xmlBuilder.createElement("ymax")  # ymax标簽
            mathData = int(((float(oneline[2])) * Pheight + 1) + (float(oneline[4])) * 0.5 * Pheight)
            ymaxContent = xmlBuilder.createTextNode(str(mathData))
            ymax.appendChild(ymaxContent)
            bndbox.appendChild(ymax)  # ymax标簽結束

            object.appendChild(bndbox)  # bndbox标簽結束

            annotation.appendChild(object)  # object标簽結束

        f = open(xmlPath + name[0:-4] + ".xml", 'w')
        xmlBuilder.writexml(f, indent='\t', newl='\n', addindent='\t', encoding='utf-8')
        f.close()


if __name__ == "__main__":
    picPath = r"D:\BaiduNetdiskDownload\fewshot\xView_split_clearE1\train\test_image/"  # 圖檔所在檔案夾路徑,後面的/一定要帶上
    txtPath = r"D:\BaiduNetdiskDownload\fewshot\xView_split_clearE1\train\test_txt/"  # txt所在檔案夾路徑,後面的/一定要帶上
    xmlPath = r"D:\BaiduNetdiskDownload\fewshot\xView_split_clearE1\train\1/"  # xml檔案儲存路徑,後面的/一定要帶上
    makexml(picPath, txtPath, xmlPath)
           

自己修改對應的路徑即可。

(4)分訓練資料trian.txt(隻含有源域資料圖檔)和測試資料test.txt(源域、目标域)(測試資料,控制每個類的數量盡量均衡),分好後,擷取訓練資料圖檔的路徑,制作train.txt,擷取測試圖檔的路徑,制作test.txt,代碼如下:

# 統計每個類别數目
# cc = 0
# c1,c2,c3 = 0,0,0
# for i in range(len(txtnames)):
#     cc =cc+1
#     with open(txtpath + '/' + txtnames[i], "r") as f:
#         file = f.readlines()
#         print(file)
#         for line in file:
#             line = line.strip("\n")
#             dd = line.split()
#             if dd[0] == '0':
#                 c1 = c1+1
#             elif dd[0] == '1':
#                 c2 = c2 + 1
#             elif dd[0] == '2':
#                 c3 = c3 + 1
import os
p = r"xxx\testtxt"
paths = r"xxx\images"
f = open(r"xxx/"+'名字.txt', 'w')
filenames = os.listdir(p)
imanames =  os.listdir(paths)
# filenames.sort()
for imaname in imanames:
    tem = imaname.replace("tif","txt")
    if tem not in filenames:
        out_path = "xxx/images/" + imaname
    # out_path = filename.replace(".txt","")
    # if filename[0] == "P":
    #     out_path = "xxx/images/" + filename.replace(".txt",".png")
    # else:
    #     out_path = "xxx/images/"+filename.replace(".txt", ".tif")
        print(out_path)
        f.write(out_path + '\n')
f.close()
           

代碼不難,看懂改改就行。

(5)制作各類别的train.txt,即cls_train下的檔案

Few-shot Object Detection via Feature Reweighting跑自己的資料集說明

代碼思路:

(2)中得到了按類的所有圖檔(train、test)的txt,按類别(檔案夾)讀取類别對應的圖檔的檔案名(replace替換,字尾得到圖檔名稱.jpg/png…)假設為listA,然後擷取test.txt中的圖檔名稱,假設為listB,在A中去除B,然後路徑+圖檔名稱 儲存為 類别_train.txt。(過程略有複雜…)

————————————

以上,第一階段就可以訓練了:

參數檔案metayolo.data配置:

metayolo=1
metain_type=2
data=voc
neg = 1
rand = 0
novel = data/voc_novels.txt           
novelid = 0                            
scale = 1
meta = data/voc_traindict_full.txt 
train = path/train.txt
valid = path/test.txt
backup = backup/metayolo
gpus=1,2,3,4
           

其中:

data = voc 代表類别為voc類别,如果上面按照我所說的替換類别對應,這裡就不用改(voc類别對應一些資料參數,如代碼中的scale參數,暫時不考慮改,預設,在調參數階段可以根據資料集進行調整)。

voc_novels.txt 為新類别名稱 novelid = 0 為 新類别的位置,例如voc_novels.txt ,novelid = 1:

bird,bus,cow,motorbike,sofa
aeroplane,bottle,cow,horse,sofa
boat,cat,motorbike,sheep,sofa
bicycle,bird,motorbike,train,tvmonitor
aeroplane,bird,bus,cat,person
car,diningtable,motorbike,pottedplant,tvmonitor
           

則新類别(目标域)為第二行對應的類别:aeroplane,bottle,cow,horse,sofa

meta = data/voc_traindict_full.txt

voc_traindict_full.txt檔案的内容為:(類别_train.txt的路徑),例如:

aeroplane /home/bykang/voc/voclist/aeroplane_train.txt
bicycle /home/bykang/voc/voclist/bicycle_train.txt
bird /home/bykang/voc/voclist/bird_train.txt
boat /home/bykang/voc/voclist/boat_train.txt
bottle /home/bykang/voc/voclist/bottle_train.txt
bus /home/bykang/voc/voclist/bus_train.txt
car /home/bykang/voc/voclist/car_train.txt
           

(6)制作測試需要的檔案,即:

Few-shot Object Detection via Feature Reweighting跑自己的資料集說明

在相應的目錄下建立上圖對應檔案夾,這裡注意名稱大小寫 VOCdevkit VOC2007 ImageSets Main,VOC2007下的Annotations直接整個複制已經有的Annotations,Main中test.txt為測試圖檔的名稱,不帶路徑和字尾。

測試第一階段:

在darknet_meta.py中的 def meta_forward(self, metax, mask):函數中

語句

for model in self.learnet_models:

     metax = model(metax)

改為:

for model in self.learnet_models:

     with torch.no_grad():

          metax = model(metax)

訓練的時候記得把 with torch.no_grad():去了哈

執行

python valid_ensemble.py cfg/metayolo.data cfg/darknet_dynamic.cfg cfg/reweighting_net.cfg path/toweightfile

執行完,得到測試的txt,在results檔案下,執行

python scripts/voc_eval.py results/path/to/comp4_det_test_

需要修改:

繼續閱讀