在本文中,我将介紹如何在 Docker 容器中使用 Tensorflow Object-detection API 來執行實時(網絡攝像頭)和視訊的目标檢測。我使用 OpenCV 和 python3 的多任務處理庫 multiprocessing、多線程庫 multi-threading。
我會重點描述我在搭建過程中遇到的問題,以及我的解決方案 (有些還未解決)。完整的代碼在這裡 my Github:
https://github.com/lbeaucourt/Object-detection

使用Youtube視訊進行視訊處理測試
動機
我們從 Dat Tran 這篇文章開始挑戰實時目标檢測。我将使用 python 的 multiprocessing 庫,增加處理網絡攝像頭時的 FPS。為了進一步提高可移植性,我将項目內建到 Docker 容器中。不過處理進出容器的視訊流可能會有一點麻煩。
此外,在次項目我還添加了一個視訊後處理功能,同樣使用 multiprocessing 庫來減少處理時間(使用 Tensorflow 原始目标檢測 API 處理時間會非常長)。
實時和視訊目辨別别都可以在我的個人筆記本電腦上以高性能運作,僅使用 8GB CPU。
Docker在資料科學中的應用
我不在這裡描述 Tensorflow 目标檢測 API 的實作,因為相關的文檔很多。我将展示資料科學家在日常工作中如何使用 Docker。注意,我會使用 Tensorflow 的經典 ssd_mobilenet_v2_coco 模型來提高性能。先将模型檔案(.pb 檔案)和相應的标簽映射檔案複制到本地,後面可能會用到。
我認為使用 Docker 應是當今資料科學家的必備技能。在資料科學和機器學習領域,每周都會釋出許多新的算法,工具和程式,直接在你的計算機目錄上安裝調試這些代碼、程式會讓系統變得淩亂不堪。為了防止這種情況,我使用 Docker 容器來建立我的資料科學工作區将程式部署在容器中。
你可以在我的代碼庫中找到這個項目的 Dockerfile。以下是我安裝配置 Tensorflow 目标檢測的方法(按照官方安裝指南):
# Install tensorFlow
RUN pip install -U tensorflow
# Install tensorflow models object detection
RUN git clone https://github.com/tensorflow/models /usr/local/lib/python3.5/dist-packages/tensorflow/models
RUN apt-get install -y protobuf-compiler python-pil python-lxml python-tk
#Set TF object detection available
ENV PYTHONPATH "$PYTHONPATH:/usr/local/lib/python3.5/dist-packages/tensorflow/models/research:/usr/local/lib/python3.5/dist-packages/tensorflow/models/research/slim"
RUN cd /usr/local/lib/python3.5/dist-packages/tensorflow/models/research && protoc object_detection/protos/*.proto --python_out=.
安裝 OpenCv 并編譯:
# Install OpenCV
RUN git clone https://github.com/opencv/opencv.git /usr/local/src/opencv
RUN cd /usr/local/src/opencv/ && mkdir build
RUN cd /usr/local/src/opencv/build && cmake -D CMAKE_INSTALL_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local/ .. && make -j4 && make install
編譯鏡像的時候有點長,之後就可以快速的調用
實時圖像目标檢測
我首先嘗試将目标檢測應用于我的網絡攝像頭。在 Dat Tran 的文章中有這部分的較長的描述。難點在于将網絡攝像頭流發送到 docker 容器并恢複輸出流以使用 X11 伺服器顯示它。
将視訊流發送到容器
Linux 系統可以在/ dev /目錄中找到攝像頭裝置,并可以将其作為檔案進行操作。通常筆記本電腦攝像頭是「0」裝置。要将其資料流發送到 docker 容器,請在運作 docker 鏡像時使用 device 參數:
docker run --device=/dev/video0
對于 Mac 和 Windows 使用者,将網絡攝像頭流發送到容器的方式并不像 Linux 那樣簡單(盡管 Mac 基于 Unix)。我不在這裡過多介紹,可以查閱相關文檔,隻提一下 Windows 使用者的解決方案是使用 Virtual Box 啟動 docker 容器。
在容器中恢複視訊流
解決這個問題我花了一段時間(然而并沒有完美解決)。我找到了一些使用 Docker 圖形界面的資料,here。特别是介紹了将容器連接配接到主機的 X 服務以顯示内容
你必須開啟 xhost,以便容器可以通過讀寫 X11 unix 套接字來正常的顯示内容。首先設定 X 伺服器主機的權限(有一定安全隐患)讓 docker 通路它:
xhost +local:docker
在完成項目後,應當恢複預設設定
xhost -local:docker
然後建立兩個環境變量 XSOCK 和 XAUTH:
XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth
第一個環境變量引用 X11 unix 套接字,第二個引用 X 驗證檔案配置适當的權限:
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
最後,我們隻需要更新我們的 docker run 指令。傳入我們的 DISPLAY 環境變量,為 X11 Unix 套接字增加一個卷,并為 X 身份驗證檔案增加一個名為 XAUTHORITY 的環境變量,并讓該變量指向它:
docker run -it --rm --device=/dev/video0 -e DISPLAY=$DISPLAY -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e XAUTHORITY=$XAUTH
現在我們可以運作 docker 容器看看效果
目标檢測結果 (我是個害羞的人⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄)
盡管主機具有X服務配置,我仍不能完全删除代碼中的bug。在OpenCV中 需要通過調用python 腳本(init-openCV.py)來進行初始化,即使用函數cv2.imshow 。用這種方法我得到了如下的錯誤消息:
The program 'frame' received an X Window System error.
然後,它可能調用主要python 腳本(my-object-detection.py) 并且将視訊流傳送到主機進行展示。我對使用第一個python 腳本去初始化X11系統的結果不是很滿意,但是目前我還沒有找到解決這個問題的方法。
後來補充:我最終(在偶然間)發現這個問題的解決方法,通過使用OpenCV (3.4.1) 這個穩定版本替代本地克隆的git庫。是以現在在主流python 腳本之前沒有必要調用 init openCV.py
視訊處理
為了能通過我的攝像頭實時運作目标檢測API ,我使用線程和多程序處理的python 庫。一個線程用于讀取攝像頭視訊流。視訊幀被放進一個隊列通過工作池去處理(Tensorflow目标檢測運作的地方)。
對于視訊處理而言,它不可能使用線程,因為所有的視訊幀都是在工作單元能将目标檢測應用在隊列第一幀之前被讀取。當輸入隊列滿後被讀取的視訊幀就會被丢失。使用大量工作單元和隊列可能可以解決這個問題(伴随巨大的算力消耗)
簡單隊列的另外一個問題是,由于分析時間的不斷變化,視訊幀在輸出隊列中不是按照與輸入隊列相同的順序。
為了增加視訊處理功能,我删掉了讀取幀率的線程。作為一個替代,我使用下面的代碼來讀取幀率。
while True:
# Check input queue is not full
if not input_q.full():
# Read frame and store in input queue
ret, frame = vs.read()
if ret:
input_q.put((int(vs.get(cv2.CAP_PROP_POS_FRAMES)),frame))
如果輸入隊列沒滿,下一幀視訊從視訊流中讀取并且放進隊列中。否則,當視訊幀沒有從輸入隊列擷取時不會處理任何事情。
為了解決幀率順序的問題,我使用了如下這種優先隊列作為第二輸入隊列:
1. 視訊幀帶着對應的視訊幀編号被讀取并放入輸入隊列中(實際上是一個python 清單對象放入了序列)。
2. 然後,工作單元從輸入隊列中提取視訊幀,處理後将它們放入第一個輸出隊列(依然帶着它們相關的視訊幀編号)。
frame = input_q.get()
frame_rgb = cv2.cvtColor(frame[1], cv2.COLOR_BGR2RGB)
output_q.put((frame[0], detect_objects(frame_rgb, sess, detection_graph)))
3. 如果輸出隊列不為空,視訊幀帶着它們相應的優先視訊幀編号被抽取并放入優先隊列。優先隊列的大小被設定為其它隊列的三倍。
# Check output queue is not empty
if not output_q.empty():
# Recover treated frame in output queue and feed priority queue
output_pq.put(output_q.get())
4. 最後,如果輸出優先隊列不為空,則取出有最高優先編号的視訊幀(最小的優先編号)(這是标準的優先隊列工作)。如果優先級編号對應于預期的編号,視訊幀被加入輸出視訊流(并且根據需要寫入),其它的視訊幀則被放回優先隊列。
# Check output priority queue is not empty
if not output_pq.empty():
prior, output_frame = output_pq.get()
if prior > countWriteFrame:
output_pq.put((prior, output_frame))
else:
countWriteFrame = countWriteFrame + 1
# Do something with your frame
為了停止這個過程,我檢查所有的隊列為空,并且所有的視訊幀已經從視訊流中抽取:
if((not ret) & input_q.empty() &
output_q.empty() & output_pq.empty()):
break
總結
在這篇文章中,我展示了如何使用docker來實作Tensorflow的實時目标檢測項目。如上所述,docker是測試新資料科學工具最安全的方法,同時可以将解決方案打包給使用者。我也将如何采用來自Dat Tran 原始的python 腳本利用多程序去進行視訊處理展示給你。
謝謝你從頭到尾閱讀這篇文章。如上所述,這個項目有許多可以提高的地方。如果您有任何意見,請不要猶豫立刻告知我,我總是熱衷得到建議或評論。