摘要:本文作者介紹了結(jié)合的WebRTC與TensorFlow實現(xiàn)圖像檢測的具體過程,不論對于TensorFlow的使用者,還是的WebRTC的開發(fā)者來講都有參考意義由于文章較長,我們將分為上下篇進行連載。
TensorFlow是目前最流行的機器學(xué)習(xí)框架之一。TensorFlow的一大優(yōu)勢是,它的很多庫都有人積極進行維護和更新。而我最喜歡的其中一個庫就是TensorFlow 對象檢測API .Tensorflow對象檢測API可以對一張圖形上的多個對象進行分類,并提供它們的具體位置。該API在將近1000個對象類上進行了預(yù)先訓(xùn)練,可提供各種經(jīng)過預(yù)先訓(xùn)練的模型,讓你可以在速度與準(zhǔn)確性之間權(quán)衡取舍。
有這些模型的指引固然很好,但所有這些模型都要使用圖像才能發(fā)揮作用,而這些圖像則需要你自行添加到一個文件夾中。我其實很想將其與實時的的WebRTC流配合到一起,通過網(wǎng)絡(luò)實現(xiàn)實時的計算機視覺。由于未能找到這方面的任何例子或指南,我決定寫這篇博文來介紹具體的實現(xiàn)方法。對于使用RTC的人,可以將本文作為一篇快速指南加以參考,了解如何使用TensorFlow來處理的WebRTC流。對于使用TensorFlow的人士,則可以將本文作為一份快速簡介,了解如何向自己的項目中添加的WebRTC。使用的WebRTC的人需要對Python的比較熟悉。而使用TensorFlow的人則需要熟悉網(wǎng)絡(luò)交互和一些JavaScript的。
本文不適合作為WebRTC或TensorFlow的入門指南使用。如需這樣的指南,應(yīng)參考TensorFlow 入門指南,WebRTC 入門指南等,網(wǎng)上的相關(guān)介紹與指南數(shù)不勝數(shù)。
利用Tensor Flow和WebRTC檢測貓咪
直接告訴我如何實現(xiàn)吧
如果你來這里只是為了快速找到一些參考信息,或者懶得讀詳細的文字介紹,按照下面的方法即可快速著手。首先安裝Docker 。加載一個命令提示窗口,接著鍵入下面的命令:
然后在瀏覽器地址欄中鍵入并轉(zhuǎn)到http:// localhost:5000 / local ,接受攝像頭權(quán)限請求,你應(yīng)該會看到類似下面的界面:
基本架構(gòu)
我們首先建立一個基本架構(gòu),用于在本地將一個本地網(wǎng)絡(luò)攝像頭流從WebRTC的getUserMedia 發(fā)送到一個Python服務(wù)器,這要用到Flask 網(wǎng)絡(luò)服務(wù)器和TensorFlow對象檢測API(Object Detection API)。具體的設(shè)置大致如下圖所示。
為搭配使用的WebRTC與TensorFlow對象檢測API而建立的基本架構(gòu)
Flask將提供html和JavaScript文件供瀏覽器呈現(xiàn)。getUserMedia.js負責(zé)抓取本地視頻流。接下來,objDetect.js將使用HTTP POST 方法向TensorFlow對象檢測API發(fā)送圖像,該API則返回它所有看到的對象(它稱之為“類”)及對象在圖像中的位置。我們會將這些詳細信息封裝到一個JSON對象中,然后將該對象發(fā)回給objDetect.js,這樣我們就能將我們所看到的對象的方框和標(biāo)簽顯示出來。
配置
設(shè)置和前提條件
在開始之前,我們需要先對Tensorflow和對象檢測API進行一些設(shè)置。
使用泊塢輕松完成設(shè)置
我在OSX,Windows 10和Raspbian已經(jīng)設(shè)置過好幾次(過程可不簡單)。各種版本依賴關(guān)系錯綜復(fù)雜,把這些關(guān)系理順并非易事,特別是當(dāng)你只是想看看一些前期工作是否行得通時,你可能會感到氣餒。我推薦使用泊塢來避免這些棘手問題。你將需要學(xué)習(xí)泊塢,這也是非學(xué)不可的東西,與其試著構(gòu)建合適的的Protobuf 版本,倒不如花些時間學(xué)習(xí)它來得更為高效。TensorFlow項目維護了一些官方的Docker映像,比如tensorflow / tensorflow 。
如果你使用泊塢窗,我們就可以使用我為這篇博文創(chuàng)建的映像在命令行中,請運行以下命令:
請注意,在Windows 10命令行中,請使用%cd%。
?吹竭@里,你應(yīng)該已經(jīng)進入了碼頭工人容器現(xiàn)在,請運行:
這樣,就會使用最新的TensorFlow Docker映像,并將Docker主機上的端口5000連接到端口5000,將容器命名為tf-webrtchacks,將一個本地目錄映射到容器中的一個新/ code目錄,將該目錄設(shè)為默認(rèn)目錄(我們接下來將在該目錄中操作),然后運行的bash以便進行命令行交互。完成這些準(zhǔn)備工作后,我們才能開始。
如果你才剛開始接觸TensorFlow,可能需要先按照噸ensorflow / tensorflow 中的說明運行初始Jupyter筆記本,然后再回來執(zhí)行上述命令。
另一種麻煩的實現(xiàn)方法
如果你打算從頭開始,則需要安裝TensorFlow,它自己有很多依賴項,比如Python.TensorFlow項目針對各種平臺都提供了指南,具體請訪問https://www.tensorflow.org/install 。對象檢測API有也。自己的安裝說明,以及一些額外的依賴項完成這些準(zhǔn)備工作后,請運行下面的命令:
這樣,就應(yīng)該安裝好了所有的Python的依賴項,將相應(yīng)的Tensorflow對象檢測API文件都復(fù)制了過來,并安裝了Protobufs。如果這一步行不通,我建議檢查setup.py,然后手動在其中運行命令,以解決存在的任何問題。
第1部分 - 確保Tensorflow正常工作
為確保TensorFlow對象檢測API正常工作,我們首先從用于演示對象檢測的官方版本JupyterNotebook經(jīng)調(diào)整后的版本著手。我將此文件保存為object_detection_tutorial.py 。
如果你剪切并粘貼該筆記本的每個部分,得到的結(jié)果應(yīng)如下所示:(由于此段代碼較長,截圖會影響閱讀,我們更換為文字排版,左右拖動可查看長代碼)
# IMPORTS
import numpy as np
import os
import six.moves.urllib as urllib
import sys
import tarfile
import tensorflow as tf
import zipfile
from collections import defaultdict
from io import StringIO
# from matplotlib import pyplot as plt ### CWH
from PIL import Image
if tf.__version__ != '1.4.0':
raise ImportError('Please upgrade your tensorflow installation to v1.4.0!')
# ENV SETUP ### CWH: remove matplot display and manually add paths to references
'''
# This is needed to display the images.
%matplotlib inline
# This is needed since the notebook is stored in the object_detection folder.
sys.path.append("")
'''
# Object detection imports
from object_detection.utils import label_map_util ### CWH: Add object_detection path
#from object_detection.utils import visualization_utils as vis_util ### CWH: used for visualization
# Model Preparation
# What model to download.
MODEL_NAME = 'ssd_mobilenet_v1_coco_2017_11_17'
MODEL_FILE = MODEL_NAME + '.tar.gz'
DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/'
# Path to frozen detection graph. This is the actual model that is used for the object detection.
PATH_TO_CKPT = MODEL_NAME + '/frozen_inference_graph.pb'
# List of the strings that is used to add correct label for each box.
PATH_TO_LABELS = os.path.join('object_detection/data', 'mscoco_label_map.pbtxt') ### CWH: Add object_detection path
NUM_CLASSES = 90
# Download Model
opener = urllib.request.URLopener()
opener.retrieve(DOWNLOAD_BASE + MODEL_FILE, MODEL_FILE)
tar_file = tarfile.open(MODEL_FILE)
for file in tar_file.getmembers():
file_name = os.path.basename(file.name)
if 'frozen_inference_graph.pb' in file_name:
tar_file.extract(file, os.getcwd())
# Load a (frozen) Tensorflow model into memory.
detection_graph = tf.Graph()
with detection_graph.as_default():
od_graph_def = tf.GraphDef()
with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid:
serialized_graph = fid.read()
od_graph_def.ParseFromString(serialized_graph)
tf.import_graph_def(od_graph_def, name='')
# Loading label map
label_map = label_map_util.load_labelmap(PATH_TO_LABELS)
categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=NUM_CLASSES, use_display_name=True)
category_index = label_map_util.create_category_index(categories)
# Helper code
def load_image_into_numpy_array(image):
。╥m_width, im_height) = image.size
return np.array(image.getdata()).reshape(
(im_height, im_width, 3)).astype(np.uint8)
# Detection
# For the sake of simplicity we will use only 2 images:
# image1.jpg
# image2.jpg
# If you want to test the code with your images, just add path to the images to the TEST_IMAGE_PATHS.
PATH_TO_TEST_IMAGES_DIR = 'object_detection/test_images' #cwh
TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1, 3) ]
# Size, in inches, of the output images.
IMAGE_SIZE = (12, 8)
with detection_graph.as_default():
with tf.Session(graph=detection_graph) as sess:
# Definite input and output Tensors for detection_graph
image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')
# Each box represents a part of the image where a particular object was detected.
detection_boxes = detection_graph.get_tensor_by_name('detection_boxes:0')
# Each score represent how level of confidence for each of the objects.
# Score is shown on the result image, together with the class label.
detection_scores = detection_graph.get_tensor_by_name('detection_scores:0')
detection_classes = detection_graph.get_tensor_by_name('detection_classes:0')
num_detections = detection_graph.get_tensor_by_name('num_detections:0')
for image_path in TEST_IMAGE_PATHS:
image = Image.open(image_path)
# the array based representation of the image will be used later in order to prepare the
# result image with boxes and labels on it.
image_np = load_image_into_numpy_array(image)
# Expand dimensions since the model expects images to have shape: [1, None, None, 3]
image_np_expanded = np.expand_dims(image_np, axis=0)
# Actual detection.
。╞oxes, scores, classes, num) = sess.run(
[detection_boxes, detection_scores, detection_classes, num_detections],
feed_dict={image_tensor: image_np_expanded})
### CWH: below is used for visualizing with Matplot
'''
# Visualization of the results of a detection.
vis_util.visualize_boxes_and_labels_on_image_array(
image_np,
np.squeeze(boxes),
np.squeeze(classes).astype(np.int32),
np.squeeze(scores),
category_index,
use_normalized_coordinates=True,
line_thickness=8)
plt.figure(figsize=IMAGE_SIZE)
plt.imshow(image_np)
'''
在這里我就不再贅述實際TensorFlow代碼的作用了,這方面的信息可在Jupyter _ECSHOP演示及其他教程中找到。我將重點介紹我們對代碼所做的修改。
我注釋了幾個小節(jié):
更改了一些位置引用
刪除了對Python matplot的所有引用。Python matplot用于在GUI環(huán)境中以可視化方式呈現(xiàn)輸出結(jié)果。在我的Docker環(huán)境中沒有設(shè)置它 - 根據(jù)你采用的具體運行方式,可以酌情決定是否保留這些引用。
對象檢測API的輸出結(jié)果
正如第111行所示,對象檢測API輸出4種對象:
- 類 - 一個由對象名組成的數(shù)組
- 分值 - 一個由置信度分值組成的數(shù)組
- 方框 - 檢測到的每個對象所在的位置
- 數(shù)量 - 檢測到的對象總數(shù)
類,分值和方框都是相互并列,大小相等的數(shù)組,因此類[N]與分?jǐn)?shù)[N]和盒[n]的都是一一對應(yīng)的。
由于我刪去了可視化功能,我們需要通過某種方式來查看結(jié)果,所以我們要把下面的命令添加到文件末尾:
第一個np.squeeze部分只是將多維數(shù)組輸出縮減成一維,這與原來的可視化代碼一樣。我認(rèn)為這是TensorFlow的一個副產(chǎn)品,因為它通常會輸出多維數(shù)組。
接著我們要為它輸出的分值設(shè)置一個閾值。好像TensorFlow默認(rèn)會返回100個對象。其中很多對象嵌套在置信度更高的對象內(nèi)或與這些對象重疊。在選擇閾值方面我還沒有發(fā)現(xiàn)任何最佳做法,不過對于這些示例圖像來說,50%似乎是合適的。
最后,我們需要循環(huán)遍歷這些數(shù)組,直接輸出那些超過閾值的分值。
如果運行下面的命令:
應(yīng)該會獲得下面的輸出:
第2部分 - 打造一項對象API網(wǎng)絡(luò)服務(wù)
在這一部分,我們將對教程代碼作一些改動,以將其作為一項網(wǎng)絡(luò)服務(wù)加以運行。我在Python方面的經(jīng)驗頗為有限(主要在Raspberry Pi項目中使用過),所以如有不對的地方,添加請備注或提交拉取請求,以便我可以修正。
2.1將演示代碼轉(zhuǎn)變成一項服務(wù)
至此我們已經(jīng)讓TensorFlow Object API能夠正常工作了,接下來我們就將它封裝成一個可以調(diào)用的函數(shù)。我將演示代碼復(fù)制到了一個名為object_detection_api.py 的新python文件中。你可以看到,我刪除了很多沒有用到或注釋掉的行,以及用于將詳細信息輸出到控制臺的部分(暫時刪除)。
由于我們要將這些信息輸出到網(wǎng)絡(luò)上,因此最好將我們的輸出結(jié)果封裝成一個JSON對象為此,請務(wù)必向你導(dǎo)入的內(nèi)容中添加一個importjso語句,然后再添加下面的命令:
接下來,我們要重復(fù)利用之前的代碼創(chuàng)建一個get_objects函數(shù):
在此函數(shù)中我們添加了一個圖像輸入?yún)?shù)和一個默認(rèn)為0.5的閾值。其余內(nèi)容都是在演示代碼的基礎(chǔ)上重構(gòu)的。
現(xiàn)在我們再向此函數(shù)添加一些代碼,以查詢具體的值并將它們輸出到一個JSON對象中:
這一次我們是使用對象類來創(chuàng)建一些初始元數(shù)據(jù)并將這些元數(shù)據(jù)添加到輸出列表中。然后我們使用循環(huán)向此列表中添加對象數(shù)據(jù)。最后,將此列表轉(zhuǎn)換成JSON并予以返回。
之后,我們來創(chuàng)建一個測試文件(這里要提醒自己:先做測試),以檢查它是否調(diào)用了object_detection_test.py :
至此萬事俱備,接下來就是運行了。
除了前面的控制臺輸出之外,你應(yīng)該還會看到一個JSON字符串:
2.2添加一個網(wǎng)絡(luò)服務(wù)器
我們已經(jīng)有了函數(shù) - 接下來我們就用它來打造一項網(wǎng)絡(luò)服務(wù)。
先使用測試用的路由(路由)運行
我們有了一個可以輕松添加到網(wǎng)絡(luò)服務(wù)的良好API。我發(fā)現(xiàn)使用Flask 是最簡單的測試方法。我們來創(chuàng)建一個server.py ,然后執(zhí)行一次快速測試:
現(xiàn)在,運行該服務(wù)器:
確保該服務(wù)正常工作
然后調(diào)用該網(wǎng)絡(luò)服務(wù)就我自己的情況而言,我只是從主機運行了下面的命令(因為我的泊塢實例現(xiàn)在正在前臺運行該服務(wù)器)。:
。json.tool將幫助你為輸出結(jié)果設(shè)置格式你應(yīng)該會看到下面的結(jié)果:
好了,接下來我們就要接受一個包含一個圖片文件及其他一些參數(shù)的POST,使用真實路由運行了為此,需要在/測試路由函數(shù)下添加一個新的和/或圖像路由:
這樣就會從一個采用表單編碼方式的POST中獲取圖片,并且可以選擇指定一個閾值,然后將該圖片傳遞給我們的object_detection_api。
我們來測試一下:
這時看到的結(jié)果應(yīng)該與上面使用/測試路徑時相同。繼續(xù)測試,可以指定你任選的其他本地圖像的路徑。
讓該服務(wù)在本地主機以外的位置也能正常工作
如果你打算在本地主機上運行瀏覽器,可能就不需要再做些什么。但如果是真實的服務(wù),甚至是在需要運行很多測試的情況下,這就不太現(xiàn)實了。如果要跨網(wǎng)絡(luò)運行網(wǎng)絡(luò)服務(wù),或者使用其他資源運行網(wǎng)絡(luò)服務(wù),都需要用到CORS幸好,在路由前添加以下代碼就可以輕松解決這一問題:
讓該服務(wù)支持安全源
最佳做法是搭配HTTPS使用的WebRTC,因為鉻和Safari瀏覽器等瀏覽器若不作專門配置,則僅支持安全源(不過鉻可以很好地支持本地主機,你也可以將野生設(shè)為允許在非安全網(wǎng)站上捕獲信息-到跳轉(zhuǎn)此處。的調(diào)試工具部分了解詳情)為此,你需要獲取一些SSL證書或生成一些自托管證書我將我自己的證書放在了SSL /目錄中,然后將最后一行app.run更改為:
如果你使用的是自簽名證書,你在使用卷曲進行測試時可能需要添加--insecure選項:
- 嚴(yán)格來講并非一定要生成你自己的證書,而且這會增加一定的工作量,所以在server.py最底部,我依然讓SSL版本保持被注釋掉的狀態(tài)。
- 如果是要投入生產(chǎn)環(huán)境中使用的應(yīng)用程序,你可能需要使用的nginx之類的代理向外發(fā)送HTTPS,同時在內(nèi)部依然使用HTTP(此外還要做很多其他方面的改進)。
- 添加一些路由以便提供我們的網(wǎng)頁
- 在開始介紹瀏覽器端的工作之前,我們先為后面需要用到的一些路由生成存根為此,請將下面的代碼放在索引()路由后面:
- 蟒蛇方面的工作到此就結(jié)束了。接下來我們將用到的JavaScript,并且需要編寫一些HTML。
- 我們將在下篇分享瀏覽器端的開發(fā),以及優(yōu)化方面經(jīng)驗。