本文來自6月份出版的新書 《 OpenCV深度學習應用與效能優化實踐 》 ,作者團隊也是OpenCV DNN 模組的主要貢獻者,是國內唯一的系統介紹OpenCV DNN 推理模組原理和實踐的書,文末有福利,留言贈書 8 本。 OpenCV 是業界使用最為廣泛的計算機視覺庫,隨著深度學習在計算機視覺領域的廣泛應用,OpenCV 自3.3開始加入對深度學習推理的支援,即OpenCV DNN模組。 它支援TensorFlow、Caffe、Torch、DarkNet、ONNX 和 OpenVINO 格式的網路模型,開發者無需考慮模型格式的差異,直接呼叫DNN模組相關介面即可快速建立深度學習應用。 OpenVINO是英特爾推出的視覺推理加速工具包。OpenCV 3.4.1版本加入了英特爾推理引擎後端(英特爾推理引擎是OpenVINO中的一個元件),為英特爾平臺的模型推理進行加速。 本文將以MobileNet-SSD模型為例,展示如何使用OpenCV和OpenVINO快速建立深度學習應用。 在深入程式碼之前,讓我們瞭解一下OpenVINO工具包以及OpenCV是如何跟OpenVINO互動的。
OpenVINO工具包 2018 年 5 月 Intel 釋出了 OpenVINO(Open Visual Inferencing and Neural Network Optimization, 開放視覺推理和神經網路優化)工具包,旨在為運行於 Intel 計算平臺的基於神經網路的視覺推理任務提供高效能加速方案。 OpenVINO 提供了一整套在 Intel 計算裝置上完成深度學習推理計算的解決方案,它支援 Intel CPU、 GPU、FPGA 和 Movidius 計算棒等多種裝置。 OpenVINO 工具包的主要元件是 DLDT(Deep Learning Deployment Toolkit,深度學習部署工具包)。DLDT主要包括模型優化器(Model Optimizer)和推理引擎(Inference engine,IE)兩部分。 模型優化器負責將各種格式的深度神經網路模型轉換成統一的自定義格式,並在轉換過程中進行模型優化;推理引擎接受經過模型優化器轉換並優化的網路模型,為Intel的各種計算裝置提供高效能的神經網路推理運算。
使用 DLDT 進行神經網路模型的部署,典型工作流程如圖所示。
OpenCV,OpenVINO,深度學習,python
1)訓練一個DLDT 支援的深度學習框架網路模型(Train a Model) ; 2)使用模型優化器對網路模型進行編譯和優化(Run Model Optimizer),生成Openvino IR(Intermediate Representation,中間表示)格式的網路配置檔案(.xml 檔案)和模型引數檔案(.bin 檔案); 3)呼叫 Inference Engine(即 Intel 推理引擎)進行網路運算,並將結果返回給 User Application(應用程式)。 OpenCV如何使用OpenVINO OpenCV的推理引擎後端使用OpenVINO的推理引擎API完成推理任務。推理引擎後端有兩種工作模式:模型優化器模式和構建器模式,如下圖所示。
OpenCV,OpenVINO,深度學習,python
模型優化器模式直接使用DLDT模型優化器編譯後的OpenVINO格式(.xml和.bin)的網路模型進行推理計算,這種模式下,網路模型將被直接載入到推理引擎中,創建出一個推理引擎網路物件。而構建器模式則需要在DNN模組內部將網路模型逐層轉換成內部表示,並通過推理引擎後端建立內部推理引擎網路。
相比構建器模式,模型優化器模式支援網路中所有的層,不需要逐層建立DNN網路,而是直接載入OpenVINO模型到推理引擎,能夠減少在網路載入和運算推理過程中報錯的情況。 瞭解了OpenCV 和 OpenVINO 相關內容之後,接下來詳細講解如何基於OpenCV和OpenVINO構建深度學習應用。 基於OpenCV和OpenVINO建立深度學習應用 第一步:安裝OpenVINO 這裡我們以 Ubuntu 18.04 上安裝 OpenVINO 為例。從官網註冊並下載OpenVINO開發包的Linux版本, 官網下載地址: https://software.intel.com/content/www/us/en/develop/tools/openvino-toolkit/choose-download/linux.html 如果下載順利,你將得到檔名為 l_openvino_toolkit_p_< 版本號>.tgz 的壓縮包,為了相容更多的網路模型,我們選擇安裝目前最新的OpenVINO版本(OpenVINO-2020.3.194)。 OpenVINO開發包中包含了相應版本的OpenCV,安裝OpenVINO時會預設安裝OpenCV,因此無需額外安裝OpenCV 。 1. 解壓並安裝 OpenVINO 開發包核心元件(Linux版本) win10 設置請看 WIN10 + OpenVINO C++範例編譯及下載(測試成功) - $ tar -xvzf l_openvino_toolkit_p_2020.3.194.tgz
- $ cd l_openvino_toolkit_p_2020.3.194
複製代碼
執行圖形化安裝命令:
然後一路選擇“Next”安裝預設元件即可。如果一切順利,安裝檔案將位於/opt/intel/openvino_2020.3.194/,同時會生成一個符號連結/opt/intel/openvino 指向最新的安裝目錄。至此,OpenVINO 核心元件安裝完成,接下來安裝依賴包。 2. 安裝依賴包 使用 OpenVINO 寫一個完整的視覺類應用,除了 OpenVINO 本身之外,還需要安裝一些依賴包,包括但不限於 FFMpeg影片框架、CMake 編譯工具、libusb(Movidius 神經計算棒 外掛需要用到)等,安裝步驟如下: - $ cd /opt/intel/openvino/install_dependencies
複製代碼
執行以下命令安裝必要的依賴包: - $ sudo -E ./install_openvino_dependencies.sh
複製代碼
設定環境變數: - $ source /opt/intel/openvino/bin/setupvars.sh
複製代碼
建議將以上環境變數設定命令加入到使用者的環境腳本當中,方法如下:
在其中加入以內容: - $ source /opt/intel/openvino/bin/setupvars.sh
複製代碼
按 Esc 鍵,然後輸入“:wq”儲存並退出。接下來配置模型優化器,依次執行以下命令: - $ cd /opt/intel/openvino/deployment_tools/model_optimizer/install_prerequisites
複製代碼
- $ sudo ./install_prerequisites.sh
複製代碼
上面這條命令會安裝所有的深度學習框架的支援,如果只希望安裝某一個框架的支援,以安裝Caffe 框架支援為例,可以這麼做: - $ sudo ./install_prerequisites_caffe.sh
複製代碼
至此,安裝工作結束,下面驗證安裝好的 OpenVINO 環境是否可以工作。 3. 驗證 OpenVINO 環境 進入推理引擎示例程式目錄: - $ cd /opt/intel/openvino/deployment_tools/demo
複製代碼
執行圖片分類示例程式的驗證指令碼: - $ ./demo_squeezenet_download_convert_run.sh
複製代碼
如果一切順利,輸出結果將如圖所示。 
確保 OpenVINO 安裝成功後,重新啟動電腦:
檢查OpenCV版本: - $ python
- > import cv2 as cv
- > print(cv.__version__)
複製代碼
如果安裝成功,可以看到如下輸出: 4.3.0-openvino-2020.3.0第二步:模型準備 首先下載MobileNet-SSD的caffe模型: • 模型引數檔案MoblieNetSSD_deploy.caffemodel下載地址: https://github.com/PINTO0309/MobileNet-SSD-RealSense/blob/master/caffemodel/MobileNetSSD/MobileNetSSD_deploy.caffemodel • 網路結構檔案MoblieNetSSD_deploy.prototxt下載地址: https://raw.githubusercontent.com/chuanqi305/MobileNet-SSD/daef68a6c2f5fbb8c88404266aa28180646d17e0/MobileNetSSD_deploy.prototxt 模型下載好後,將caffe模型轉換成OpenVINO格式: 1. 進入OpenVINO安裝目錄下的模型優化器目錄: - cd <INSTALL_DIR>/deployment_tools/model_optimizer
複製代碼
2. 使用OpenVINO模型優化器指令碼mo.py將caffe模型轉換成OpenVINO格式的模型: - python3 mo.py --input_model <working_dir>/MobileNetSSD_deploy.caffemodel --input_proto <working_dir>/MobileNetSSD_deploy.prototxt -o <output_path>
複製代碼
通過input_model和input_proto 兩個引數指明模型的引數和結構,並指定轉換後網路的儲存路徑。
通過這一步,我們可以得到OpenVINO格式的MobileNet-SSD網路模型,包括MobileNetSSD_deploy.xml檔案和MobileNetSSD_deploy.bin 檔案。 第三步:使用OpenVINO模型進行目標檢測 接下來開始進入我們的最後一步,使用轉換好的OpenVINO格式的MobileNet-SSD模型進行實時目標檢測。下面是整個目標檢測過程的流程圖。 流程圖
OpenCV,OpenVINO,深度學習,python
程式程式碼 為方便起見,我們採用Python語言來建立應用。首先匯入必要的Python庫,包括numpy、argparse和cv2(OpenCV)。 - # 匯入必要的庫
- import numpy as np
- import argparse
- import cv2
複製代碼
接下來使用argparse對命令列輸入引數進行解析。 # 組建引數parse - # 組建引數parse
- parser = argparse.ArgumentParser(
- description='Script to run MobileNet-SSD object detection network ')
- parser.add_argument("--video", help="path to video file. If empty, camera's stream will be used")
- parser.add_argument("--model", type=str, default="MobileNetSSD_deploy",help="path to trained model")
- parser.add_argument("--thr", default=0.2, type=float, help="confidence threshold to filter out weak detections")
- args = parser.parse_args(args=[])
複製代碼
程式執行命令列只需輸入下列 3 個引數。 • video:圖片或影片路徑,不設定則從攝像頭讀取資料。 • model:訓練好的模型路徑。 • thr:分類閾值。 定義一個變數classNames,儲存分類標籤。注意,我們下載的模型是基於20分類資料集訓練出來的,因此這裡的類別標籤有20個(加上背景是21個)。也有針對90分類資料集訓練的模型,此處需要使用相對應的標籤定義。 - # 類別標籤變數.
- classNames = { 0: 'background',
- 1: 'aeroplane', 2: 'bicycle', 3: 'bird', 4: 'boat',
- 5: 'bottle', 6: 'bus', 7: 'car', 8: 'cat', 9: 'chair',
- 10: 'cow', 11: 'diningtable', 12: 'dog', 13: 'horse',
- 14: 'motorbike', 15: 'person', 16: 'pottedplant',
- 17: 'sheep', 18: 'sofa', 19: 'train', 20: 'tvmonitor' }
複製代碼
定義模型輸入大小,MobileNet-SSD接受的輸入圖片大小為300*300
接下來通過cv2.dnn.Net_readFromModelOptimizer()函式讀取轉換好的OpenVINO模型檔案,初始化網路物件 net,並設定加速後端: - # 載入模型
- net = cv2.dnn.Net_readFromModelOptimizer(args.model+".xml", args.model+".bin")
- # 設定推理引擎後端
- net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)
複製代碼
注意,這裡cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE指明使用的是推理引擎後端。 - # 設定運算裝置
- net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
複製代碼
推理引擎後端支援多種型別的運算裝置,這裡指定使用CPU作為運算裝置。如果平臺有英特爾整合顯示卡,也可以設定成 cv2.dnn.DNN_TARGET_OPENCL ,使用GPU來進行加速。 下一步設定影象輸入裝置,根據引數video選擇可用輸入裝置或者輸入路徑: - # 開啟影片檔案或攝像頭
- if args.video:
- cap = cv2.VideoCapture(args.video)
- else:
- cap = cv2.VideoCapture(0)
複製代碼
現在進入程式關鍵步驟,開始迴圈處理輸入裝置讀到的幀影象,將讀取到的影象轉換成網路輸入: - while True:
- # 讀取一幀影象
- ret, frame = cap.read()
- # 將圖片轉換成模型輸入
- blob = cv2.dnn.blobFromImage(frame, 0.007843, input_size, (127.5, 127.5, 127.5), False)
複製代碼
這一步引數比較多,也比較重要,做一下重點講解: 1)frame是輸入影象,0.007843是縮放因子, 2)input_size是模型所接受的輸入大小,(127.5, 127.5, 127.5)是影象均值,它結合前面的縮放因子,在函式內部對輸入影象做正規化處理。 3)False表示不進行R和B通道置換。當模型接受的通道順序和影象均值的通道順序不一致時,swapRB 需要設定成 true。 接下來是設定網路輸入和執行網路推理: - # 轉換後的待輸入物件blob設定為網路輸入
- net.setInput(blob)
- # 開始進行網路推理運算
- detections = net.forward()
複製代碼
然後是結果解析。 MobileNet-SSD 模型的檢測結果detections是一個4維陣列,陣列的4個維度大小分別是[1,1,N,7]。
N表示檢測出的物件數目,7表示每個物件用一個含有7個元素的陣列描述,7個元素分別代表圖片id、型別id、置信度、物件框左上角x座標,物件框左下角y座標、物件框右下角x座標、物件框右下角y座標,接下來根據這個4維陣列繪製執行結果。 首先獲取影象大小,根據再通過遍歷所有檢測出的物件,通過引數 thr 篩除部分置信度較低的物件: - # 獲取輸入影象尺寸(300x300)
- cols = input_size[1]
- rows = input_size[0]
- for i in range(detections.shape[2]):
- confidence = detections[0, 0, i, 2] # 目標物件置信度
- if confidence > args.thr: # Filter prediction
- class_id = int(detections[0, 0, i, 1]) # 目標物件類別標籤
複製代碼
獲取被檢測物件框的頂點座標和影象的縮放比,並獲取實際目標物件框的座標: - # 目標位置
- xLeftBottom = int(detections[0, 0, i, 3] * cols)
- yLeftBottom = int(detections[0, 0, i, 4] * rows)
- xRightTop = int(detections[0, 0, i, 5] * cols)
- yRightTop = int(detections[0, 0, i, 6] * rows)
- # 變換尺度
- heightFactor = frame.shape[0]/300.0
- widthFactor = frame.shape[1]/300.0
- # 獲取目標實際座標
- xLeftBottom = int(widthFactor * xLeftBottom)
- yLeftBottom = int(heightFactor * yLeftBottom)
- xRightTop = int(widthFactor * xRightTop)
- yRightTop = int(heightFactor * yRightTop)
複製代碼
最後將檢測結果繪製到原始影象上: - # 框出目標物件
- cv2.rectangle(frame, (xLeftBottom, yLeftBottom), (xRightTop, yRightTop),
- (0, 255, 0))
- # 標記標籤和置信度
- if class_id in classNames:
- label = classNames[class_id] + ": " + str(confidence)
- labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
- yLeftBottom = max(yLeftBottom, labelSize[1])
- cv2.rectangle(frame, (xLeftBottom, yLeftBottom - labelSize[1]),
- (xLeftBottom + labelSize[0], yLeftBottom + baseLine),
- (255, 255, 255), cv2.FILLED)
- cv2.putText(frame, label, (xLeftBottom, yLeftBottom),
- cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
- print(label) # 輸出類別和置信度
- cv2.namedWindow("frame", cv2.WINDOW_NORMAL)
- cv2.imshow("frame", frame)
- key = cv2.waitKey(1) & 0xFF
- if key == ord("q"): # 按下q鍵退出程式
- break
- if key == ord("s"): # 按下s鍵儲存檢測影象
- cv2.imwrite('detection.jpg', frame)
複製代碼
至此,整個目標檢測應用的程式碼就完成了,接下來輸入以下命令執行目標檢測程式: - $ python mobilenet_ssd_python.py
複製代碼
這裡我們使用的預設引數,通過攝像頭直接採集影象,預設模型採用我們轉換好的MobileNetSSD_deploy(.xml/.bin)檔案,閾值設為0.2。 這時候我們可以看到終端中將輸出檢測到的目標置信度,同時在影片影象中框處檢測到的目標,左上角為目標分類結果和分類置信度。
終端中的檢測結果輸出: chair: 0.9xx chair: 0.2xx person: 0.9xx tvmonitor: 0.xx tvmonitor: 0.xx ... 實際檢測影象如下:
OpenCV,OpenVINO,深度學習,python
參考資料 [1] Ubuntu 安裝OpenVINO : https://docs.openvinotoolkit.org ... openvino_linux.html [2] 《OpenCV深度學習應用與效能優化實踐》 以上內容摘自本月剛剛出版的《 OpenCV深度學習應用與效能優化實踐 》一書,經出版方授權釋出。
完整程式碼 - # 匯入必要的庫
- import numpy as np
- import argparse
- import cv2
- parser = argparse.ArgumentParser( description='Script to run MobileNet-SSD object detection network ')
- parser.add_argument("--video", help="path to video file. If empty, camera's stream will be used")
- parser.add_argument("--model", type=str, default="MobileNetSSD_deploy",help="path to trained model")
- parser.add_argument("--thr", default=0.2, type=float, help="confidence threshold to filter out weak detections")
- args = parser.parse_args(args=[])
- # 類別標籤變數.
- classNames = { 0: 'background',
- 1: 'aeroplane', 2: 'bicycle', 3: 'bird', 4: 'boat',
- 5: 'bottle', 6: 'bus', 7: 'car', 8: 'cat', 9: 'chair',
- 10: 'cow', 11: 'diningtable', 12: 'dog', 13: 'horse',
- 14: 'motorbike', 15: 'person', 16: 'pottedplant',
- 17: 'sheep', 18: 'sofa', 19: 'train', 20: 'tvmonitor' }
- input_size = (300, 300)
- # 載入模型
- net = cv2.dnn.Net_readFromModelOptimizer(args.model+".xml", args.model+".bin")
- # 設定推理引擎後端
- net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)
- # 開啟影片檔案或攝像頭
- if args.video:
- cap = cv2.VideoCapture(args.video)
- else:
- cap = cv2.VideoCapture(0)
- i = 0
- while True:
- # 讀取一幀影象
- ret, frame = cap.read()
- # 將圖片轉換成模型輸入
- blob = cv2.dnn.blobFromImage(frame, 0.007843, input_size, (127.5, 127.5, 127.5), False)
- #blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)),0.007843, (300, 300), 127.5)
- # 轉換後的待輸入物件blob設定為網路輸入
- net.setInput(blob)
- # 開始進行網路推理運算
- detections = net.forward()
- # 獲取輸入影象尺寸(300x300)
- cols = input_size[1]
- rows = input_size[0]
- for i in range(detections.shape[2]):
- confidence = detections[0, 0, i, 2] # 目標物件置信度
- if confidence > args.thr: # Filter prediction
- class_id = int(detections[0, 0, i, 1]) # 目標物件類別標籤
- # 目標位置
- xLeftBottom = int(detections[0, 0, i, 3] * cols)
- yLeftBottom = int(detections[0, 0, i, 4] * rows)
- xRightTop = int(detections[0, 0, i, 5] * cols)
- yRightTop = int(detections[0, 0, i, 6] * rows)
- # 變換尺度
- heightFactor = frame.shape[0] / 300.0
- widthFactor = frame.shape[1] / 300.0
- # 獲取目標實際座標
- xLeftBottom = int(widthFactor * xLeftBottom)
- yLeftBottom = int(heightFactor * yLeftBottom)
- xRightTop = int(widthFactor * xRightTop)
- yRightTop = int(heightFactor * yRightTop)
- # 框出目標物件
- cv2.rectangle(frame, (xLeftBottom, yLeftBottom), (xRightTop, yRightTop),(0, 255, 0))
- # 標記標籤和置信度
- if class_id in classNames:
- label = classNames[class_id] + ": " + str(confidence)
- labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
- yLeftBottom = max(yLeftBottom, labelSize[1])
- cv2.rectangle(frame, (xLeftBottom, yLeftBottom - labelSize[1]),
- (xLeftBottom + labelSize[0], yLeftBottom + baseLine), (255, 255, 255), cv2.FILLED)
- cv2.putText(frame, label, (xLeftBottom, yLeftBottom),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
- # 輸出類別和置信度
- print(label)
- cv2.namedWindow("frame", cv2.WINDOW_NORMAL)
- cv2.imshow("frame", frame)
- key = cv2.waitKey(1) & 0xFF
- if key == ord("q"): # 按下q鍵退出程式
- break
- if key == ord("s"): # 按下s鍵儲存檢測影象
- cv2.imwrite('detection.jpg', frame)
複製代碼
文章出處
|