kitti數據集轉換成voc數據集格式(rpn訓練三)

前言

  近來冠狀病毒肆虐,還是好好響應國家號召待在家裏。閒來無事就搞一下用於faster-rcnn訓練用的數據集。這篇博客詳細地註釋了將kitti數據集轉換成pascal voc格式的代碼,結合網上各個前輩的博客,記錄了自己動手實驗的過程。轉換完成後的標籤以及圖片,將用於訓練faster-rcnn關於車輛檢測識別的模型。

第一部分:數據集的準備

1. 下載kitti數據集
  參考博主micro wen的博客,可以通過kitti數據集的介紹進行詳細瞭解數據集的形式。通過進入原網址下載或者Kitti數據集下載百度雲下載data_object_image_2.zip和data_object_label_2.zip。
  下載後得到兩個文件:

  •    原始圖片集:data_object_image_2.zip,解壓後得到png格式的訓練集和測試集對應元素圖片;
  •   標籤集爲:data_object_label_2.zip,解壓後得到爲txt格式的訓練集對應標籤文件,包含諸如下列形式的內容:
Car 0.00 0 -1.75 685.77 178.12 767.02 235.21 1.50 1.62 3.89 3.27 1.67 21.18 -1.60

  每一行就是一個object,第一個即爲類別信息,後面是bounding box信息,具體可以查看相關博客

2. pascal voc2007介紹
  這裏只簡單介紹一下,詳細可移步這裏。VOC2007包含3個文件夾:

  • JPEGImages ==》》 用來存放所有的原始圖片,格式爲JPG。

  • ImageSets ==》》(包含多個子文件夾,目標檢測中只用到Main文件夾)

    Main —>存放一些txt文件,用來標明訓練時候的train數據集、val數據集和test數據集

    Layout

    Segmentation

  • Annotation ==》》 存放一些xml文件,xml文件中包含相對應的bounding box位置信息,以及類別,每個xml文件對應JPEGImages文件夾中的一張圖片。內容如下:
<?xml version="1.0"?>

-<annotation>

<folder>VOC2007</folder>

<filename>000001.jpg</filename>


-<source>

<database>The VOC2007 Database</database>

<annotation>PASCAL VOC2007</annotation>

<image>flickr</image>

<flickrid>341012865</flickrid>

</source>


-<owner>

<flickrid>Fried Camels</flickrid>

<name>Jinky the Fruit Bat</name>

</owner>


-<size>

<width>353</width>

<height>500</height>

<depth>3</depth>

</size>

<segmented>0</segmented>


-<object>

<name>dog</name>

<pose>Left</pose>

<truncated>1</truncated>

<difficult>0</difficult>


-<bndbox>

<xmin>48</xmin>

<ymin>240</ymin>

<xmax>195</xmax>

<ymax>371</ymax>

</bndbox>

</object>


-<object>

<name>person</name>

<pose>Left</pose>

<truncated>1</truncated>

<difficult>0</difficult>


-<bndbox>

<xmin>8</xmin>

<ymin>12</ymin>

<xmax>352</xmax>

<ymax>498</ymax>

</bndbox>

</object>

</annotation>

3. 轉換成JPG格式
  因爲voc是JPG格式的,還沒拿去訓練不知道PNG格式的會不會有影響,所以先轉換成JPG格式以防萬一。採用格式工廠批量處理,一次只能999張圖片,所以要先劃分出各個文件夾方便處理。PS也可以通過動作和錄製功能批處理,不過效率有點慢就沒采用。
4. 創建VOC2007文件夾
VOC2007文件目錄
  在JPEGImages放入轉換完成的train圖片的JPG圖片(有博客說直接將下載的PNG放入,但有test和train兩個文件夾,查看原voc2007文件夾,發現裏面只有train的圖片,於是只放入轉換完成的trainJPG圖片)。
  在ImageSet文件夾裏創建如下3個文件夾。
ImagSets

第二部分:轉換數據集

1. 介紹
  這部分主要還是參考micro wen的博客。之前看的時候實驗做起來很簡單,不過還是要認真看一下代碼。看代碼時候就很多不懂,所以更新了代碼備註,希望能幫到有需要的人。
   由於“DontCare”,“Misc”,“Cyclist”三個類別在圖片中太小且標註信息也不準確,在轉換的時候需要將其忽略。根據之前的博客還將“Person_sitting”和“Pedestrian”合併爲一個類別,統一標記爲“Pedestrian”。採用python語言進行轉換。
  創建的python文件我是放在和label同一級目錄下,不過把代碼裏的絕對路徑寫好應該也可以放在別的地方。
總需要文件
2. 轉換kitti數據集類別
創建python文件modify_kitti_type.py

# -*-coding:utf-8-*-

import glob
import string

# glob: return document/file path
# 存儲Labels文件夾所有txt文件路徑,Win下修改路徑要注意反斜槓用法,不要用成\
txt_list = glob.glob('E:/111/rpntrain-data/data_object_label_2/training/label_2/*.txt')

# 輸出所有文件中的物體類別
def show_category(txt_list):
   category_list = []
   for item in txt_list:
       try:
           with open(item) as tdf:
               for each_line in tdf:
                   labeldata = each_line.strip().split(' ')  # 去掉前後多餘的字符並把其分開
                   category_list.append(labeldata[0])  # 只要第一個字段,即類別
       except IOError as ioerr:
           print('File error:' + str(ioerr))
   print(set(category_list))  # 輸出集合


# 這裏是將每個文件的每行寫入line中
def merge(line):
   # 列表推導式(又稱列表解析式)提供了一種簡明扼要的方法來創建列表,
   # 它是利用其創建新列表list的一個簡單方法。列表推導式比較像for循環語句,
   # 必要時也可以加入if條件語句完善推導式。
   each_line = ''
   # add space to last word in each line
   for i in range(len(line)):
       if i != (len(line) - 1):
           each_line = each_line + line[i] + ' '
       else:
           each_line = each_line + line[i]  # 最後一條字段後面不加空格
   each_line = each_line + '\n'
   return (each_line)


# print('before modify categories are:\n')
print('在修改類別之前:\n')
# 顯示原有的類別
show_category(txt_list)

# item爲當前操作的文件,
for item in txt_list:   
   new_txt = []
   try:
       # with as是控制流語句,可以處理異常而不用多寫代碼
       # 對item進行讀取
       with open(item, 'r') as r_tdf:
           for each_line in r_tdf:
               # 實際上strip是刪除的意思;而split則是分割的意思。因此也表示了這兩個功能是完全不一樣的,
               # strip可以刪除字符串的某些字符,而split則是根據規定的字符將字符串進行分割。
               labeldata = each_line.strip().split(' ')

               '''if labeldata[0] in ['Truck','Van','Tram','Car']: # 合併汽車類  
                   labeldata[0] = labeldata[0].replace(labeldata[0],'car')  
               if labeldata[0] in ['Person_sitting','Cyclist','Pedestrian']: # 合併行人類  
                   labeldata[0] = labeldata[0].replace(labeldata[0],'pedestrian')'''
               # print type(labeldata[4])
               # Pedestrian 0.00 0 -0.20 712.40 143.00 810.73 307.92 1.89 0.48 1.20 1.84 1.47 8.41 0.01
               # 沒有研究label裏數字具體的意思,直接使用前人的代碼
               if labeldata[4] == '0.00':
                   labeldata[4] = labeldata[4].replace(labeldata[4], '1.00')
               if labeldata[5] == '0.00':
                   labeldata[5] = labeldata[5].replace(labeldata[5], '1.00')
               # 如果當前行0位置爲truck則進行替換,使用小寫怕訓練時候出錯
               if labeldata[0] == 'Truck':
                   labeldata[0] = labeldata[0].replace(labeldata[0], 'truck')
               if labeldata[0] == 'Van':
                   labeldata[0] = labeldata[0].replace(labeldata[0], 'van')
               if labeldata[0] == 'Tram':
                   labeldata[0] = labeldata[0].replace(labeldata[0], 'tram')
               if labeldata[0] == 'Car':
                   labeldata[0] = labeldata[0].replace(labeldata[0], 'car')
               if labeldata[0] in ['Person_sitting', 'Pedestrian']:  # 合併行人類
                   labeldata[0] = labeldata[0].replace(labeldata[0], 'pedestrian')
               if labeldata[0] == 'Cyclist':
                   continue
               if labeldata[0] == 'DontCare':  # 忽略Dontcare類
                   continue
               if labeldata[0] == 'Misc':  # 忽略Misc類
                   continue
               new_txt.append(merge(labeldata))  # 重新寫入新的txt文件
       with open(item, 'w+') as w_tdf:  # w+是打開原文件將內容刪除,另寫新內容進去
           for temp in new_txt:
               w_tdf.write(temp)
   except IOError as ioerr:
       print('File error:' + str(ioerr))

# 顯示處理後的種類,這裏已經完成了對label的修改
print('\nafter modify categories are:\n')
show_category(txt_list)

3. 轉換標註信息格式:txt到xml
  處理完原始的txt文件後,接下來需要將標籤集的txt標註文件轉換成xml文件。去掉標註信息中用不上的部分,把座標值從float類型轉化爲int類型,最後代碼會將生成的xml文件存放到VOC數據集的Annotations文件夾中。創建python文件txt_to_xml.py

#-*-coding:utf-8-*-
# 根據一個給定的XML Schema,使用DOM樹的形式從空白文件生成一個XML
from xml.dom.minidom import Document
# python3.7下安裝cv2,本機不能直接pip安裝,搜索安裝包進行安裝
import cv2
import os

# 傳入序號,
def generate_xml(name,split_lines,img_size,class_ind):
   doc = Document()  # 創建DOM文檔對象
   # 問題:創建document對象,如果存在則會把文件價名加在裏面的文件前面?
   # 上述問題是在寫路徑時候Annotations沒加反斜槓
   annotation = doc.createElement('annotation')
   # appendChild方法的規定就是向節點添加最後一個子節點。
   doc.appendChild(annotation)

   # 測試輸出文件位置問題
   # print("document start")

   # 這裏開始寫入XML,可具體看每個xml的內容
   title = doc.createElement('folder')
   title_text = doc.createTextNode('VOC2007')#這裏修改了文件夾名
   # appendChild:添加子節點
   title.appendChild(title_text)
   annotation.appendChild(title)

   # img_name=name+'.png'#要用jpg格式
   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 VOC2007 Database')#修改爲VOC
   title.appendChild(title_text)
   source.appendChild(title)

   title = doc.createElement('annotation')
   title_text = doc.createTextNode('PASCAL VOC2007')#修改爲VOC
   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)

           title = doc.createElement('difficult')
           title_text = doc.createTextNode('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[4]))))
           title.appendChild(title_text)
           bndbox.appendChild(title)
           title = doc.createElement('ymin')
           title_text = doc.createTextNode(str(int(float(line[5]))))
           title.appendChild(title_text)
           bndbox.appendChild(title)
           title = doc.createElement('xmax')
           title_text = doc.createTextNode(str(int(float(line[6]))))
           title.appendChild(title_text)
           bndbox.appendChild(title)
           title = doc.createElement('ymax')
           title_text = doc.createTextNode(str(int(float(line[7]))))
           title.appendChild(title_text)
           bndbox.appendChild(title)

   # 將DOM對象doc寫入文件
   f = open('E:/111/rpntrain-data/VOC2007/Annotations/' + name + '.xml', 'w')
   f.write(doc.toprettyxml(indent = ''))
   f.close()

if __name__ == '__main__':
   class_ind=('van', 'tram', 'car', 'pedestrian', 'truck')# 修改爲了5類
   # cur_dir=os.getcwd()
   # 這個路徑是label的上一層,不加反斜槓
   labels_dir = os.path.join("E:/111/rpntrain-data/data_object_label_2/training", 'label_2')
   # os.walk方法,主要用來遍歷一個目錄內各個子目錄和子文件
   for parent, dirnames, filenames in os.walk(labels_dir): # 分別得到根目錄,子目錄和根目錄下文件
       # 對於根目錄下的文件,先取得路徑得到前面的文件序號,再生成新的xml
       for file_name in filenames:
           full_path=os.path.join(parent, file_name) # 獲取文件全路徑
           #print full_path
           f=open(full_path)
           # 讀取所有行
           split_lines = f.readlines()
           name= file_name[:-4] # 後四位是擴展名.txt,只取前面的文件名
           #print name
           img_name = name + '.jpg'
           # 將要訓練的圖片路徑添加入img_path中
           img_path = os.path.join('E:/111/rpntrain-data/VOC2007/JPEGImage/trianing', img_name)
           #print img_path
           img_size=cv2.imread(img_path).shape
           # 文件序號,每一行,圖片大小,目標類別
           generate_xml(name,split_lines,img_size,class_ind)
print('all txts has converted into xmls')

4. 生成訓練驗證集和測試集列表
  創建python文件generate_train_test_txt.py

#-*-coding:utf-8-*-
# 生成訓練驗證集和測試集列表

import pdb
import glob
import os
import random
import math

# 這個函數是傳入要比較的標籤類別和查詢原圖是否有這個標籤
def get_sample_value(txt_name, category_name):
   # kitti數據集的label位置
   label_path = 'E:/111/rpntrain-data/data_object_label_2/training/label_2/'
   # 建立路徑,用於記錄哪個訓練/測試用的數據集搭配哪個種類的文件名
   txt_path = label_path + txt_name+'.txt'
   try:
       with open(txt_path) as r_tdf:
           # 如果當前的這個種類在label裏有則返回1,用於上層記錄是否歸屬該類別
           if category_name in r_tdf.read():
               return ' 1'
           else:
               return '-1'
   except IOError as ioerr:
       print('File error:'+str(ioerr))

# glob文件搜索,找到符合這個路徑的路徑
# 也有博客有這個寫法:txt_list_path = glob.glob('./Labels/*.txt')
txt_list_path = glob.glob('E:/111/rpntrain-data/data_object_label_2/training/label_2/*.txt')
# 用於下文記錄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')

# 有博客建議train:val:test=8:1:1,先嚐試用一下
# 思路是將trainval分得9份,8份給train,1份給val。
# val用於訓練時候驗證集,也就是訓練結束時得到的準確率。和測試集不一樣

# random.sample多用於截取列表的指定長度的隨機數,但是不會改變列表本身的排序
# math.floor是得到小於或者等於參數的最大整數
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')

# 這裏用set().difference()得到和剛纔得到trian數據集不一樣的部分,也就是9份裏的剩下1份
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()

# 自己創建VOC裏imagesets的Main_path文件夾路徑
Main_path = 'E:/111/rpntrain-data/VOC2007/ImageSets/Main/'
# 數據集種類,用於區分訓練和測試用
train_test_name = ['trainval','train','val','test']
# 數據集裏的各個類別
category_name = ['van', 'tram', 'car', 'pedestrian', 'truck']#修改類別

# 循環寫trainvl train val test
for item_train_test_name in train_test_name:
   list_name = 'num_'
   # 這個num_加上數據集種類,可以得到上文8:1:1裏的各個數據集名稱
   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:
           # 一行一行寫,eval是讀取這個list的內容,也就是數據集種類裏的照片名
           # 最終能得到四類:trainval.txt之類的四個
           for item in eval(list_name):
               w_tdf.write(item+'\n')
       # 循環寫Car Pedestrian Cyclist
       for item_category_name in category_name:
           # 得到:路徑+car+_+trainval+.txt的文件部分,
           # 也就是數據類別在當前數據集的分類數據判斷,如00001 1爲含有該類00002 -1爲不含該類
           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))

  生成的txt文件如下:
生成的txt
   至此訓練車聯網需要的數據集就準備好了。因爲還不能回學校,所以進行訓練的過程只能等以後了。

參考鏈接

https://blog.csdn.net/xw_2_xh/article/details/86617595
https://blog.csdn.net/flztiii/article/details/73881954
https://blog.csdn.net/u014256231/article/details/79801665

發佈了6 篇原創文章 · 獲贊 4 · 訪問量 2095
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章