0
本文作者: AI研習(xí)社-譯站 | 2018-07-23 15:25 |
雷鋒網(wǎng)按:本文為雷鋒網(wǎng)字幕組編譯的技術(shù)博客,原標(biāo)題 Real-time and video processing object detection using Tensorflow, OpenCV and Docker,作者為 Léo Beaucourt 。
翻譯 | 于志鵬 徐普 校對(duì) | 陶玉龍 整理 | 孔令雙
在本文中,我將介紹如何在 Docker 容器中使用 Tensorflow Object-detection API 來(lái)執(zhí)行實(shí)時(shí)(網(wǎng)絡(luò)攝像頭)和視頻的目標(biāo)檢測(cè)。我使用 OpenCV 和 python3 的多任務(wù)處理庫(kù) multiprocessing、多線程庫(kù) multi-threading。
我會(huì)重點(diǎn)描述我在搭建過程中遇到的問題,以及我的解決方案 (有些還未解決)。完整的代碼在這里 my Github:
https://github.com/lbeaucourt/Object-detection
使用Youtube視頻進(jìn)行視頻處理測(cè)試
我們從 Dat Tran 這篇文章開始挑戰(zhàn)實(shí)時(shí)目標(biāo)檢測(cè)。我將使用 python 的 multiprocessing 庫(kù),增加處理網(wǎng)絡(luò)攝像頭時(shí)的 FPS。為了進(jìn)一步提高可移植性,我將項(xiàng)目集成到 Docker 容器中。不過處理進(jìn)出容器的視頻流可能會(huì)有一點(diǎn)麻煩。
此外,在次項(xiàng)目我還添加了一個(gè)視頻后處理功能,同樣使用 multiprocessing 庫(kù)來(lái)減少處理時(shí)間(使用 Tensorflow 原始目標(biāo)檢測(cè) API 處理時(shí)間會(huì)非常長(zhǎng))。
實(shí)時(shí)和視頻目標(biāo)識(shí)別都可以在我的個(gè)人筆記本電腦上以高性能運(yùn)行,僅使用 8GB CPU。
我不在這里描述 Tensorflow 目標(biāo)檢測(cè) API 的實(shí)現(xiàn),因?yàn)橄嚓P(guān)的文檔很多。我將展示數(shù)據(jù)科學(xué)家在日常工作中如何使用 Docker。注意,我會(huì)使用 Tensorflow 的經(jīng)典 ssd_mobilenet_v2_coco 模型來(lái)提高性能。先將模型文件(.pb 文件)和相應(yīng)的標(biāo)簽映射文件復(fù)制到本地,后面可能會(huì)用到。
我認(rèn)為使用 Docker 應(yīng)是當(dāng)今數(shù)據(jù)科學(xué)家的必備技能。在數(shù)據(jù)科學(xué)和機(jī)器學(xué)習(xí)領(lǐng)域,每周都會(huì)發(fā)布許多新的算法,工具和程序,直接在你的計(jì)算機(jī)目錄上安裝調(diào)試這些代碼、程序會(huì)讓系統(tǒng)變得凌亂不堪。為了防止這種情況,我使用 Docker 容器來(lái)創(chuàng)建我的數(shù)據(jù)科學(xué)工作區(qū)將程序部署在容器中。
你可以在我的代碼庫(kù)中找到這個(gè)項(xiàng)目的 Dockerfile。以下是我安裝配置 Tensorflow 目標(biāo)檢測(cè)的方法(按照官方安裝指南):
# 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
編譯鏡像的時(shí)候有點(diǎn)長(zhǎng),之后就可以快速的調(diào)用
我首先嘗試將目標(biāo)檢測(cè)應(yīng)用于我的網(wǎng)絡(luò)攝像頭。在 Dat Tran 的文章中有這部分的詳細(xì)描述。難點(diǎn)在于將網(wǎng)絡(luò)攝像頭流發(fā)送到 docker 容器并恢復(fù)輸出流以使用 X11 服務(wù)器顯示它。
將視頻流發(fā)送到容器
Linux 系統(tǒng)可以在/ dev /目錄中找到攝像頭設(shè)備,并可以將其作為文件進(jìn)行操作。通常筆記本電腦攝像頭是「0」設(shè)備。要將其數(shù)據(jù)流發(fā)送到 docker 容器,請(qǐng)?jiān)谶\(yùn)行 docker 鏡像時(shí)使用 device 參數(shù):
docker run --device=/dev/video0
對(duì)于 Mac 和 Windows 用戶,將網(wǎng)絡(luò)攝像頭流發(fā)送到容器的方式并不像 Linux 那樣簡(jiǎn)單(盡管 Mac 基于 Unix)。我不在這里過多介紹,可以查閱相關(guān)文檔,只提一下 Windows 用戶的解決方案是使用 Virtual Box 啟動(dòng) docker 容器。
在容器中恢復(fù)視頻流
解決這個(gè)問題我花了一段時(shí)間(然而并沒有完美解決)。我找到了一些使用 Docker 圖形界面的資料,here。特別是介紹了將容器連接到主機(jī)的 X 服務(wù)以顯示內(nèi)容
你必須開啟 xhost,以便容器可以通過讀寫 X11 unix 套接字來(lái)正常的顯示內(nèi)容。首先設(shè)置 X 服務(wù)器主機(jī)的權(quán)限(有一定安全隱患)讓 docker 訪問它:
xhost +local:docker
在完成項(xiàng)目后,應(yīng)當(dāng)恢復(fù)默認(rèn)設(shè)置
xhost -local:docker
然后創(chuàng)建兩個(gè)環(huán)境變量 XSOCK 和 XAUTH:
XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth
第一個(gè)環(huán)境變量引用 X11 unix 套接字,第二個(gè)引用 X 驗(yàn)證文件配置適當(dāng)?shù)臋?quán)限:
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
最后,我們只需要更新我們的 docker run 命令。傳入我們的 DISPLAY 環(huán)境變量,為 X11 Unix 套接字增加一個(gè)卷,并為 X 身份驗(yàn)證文件增加一個(gè)名為 XAUTHORITY 的環(huán)境變量,并讓該變量指向它:
docker run -it --rm --device=/dev/video0 -e DISPLAY=$DISPLAY -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e XAUTHORITY=$XAUTH
現(xiàn)在我們可以運(yùn)行 docker 容器看看效果
目標(biāo)檢測(cè)結(jié)果 (我是個(gè)害羞的人?(? ???ω??? ?)?)
盡管主機(jī)具有X服務(wù)配置,我仍不能完全刪除代碼中的bug。在OpenCV中 需要通過調(diào)用python 腳本(init-openCV.py)來(lái)進(jìn)行初始化,即使用函數(shù)cv2.imshow 。用這種方法我得到了如下的錯(cuò)誤消息:
The program 'frame' received an X Window System error.
然后,它可能調(diào)用主要python 腳本(my-object-detection.py) 并且將視頻流傳送到主機(jī)進(jìn)行展示。我對(duì)使用第一個(gè)python 腳本去初始化X11系統(tǒng)的結(jié)果不是很滿意,但是目前我還沒有找到解決這個(gè)問題的方法。
后來(lái)補(bǔ)充:我最終(在偶然間)發(fā)現(xiàn)這個(gè)問題的解決方法,通過使用OpenCV (3.4.1) 這個(gè)穩(wěn)定版本替代本地克隆的git庫(kù)。因此現(xiàn)在在主流python 腳本之前沒有必要調(diào)用 init openCV.py
為了能通過我的攝像頭實(shí)時(shí)運(yùn)行目標(biāo)檢測(cè)API ,我使用線程和多進(jìn)程處理的python 庫(kù)。一個(gè)線程用于讀取攝像頭視頻流。視頻幀被放進(jìn)一個(gè)隊(duì)列通過工作池去處理(Tensorflow目標(biāo)檢測(cè)運(yùn)行的地方)。
對(duì)于視頻處理而言,它不可能使用線程,因?yàn)樗械囊曨l幀都是在工作單元能將目標(biāo)檢測(cè)應(yīng)用在隊(duì)列第一幀之前被讀取。當(dāng)輸入隊(duì)列滿后被讀取的視頻幀就會(huì)被丟失。使用大量工作單元和隊(duì)列可能可以解決這個(gè)問題(伴隨巨大的算力消耗)
簡(jiǎn)單隊(duì)列的另外一個(gè)問題是,由于分析時(shí)間的不斷變化,視頻幀在輸出隊(duì)列中不是按照與輸入隊(duì)列相同的順序。
為了增加視頻處理功能,我刪掉了讀取幀率的線程。作為一個(gè)替代,我使用下面的代碼來(lái)讀取幀率。
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))
如果輸入隊(duì)列沒滿,下一幀視頻從視頻流中讀取并且放進(jìn)隊(duì)列中。否則,當(dāng)視頻幀沒有從輸入隊(duì)列獲取時(shí)不會(huì)處理任何事情。
為了解決幀率順序的問題,我使用了如下這種優(yōu)先隊(duì)列作為第二輸入隊(duì)列:
1. 視頻幀帶著對(duì)應(yīng)的視頻幀編號(hào)被讀取并放入輸入隊(duì)列中(實(shí)際上是一個(gè)python 列表對(duì)象放入了序列)。
2. 然后,工作單元從輸入隊(duì)列中提取視頻幀,處理后將它們放入第一個(gè)輸出隊(duì)列(依然帶著它們相關(guān)的視頻幀編號(hào))。
while True:
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. 如果輸出隊(duì)列不為空,視頻幀帶著它們相應(yīng)的優(yōu)先視頻幀編號(hào)被抽取并放入優(yōu)先隊(duì)列。優(yōu)先隊(duì)列的大小被設(shè)置為其它隊(duì)列的三倍。
# 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. 最后,如果輸出優(yōu)先隊(duì)列不為空,則取出有最高優(yōu)先編號(hào)的視頻幀(最小的優(yōu)先編號(hào))(這是標(biāo)準(zhǔn)的優(yōu)先隊(duì)列工作)。如果優(yōu)先級(jí)編號(hào)對(duì)應(yīng)于預(yù)期的編號(hào),視頻幀被加入輸出視頻流(并且根據(jù)需要寫入),其它的視頻幀則被放回優(yōu)先隊(duì)列。
# 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
為了停止這個(gè)過程,我檢查所有的隊(duì)列為空,并且所有的視頻幀已經(jīng)從視頻流中抽取:
if((not ret) & input_q.empty() &
output_q.empty() & output_pq.empty()):
break
在這篇文章中,我展示了如何使用docker來(lái)實(shí)現(xiàn)Tensorflow的實(shí)時(shí)目標(biāo)檢測(cè)項(xiàng)目。如上所述,docker是測(cè)試新數(shù)據(jù)科學(xué)工具最安全的方法,同時(shí)可以將解決方案打包給用戶。我也將如何采用來(lái)自Dat Tran 原始的python 腳本利用多進(jìn)程去進(jìn)行視頻處理展示給你。
謝謝你從頭到尾閱讀這篇文章。如上所述,這個(gè)項(xiàng)目有許多可以提高的地方。如果您有任何意見,請(qǐng)不要猶豫立刻告知我,我總是熱衷得到建議或評(píng)論。
原文鏈接:
雷鋒網(wǎng)字幕組編譯。
號(hào)外號(hào)外~
一個(gè)專注于
AI技術(shù)發(fā)展和AI工程師成長(zhǎng)的求知求職社區(qū)
誕生啦!
歡迎大家訪問以下鏈接或者掃碼體驗(yàn)
https://club.leiphone.com/page/home
雷峰網(wǎng)原創(chuàng)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。