TensorFlow 2 實(shí)戰(zhàn)之從零開始構(gòu)建 YOLOv3 目標(biāo)檢測(cè)網(wǎng)絡(luò)


引言
今天為大家?guī)?lái)社區(qū)作者的精選推薦《Tensorfow 2 實(shí)戰(zhàn)之從零開始構(gòu)建 YOLOv3 目標(biāo)檢測(cè)網(wǎng)絡(luò)》。想知道如何正確使用 Tensorflow 2 實(shí)現(xiàn) YOLOv3 算法?CSDN 認(rèn)證博客專家 @ZONG_XP 為你帶來(lái)專業(yè)講解,手把手教你搭建目標(biāo)檢測(cè)網(wǎng)絡(luò)!



網(wǎng)上雖然有很多利用 TensorFlow 實(shí)現(xiàn) YOLOv3 的代碼和文章,但感覺講解得還不夠透徹,對(duì)于新手而言,存在一定的理解難度。本文目的是為了從零開始構(gòu)建 YOLOv3 目標(biāo)檢測(cè)網(wǎng)絡(luò),對(duì)網(wǎng)絡(luò)和代碼細(xì)節(jié)做詳細(xì)地介紹,使大家對(duì) TensorFlow 的使用方法有一個(gè)基本的掌握。?
本文主要包含以下四個(gè)方面的內(nèi)容:
為什么選擇學(xué)習(xí) TensorFlow,以及如何安裝 TensorFlow?
為什么選擇 YOLOv3 目標(biāo)檢測(cè)網(wǎng)絡(luò)?
深度學(xué)習(xí)前向推理的整個(gè)流程是什么樣的?
對(duì) YOLOv3 網(wǎng)絡(luò)構(gòu)建部分進(jìn)行詳細(xì)的代碼介紹。
1 Why TensorFlow ?
1.1 TensorFlow 特點(diǎn)
在開發(fā)語(yǔ)言方面,它支持 Python 開發(fā)、C++ 開發(fā)、JavaScript 開發(fā)、Swift 開發(fā); 在模型開發(fā)方面,支持?jǐn)?shù)據(jù)輸入預(yù)處理、模型構(gòu)建、模型訓(xùn)練、模型導(dǎo)出全流程; 在模型部署方面,支持在移動(dòng)設(shè)備、嵌入式設(shè)備、服務(wù)端多種方式部署; 在硬件平臺(tái)方面,支持在 CPU、GPU、TPU、RPI 等不同硬件上運(yùn)行; 在開發(fā)工具方面,支持 TensorBoard 訓(xùn)練可視化、TensorFlow Hub 豐富的模型庫(kù)

基于 TensorFlow 豐富的生態(tài)特點(diǎn),我將 TensorFlow 作為開發(fā)的第一框架,便于自己快速在生產(chǎn)環(huán)境中部署深度學(xué)習(xí)模型。
1.2 TensorFlow 安裝
在官方安裝教程中,介紹了兩種安裝方法,分別是 pip 軟件包管理器以及 docker 容器兩種方式,同時(shí)還提供了在 Google Colab 上調(diào)試的方法(需要穩(wěn)定的網(wǎng)絡(luò))。
官方安裝教程
https://tensorflow.google.cn/installGoogle Colab
https://colab.research.google.com/notebooks/welcome.ipynb
docker 可以有效的對(duì)環(huán)境進(jìn)行隔離,容器中已經(jīng)配置好了相關(guān)環(huán)境,可以免去很多手動(dòng)安裝操作,但對(duì)于 docker 操作不熟悉的同學(xué)來(lái)說(shuō),使用起來(lái)不太方便。
為了避免環(huán)境之間的影響,這里我推薦使用 conda 來(lái)創(chuàng)建虛擬環(huán)境。本文不介紹 conda 的安裝及使用方法,沒(méi)有安裝的同學(xué)可以參考相關(guān)教程。
創(chuàng)建 conda 環(huán)境,安裝GPU 版本的 TensorFlow?
conda create -n tensorflow-gpu-2 python=3.7
conda activate tensorflow-gpu-2
pip install tensorflow-gpu
經(jīng)過(guò)以上步驟,會(huì)在你的系統(tǒng)中安裝好最新版本的 TensorFlow 。
2 Why YOLOv3?
YOLOv3 作為 one-stage 目標(biāo)檢測(cè)算法典型代表,在工業(yè)界應(yīng)用非常廣泛,尤其是對(duì)于實(shí)時(shí)性要求較高的場(chǎng)合,YOLOv3 及其各種變體基本上是第一選擇。雖然現(xiàn)在已經(jīng)有 YOLOv4 和 YOLOv5 更先進(jìn)的算法,但也都是在 YOLOv3 的基礎(chǔ)上改進(jìn)而來(lái),因此有必要先掌握 YOLOv3 再學(xué)習(xí) YOLOv4 和 YOLOv5。
YOLOv3 原論文寫得還是很“隨意”的,很多技術(shù)細(xì)節(jié)沒(méi)有講,但這并不妨礙大家對(duì)他的肯定,關(guān)于對(duì) YOLOv3 的解讀,推薦兩篇文章,分別是《深入淺出Yolo系列之 Yolov3 & Yolov4 & Yolov5 核心基礎(chǔ)知識(shí)完整講解》以及《你一定從未看過(guò)如此通俗易懂的 YOLO 系列(從 V1 到 V5 )模型解讀!》。
深入淺出Yolo系列之 Yolov3 & Yolov4 & Yolov5 核心基礎(chǔ)知識(shí)完整講解
https://zhuanlan.zhihu.com/p/143747206
YOLOv3 整體結(jié)構(gòu)如圖所示,可以看到尺寸為 416 x 416 的輸入圖片進(jìn)入 Darknet-53 網(wǎng)絡(luò)后得到了 3 個(gè)分支,這些分支在經(jīng)過(guò)一系列的卷積、上采樣以及合并等操作后得到三個(gè)尺寸不一的 feature map,形狀分別為 [13, 13, 255]、[26, 26, 255] 和 [52, 52, 255],對(duì)這三個(gè)尺度的 feature map 經(jīng)過(guò)一系列的后處理,即可得到輸入圖像的目標(biāo)檢測(cè)框。

采用 darknet53 作為特征提取網(wǎng)絡(luò); 多尺度預(yù)測(cè),輸出三個(gè)分支,分別是 8 倍、16 倍、32 倍下采樣,分別實(shí)現(xiàn)對(duì)小目標(biāo)、中目標(biāo)、大目標(biāo)的檢測(cè); 每個(gè)尺度使用三種寬高比的 anchor 進(jìn)行預(yù)測(cè),所以總共包含 9 個(gè) anchor; 網(wǎng)絡(luò)的三個(gè)基本組件,分別是 DBL、res unit、rexn,具體細(xì)節(jié)參考上圖; 沒(méi)有池化層和全連接層,通過(guò)改變卷積核的步長(zhǎng)來(lái)調(diào)整張量的尺寸;
通過(guò)上邊的歸納,相信你對(duì) YOLOv3 的算法原理有了基本的了解,接下來(lái)重點(diǎn)介紹代碼實(shí)現(xiàn)部分。
3 Show me code
3.1 總體流程
定義模型輸入 定義模型輸出 加載權(quán)重文件 準(zhǔn)備輸入數(shù)據(jù) 模型前向推理 輸出后處理 結(jié)果可視化
總體代碼如下(代碼是在 YunYang1994/TensorFlow2.0-Examples (github.com/YunYang1994/TensorFlow2.0-Examples)?的基礎(chǔ)上修改),本質(zhì)上深度學(xué)習(xí)模型推理都是類似的流程,只不過(guò)不同的算法在網(wǎng)絡(luò)實(shí)現(xiàn)部分會(huì)有差異。
# 1、定義模型輸入
input_size = 416
input_layer = tf.keras.layers.Input([input_size, input_size, 3])
# 2、定義模型輸出
# 獲得三種尺度的卷積輸出
# 具體實(shí)現(xiàn)見 YOLOv3 函數(shù)說(shuō)明
feature_maps = YOLOv3(input_layer)
bbox_tensors = []
# 依次遍歷小、中、大尺寸的特征圖
for i, fm in enumerate(feature_maps):
# 對(duì)每個(gè)分支的通道信息進(jìn)行解碼,得到預(yù)測(cè)框的大小、置信度和類別概率
# 具體操作見 decode 函數(shù)說(shuō)明
bbox_tensor = decode(fm, i)
bbox_tensors.append(bbox_tensor)
# 3 加載權(quán)重文件
# 根據(jù)上邊定義好的輸入輸出,實(shí)例化模型
model = tf.keras.Model(input_layer, bbox_tensors)
# 加載權(quán)重文件
utils.load_weights(model, "./yolov3.weights")
# 輸出模型信息
model.summary()
# 4、準(zhǔn)備輸入數(shù)據(jù)
image_path = "./docs/kite.jpg"
original_image = cv2.imread(image_path)
original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)
original_image_size = original_image.shape[:2]
image_data = utils.image_preporcess(np.copy(original_image), [input_size, input_size])
image_data = image_data[np.newaxis, ...].astype(np.float32)
# 5、模型前向推理
pred_bbox = model.predict(image_data)
pred_bbox = [tf.reshape(x, (-1, tf.shape(x)[-1])) for x in pred_bbox]
pred_bbox = tf.concat(pred_bbox, axis=0)
# 6、輸出后處理,
bboxes = utils.postprocess_boxes(pred_bbox, original_image_size, input_size, 0.3)
bboxes = utils.nms(bboxes, 0.45, method='nms')
# 7、結(jié)果可視化
image = utils.draw_bbox(original_image, bboxes)
image = Image.fromarray(image)
image.save("result.jpg")
# image.show()
? ??
其中模型定義、數(shù)據(jù)后處理是關(guān)鍵,這里對(duì)其具體的實(shí)現(xiàn)進(jìn)行介紹。
3.2 網(wǎng)絡(luò)結(jié)構(gòu)
代碼中的 common.convolutional() 和 common.upsample() 是實(shí)現(xiàn)卷積層和上采樣操作,其定義在工程中?(TensorFlow2.0-Examples)?common.py 文件中,這里不再進(jìn)一步展開。?
def YOLOv3(input_layer):
# 輸入層進(jìn)入 Darknet-53 網(wǎng)絡(luò)后,得到了三個(gè)分支
route_1, route_2, conv = backbone.darknet53(input_layer)
# 見上圖中的橘黃色模塊(DBL),一共需要進(jìn)行5次卷積操作
conv = common.convolutional(conv, (1, 1, 1024, 512))
conv = common.convolutional(conv, (3, 3, 512, 1024))
conv = common.convolutional(conv, (1, 1, 1024, 512))
conv = common.convolutional(conv, (3, 3, 512, 1024))
conv = common.convolutional(conv, (1, 1, 1024, 512))
conv_lobj_branch = common.convolutional(conv, (3, 3, 512, 1024))
# conv_lbbox 用于預(yù)測(cè)大尺寸物體,shape = [None, 13, 13, 255]
conv_lbbox = common.convolutional(conv_lobj_branch, (1, 1, 1024, 3*(NUM_CLASS + 5)),
activate=False, bn=False)
conv = common.convolutional(conv, (1, 1, 512, 256))
# 這里的 upsample 使用的是最近鄰插值方法,這樣的好處在于上采樣過(guò)程不需要學(xué)習(xí),從而減少了網(wǎng)絡(luò)參數(shù)
conv = common.upsample(conv)
conv = tf.concat([conv, route_2], axis=-1)
conv = common.convolutional(conv, (1, 1, 768, 256))
conv = common.convolutional(conv, (3, 3, 256, 512))
conv = common.convolutional(conv, (1, 1, 512, 256))
conv = common.convolutional(conv, (3, 3, 256, 512))
conv = common.convolutional(conv, (1, 1, 512, 256))
conv_mobj_branch = common.convolutional(conv, (3, 3, 256, 512))
# conv_mbbox 用于預(yù)測(cè)中等尺寸物體,shape = [None, 26, 26, 255]
conv_mbbox = common.convolutional(conv_mobj_branch, (1, 1, 512, 3*(NUM_CLASS + 5)),
activate=False, bn=False)
conv = common.convolutional(conv, (1, 1, 256, 128))
conv = common.upsample(conv)
conv = tf.concat([conv, route_1], axis=-1)
conv = common.convolutional(conv, (1, 1, 384, 128))
conv = common.convolutional(conv, (3, 3, 128, 256))
conv = common.convolutional(conv, (1, 1, 256, 128))conv = common.convolutional(conv, (3, 3, 128, 256))
conv = common.convolutional(conv, (1, 1, 256, 128))
conv_sobj_branch = common.convolutional(conv, (3, 3, 128, 256))
# conv_sbbox 用于預(yù)測(cè)小尺寸物體,shape = [None, 52, 52, 255]
conv_sbbox = common.convolutional(conv_sobj_branch, (1, 1, 256, 3*(NUM_CLASS +5)),
activate=False, bn=False)
return [conv_sbbox, conv_mbbox, conv_lbbox]
3.3 darknet53
darknet53 網(wǎng)絡(luò)結(jié)構(gòu)如圖所示,主要是由卷積層、殘差模塊組成。

利用 common.residual_block() 實(shí)現(xiàn)殘差連接。
def darknet53(input_data):
input_data = common.convolutional(input_data, (3, 3, 3, 32))
input_data = common.convolutional(input_data, (3, 3, 32, 64), downsample=True)
for i in range(1):
input_data = common.residual_block(input_data, 64, 32, 64)
input_data = common.convolutional(input_data, (3, 3, 64, 128), downsample=True)
for i in range(2):
input_data = common.residual_block(input_data, 128, 64, 128)
input_data = common.convolutional(input_data, (3, 3, 128, 256), downsample=True)
for i in range(8):
input_data = common.residual_block(input_data, 256, 128, 256)
route_1 = input_data
input_data = common.convolutional(input_data, (3, 3, 256, 512), downsample=True)
for i in range(8):
input_data = common.residual_block(input_data, 512, 256, 512)
route_2 = input_data
input_data = common.convolutional(input_data, (3, 3, 512, 1024), downsample=True)
for i in range(4):
input_data = common.residual_block(input_data, 1024, 512, 1024)
return route_1, route_2, input_data
3.4 decode 處理
YOLOv3 網(wǎng)絡(luò)的三個(gè)分支輸出會(huì)被送入 decode 模塊對(duì) feature map 的通道信息進(jìn)行解碼,輸出的是預(yù)測(cè)框在原圖上的 [x, y, w, h, score, prob]。
def decode(conv_output, i=0):
"""
return tensor of shape [batch_size, output_size, output_size, anchor_per_scale, 5 + num_classes]
contains (x, y, w, h, score, probability)
"""
conv_shape = tf.shape(conv_output)
batch_size = conv_shape[0]
output_size = conv_shape[1]
# 對(duì) tensor 進(jìn)行 reshape
conv_output = tf.reshape(conv_output, (batch_size, output_size, output_size, 3, 5 + NUM_CLASS))
# 按順序提取[x, y, w, h, c]
conv_raw_dxdy = conv_output[:, :, :, :, 0:2] # 中心位置的偏移量
conv_raw_dwdh = conv_output[:, :, :, :, 2:4] # 預(yù)測(cè)框長(zhǎng)寬的偏移量
conv_raw_conf = conv_output[:, :, :, :, 4:5] # 預(yù)測(cè)框的置信度
conv_raw_prob = conv_output[:, :, :, :, 5: ] # 預(yù)測(cè)框的類別概率
# 好了,接下來(lái)是畫網(wǎng)格。其中,output_size 等于 13、26 或者 52
y = tf.tile(tf.range(output_size, dtype=tf.int32)[:, tf.newaxis], [1, output_size])
x = tf.tile(tf.range(output_size, dtype=tf.int32)[tf.newaxis, :], [output_size, 1])
xy_grid = tf.concat([x[:, :, tf.newaxis], y[:, :, tf.newaxis]], axis=-1)
xy_grid = tf.tile(xy_grid[tf.newaxis, :, :, tf.newaxis, :], [batch_size, 1, 1, 3, 1])
xy_grid = tf.cast(xy_grid, tf.float32) # 計(jì)算網(wǎng)格左上角的位置,即cx cy的值
# 根據(jù)上圖公式計(jì)算預(yù)測(cè)框的中心位置
# 這里的 i=0、1 或者 2, 以分別對(duì)應(yīng)三種網(wǎng)格尺度
pred_xy = (tf.sigmoid(conv_raw_dxdy) + xy_grid) * STRIDES[i] # 計(jì)算預(yù)測(cè)框在原圖尺寸上的x y
pred_wh = (tf.exp(conv_raw_dwdh) * ANCHORS[i]) * STRIDES[i] # 計(jì)算預(yù)測(cè)框在原圖尺寸上的w h
pred_xywh = tf.concat([pred_xy, pred_wh], axis=-1) # 拼接起來(lái)
pred_conf = tf.sigmoid(conv_raw_conf) # 計(jì)算預(yù)測(cè)框里object的置信度
pred_prob = tf.sigmoid(conv_raw_prob) # 計(jì)算預(yù)測(cè)框里object的類別概率
return tf.concat([pred_xywh, pred_conf, pred_prob], axis=-1)
3.5 后處理
從網(wǎng)絡(luò)結(jié)構(gòu)拿到檢測(cè)框之后,就需要對(duì)框進(jìn)行后處理了,包括根據(jù)閾值去掉得分低的檢測(cè)框、NMS過(guò)濾掉多余的檢測(cè)框等,然后再把框在原圖上繪制出來(lái),就完成了整個(gè)檢測(cè)流程。
4 Summary
通過(guò)本文的介紹,相信大家對(duì) TensorFlow 的特點(diǎn)以及實(shí)現(xiàn)目標(biāo)檢測(cè)算法的流程有了基本的了解,在后續(xù)的文章中,我會(huì)進(jìn)一步的介紹如何訓(xùn)練 YOLOv3 網(wǎng)絡(luò),以及如何在生產(chǎn)環(huán)境中部署 YOLOv3 網(wǎng)絡(luò),敬請(qǐng)期待!
最后
本文由 TensorFlow 社區(qū)作者創(chuàng)作,文章已入選 “TensorFlow 開發(fā)者出道計(jì)劃” 精選推薦,并由 TensorFlow CSDN 社區(qū)進(jìn)行收錄。

—推薦閱讀—
谷歌提出Meta Pseudo Labels,刷新ImageNet上的SOTA!
漲點(diǎn)神器FixRes:兩次超越ImageNet數(shù)據(jù)集上的SOTA
SWA:讓你的目標(biāo)檢測(cè)模型無(wú)痛漲點(diǎn)1% AP
CondInst:性能和速度均超越Mask RCNN的實(shí)例分割模型
mmdetection最小復(fù)刻版(十一):概率Anchor分配機(jī)制PAA深入分析
MMDetection新版本V2.7發(fā)布,支持DETR,還有YOLOV4在路上!
無(wú)需tricks,知識(shí)蒸餾提升ResNet50在ImageNet上準(zhǔn)確度至80%+
不妨試試MoCo,來(lái)替換ImageNet上pretrain模型!
mmdetection最小復(fù)刻版(七):anchor-base和anchor-free差異分析
mmdetection最小復(fù)刻版(四):獨(dú)家yolo轉(zhuǎn)化內(nèi)幕
機(jī)器學(xué)習(xí)算法工程師
? ??? ? ? ? ? ? ? ? ? ? ? ??????? ??一個(gè)用心的公眾號(hào)
?

