Tensorflow學習筆記No.11

VioletOrz發表於2020-12-12

影像定位

影像定位是指在影像中將我們需要識別的部分使用定位框進行定位標記,本次主要講述如何使用tensorflow2.0實現簡單的影像定位任務。

我所使用的定位方法是訓練神經網路使它輸出定位框的四個頂點的座標,通過這四個座標來定位需要識別物件的位置。

資料集:https://pan.baidu.com/s/1dv-r19KixYhA1CfX2n06Hg  提取碼:2kbc (資料集中的壓縮檔案需要解壓)

1.資料讀入

1.1圖片讀入

圖片的讀入在前面的部落格中已經展示過很多次了,這裡不再贅述,詳情可以參考Tensorflow學習筆記No.5裡面詳細介紹了讀取圖片的過程。

影像定位資料集的標籤與之前的分類任務不同,是一個xml檔案,我們需要使用爬蟲從檔案中爬取需要的資訊。

匯入需要的庫

1 import tensorflow as tf
2 import numpy as np
3 import matplotlib.pyplot as plt
4 from lxml import etree
5 import glob
6 %matplotlib inline
7 import pathlib
8 from matplotlib.patches import Rectangle

首先設定路徑

1 image_root =  pathlib.Path("E:/BaiduNetdiskDownload/圖片定位與分割資料集/images")
2 label_root = pathlib.Path('E:/BaiduNetdiskDownload/圖片定位與分割資料集/annotations/xmls')

通過.glob()方法獲得所有的圖片和標籤路徑,並轉換為字串的形式。

1 all_image_path = list(image_root.glob('*.jpg'))
2 all_label_path = list(label_root.glob('*.xml'))
3 
4 all_image_path = [str(p) for p in all_image_path]
5 all_label_path = [str(p) for p in all_label_path]

然後簡單展示一下我們的資料集是什麼樣子的,同時簡單講解一下如何使用爬蟲爬取需要的資訊。

隨便找一找圖片作為例子。

1 path = all_image_path[0]
2 path_ = all_label_path[0]
3 
4 #path E:\\BaiduNetdiskDownload\\圖片定位與分割資料集\\images\\Abyssinian_1.jpg
5 #path_ E:\\BaiduNetdiskDownload\\圖片定位與分割資料集\\annotations\\xmls\\Abyssinian_1.xml

首先解碼並輸出這張圖片(我使用的是jypyter notebook進行視覺化)

1 img = tf.io.read_file(path)
2 img = tf.image.decode_jpeg(img)
3 plt.imshow(img)

得到如下圖片:

1.2xml檔案解析與資料爬取

我們本次的影像定位任務是定位動物的頭部,也就是說我們得到的輸出結果是把動物的頭部框起來。

接下來對xml檔案進行解析,檔案內容如下:

檔案中是非常整齊的xml格式

<name>和</name>就相當於一對括號把其中的內容括起來,裡面的內容就屬於這個標籤之下。例如上圖中的annotation就是最大的標籤,裡面包含了folder、source等標籤(有點類似電腦裡的資料夾?)。

我們可以使用爬蟲來訪問這種整齊格式之中的內容

首先使用python自帶的open方法開啟這個xml檔案

1 xml = open(path_).read()

然後建立一個選擇器來對內容進行訪問

1 sel = etree.HTML(xml)

sel.xpath()方法可以訪問xml檔案中某個目錄下的內容,我們用這個方法獲得其中的文字資訊。

例如,我們可以獲得長寬資訊,width和height位於size標籤下,用text()訪問其中的文字內容,內容會以字串列表的形式返回。

由於只有一個長寬資訊,我們直接取列表的首位元素轉換成int型別即可。

1 width = int(sel.xpath('//size/width/text()')[0])
2 height = int(sel.xpath('//size/height/text()')[0])

同樣的我們獲取其他需要的資訊。

1 xmin = int(sel.xpath('//bndbox/xmin/text()')[0])
2 ymin = int(sel.xpath('//bndbox/ymin/text()')[0])
3 xmax = int(sel.xpath('//bndbox/xmax/text()')[0])
4 ymax = int(sel.xpath('//bndbox/ymax/text()')[0])

事實上我們只需要知道左上和右下的頂點座標即可確定一個矩形框,xmin,ymin代表左上角的座標,xmax,ymax代表右上角的座標。

我們把這個框展示在圖片中看一下效果

1 plt.imshow(img)
2 rect = Rectangle((xmin, ymin), (xmax - xmin), (ymax - ymin), fill = False, color = 'blue')
3 pimg = plt.gca()
4 pimg.axes.add_patch(rect)

得到如下所示圖片:

可以看到貓貓的頭部被框起來了(愛貓人士表示強烈譴責),這就是我們最終想要得到的效果。我們希望神經網路能夠識別出動物的頭像並把它框出來。

事實上我們的圖片大小各不相同,但神經網路的輸入尺寸是固定的,所有我們要把圖片和lable座標轉換到同一尺度上,即224×224。

方法如下,同時輸出效果圖:

 1 img = tf.image.resize(img, (256, 256))
 2 img = img / 255
 3 plt.imshow(img)
 4 
 5 xmin = xmin / width * 256
 6 xmax = xmax / width * 256
 7 ymin = ymin / height * 256
 8 ymax = ymax / height * 256
 9 
10 plt.imshow(img)
11 rect = Rectangle((xmin, ymin), (xmax - xmin), (ymax - ymin), fill = False, color = 'blue')
12 pimg = plt.gca()
13 pimg.axes.add_patch(rect)

1.3資料集構建

我們的資料集中並非每一張圖片都有對應的xml檔案,所以我們只用有label的資料作為訓練集和驗證集。(共3686張可訓練資料)

首先我們把標籤的檔名從路徑中分割出來,圖片與標籤名稱一致,通過這種方式來篩選出我們需要的圖片。

1 names = [x.split('\\')[-1].split('.xml')[0] for x in all_label_path]
2 train_image = [i for i in all_image_path if i.split('\\')[-1].split('.jpg')[0] in names]
3 train_image.sort(key=lambda x: x.split('\\')[-1].split('.jpg')[0])
4 all_label_path.sort(key=lambda x: x.split('\\')[-1].split('.xml')[0])

通過排序可以保證label和圖片一一對應

然後我們將之前爬取並處理資料尺寸的方法寫成函式

 1 def to_label(path):
 2     xml = open(r'{}'.format(path)).read()
 3     sel = etree.HTML(xml)
 4     width = int(sel.xpath('//size/width/text()')[0])
 5     height = int(sel.xpath('//size/height/text()')[0])
 6     xmin = int(sel.xpath('//bndbox/xmin/text()')[0])
 7     ymin = int(sel.xpath('//bndbox/ymin/text()')[0])
 8     xmax = int(sel.xpath('//bndbox/xmax/text()')[0])
 9     ymax = int(sel.xpath('//bndbox/ymax/text()')[0])
10     return [xmin / width, ymin / height, xmax / width, ymax / height]

我們用這個函式來處理資料的標籤部分,同時分為四部分,對應了兩個頂點的xy座標,也就是神經網路的四個輸出。

1 labels = [to_label(p) for p in all_label_path]
2 out1, out2, out3, out4 = list(zip(*labels))

下面進行亂序處理(這一步非常重要,否則模型訓練的效果非常差,我一開始訓練的時候擬合效果差就是因為沒亂序。。。嚶嚶嚶ε(┬┬﹏┬┬)3

1 index = np.random.permutation(len(train_image))
2 images = np.array(train_image)[index]
3 
4 out1 = np.array(out1)[index]
5 out2 = np.array(out2)[index]
6 out3 = np.array(out3)[index]
7 out4 = np.array(out4)[index]

使用index列表來保證亂序後圖片和標籤依然一一對應。

然後將其封裝為dataset型別的資料

1 label_data = tf.data.Dataset.from_tensor_slices((out1, out2, out3, out4))

下面就是對圖片的尺寸變換和封裝處理了

 1 def load_image(path):
 2     img = tf.io.read_file(path)
 3     img = tf.image.decode_jpeg(img, channels = 3)
 4     img = tf.image.resize(img, (224, 224))
 5     img = tf.cast(img, tf.float32)
 6     img = img / 127.5 - 1
 7     return img
 8 
 9 image_data = tf.data.Dataset.from_tensor_slices(images)
10 image_data = image_data.map(load_image)

完成後再將image_data和label_data合併成為一個dataset,然後分成訓練集和驗證集。

 1 dataset = tf.data.Dataset.zip((image_data, label_data))
 2 
 3 image_count = len(train_image)
 4 train_count = int(image_count * 0.8)
 5 test_count = image_count - train_count
 6 train_dataset = dataset.take(train_count)
 7 test_dataset = dataset.skip(train_count)
 8 
 9 BATCH_SIZE = 8
10 STEPS_PER_EPOCH = train_count // BATCH_SIZE
11 VALIDATION_STEPS = test_count // BATCH_SIZE
12 
13 train_dataset = train_dataset.shuffle(train_count).repeat().batch(BATCH_SIZE)
14 train_dataset = train_dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
15 test_dataset = test_dataset.batch(BATCH_SIZE)

資料集構建完畢,下一步就是模型的構建。

2.模型構建與訓練

不難發現這次的任務依然需要多輸出模型來完成。我們選用預訓練的Xception-Net的卷積部分作為卷積基來構建多輸出模型。

遷移學習請參考Tensorflow學習筆記No.8,多輸出模型請參考Tensorflow學習筆記No.10

模型如下:

 1 xception = tf.keras.applications.Xception(weights='imagenet', 
 2                                           include_top=False,
 3                                           input_shape=(224, 224, 3))
 4 
 5 xception.trianable = False
 6 
 7 inputs = tf.keras.layers.Input(shape=(224, 224, 3))
 8 
 9 x = xception(inputs)
10 
11 x = tf.keras.layers.GlobalAveragePooling2D()(x)
12 
13 x = tf.keras.layers.Dense(2048, activation='relu')(x)
14 x = tf.keras.layers.Dense(256, activation='relu')(x)
15 
16 out1 = tf.keras.layers.Dense(1)(x)
17 out2 = tf.keras.layers.Dense(1)(x)
18 out3 = tf.keras.layers.Dense(1)(x)
19 out4 = tf.keras.layers.Dense(1)(x)
20 
21 predictions = [out1, out2, out3, out4]
22 
23 model = tf.keras.models.Model(inputs=inputs, outputs=predictions)

由於輸出的是座標,是一個大於0的數字,所以輸出層可以直接去掉啟用函式。

隨後對模型進行訓練,損失函式選擇均方誤差MSE

 1 model.compile(optimizer = tf.keras.optimizers.Adam(lr = 0.0001),
 2               loss = 'mse',
 3               metrics = ['mae']
 4              )
 5 
 6 history = model.fit(train_dataset, 
 7                           epochs=10,
 8                           steps_per_epoch=STEPS_PER_EPOCH,
 9                           validation_steps=VALIDATION_STEPS,
10                           validation_data=test_dataset)

訓練結果如圖所示:

 1 loss = history.history['loss']
 2 val_loss = history.history['val_loss']
 3 
 4 epochs = range(10)
 5 
 6 plt.figure()
 7 plt.plot(epochs, loss, 'r', label='Training loss')
 8 plt.plot(epochs, val_loss, 'bo', label='Validation loss')
 9 plt.title('Training and Validation Loss')
10 plt.xlabel('Epoch')
11 plt.ylabel('Loss Value')
12 plt.ylim([0, 0.15])
13 plt.legend()
14 plt.show()

可以發現loss最初下降的很快然後逐漸減緩,最終的擬合效果也不錯。

然後我們找一組圖片試試模型效果。

 1 plt.figure(figsize = (8, 8))
 2 for img, _ in test_dataset.skip(1).take(1):
 3     out1, out2, out3, out4 = model.predict(img)
 4     for i in range(0, 6):
 5         plt.subplot(2, 3, i + 1)
 6         plt.imshow(tf.keras.preprocessing.image.array_to_img(img[i]))
 7         xmin, ymin, xmax, ymax = out1[i] * 224, out2[i] * 224, out3[i] * 224, out4[i] * 224,
 8         rect = Rectangle((xmin, ymin), (xmax - xmin), (ymax - ymin), fill = False, color = 'red')
 9         ax = plt.gca()
10         ax.axes.add_patch(rect)

效果還不錯,嘿嘿ヾ(≧▽≦*)o

本次對影像定位的介紹到這裡就結束了,Bey~ o(* ̄▽ ̄*)ブ

 

相關文章