1. 程式人生 > >Udacity Self-Driving資料集介紹

Udacity Self-Driving資料集介紹

前言

之前,博主為了得到更好的車載視訊目標檢測效果(偏工程實際,非刷榜),使用SSD框架訓練過KITTI資料集,幾次訓練下來,結果不太理想。自己分析,原因較多,其中很重要的一條就是KITTI資料集不夠大(標註圖片僅7000多張),而且還是fine-tune from reduced VGG model,精度自然不會太高。解決方法大約有兩種:一是擴充資料集;二是fine-tune已經訓練好的SSD model,這樣資料集小一點也能接受。這裡先介紹方法一,而方法二還在探索中。

PS.資料集標註貌似不如KITTI嚴謹,存在幾處關鍵錯誤,請慎重使用,已知的錯誤已更新在文中。

關於Udacity資料集

既然覺得資料偏少,一是可以增加類似的道路標註圖片,如果自己動手,可以參考之前博文中介紹的LabelImg工具;二是直接換一個更大的目標檢測資料集。這裡本文就介紹一下偶然發現的Udacity目標檢測資料集 ,這是Udacity為其自動駕駛演算法比賽專門準備的資料集,對連續視訊圖片進行了仔細的標註(2D座標),主要有汽車、行人、大型車輛等類別。

這裡寫圖片描述

整個資料集分為兩個子資料集,分別有9423和15000張圖片,解析度都是1920×1200,兩個子資料集的標註檔案稍有不同,不過接下來我會把他們合在一起,統一轉換成Pascal VOC的格式。算下來Udacity資料集圖片數量是KITTI的3倍多,在我看來訓練SSD是差不多夠用了。

製作資料集

首先要下載Dataset1Dataset2,一個是1.5GB,一個是3.3GB。如果常規方式下載太慢,建議使用EagleGet工具,用它下載國外連結就比較快了。

PS.找到了一個轉換工具,還沒測試過,感興趣的可以試試:code to convert between KITTI, KITTI tracking, Pascal VOC, Udacity, CrowdAI and AUTTI

壓縮圖片

訓練SSD300×300,原始圖片解析度可能偏高,個人感覺將圖片大小壓縮到原來的1/4,即960×600就比較合適。這裡找到看圖工具IrfanView,使用其批量轉換功能來調整解析度,得到新的同名壓縮圖片。

PS.這一步非必須,不做修改也是可用的!

生成標註txt檔案

兩個子資料集各有一個標註檔案,名字應該都叫labels.csv,這是純文字格式的檔案,我們使用的話,直接把副檔名改為txt,方便python程式讀取。由於標註資訊都寫在了一個檔案內,我們需要將其按圖片名分成若干個txt檔案,最後再轉化為xml檔案。

PS.程式寫的有點亂,具體路徑還望各位自行修改。還有,兩個標註檔案中的已知錯誤如下,目前找出了6行標註有誤,至於錯誤型別,相信看一眼就懂,然後可以直接修改或刪除該行:

# 出錯的標註語句
['912', '0', '951', '0', '1479498564477313399.jpg', 'car', 'http://crowdai.com/images/k-zz9yqpJIit7OuX/visualize']
['705', '0', '732', '0', '1479498820473341507.jpg', 'car', 'http://crowdai.com/images/Ng_nd_wBlqkgNDGb/visualize']
['721', '0', '751', '0', '1479499937073018706.jpg', 'car', 'http://crowdai.com/images/sbjD-93YWUi9hJ0c/visualize']
['763', '0', '793', '0', '1479505030914958665.jpg', 'pedestrian', 'http://crowdai.com/images/ONPwFZIwJ9yjwNQ1/visualize']

['1478020279199978858.jpg', '1614', '536', '1924', '668', '1', 'car']
['1478897138029169863.jpg', '1728', '570', '1922', '724', '1', 'car']
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Part 1

先看1.5GB資料集的標註資訊,複製幾行如下:

xmin,xmax,ymin,ymax,Frame,Label,Preview URL
785,533,905,644,1479498371963069978.jpg,Car,http://crowdai.com/images/Wwj-gorOCisE7uxA/visualize
89,551,291,680,1479498371963069978.jpg,Car,http://crowdai.com/images/Wwj-gorOCisE7uxA/visualize
268,546,383,650,1479498371963069978.jpg,Car,http://crowdai.com/images/Wwj-gorOCisE7uxA/visualize
455,522,548,615,1479498371963069978.jpg,Truck,http://crowdai.com/images/Wwj-gorOCisE7uxA/visualize
  
  • 1
  • 2
  • 3
  • 4
  • 5

可以看出,標註資訊相對簡單,分別是左上角和右下角座標、圖片名、類別(Car,Pedestrian,Truck)以及預覽連結(現在已經打不開了)。

下面使用轉換工具generate_label_1.py工具生成若干個txt標註檔案,每個檔案都和同名圖片對應。

# generate_label_1.py
# encoding:utf-8
file=open('/home/mx/tempfile/labels.txt','r') # 原始labels.txt的地址
for eachline in file:
    data=eachline.strip().split(',')
    filename=data[4]
    filename=filename[:-4]
    txt_path='/home/mx/tempfile/label_txt/'+filename+'.txt' # 生成的txt標註檔案地址
    txt=open(txt_path,'a')
    # new_line=data[5]+' '+data[0]+' '+data[1]+' '+data[2]+' '+data[3] 如使用原始圖片尺寸,該句取消註釋
    # new_line=data[5]+' '+str(int(data[0])/2)+' '+str(int(data[1])/2)+' '+str(int(data[2])/2)+' '+str(int(data[3])/2) 如使用1/4圖片尺寸,該句取消註釋
    txt.writelines(new_line)
    txt.write('\n')
    txt.close()
file.close()
print('generate label success')
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

執行程式,可在指定資料夾內生成若干個txt標註檔案,其內容如下:

# 1478019954685370994.txt
Truck 320 280 347 307
Car 331 288 354 312
Car 388 289 415 312
Car 746 236 915 304
  
  • 1
  • 2
  • 3
  • 4
  • 5

Part 2

再看3.3GB資料集的標註資訊,這個稍有不同,仍復制幾行如下:

1478019952686311006.jpg 950 574 1004 620 0 "car"
1478019952686311006.jpg 1748 482 1818 744 0 "pedestrian"
1478019953180167674.jpg 872 586 926 632 0 "car"
1478019953689774621.jpg 686 566 728 618 1 "truck"
1478019971185917857.jpg 822 468 846 506 0 "trafficLight" "Red"
1478019971686116476.jpg 546 516 568 550 0 "trafficLight" "RedLeft"
1478019971686116476.jpg 584 568 638 606 1 "biker"
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

標註資訊中的0和1代表是否被遮擋,此處用不上,且注意到出現了新的類別:訊號燈(trafficLight)和騎車人(biker)。這裡首先需要對labels.txt作出一定的修改:首先把所有雙引號去掉,然後使用替換功能,將car替換成Car,pedestrian換成Pedestrian,truck換成Truck,biker換成Cyclist(適應KITTI的風格),類別名首字母轉為大寫是為了保證整個資料集的統一,否則出錯。接下來使用generate_label_2.py 工具生成若干個txt標註檔案。

# generate_label_2.py
# encoding:utf-8
file=open('D:\\DataSet\\labels.txt','r') # 原始labels.txt的地址
for eachline in file:
    data=eachline.strip().split(' ')
    filename=data[0]
    filename=filename[:-4]
    txt_path='D:\\DataSet\\labels\\'+filename+'.txt' # 生成的txt標註檔案地址
    txt=open(txt_path,'a')
    if data[6]!='trafficLight': # 忽略訊號燈的標註資訊    
        new_line=data[6]+' '+str(int(data[1])/2)+' '+str(int(data[2])/2)+' '+str(int(data[3])/2)+' '+str(int(data[4])/2) # 使用了1/4圖片尺寸,座標均除以2
        txt.writelines(new_line)
        txt.write('\n')
        txt.close()
file.close()
print('generate label success')
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

去掉無標註的圖片

上一步生成了若干標註txt檔案,回頭卻發現和圖片數量對應不上:子資料集1圖片為9423,標註txt為9218;子資料集2圖片為15000,標註txt為13063。原因是圖片集是抽取視訊而來,存在一些圖片不含任何車輛行人目標,也就沒有標註資訊,而這些圖片在SSD訓練中是不能用的。因此需要用求補的方式,剔除無標註圖片,參考使用remove_no_label_image.py

# remove_no_label_image.py
# encoding:utf-8

import os

cur_dir='C:\\Users\\Jesse Mx\\Desktop'
txt_dir=os.path.join(cur_dir,'labels') # 標註txt資料夾地址
pic_dir=os.path.join(cur_dir,'image-half') # 圖片集資料夾地址
txtlist=[]
piclist=[]
for parent,dirnames,filenames in os.walk(txt_dir):
    for txt_name in filenames:
        txt_name=txt_name[:-4]
        txtlist.append(txt_name)
for parent,dirnames,filenames in os.walk(pic_dir):
    for pic_name in filenames:
        pic_name=pic_name[:-4]
        piclist.append(pic_name)
txt_set=set(txtlist)
pic_set=set(piclist)
comp=pic_set.difference(txt_set) # 求補集

print("ok")
print(len(comp)) # 無標註圖片數量

for item in comp:
    file=pic_dir+'\\'+item+'.jpg'
    if os.path.exists(file):
        os.remove(file)
        print(file)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

生成xml檔案

這一部分就需要參考我之前的博文了:SSD: Single Shot MultiBox Detector 訓練KITTI資料集(1),按照VOC格式,在/home/mx/data之下新建CITYdevkit/CITY目錄,然後CITY目錄中新建四個資料夾:JPEGImages,Annotations,ImageSets,Labels。把22281張圖片和22281個標註檔案分別放入,轉換xml的py檔案也放在旁邊備用,參考下圖。

這裡寫圖片描述

這裡轉換xml的工具程式碼需要稍作修改,一是修改類別,二是修改對應函式,避免讀取錯誤。修改後的txt_to_xml.py程式碼如下:

# txt_to_xml.py
# txt_to_xml.py
# encoding:utf-8
# 根據一個給定的XML Schema,使用DOM樹的形式從空白檔案生成一個XML
from xml.dom.minidom import Document
import cv2
import os

def generate_xml(name,split_lines,img_size,class_ind):
    doc = Document()  # 建立DOM文件物件

    annotation = doc.createElement('annotation')
    doc.appendChild(annotation)

    title = doc.createElement('folder')
    title_text = doc.createTextNode('CITY')
    title.appendChild(title_text)
    annotation.appendChild(title)

    img_name=name+'.jpg'

    title = doc.createElement('filename')
    title_text = doc.createTextNode(img_name)
    title.appendChild(title_text)
    annotation.appendChild(title)

    source = doc.createElement('source')
    annotation.appendChild(source)

    title = doc.createElement('database')
    title_text = doc.createTextNode('The CITY Database')
    title.appendChild(title_text)
    source.appendChild(title)

    title = doc.createElement('annotation')
    title_text = doc.createTextNode('CITY')
    title.appendChild(title_text)
    source.appendChild(title)

    size = doc.createElement('size')
    annotation.appendChild(size)

    title = doc.createElement('width')
    title_text = doc.createTextNode(str(img_size[1]))
    title.appendChild(title_text)
    size.appendChild(title)

    title = doc.createElement('height')
    title_text = doc.createTextNode(str(img_size[0]))
    title.appendChild(title_text)
    size.appendChild(title)

    title = doc.createElement('depth')
    title_text = doc.createTextNode(str(img_size[2]))
    title.appendChild(title_text)
    size.appendChild(title)

    for split_line in split_lines:
        line=split_line.strip().split()
        if line[0] in class_ind:
            object = doc.createElement('object')
            annotation.appendChild(object)

            title = doc.createElement('name')
            title_text = doc.createTextNode(line[0])
            title.appendChild(title_text)
            object.appendChild(title)

            bndbox = doc.createElement('bndbox')
            object.appendChild(bndbox)
            title = doc.createElement('xmin')
            title_text = doc.createTextNode(str(int(float(line[1]))))
            title.appendChild(title_text)
            bndbox.appendChild(title)
            title = doc.createElement('ymin')
            title_text = doc.createTextNode(str(int(float(line[2]))))
            title.appendChild(title_text)
            bndbox.appendChild(title)
            title = doc.createElement('xmax')
            title_text = doc.createTextNode(str(int(float(line[3]))))
            title.appendChild(title_text)
            bndbox.appendChild(title)
            title = doc.createElement('ymax')
            title_text = doc.createTextNode(str(int(float(line[4]))))
            title.appendChild(title_text)
            bndbox.appendChild(title)

    # 將DOM物件doc寫入檔案
    f = open('Annotations/'+name+'.xml','w')
    f.write(doc.toprettyxml(indent = ''))
    f.close()

if __name__ == '__main__':
    class_ind=('Pedestrian', 'Car', 'Truck','Cyclist')
    cur_dir=os.getcwd()
    labels_dir=os.path.join(cur_dir,'Labels')
    for parent, dirnames, filenames in os.walk(labels_dir): # 分別得到根目錄,子目錄和根目錄下檔案   
        for file_name in filenames:
            full_path=os.path.join(parent, file_name) # 獲取檔案全路徑
            f=open(full_path)
            split_lines = f.readlines()
            name= file_name[:-4] # 後四位是副檔名.txt,只取前面的檔名
            img_name=name+'.jpg' 
            img_path=os.path.join('/home/its/data/CITYdevkit/CITY/JPEGImages/',img_name) # 路徑需要自行修改            
            img_size=cv2.imread(img_path).shape
            generate_xml(name,split_lines,img_size,class_ind)
print('all txts has converted into xmls')

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

生成訓練集和測試集列表

現在完成最後一個關鍵步驟,生成trainval.txt,test.txt等列表檔案,均存放於ImageSets/Main資料夾下。所用工具為create_train_test_txt.py,注意,該檔案需要使用python3執行。

# create_train_test_txt.py
# create_train_test_txt.py
# encoding:utf-8
import pdb
import glob
import os
import random
import math

def get_sample_value(txt_name, category_name):
    label_path = './Labels/'
    txt_path = label_path + txt_name+'.txt'
    try:
        with open(txt_path) as r_tdf:
            if category_name in r_tdf.read():
                return ' 1'
            else:
                return '-1'
    except IOError as ioerr:
        print('File error:'+str(ioerr))

txt_list_path = glob.glob('./Labels/*.txt')
txt_list = []

for item in txt_list_path:
    temp1,temp2 = os.path.splitext(os.path.basename(item))
    txt_list.append(temp1)
txt_list.sort()
print(txt_list, end = '\n\n')

num_trainval = random.sample(txt_list, math.floor(len(txt_list)*9/10.0)) # 可修改百分比
num_trainval.sort()
print(num_trainval, end = '\n\n')

num_train = random.sample(num_trainval,math.floor(len(num_trainval)*8/9.0)) # 可修改百分比
num_train.sort()
print(num_train, end = '\n\n')

num_val = list(set(num_trainval).difference(set(num_train)))
num_val.sort()
print(num_val, end = '\n\n')

num_test = list(set(txt_list).difference(set(num_trainval)))
num_test.sort()
print(num_test, end = '\n\n')

pdb.set_trace()

Main_path = './ImageSets/Main/'
train_test_name = ['trainval','train','val','test']
category_name = ['Car','Pedestrian','Truck','Cyclist']

# 迴圈寫trainvl train val test
for item_train_test_name in train_test_name:
    list_name = 'num_'
    list_name += item_train_test_name
    train_test_txt_name = Main_path + item_train_test_name + '.txt' 
    try:
        # 寫單個檔案
        with open(train_test_txt_name, 'w') as w_tdf:
            # 一行一行寫
            for item in eval(list_name):
                w_tdf.write(item+'\n')
        # 迴圈寫Car Pedestrian Cyclist Truck
#        for item_category_name in category_name:
#            category_txt_name = Main_path + item_category_name + '_' + item_train_test_name + '.txt'
#            with open(category_txt_name, 'w') as w_tdf:
                # 一行一行寫
#                for item in eval(list_name):
#                    w_tdf.write(item+' '+ get_sample_value(item, item_category_name)+'\n')
    except IOError as ioerr:
        print('File error:'+str(ioerr))

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

接下來,博主將會使用該資料集進行訓練,至於效果如何,且看下回分解。

前言