教你如何結合WebRTC與TensorFlow實現圖像檢測(上篇)
原文作者:Chad Hart
原文地址:Computer Vision on the Web with WebRTC and TensorFlow - webrtcHacks摘要:本文作者介紹了結合WebRTC與TensorFlow實現圖像檢測的具體過程,不論對於TensorFlow的使用者,還是WebRTC的開發者來講都有參考意義。由於文章較長,我們將分為上下篇進行連載。
TensorFlow是目前最流行的機器學習框架之一。TensorFlow的一大優勢是,它的很多庫都有人積極進行維護和更新。而我最喜歡的其中一個庫就是TensorFlow對象檢測API。Tensorflow對象檢測API可以對一張圖形上的多個對象進行分類,並提供它們的具體位置。該API在將近1000個對象類上進行了預先訓練,可提供各種經過預先訓練的模型,讓你可以在速度與準確性之間權衡取捨。
有這些模型的指引固然很好,但所有這些模型都要使用圖像才能發揮作用,而這些圖像則需要你自行添加到一個文件夾中。我其實很想將其與實時的WebRTC流配合到一起,通過網路實現實時的計算機視覺。由於未能找到這方面的任何例子或指南,我決定寫這篇博文來介紹具體的實現方法。對於使用RTC的人,可以將本文作為一篇快速指南加以參考,了解如何使用TensorFlow來處理WebRTC流。對於使用TensorFlow的人士,則可以將本文作為一份快速簡介,了解如何向自己的項目中添加WebRTC。使用WebRTC的人需要對Python比較熟悉。而使用TensorFlow的人則需要熟悉網路交互和一些JavaScript。
本文不適合作為WebRTC或TensorFlow的入門指南使用。如需這樣的指南,應參考TensorFlow入門指南、WebRTC入門指南等,網上的相關介紹與指南數不勝數。
直接告訴我如何實現吧
如果你來這裡只是為了快速找到一些參考信息,或者懶得讀詳細的文字介紹,按照下面的方法即可快速著手。首先安裝Docker。載入一個命令提示窗口,接著鍵入下面的命令:
docker run -it -p 5000:5000 chadhart/tensorflow-object-detection:runserver
然後在瀏覽器地址欄中鍵入並轉到http://localhost:5000/local,接受攝像頭許可權請求,你應該會看到類似下面的界面:
基本架構
我們首先建立一個基本架構,用以在本地將一個本地網路攝像頭流從WebRTC的getUserMedia發送到一個Python伺服器,這要用到Flask網路伺服器和TensorFlow 對象檢測API(Object Detection API)。具體的設置大致如下圖所示。
Flask將提供html和JavaScript文件供瀏覽器呈現。getUserMedia.js負責抓取本地視頻流。接下來,objDetect.js會使用HTTP POST方法向TensorFlow對象檢測API發送圖像,該API則返回它所看到的對象(它稱之為「類」)及對象在圖像中的位置。我們會將這些詳細信息封裝到一個JSON對象中,然後將該對象發回給objDetect.js,這樣我們就能將我們所看到的對象的方框和標籤顯示出來。
配置
設置和前提條件
在開始之前,我們需要先對Tensorflow和對象檢測API進行一些設置。
使用Docker輕鬆完成設置
我在OSX、Windows 10和Raspbian已經設置過好幾次(過程可不簡單)。各種版本依賴關係錯綜複雜,把這些關係理順並非易事,特別是當你只是想看看一些前期工作是否行得通時,你可能會感到氣餒。我推薦使用Docker來避免這些棘手問題。你將需要學習Docker,這也是非學不可的東西,與其試著構建合適的Protobuf版本,倒不如花些時間學習它來得更為高效。TensorFlow項目維護了一些官方的Docker映像,比如tensorflow/tensorflow。
如果你使用Docker,我們就可以使用我為這篇博文創建的映像。在命令行中,請運行以下命令:
git clone https://github.com/webrtcHacks/tfObjWebrtc.gitcd tfObjWebrtcdocker run -it -p 5000:5000 --name tf-webrtchacks -v $(pwd):/code chadhart/tensorflow-object-detection:webrtchacks
請注意,docker run中的$(pwd)僅適用於Linux和Windows Powershell。在Windows 10命令行中,請使用%cd%。
看到這裡,你應該已經進入了Docker容器。現在,請運行:
python setup.py install
這樣,就會使用最新的TensorFlow Docker映像,並將Docker主機上的埠5000連接到埠5000,將容器命名為tf-webrtchacks,將一個本地目錄映射到容器中的一個新/code目錄,將該目錄設為默認目錄(我們接下來將在該目錄中操作),然後運行bash以便進行命令行交互。完成這些準備工作後,我們才能開始。
如果你才剛開始接觸TensorFlow,可能需要先按照tensorflow/tensorflow中的說明運行初始Jupyter notebook,然後再回來執行上述命令。
另一種麻煩的實現方法
如果你打算從頭開始,則需要安裝TensorFlow,它自身有很多依賴項,比如Python。TensorFlow項目針對各種平台都提供了指南,具體請訪問https://www.tensorflow.org/install。對象檢測API也有自己的安裝說明,以及一些額外的依賴項。完成這些準備工作後,請運行下面的命令:
git clone https://github.com/webrtcHacks/tfObjWebrtc.gitcd tfObjWebrtcpython setup.py install
這樣,就應該安裝好了所有的Python依賴項,將相應的Tensorflow對象檢測API文件都複製了過來,並安裝了Protobufs。如果這一步行不通,我建議檢查setup.py,然後手動在其中運行命令,以解決存在的任何問題。
第1部分——確保Tensorflow正常工作
為確保TensorFlow對象檢測API正常工作,我們首先從用於演示對象檢測的官方版JupyterNotebook經調整後的版本著手。我將此文件保存為object_detection_tutorial.py。
如果你剪切並粘貼該notebook的每個部分,得到的結果應如下所示:(由於此段代碼較長,截圖會影響閱讀,我們更換為文字排版,左右拖動可查看長代碼)
# IMPORTSimport numpy as npimport osimport six.moves.urllib as urllibimport sysimport tarfileimport tensorflow as tfimport zipfilefrom collections import defaultdictfrom io import StringIO# from matplotlib import pyplot as plt ### CWHfrom PIL import Imageif 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 importsfrom 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_17MODEL_FILE = MODEL_NAME + .tar.gzDOWNLOAD_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 pathNUM_CLASSES = 90# Download Modelopener = 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 maplabel_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 codedef load_image_into_numpy_array(image): (im_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 #cwhTEST_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. (boxes, 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演示及其他教程中找到。我將重點介紹我們對代碼所做的修改。
我注釋了幾個小節:
- 更改了一些位置引用
- 刪除了對Python matplot的所有引用。Python matplot用於在GUI環境中以可視化方式呈現輸出結果。在我的Docker環境中沒有設置它——根據你採用的具體運行方式,可以酌情決定是否保留這些引用。
對象檢測API的輸出結果
正如第111行所示,對象檢測API輸出4種對象:
- 類——一個由對象名組成的數組
- 分值——一個由置信度分值組成的數組
- 方框——檢測到的每個對象所在的位置
- 數量——檢測到的對象總數
類、分值和方框都是相互並列、大小相等的數組,因此classes[n]與scores[n]和boxes[n]都是一一對應的。
由於我刪去了可視化功能,我們需要通過某種方式來查看結果,所以我們要把下面的命令添加到文件末尾:
### CWH: Print the object details to the console instead of visualizing them with the code above classes = np.squeeze(classes).astype(np.int32)scores = np.squeeze(scores)boxes = np.squeeze(boxes) threshold = 0.50 #CWH: set a minimum score threshold of 50%obj_above_thresh = sum(n > threshold for n in scores)print("detected %s objects in %s above a %s score" % ( obj_above_thresh, image_path, threshold)) for c in range(0, len(classes)): if scores[c] > threshold: class_name = category_index[classes[c]][name] print(" object %s is a %s - score: %s, location: %s" % (c, class_name, scores[c], boxes[c]))
第一個np.squeeze部分只是將多維數組輸出縮減成一維,這與原來的可視化代碼一樣。我認為這是TensorFlow的一個副產品,因為它通常會輸出多維數組。
接著我們要為它輸出的分值設置一個閾值。好像TensorFlow默認會返回100個對象。其中很多對象嵌套在置信度更高的對象內或與這些對象重疊。在選擇閾值方面我還沒有發現任何最佳做法,不過對於這些示例圖像來說,50%似乎是合適的。
最後,我們需要循環遍歷這些數組,直接輸出那些超過閾值的分值。
如果運行下面的命令:
python object_detection_tutorial.py
應該會獲得下面的輸出:
detected 2 objects in object_detection/test_images/image1.jpg above a 0.5 score object 0 is a dog - score: 0.940691, location: [ 0.03908405 0.01921503 0.87210345 0.31577349] object 1 is a dog - score: 0.934503, location: [ 0.10951501 0.40283561 0.92464608 0.97304785]detected 10 objects in object_detection/test_images/image2.jpg above a 0.5 score object 0 is a person - score: 0.916878, location: [ 0.55387682 0.39422381 0.59312469 0.40913767] object 1 is a kite - score: 0.829445, location: [ 0.38294643 0.34582412 0.40220094 0.35902989] object 2 is a person - score: 0.778505, location: [ 0.57416666 0.057667 0.62335181 0.07475379] object 3 is a kite - score: 0.769985, location: [ 0.07991442 0.4374091 0.16590245 0.50060284] object 4 is a kite - score: 0.755539, location: [ 0.26564282 0.20112294 0.30753511 0.22309387] object 5 is a person - score: 0.634234, location: [ 0.68338078 0.07842994 0.84058815 0.11782578] object 6 is a kite - score: 0.607407, location: [ 0.38510025 0.43172216 0.40073246 0.44773054] object 7 is a person - score: 0.589102, location: [ 0.76061964 0.15739655 0.93692541 0.20186904] object 8 is a person - score: 0.512377, location: [ 0.54281253 0.25604743 0.56234604 0.26740867] object 9 is a person - score: 0.501464, location: [ 0.58708113 0.02699314 0.62043804 0.04133803]
第2部分——打造一項對象API網路服務
在這一部分,我們將對教程代碼作一些改動,以將其作為一項網路服務加以運行。我在Python方面的經驗頗為有限(主要在Raspberry Pi項目中使用過),所以如有不對的地方,請添加備註或提交拉取請求,以便我可以修正。
2.1將演示代碼轉變成一項服務
至此我們已經讓TensorFlow Object API能夠正常工作了,接下來我們就將它封裝成一個可以調用的函數。我將演示代碼複製到了一個名為object_detection_api.py的新python文件中。你可以看到,我刪除了很多沒有用到或注釋掉的行,以及用於將詳細信息輸出到控制台的部分(暫時刪除)。
由於我們要將這些信息輸出到網路上,因此最好將我們的輸出結果封裝成一個JSON對象。為此,請務必向你導入的內容中添加一個importjso語句,然後再添加下面的命令:
# added to put object in JSONclass Object(object): def __init__(self): self.name="Tensor Flow Object API Service 0.0.1" def toJSON(self): return json.dumps(self.__dict__)
接下來,我們要重複利用之前的代碼創建一個get_objects函數:
def get_objects(image, threshold=0.5): 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. (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_np_expanded}) classes = np.squeeze(classes).astype(np.int32) scores = np.squeeze(scores) boxes = np.squeeze(boxes)obj_above_thresh = sum(n > threshold for n in scores) obj_above_thresh = sum(n > threshold for n in scores) print("detected %s objects in image above a %s score" % (obj_above_thresh, threshold))
在此函數中我們添加了一個圖像輸入參數和一個默認為0.5的threshold值。其餘內容都是在演示代碼的基礎上重構的。
現在我們再向此函數添加一些代碼,以查詢具體的值並將它們輸出到一個JSON對象中:
output = [] #Add some metadata to the output item = Object() item.numObjects = obj_above_thresh item.threshold = threshold output.append(item) for c in range(0, len(classes)): class_name = category_index[classes[c]][name] if scores[c] >= threshold: # only return confidences equal or greater than the threshold print(" object %s - score: %s, coordinates: %s" % (class_name, scores[c], boxes[c])) item = Object() item.name = Object item.class_name = class_name item.score = float(scores[c]) item.y = float(boxes[c][0]) item.x = float(boxes[c][1]) item.height = float(boxes[c][2]) item.width = float(boxes[c][3]) output.append(item) outputJson = json.dumps([ob.__dict__ for ob in output]) return outputJson
這一次我們是使用Object類來創建一些初始元數據並將這些元數據添加到output列表中。然後我們使用循環向此列表中添加Object數據。最後,將此列錶轉換成JSON並予以返回。
之後,我們來創建一個測試文件(這裡要提醒自己:先做測試),以檢查它是否調用了object_detection_test.py:
import scan_imageimport osfrom PIL import Image # 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 #cwhTEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, image{}.jpg.format(i)) for i in range(1, 3) ] for image_path in TEST_IMAGE_PATHS: image = Image.open(image_path) response = object_detection_api.get_objects(image) print("returned JSON:
%s" % response)
至此萬事俱備,接下來就是運行了。
python object_detection_test.py
除了前面的控制台輸出之外,你應該還會看到一個JSON字元串:
returned JSON: [{"threshold": 0.5, "name": "webrtcHacks Sample Tensor Flow Object API Service 0.0.1", "numObjects": 10}, {"name": "Object", "class_name": "person", "height": 0.5931246876716614, "width": 0.40913766622543335, "score": 0.916878342628479, "y": 0.5538768172264099, "x": 0.39422380924224854}, {"name": "Object", "class_name": "kite", "height": 0.40220093727111816, "width": 0.3590298891067505, "score": 0.8294452428817749, "y": 0.3829464316368103, "x": 0.34582412242889404}, {"name": "Object", "class_name": "person", "height": 0.6233518123626709, "width": 0.0747537910938263, "score": 0.7785054445266724, "y": 0.5741666555404663, "x": 0.057666998356580734}, {"name": "Object", "class_name": "kite", "height": 0.16590245068073273, "width": 0.5006028413772583, "score": 0.7699846625328064, "y": 0.07991442084312439, "x": 0.43740910291671753}, {"name": "Object", "class_name": "kite", "height": 0.3075351119041443, "width": 0.22309386730194092, "score": 0.7555386424064636, "y": 0.26564282178878784, "x": 0.2011229395866394}, {"name": "Object", "class_name": "person", "height": 0.8405881524085999, "width": 0.11782577633857727, "score": 0.6342343688011169, "y": 0.6833807826042175, "x": 0.0784299373626709}, {"name": "Object", "class_name": "kite", "height": 0.40073245763778687, "width": 0.44773054122924805, "score": 0.6074065566062927, "y": 0.38510024547576904, "x": 0.43172216415405273}, {"name": "Object", "class_name": "person", "height": 0.9369254112243652, "width": 0.20186904072761536, "score": 0.5891017317771912, "y": 0.7606196403503418, "x": 0.15739655494689941}, {"name": "Object", "class_name": "person", "height": 0.5623460412025452, "width": 0.26740866899490356, "score": 0.5123767852783203, "y": 0.5428125262260437, "x": 0.25604742765426636}, {"name": "Object", "class_name": "person", "height": 0.6204380393028259, "width": 0.04133802652359009, "score": 0.5014638304710388, "y": 0.5870811343193054, "x": 0.026993142440915108}]
2.2添加一個網路伺服器
我們已經有了函數——接下來我們就用它來打造一項網路服務。
先使用測試用的路由(Route)運行
我們有了一個可以輕鬆添加到網路服務的良好API。我發現使用Flask是最簡單的測試方法。我們來創建一個server.py,然後執行一次快速測試:
import object_detection_apiimport osfrom PIL import Imagefrom flask import Flask, request, Responseapp = Flask(__name__)@app.route(/)def index(): return Response(Tensor Flow object detection)@app.route(/test)def test(): 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)] image = Image.open(TEST_IMAGE_PATHS[0]) objects = object_detection_api.get_objects(image) return objectsif __name__ == __main__: app.run(debug=True, host=0.0.0.0)
現在,運行該伺服器:
python server.py
確保該服務正常工作
然後調用該網路服務。就我自己的情況而言,我只是從主機運行了下面的命令(因為我的Docker實例現在正在前台運行該伺服器):
curl http://localhost:5000/test | python -m json.tool
json.tool將幫助你為輸出結果設置格式。你應該會看到下面的結果:
% Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 467 100 467 0 0 300 0 0:00:01 0:00:01 --:--:-- 300[ { "name": "webrtcHacks Sample Tensor Flow Object API Service 0.0.1", "numObjects": 2, "threshold": 0.5 }, { "class_name": "dog", "height": 0.8721034526824951, "name": "Object", "score": 0.9406907558441162, "width": 0.31577348709106445, "x": 0.01921503245830536, "y": 0.039084047079086304 }, { "class_name": "dog", "height": 0.9246460795402527, "name": "Object", "score": 0.9345026612281799, "width": 0.9730478525161743, "x": 0.4028356075286865, "y": 0.10951501131057739 }]
好了,接下來我們就要接受一個包含一個圖片文件及其他一些參數的POST,使用真實路由運行了。為此,需要在/test路由函數下添加一個新的/image路由:
@app.route(/image, methods=[POST])def image(): try: image_file = request.files[image] # get the image # Set an image confidence threshold value to limit returned data threshold = request.form.get(threshold) if threshold is None: threshold = 0.5 else: threshold = float(threshold) # finally run the image through tensor flow object detection` image_object = Image.open(image_file) objects = object_detection_api.get_objects(image_object, threshold) return objects except Exception as e: print(POST /image error: %e % e) return e
這樣就會從一個採用表單編碼方式的POST中獲取圖片,並且可以選擇指定一個閾值,然後將該圖片傳遞給我們的object_detection_api。
我們來測試一下:
curl -F "image=@./object_detection/test_images/image1.jpg" http://localhost:5000/image | python -m json.tool
這時看到的結果應該與上面使用/test路徑時相同。繼續測試,可以指定你任選的其他本地圖像的路徑。
讓該服務在localhost以外的位置也能正常工作
如果你打算在localhost上運行瀏覽器,可能就不需要再做些什麼。但如果是真實的服務,甚至是在需要運行很多測試的情況下,這就不太現實了。如果要跨網路運行網路服務,或者使用其他資源運行網路服務,都需要用到CORS。幸好,在路由前添加以下代碼就可以輕鬆解決這一問題:
# for CORS@app.after_requestdef after_request(response): response.headers.add(Access-Control-Allow-Origin, *) response.headers.add(Access-Control-Allow-Headers, Content-Type,Authorization) response.headers.add(Access-Control-Allow-Methods, GET,POST) # Put any other methods you need here return response
讓該服務支持安全源
最佳做法是搭配HTTPS使用WebRTC,因為Chrome和Safari等瀏覽器若不作專門配置,則僅支持安全源(不過Chrome可以很好地支持localhost,你也可以將Safari設為允許在非安全網站上捕獲信息——跳轉到此處的調試工具部分了解詳情)。為此,你需要獲取一些SSL證書或生成一些自託管證書。我將我自己的證書放在了ssl/目錄中,然後將最後一行app.run更改為:
app.run(debug=True, host=0.0.0.0, ssl_context=(ssl/server.crt, ssl/server.key))
如果你使用的是自簽名證書,你在使用CURL進行測試時可能需要添加--insecure選項:
curl -F "image=@./object_detection/test_images/image2.jpg" --insecure https://localhost:5000/image | python -m json.tool
嚴格來講並非一定要生成你自己的證書,而且這會增加一定的工作量,所以在server.py最底部,我依然讓SSL版本保持被注釋掉的狀態。
如果是要投入生產環境中使用的應用程序,你可能需要使用nginx之類的代理向外發送HTTPS,同時在內部依然使用HTTP(此外還要做很多其他方面的改進)。
添加一些路由以便提供我們的網頁
在開始介紹瀏覽器端的工作之前,我們先為後面需要用到的一些路由生成存根。為此,請將下面的代碼放在index()路由後面:
@app.route(/local)def local(): return Response(open(./static/local.html).read(), mimetype="text/html")@app.route(/video)def remote(): return Response(open(./static/video.html).read(), mimetype="
Python方面的工作到此就結束了。接下來我們將用到JavaScript,並且需要編寫一些HTML。
我們將在下篇分享瀏覽器端的開發,以及優化方面經驗。
推薦閱讀:
※如何評價deep mind開源的sonnet?
※學習筆記TF004:張量表示、類型、形狀、計算
※Caffe學習筆記--如何創建自定義Layer
※一個基於 TensorFlow 的「顏值評分」開源項目:FaceRank
TAG:WebRTC | TensorFlow | 计算机视觉 |