樹莓派3B+英特爾神經(jīng)計(jì)算棒進(jìn)行高速目標(biāo)檢測(cè)
轉(zhuǎn)載請(qǐng)注明作者夢(mèng)里茶

代碼:
訓(xùn)練數(shù)據(jù)預(yù)處理:
https://gist.github.com/ahangchen/ae1b7562c1f93fdad1de58020e94fbdf
測(cè)試:https://github.com/ahangchen/ncs_detection
Star是一種美德。
Background
最近在做一個(gè)項(xiàng)目,要在樹莓派上分析視頻中的圖片,檢測(cè)目標(biāo),統(tǒng)計(jì)目標(biāo)個(gè)數(shù),這是一張樣例圖片:

Motivation
當(dāng)下效果最好的目標(biāo)檢測(cè)都是基于神經(jīng)網(wǎng)絡(luò)來做的,包括faster rcnn, ssd, yolo2等等,要在樹莓派這種資源緊張的設(shè)備上運(yùn)行檢測(cè)模型,首先想到的就是用最輕量的MobileNet SSD,使用Tensorflow object detection api實(shí)現(xiàn)的MobileNet SSD雖然已經(jīng)非常輕,但在樹莓派上推導(dǎo)一張1280x720的圖仍然需要2秒,有興趣的同學(xué)可以參考這兩個(gè)項(xiàng)目:
- armv7版Tensorflow(必須是1.4及以上):https://github.com/lhelontra/tensorflow-on-arm/releases
- Tensorflow Object detection API: https://github.com/tensorflow/models/tree/master/research/object_detection
具體的操作在Tensorflow文檔里都說的很清楚了,在樹莓派上的操作也是一樣的,有問題可以評(píng)論區(qū)討論
Hardware
極限的模型仍然不能滿足性能需求,就需要請(qǐng)出我們今天的主角了,Intel Movidius Neural Computing Stick

| 處理器 | Intel Movidius VPU |
|---|---|
| 支持框架 | TensorFlow, Caffe |
| 連接方式 | USB 3.0 Type-A |
| 尺寸 | USB stick (72.5mm X 27mm X 14mm) |
| 工作溫度 | 0° - 40° C |
| x86_64 Ubuntu 16.04主機(jī) | |
| Raspberry Pi 3B Stretch desktop | |
| Ubuntu 16.04 虛擬機(jī) | |
| 系統(tǒng)要求 | USB 2.0 以上 (推薦 USB 3.0) |
| 1GB 內(nèi)存 | |
| 4GB 存儲(chǔ) | |
實(shí)際上這不是一個(gè)GPU,而是一個(gè)專用計(jì)算芯片,但能起到類似GPU對(duì)神經(jīng)網(wǎng)絡(luò)運(yùn)算的加速作用。
京東上搜名字可以買到,只要500元左右,想想一塊GPU都要幾千塊錢,就會(huì)覺得很值了。
SDK是開源的:https://github.com/movidius/ncsdk
提問不在GitHub issue里,而是在一個(gè)專門的論壇:https://ncsforum.movidius.com/
雖然目前NCSDK支持的框架包含Tensorflow和Caffe,但并不是支持所有的模型,目前已支持的模型列表可以在這里查到:https://github.com/movidius/ncsdk/releases
截止到2018年3月15日,NCSDK還沒有支持Tensorflow版的MobileNet SSD(比如tf.cast這個(gè)操作還未被支持),所以我們需要用Caffe來訓(xùn)練模型,部署到樹莓派上。
Environment
ncsdk的環(huán)境分為兩部分,訓(xùn)練端和測(cè)試端。
- 訓(xùn)練端通常是一個(gè)Ubuntu 帶GPU主機(jī),訓(xùn)練Caffe或TensorFlow模型,編譯成NCS可以執(zhí)行的graph;
- 測(cè)試端則面向ncs python mvnc api編程,可以運(yùn)行在樹莓派上raspbian stretch版本,也可以運(yùn)行在訓(xùn)練端這種機(jī)器上。
訓(xùn)練端
安裝
安裝這個(gè)過程,說難不難,也就幾行命令的事情,但也有很多坑
在訓(xùn)練端主機(jī)上,插入神經(jīng)計(jì)算棒,然后:
git clone https://github.com/movidius/ncsdk
cd ncsdk
make install
其中,make install干的是這些事情:
- 檢查安裝Tensorflow
- 檢查安裝Caffe(SSD-caffe)
- 編譯安裝ncsdk(不包含inference模塊,只包含mvNCCompile相關(guān)模塊,用來將Caffe或Tensorflow模型轉(zhuǎn)成NCS graph的)
注意,
這些庫都是安裝到
/opt/movidius/這個(gè)目錄下,并關(guān)聯(lián)到系統(tǒng)python3里邊的(/usr/bin/python3),如果你電腦里原來有tf或caffe,也不會(huì)被關(guān)聯(lián)上去NCSDK mvNCCompile模塊目前只兼容python3,我嘗試過將安裝完的SDK改成兼容python2的版本,可以將模型編譯出來,但是在運(yùn)行時(shí)會(huì)報(bào)錯(cuò),所以暫時(shí)放棄兼容python2了,也建議大家用默認(rèn)的python3版本
-
這個(gè)步驟主要的坑來自萬惡的Caffe,如果你裝過python3版的caffe,大概會(huì)有經(jīng)驗(yàn)一些,這里有幾個(gè)小坑提示一下:
- 最好在ncsdk目錄中的ncsdk.conf中,開啟caffe的cuda支持,即設(shè)置
CAFFE_USE_CUDA=yes,這樣你之后也能用這個(gè)caffe來訓(xùn)練模型 - caffe的依賴會(huì)在腳本中安裝,但有些Debian兼容問題要解決
- 開啟CUDA支持后,編譯caffe會(huì)找不到libboost-python3,因?yàn)樵赨buntu16.04里,它叫l(wèi)ibboost-python3.5,所以要軟鏈接一下:
- 最好在ncsdk目錄中的ncsdk.conf中,開啟caffe的cuda支持,即設(shè)置
cd /usr/lib/x86_64-linux-gnu/
sudo ln -s libboost_python-py35.so libboost_python3.so
- 其他可能出現(xiàn)的caffe的坑,可以在我博客找找答案,如果沒有的話,就去caffe的GitHub issue搜吧
測(cè)試
一波操作之后,我們裝好了ncsdk編譯模塊,可以下載我訓(xùn)練的caffe模型,嘗試編譯成ncs graph
git clone https://github.com/ahangchen/MobileNetSSD
mvNCCompile example/MobileNetSSD_deploy.prototxt -w MobileNetSSD_deploy.caffemodel -s 12 -is 300 300 -o ncs_mobilenet_ssd_graph
這里其實(shí)是調(diào)用python3去執(zhí)行/usr/local/bin/ncsdk/mvNCCompile.py這個(gè)文件, 不出意外在當(dāng)前版本(1.12.00)你會(huì)遇到這個(gè)錯(cuò)誤:
[Error 17] Toolkit Error: Internal Error: Could not build graph. Missing link: conv11_mbox_conf
這是因?yàn)镹CSDK在處理caffe模型的時(shí)候,會(huì)把conv11_mbox_conf_new節(jié)點(diǎn)叫做conv11_mbox_conf,所以build graph的時(shí)候就會(huì)找不著。因此需要為這種節(jié)點(diǎn)起一個(gè)別名,即,將conv11_mbox_conf_new起別名為conv11_mbox_conf,修改SDK代碼中的/usr/local/bin/ncsdk/Models/NetworkStage.py,在第85行后面添加:
if ''_new' in name:
self.alias.append(name[:-4])
于是就能編譯生成graph了,你會(huì)看到一個(gè)名為ncs_mobilenet_ssd_graph的文件。
上邊這個(gè)bug我已經(jīng)跟NCSDK的工程師講了,他們?cè)诟M(jìn)修這個(gè)bug:

測(cè)試端
NCSDK
測(cè)試端要安裝ncsdk python api,用于inference,實(shí)際上測(cè)試端能做的操作,訓(xùn)練端也都能做
git clone https://github.com/movidius/ncsdk
cd api/src
make install
從輸出日志可以發(fā)現(xiàn),將ncsdk的lib和include文件分別和系統(tǒng)的python2(/usr/bin/python2)和python3(/usr/bin/python3)做了關(guān)聯(lián)。
然后你可以下一個(gè)GitHub工程來跑一些測(cè)試:
git clone https://github.com/movidius/ncappzoo
cd ncappzoo/apps/hello_ncs_py
python3 hello_ncs.py
python2 hello_ncs.py
沒報(bào)錯(cuò)就是裝好了,測(cè)試端很簡(jiǎn)單。
OpenCV
看pyimagesearch這個(gè)教程
Caffe模型訓(xùn)練
就是正常的用caffe訓(xùn)練MobileNet-SSD,主要參考這個(gè)倉庫:
- MobileNet-SSD: https://github.com/chuanqi305/MobileNet-SSD
README里將步驟講得很清楚了
- 下載SSD-caffe(這個(gè)我們已經(jīng)在NCSDK里裝了)
- 下載chuanqi在VOC0712上預(yù)訓(xùn)練的模型
- 把MobileNet-SSD這個(gè)項(xiàng)目放到SSD-Caffe的examples目錄下,這一步可以不做,但是要對(duì)應(yīng)修改train.sh里的caffe目錄位置
- 創(chuàng)建你自己的
labelmap.prototxt,放到MobileNet-SSD目錄下,比如說,你是在coco預(yù)訓(xùn)練模型上訓(xùn)練的話,可以把coco的標(biāo)簽文件復(fù)制過來,將其中與你的目標(biāo)類(比如我的目標(biāo)類是Cattle)相近的類(比如Coco中是Cow)改成對(duì)應(yīng)的名字,并用它的label作為你的目標(biāo)類的label。(比如我用21這個(gè)類代表Cattle) - 用你自己的數(shù)據(jù)訓(xùn)練MobileNet-SSD,參考SSD-caffe的wiki,主要思路還是把你的數(shù)據(jù)轉(zhuǎn)換成類似VOC或者COCO的格式,然后生成lmdb,坑也挺多的:
- 假設(shè)你的打的標(biāo)簽是這樣一個(gè)文件
raw_label.txt,假裝我們數(shù)據(jù)集只有兩張圖片:
data/strange_animal/1017.jpg 0.487500 0.320675 0.670000 0.433193
data/strange_animal/1018.jpg 0.215000 0.293952 0.617500 0.481013
- 我們的目標(biāo)是將標(biāo)簽中涉及的
圖片和位置信息轉(zhuǎn)成這樣一個(gè)目錄(在ssd-caffe/data/coco目錄基礎(chǔ)上生成的):
coco_cattle
├── all # 存放全部圖片和xml標(biāo)簽文件
│ ├── 1017.jpg
│ ├── 1017.xml
│ ├── 1018.jpg
│ └── 1018.xml
├── Annotations # 存放全部標(biāo)簽xml
│ ├── 1017.xml
│ └── 1018.xml
├── create_data.sh # 將圖片轉(zhuǎn)為lmdb的腳本
├── create_list.py # 根據(jù)ImageSets里的數(shù)據(jù)集劃分文件,生成jpg和xml的對(duì)應(yīng)關(guān)系文件到coco_cattle目錄下,但我發(fā)現(xiàn)這個(gè)對(duì)應(yīng)關(guān)系文件用不上
├── images # 存放全部圖片
│ ├── 1017.jpg
│ └── 1018.jpg
├── ImageSets # 劃分訓(xùn)練集,驗(yàn)證集和測(cè)試集等,如果只想分訓(xùn)練和驗(yàn)證的話,可以把minival.txt,testdev.txt,test.txt內(nèi)容改成一樣的
│ ├── minival.txt
│ ├── testdev.txt
│ ├── test.txt
│ └── train.txt
├── labelmap_coco.prototxt # 如前所述的標(biāo)簽文件,改一下可以放到MobileNet-SSD目錄下
├── labels.txt
├── lmdb # 手動(dòng)創(chuàng)建這個(gè)目錄
│ ├── coco_cattle_minival_lmdb # 自動(dòng)創(chuàng)建的,由圖片和標(biāo)簽轉(zhuǎn)換來的LMDB文件
│ ├── coco_cattle_testdev_lmdb
│ ├── coco_cattle_test_lmdb
│ └── coco_cattle_train_lmdb
├── minival.log
├── README.md
├── testdev.log
├── test.log
└── train.log
- 其中,標(biāo)簽xml的格式如下:
<annotation>
<folder>train</folder>
<filename>86</filename>
<source>
<database>coco_cattle</database>
</source>
<size>
<width>720</width>
<height>1280</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>21</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>169</xmin>
<ymin>388</ymin>
<xmax>372</xmax>
<ymax>559</ymax>
</bndbox>
</object>
<object>
<name>21</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>169</xmin>
<ymin>388</ymin>
<xmax>372</xmax>
<ymax>559</ymax>
</bndbox>
</object>
</annotation>
代表一張圖中多個(gè)對(duì)象所在位置(bndbox節(jié)點(diǎn)表示),以及類別(name)。
一開始,
all,Annotations,images,ImageSets,lmdb四個(gè)目錄都是空的,你可以把自己的圖片放到隨便哪個(gè)地方,只要在raw_label.txt里寫好圖片路徑就行讀取
raw_label.txt,利用lxml構(gòu)造一棵dom tree,然后寫到Annotations對(duì)應(yīng)的xml里,并將對(duì)應(yīng)的圖片移動(dòng)到image目錄里,可以參考這份代碼。并根據(jù)我們?cè)O(shè)置的train or not標(biāo)志符將當(dāng)前這張圖片分配到訓(xùn)練集或測(cè)試集中(也就是往ImageSet/train.txt中寫對(duì)應(yīng)的圖片名)這樣一波操作之后,我們的
images和Annotations目錄里都會(huì)有數(shù)據(jù)了,接下來我們需要把它們一塊復(fù)制到all目錄下
cp images/* all/
cp Annotations/* all/
- 然后用create_data.sh將
all中的數(shù)據(jù),根據(jù)ImageSet中的數(shù)據(jù)集劃分,創(chuàng)建訓(xùn)練集和測(cè)試集的lmdb,這里對(duì)coco的create_data.sh做了一點(diǎn)修改:
cur_dir=$(cd $( dirname ${BASH_SOURCE[0]} ) && pwd )
root_dir=$cur_dir/../..
cd $root_dir
redo=true
# 這里改成all目錄
data_root_dir="$cur_dir/all"
# 這里改成自己的數(shù)據(jù)集名,也是我們這個(gè)目錄的名字
dataset_name="coco_cattle"
# 指定標(biāo)簽文件
mapfile="$root_dir/data/$dataset_name/labelmap_coco.prototxt"
anno_type="detection"
label_type="xml"
db="lmdb"
min_dim=0
max_dim=0
width=0
height=0
extra_cmd="--encode-type=jpg --encoded"
if $redo
then
extra_cmd="$extra_cmd --redo"
fi
for subset in minival testdev train test
do
python3 $root_dir/scripts/create_annoset.py --anno-type=$anno_type --label-type=$label_type --label-map-file=$mapfile --min-dim=$min_dim --max-dim=$max_dim --resize-width=$width --resize-height=$height --check-label $extra_cmd $data_root_dir $root_dir/data/$dataset_name/ImageSets/$subset.txt $data_root_dir/../$db/$dataset_name"_"$subset"_"$db examples/$dataset_name 2>&1 | tee $root_dir/data/$dataset_name/$subset.log
done
于是會(huì)lmdb目錄下會(huì)為每個(gè)劃分集合創(chuàng)建一個(gè)目錄,存放數(shù)據(jù)
├── lmdb
│ ├── coco_cattle_minival_lmdb
│ │ ├── data.mdb
│ │ └── lock.mdb
│ ├── coco_cattle_testdev_lmdb
│ │ ├── data.mdb
│ │ └── lock.mdb
│ ├── coco_cattle_test_lmdb
│ │ ├── data.mdb
│ │ └── lock.mdb
│ └── coco_cattle_train_lmdb
│ ├── data.mdb
│ └── lock.mdb
- 將5生成的lmdb鏈接到MobileNet-SSD的目錄下:
cd MobileNet-SSD
ln -s PATH_TO_YOUR_TRAIN_LMDB trainval_lmdb
ln -s PATH_TO_YOUR_TEST_LMDB test_lmdb
- 運(yùn)行
gen_model.sh生成三個(gè)prototxt(train, test, deploy)
# 默認(rèn)clone下來的目錄是沒有example這個(gè)目錄的,而gen_model.sh又會(huì)把文件生成到example目錄
mkdir example
./gen_model.sh
- 訓(xùn)練
./train.sh
這里如果爆顯存了,可以到example/MobileNetSSD_train.prototxt修改batch size,假如你batch size改到20,剛好可以吃滿GTX1060的6G顯存,但是跑到一定步數(shù)(設(shè)置在solver_test.prototxt里的test_interval變量),會(huì)執(zhí)行另一個(gè)小batch的test(這個(gè)batch size定義在example/MobileNetSSD_test.prototxt里),這樣就會(huì)再爆顯存,所以如果你的train_batch_size + test_batch_size <= 20的話才可以保證你在6G顯存上能順利完成訓(xùn)練,我的設(shè)置是train_batch_size=16, test_batch_size=4
一開始的training loss可能比較大,30左右,等到loss下降到2.x一段時(shí)間就可以ctrl+c退出訓(xùn)練了,模型權(quán)重會(huì)自動(dòng)保存在snapshot目錄下
運(yùn)行merge_bn.py將訓(xùn)練得到的模型去除bn層,得到可部署的Caffe模型,這樣你就能得到一個(gè)名為
MobileNetSSD_deploy.caffemodel的權(quán)重文件,對(duì)應(yīng)的prototxt為example/MobileNetSSD_deploy.prototxt離題那么久,終于來到主題,我們要把這個(gè)caffemodel編譯成NCS可運(yùn)行的graph,這個(gè)操作之前在搭環(huán)境的部分也提過:
mvNCCompile example/MobileNetSSD_deploy.prototxt -w MobileNetSSD_deploy.caffemodel -s 12 -is 300 300 -o ncs_mobilenet_ssd_graph
參數(shù)格式:
mvNCCompile prototxt路徑 -w 權(quán)重文件路徑 -s 最大支持的NCS數(shù)目 -is 輸入圖片寬度 輸入圖片高度 -o 輸出graph路徑
其實(shí)訓(xùn)練端相對(duì)于chuanqi的MobileNet-SSD沒啥改動(dòng),甚至訓(xùn)練參數(shù)也不用怎么改動(dòng),主要工作還是在數(shù)據(jù)預(yù)處理上,可以參考我的預(yù)處理代碼
樹莓派NCS模型測(cè)試
現(xiàn)在我們要用ncs版的ssd模型在樹莓派上進(jìn)行對(duì)圖片做檢測(cè),這個(gè)目標(biāo)一旦達(dá)成我們自然也能對(duì)視頻或攝像頭數(shù)據(jù)進(jìn)行檢測(cè)了。
倉庫結(jié)構(gòu)
ncs_detection
├── data # 標(biāo)簽文件
│ └── mscoco_label_map.pbtxt
├── file_helper.py # 文件操作輔助函數(shù)
├── model # 訓(xùn)練好的模型放在這里
│ ├── ncs_mobilenet_ssd_graph
│ └── README.md
├── ncs_detection.py # 主入口
├── object_detection # 改了一下TF的Object detection包中的工具類來用
│ ├── __init__.py
│ ├── protos
│ │ ├── __init__.py
│ │ ├── string_int_label_map_pb2.py
│ │ └── string_int_label_map.proto
│ └── utils
│ ├── __init__.py
│ ├── label_map_util.py
│ └── visualization_utils.py
├── r10 # 圖片數(shù)據(jù)
│ ├── 00000120.jpg
│ ├── 00000133.jpg
│ ├── 00000160.jpg
│ ├── 00000172.jpg
│ ├── 00000192.jpg
│ ├── 00000204.jpg
│ ├── 00000220.jpg
│ └── 00000236.jpg
├── README.md
└── total_cnt.txt
- 由于這個(gè)工程一開始是用Tensorflow Object Detection API做的,所以改了其中的幾個(gè)文件來讀標(biāo)簽和畫檢測(cè)框,將其中跟tf相關(guān)的代碼去掉。
- TF的圖片IO是用pillow做的,在樹莓派上速度奇慢,對(duì)一張1280x720的圖使用Image的get_data這個(gè)函數(shù)獲取數(shù)據(jù)需要7秒,所以我改成了OpenCV來做IO。
任務(wù)目標(biāo)
檢測(cè)r10目錄中的圖片中的對(duì)象,標(biāo)記出來,存到r10_tmp目錄里
流程
- 準(zhǔn)備目標(biāo)目錄
def config_init(dataset_pref):
os.system('mkdir %s_tmp' % dataset_pref)
os.system('rm %s_tmp/*' % dataset_pref)
- 指定模型路徑,標(biāo)簽位置,類別總數(shù),測(cè)試圖片路徑
PATH_TO_CKPT = 'model/ncs_mobilenet_ssd_graph'
PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt')
NUM_CLASSES = 81
TEST_IMAGE_PATHS = [os.path.join(img_dir, '%08d.jpg' % i) for i in range(start_index, end_index)]
- 發(fā)現(xiàn)并嘗試打開神經(jīng)計(jì)算棒
def ncs_prepare():
print("[INFO] finding NCS devices...")
devices = mvnc.EnumerateDevices()
if len(devices) == 0:
print("[INFO] No devices found. Please plug in a NCS")
quit()
print("[INFO] found {} devices. device0 will be used. "
"opening device0...".format(len(devices)))
device = mvnc.Device(devices[0])
device.OpenDevice()
return device
- 將NCS模型加載到NCS中
def graph_prepare(PATH_TO_CKPT, device):
print("[INFO] loading the graph file into RPi memory...")
with open(PATH_TO_CKPT, mode="rb") as f:
graph_in_memory = f.read()
# load the graph into the NCS
print("[INFO] allocating the graph on the NCS...")
detection_graph = device.AllocateGraph(graph_in_memory)
return detection_graph
- 準(zhǔn)備好標(biāo)簽與類名對(duì)應(yīng)關(guān)系
category_index = label_prepare(PATH_TO_LABELS, NUM_CLASSES)
- 讀取圖片,由于Caffe訓(xùn)練圖片采用的通道順序是RGB,而OpenCV模型通道順序是BGR,需要轉(zhuǎn)換一下
image_np = cv2.imread(image_path)
image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB)
- 使用NCS模型為輸入圖片推斷目標(biāo)位置
def predict(image, graph):
image = preprocess_image(image)
graph.LoadTensor(image, None)
(output, _) = graph.GetResult()
num_valid_boxes = output[0]
predictions = []
for box_index in range(num_valid_boxes):
base_index = 7 + box_index * 7
if (not np.isfinite(output[base_index]) or
not np.isfinite(output[base_index + 1]) or
not np.isfinite(output[base_index + 2]) or
not np.isfinite(output[base_index + 3]) or
not np.isfinite(output[base_index + 4]) or
not np.isfinite(output[base_index + 5]) or
not np.isfinite(output[base_index + 6])):
continue
(h, w) = image.shape[:2]
x1 = max(0, output[base_index + 3])
y1 = max(0, output[base_index + 4])
x2 = min(w, output[base_index + 5])
y2 = min(h, output[base_index + 6])
pred_class = int(output[base_index + 1]) + 1
pred_conf = output[base_index + 2]
pred_boxpts = (y1, x1, y2, x2)
prediction = (pred_class, pred_conf, pred_boxpts)
predictions.append(prediction)
return predictions
其中,首先將圖片處理為Caffe輸入格式,縮放到300x300,減均值,縮放到0-1范圍,轉(zhuǎn)浮點(diǎn)數(shù)
def preprocess_image(input_image):
PREPROCESS_DIMS = (300, 300)
preprocessed = cv2.resize(input_image, PREPROCESS_DIMS)
preprocessed = preprocessed - 127.5
preprocessed = preprocessed * 0.007843
preprocessed = preprocessed.astype(np.float16)
return preprocessed
graph推斷得到目標(biāo)位置,類別,分?jǐn)?shù)
graph.LoadTensor(image, None)
(output, _) = graph.GetResult()
其中的output格式為,
[
目標(biāo)數(shù)量,
class,score,xmin, ymin, xmax, ymax,
class,score,xmin, ymin, xmax, ymax,
...
]
- 根據(jù)我們感興趣的類別和分?jǐn)?shù)進(jìn)行過濾
def predict_filter(predictions, score_thresh):
num = 0
boxes = list()
scores = list()
classes = list()
for (i, pred) in enumerate(predictions):
(cl, score, box) = pred
if cl == 21 or cl == 45 or cl == 19 or cl == 76 or cl == 546 or cl == 32:
if score > score_thresh:
boxes.append(box)
scores.append(score)
classes.append(cl)
num += 1
return num, boxes, classes, scores
- 用OpenCV將當(dāng)前圖片的對(duì)象數(shù)量寫到圖片右上角,用pillow(tf庫中的實(shí)現(xiàn))將當(dāng)前圖片的對(duì)象位置和類別在圖中標(biāo)出
def add_str_on_img(image, total_cnt):
cv2.putText(image, '%d' % total_cnt, (image.shape[1] - 100, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
result = vis_util.visualize_boxes_and_labels_on_image_array(
image_np,
np.squeeze(valid_boxes).reshape(num, 4),
np.squeeze(valid_classes).astype(np.int32).reshape(num, ),
np.squeeze(valid_scores).reshape(num, ),
category_index,
use_normalized_coordinates=True,
min_score_thresh=score_thresh,
line_thickness=8)
- 保存圖片
cv2.imwrite('%s_tmp/%s' % (dataset_pref, image_path.split('/')[-1]),
cv2.cvtColor(result, cv2.COLOR_RGB2BGR))
- 釋放神經(jīng)計(jì)算棒
def ncs_clean(detection_graph, device):
detection_graph.DeallocateGraph()
device.CloseDevice()
運(yùn)行
python2 ncs_detection.py
結(jié)果
| 框架 | 圖片數(shù)量/張 | 耗時(shí) |
|---|---|---|
| TensorFlow | 1800 | 60min |
| NCS | 1800 | 10min |
| TensorFlow | 1 | 2sec |
| NCS | 1 | 0.3sec |
性能提升6倍!單張圖300毫秒,可以說是毫秒級(jí)檢測(cè)了。在論壇上有霓虹國的同行嘗試后,甚至評(píng)價(jià)其為“超爆速”。
擴(kuò)展
單根NCS一次只能運(yùn)行一個(gè)模型,但是我們可以用多根NCS,多線程做檢測(cè),達(dá)到更高的速度,具體可以看Reference第二條。
Reference
- https://www.pyimagesearch.com/2018/02/19/real-time-object-detection-on-the-raspberry-pi-with-the-movidius-ncs/
- https://qiita.com/PINTO/items/b97b3334ed452cb555e2
看了這么久,還不快去給我的GitHub點(diǎn)star!