手把手教你使用OpenVINO部署NanoDet模型

2021-02-07 PandaCV

【GiantPandaCV】本文為大家介紹了一個手把手使用OpenVINO部署NanoDet的教程,並開源部署的全部代碼,在Intel i7-7700HQ CPU做到了6ms一幀的速度。

0x0. nanodet簡介

NanoDet (https://github.com/RangiLyu/nanodet)是一個速度超快和輕量級的Anchor-free 目標檢測模型。想了解算法本身的可以去搜一搜之前機器之心的介紹。

0x1. 環境配置

OpenVINO和OpenCV安裝包(編譯好了,也可以自己從官網下載自己編譯)可以從連結: https://pan.baidu.com/s/1zxtPKm-Q48Is5mzKbjGHeg 密碼: gw5c下載

tar -xvzf l_openvino_toolkit_p_2020.4.287.tgz
cd l_openvino_toolkit_p_2020.4.287
sudo ./install_GUI.sh 一路next安裝
cd /opt/intel/openvino/install_dependencies
sudo ./install_openvino_dependencies.sh
vi ~/.bashrc

source /opt/intel/openvino/bin/setupvars.sh
source /opt/intel/openvino/opencv/setupvars.sh

cd /opt/intel/openvino/deployment_tools/model_optimizer/install_prerequisites
sudo ./install_prerequisites_onnx.sh(模型是從onnx轉為IR文件,只需配置onnx依賴)

tar -xvzf opencv-3.4.2.zip 解壓OpenCV到用戶根目錄即可,以便後續調用。(這是我編譯好的版本,有需要可以自己編譯)

0x2. NanoDet模型訓練和轉換ONNX

git clone https://github.com/Wulingtian/nanodet.git

定位到nanodet目錄,進入tools目錄,打開export.py文件,配置cfg_path model_path out_path三個參數

定位到nanodet目錄,運行 python tools/export.py 得到轉換後的onnx模型

python /opt/intel/openvino/deployment_tools/model_optimizer/mo_onnx.py --input_model onnx模型 --output_dir 期望模型輸出的路徑。得到IR文件

0x3. NanoDet模型部署

sudo apt install cmake 安裝cmake

git clone https://github.com/Wulingtian/nanodet_openvino.git (求star!)

cd nanodet_openvino 打開CMakeLists.txt文件,修改OpenCV_INCLUDE_DIRS和OpenCV_LIBS_DIR,之前已經把OpenCV解壓到根目錄了,所以按照你自己的路徑指定

定位到nanodet_openvino,cd models 把之前生成的IR模型(包括bin和xml文件)文件放到該目錄下

定位到nanodet_openvino, cd test_imgs 把需要測試的圖片放到該目錄下

定位到nanodet_openvino,編輯main.cpp,xml_path參數修改為"../models/你的模型名稱.xml"

編輯 num_class 設置類別數,例如:我訓練的模型是安全帽檢測,只有1類,那麼設置為1

編輯 src 設置測試圖片路徑,src參數修改為"../test_imgs/你的測試圖片"

mkdir build; cd build; cmake .. ;make

./detect_test 輸出平均推理時間,以及保存預測圖片到當前目錄下,至此,部署完成!

0x4. 核心代碼一覽
//主要對圖片進行預處理,包括resize和歸一化
std::vector<float> Detector::prepareImage(cv::Mat &src_img){

    std::vector<float> result(INPUT_W * INPUT_H * 3);
    float *data = result.data();
    float ratio = float(INPUT_W) / float(src_img.cols) < float(INPUT_H) / float(src_img.rows) ? float(INPUT_W) / float(src_img.cols) : float(INPUT_H) / float(src_img.rows);
    cv::Mat flt_img = cv::Mat::zeros(cv::Size(INPUT_W, INPUT_H), CV_8UC3);
    cv::Mat rsz_img = cv::Mat::zeros(cv::Size(src_img.cols*ratio, src_img.rows*ratio), CV_8UC3);
    cv::resize(src_img, rsz_img, cv::Size(), ratio, ratio);

    rsz_img.copyTo(flt_img(cv::Rect(0, 0, rsz_img.cols, rsz_img.rows)));
    flt_img.convertTo(flt_img, CV_32FC3);

    int channelLength = INPUT_W * INPUT_H;
    std::vector<cv::Mat> split_img = {
            cv::Mat(INPUT_W, INPUT_H, CV_32FC1, data + channelLength * 2),
            cv::Mat(INPUT_W, INPUT_H, CV_32FC1, data + channelLength),
            cv::Mat(INPUT_W, INPUT_H, CV_32FC1, data)
    };

    cv::split(flt_img, split_img);
    for (int i = 0; i < 3; i++) {

        split_img[i] = (split_img[i] - img_mean[i]) / img_std[i];
    }

    return result;
}

//加載IR模型,初始化網絡
bool Detector::init(string xml_path,double cof_threshold,double nms_area_threshold,int input_w, int input_h, int num_class, int r_rows, int r_cols, std::vector<int> s, std::vector<float> i_mean,std::vector<float> i_std){
    _xml_path = xml_path;
    _cof_threshold = cof_threshold;
    _nms_area_threshold = nms_area_threshold;
    INPUT_W = input_w;
    INPUT_H = input_h;
    NUM_CLASS = num_class;
    refer_rows = r_rows;
    refer_cols = r_cols;
    strides = s;
    img_mean = i_mean;
    img_std = i_std;
    Core ie;
    auto cnnNetwork = ie.ReadNetwork(_xml_path); 

    InputsDataMap inputInfo(cnnNetwork.getInputsInfo());
    InputInfo::Ptr& input = inputInfo.begin()->second;
    _input_name = inputInfo.begin()->first;
    input->setPrecision(Precision::FP32);
    input->getInputData()->setLayout(Layout::NCHW);
    ICNNNetwork::InputShapes inputShapes = cnnNetwork.getInputShapes();
    SizeVector& inSizeVector = inputShapes.begin()->second;
    cnnNetwork.reshape(inputShapes);
    _outputinfo = OutputsDataMap(cnnNetwork.getOutputsInfo());
    for (auto &output : _outputinfo) {
        output.second->setPrecision(Precision::FP32);
    }
    _network =  ie.LoadNetwork(cnnNetwork, "CPU");
    return true;
}
//模型推理及獲取輸出結果
vector<Detector::Bbox> Detector::process_frame(Mat& inframe){

    cv::Mat showImage = inframe.clone();
    std::vector<float> pr_img = prepareImage(inframe);
    InferRequest::Ptr infer_request = _network.CreateInferRequestPtr();
    Blob::Ptr frameBlob = infer_request->GetBlob(_input_name);
    InferenceEngine::LockedMemory<void> blobMapped = InferenceEngine::as<InferenceEngine::MemoryBlob>(frameBlob)->wmap();
    float* blob_data = blobMapped.as<float*>();

    memcpy(blob_data, pr_img.data(), 3 * INPUT_H * INPUT_W * sizeof(float));

    infer_request->Infer();
    vector<Rect> origin_rect;
    vector<float> origin_rect_cof;
    int i=0;
    vector<Bbox> bboxes;
    for (auto &output : _outputinfo) {
        auto output_name = output.first;
        Blob::Ptr blob = infer_request->GetBlob(output_name);
        LockedMemory<const void> blobMapped = as<MemoryBlob>(blob)->rmap();
        float *output_blob = blobMapped.as<float *>();
        bboxes = postProcess(showImage,output_blob);
        ++i;
    }
    return bboxes;
}
//對模型輸出結果進行解碼及nms
std::vector<Detector::Bbox> Detector::postProcess(const cv::Mat &src_img,
                              float *output) {
    GenerateReferMatrix();
    std::vector<Detector::Bbox> result;
    float *out = output;
    float ratio = std::max(float(src_img.cols) / float(INPUT_W), float(src_img.rows) / float(INPUT_H));
    cv::Mat result_matrix = cv::Mat(refer_rows, NUM_CLASS + 4, CV_32FC1, out);
    for (int row_num = 0; row_num < refer_rows; row_num++) {
        Detector::Bbox box;
        auto *row = result_matrix.ptr<float>(row_num);
        auto max_pos = std::max_element(row + 4, row + NUM_CLASS + 4);
        box.prob = row[max_pos - row];
        if (box.prob < _cof_threshold)
            continue;
        box.classes = max_pos - row - 4;
        auto *anchor = refer_matrix.ptr<float>(row_num);
        box.x = (anchor[0] - row[0] * anchor[2] + anchor[0] + row[2] * anchor[2]) / 2 * ratio;
        box.y = (anchor[1] - row[1] * anchor[2] + anchor[1] + row[3] * anchor[2]) / 2 * ratio;
        box.w = (row[2] + row[0]) * anchor[2] * ratio;
        box.h = (row[3] + row[1]) * anchor[2] * ratio;
        result.push_back(box);
    }
    NmsDetect(result);
    return result;
}//主要對圖片進行預處理,包括resize和歸一化
std::vector<float> Detector::prepareImage(cv::Mat &src_img){

    std::vector<float> result(INPUT_W * INPUT_H * 3);
    float *data = result.data();
    float ratio = float(INPUT_W) / float(src_img.cols) < float(INPUT_H) / float(src_img.rows) ? float(INPUT_W) / float(src_img.cols) : float(INPUT_H) / float(src_img.rows);
    cv::Mat flt_img = cv::Mat::zeros(cv::Size(INPUT_W, INPUT_H), CV_8UC3);
    cv::Mat rsz_img = cv::Mat::zeros(cv::Size(src_img.cols*ratio, src_img.rows*ratio), CV_8UC3);
    cv::resize(src_img, rsz_img, cv::Size(), ratio, ratio);

    rsz_img.copyTo(flt_img(cv::Rect(0, 0, rsz_img.cols, rsz_img.rows)));
    flt_img.convertTo(flt_img, CV_32FC3);

    int channelLength = INPUT_W * INPUT_H;
    std::vector<cv::Mat> split_img = {
            cv::Mat(INPUT_W, INPUT_H, CV_32FC1, data + channelLength * 2),
            cv::Mat(INPUT_W, INPUT_H, CV_32FC1, data + channelLength),
            cv::Mat(INPUT_W, INPUT_H, CV_32FC1, data)
    };

    cv::split(flt_img, split_img);
    for (int i = 0; i < 3; i++) {

        split_img[i] = (split_img[i] - img_mean[i]) / img_std[i];
    }

    return result;
}

//加載IR模型,初始化網絡
bool Detector::init(string xml_path,double cof_threshold,double nms_area_threshold,int input_w, int input_h, int num_class, int r_rows, int r_cols, std::vector<int> s, std::vector<float> i_mean,std::vector<float> i_std){
    _xml_path = xml_path;
    _cof_threshold = cof_threshold;
    _nms_area_threshold = nms_area_threshold;
    INPUT_W = input_w;
    INPUT_H = input_h;
    NUM_CLASS = num_class;
    refer_rows = r_rows;
    refer_cols = r_cols;
    strides = s;
    img_mean = i_mean;
    img_std = i_std;
    Core ie;
    auto cnnNetwork = ie.ReadNetwork(_xml_path); 

    InputsDataMap inputInfo(cnnNetwork.getInputsInfo());
    InputInfo::Ptr& input = inputInfo.begin()->second;
    _input_name = inputInfo.begin()->first;
    input->setPrecision(Precision::FP32);
    input->getInputData()->setLayout(Layout::NCHW);
    ICNNNetwork::InputShapes inputShapes = cnnNetwork.getInputShapes();
    SizeVector& inSizeVector = inputShapes.begin()->second;
    cnnNetwork.reshape(inputShapes);
    _outputinfo = OutputsDataMap(cnnNetwork.getOutputsInfo());
    for (auto &output : _outputinfo) {
        output.second->setPrecision(Precision::FP32);
    }
    _network =  ie.LoadNetwork(cnnNetwork, "CPU");
    return true;
}
//模型推理及獲取輸出結果
vector<Detector::Bbox> Detector::process_frame(Mat& inframe){

    cv::Mat showImage = inframe.clone();
    std::vector<float> pr_img = prepareImage(inframe);
    InferRequest::Ptr infer_request = _network.CreateInferRequestPtr();
    Blob::Ptr frameBlob = infer_request->GetBlob(_input_name);
    InferenceEngine::LockedMemory<void> blobMapped = InferenceEngine::as<InferenceEngine::MemoryBlob>(frameBlob)->wmap();
    float* blob_data = blobMapped.as<float*>();

    memcpy(blob_data, pr_img.data(), 3 * INPUT_H * INPUT_W * sizeof(float));

    infer_request->Infer();
    vector<Rect> origin_rect;
    vector<float> origin_rect_cof;
    int i=0;
    vector<Bbox> bboxes;
    for (auto &output : _outputinfo) {
        auto output_name = output.first;
        Blob::Ptr blob = infer_request->GetBlob(output_name);
        LockedMemory<const void> blobMapped = as<MemoryBlob>(blob)->rmap();
        float *output_blob = blobMapped.as<float *>();
        bboxes = postProcess(showImage,output_blob);
        ++i;
    }
    return bboxes;
}
//對模型輸出結果進行解碼及nms
std::vector<Detector::Bbox> Detector::postProcess(const cv::Mat &src_img,
                              float *output) {
    GenerateReferMatrix();
    std::vector<Detector::Bbox> result;
    float *out = output;
    float ratio = std::max(float(src_img.cols) / float(INPUT_W), float(src_img.rows) / float(INPUT_H));
    cv::Mat result_matrix = cv::Mat(refer_rows, NUM_CLASS + 4, CV_32FC1, out);
    for (int row_num = 0; row_num < refer_rows; row_num++) {
        Detector::Bbox box;
        auto *row = result_matrix.ptr<float>(row_num);
        auto max_pos = std::max_element(row + 4, row + NUM_CLASS + 4);
        box.prob = row[max_pos - row];
        if (box.prob < _cof_threshold)
            continue;
        box.classes = max_pos - row - 4;
        auto *anchor = refer_matrix.ptr<float>(row_num);
        box.x = (anchor[0] - row[0] * anchor[2] + anchor[0] + row[2] * anchor[2]) / 2 * ratio;
        box.y = (anchor[1] - row[1] * anchor[2] + anchor[1] + row[3] * anchor[2]) / 2 * ratio;
        box.w = (row[2] + row[0]) * anchor[2] * ratio;
        box.h = (row[3] + row[1]) * anchor[2] * ratio;
        result.push_back(box);
    }
    NmsDetect(result);
    return result;
}

0x5. 推理時間展示及預測結果展示平均推理時間6ms左右,CPU下實時推理安全帽檢測結果

至此完成了NanoDet在X86 CPU上的部署,希望有幫助到大家。

歡迎關注GiantPandaCV/PandaCV, 在這裡你將看到獨家的深度學習分享,堅持原創,每天分享我們學習到的新鮮知識。( • ̀ω•́ )✧

有對文章相關的問題,或者想要加入交流群,歡迎添加BBuf微信:

二維碼

為了方便讀者獲取資料以及我們公眾號的作者發布一些Github工程的更新,我們成立了一個QQ群,二維碼如下,感興趣可以加入。

公眾號QQ交流群

相關焦點

  • 乾貨|OpenVINO 模型性能評估工具—DL Workbench 的介紹與使用
    這裡我們選擇單一流推理模式,批量大小保持不變,分別兩次修改流處理單元數為2和16進行推理,可以發現不同的流處理單元下,模型的性能會有不同的表現。默認方式又叫最大性能校準,可優化模型以獲得最佳性能。適用於注釋或未注釋的數據集,是不可控制的模型精度下降,將支持INT8執行的所有層都轉換為INT8精度。
  • C++實現yolov5的OpenVINO部署
    [GiantPandaCV導語] 本文介紹了一種使用c++實現的,使用OpenVINO部署yolov5的方法。此方法在2020年9月結束的極市開發者榜單中取得後廚老鼠識別賽題第四名。2020年12月,注意到yolov5有了許多變化,對部署流程重新進行了測試,並進行了整理。希望能給需要的朋友一些參考,節省一些踩坑的時間。模型訓練1.
  • tensorflow+目標檢測:龍哥教你學視覺—LabVIEW深度學習教程
    實現觀察模型評估結果圖像7、學會利用labview實現導出tensorflow凍結圖模型文件pb8、學會利用labview實現導出tensorflow凍結圖模型文件pb轉為openvino模型文件IR9、學會利用labview實現tensorflow/openvino 模型pb/IR文件的加載10、學會利用labview實現tensorflow
  • 看OpenVINO DL Streamer如何加速效率
    啟動Docker後這些範列預設會安裝在 /opt/intel/openvino_2021.2.185/data_processing/dl_streamer/samples 路徑下(openvino後方數字代表對應版本)。執行範例前要先下載相關預訓練模型及參數文件,其工作命令如下所示。
  • 輕鬆學pytorch之使用onnx runtime實現pytorch模型部署
    /paddle等深度學習框架訓練的模型都可以轉換為ONNX格式,然後ONNX格式模型可以通過ONNX runtime組件實現模型的推理預測並加速,從而實現不基於原來框架的模型部署。CPU版本,想要完成CUDA版本的推理,需要安裝onnxruntime-gpu版本,安裝的命令行如下:pip install onnxruntime-gpu使用GPU推理支持需要VC++與CUDA版本匹配支持,這個坑比較多,而且onnxruntime版本不同支持的CUDA版本也不一樣。
  • 推薦 :手把手教你用Flask輕鬆部署機器學習模型(附代碼&連結)
    作者:Abhinav Sagar 翻譯:申利彬 校對:吳金笛本文可以讓你把訓練好的機器學習模型使用
  • Tensorflow實戰系列:手把手教你使用CNN進行圖像分類(附完整代碼)
    【導讀】專知小組計劃近期推出Tensorflow實戰系列,計劃教大家手把手實戰各項子任務。
  • 英特爾重磅開源OpenVINO !附送的預訓練模型是最大亮點
    各種AI技術落地,不僅要依賴強大的GPU訓練模型,也需要在各種邊緣設備上部署的加速。目前,AI模型的訓練基本被NVIDIA的晶片所壟斷,但顯然模型推斷是更大的市場。英特爾正是瞄準這一市場默默布局!OpenVINO全稱Open Visual Inference Neural network Optimization,專為計算機視覺神經網絡推斷而生,使用它可以使得模型在英特爾的晶片上計算更快,同時適合異構計算平臺(CPU+GPU)的部署,通過它可以直接調用超過100個目前公開的和定製化的模型。
  • 模型部署翻車記:PyTorch轉onnx踩坑實錄
    在深度學習模型部署時,從pytorch轉換onnx的過程中,踩了一些坑。本文總結了這些踩坑記錄,希望可以幫助其他人。首先,簡單說明一下pytorch轉onnx的意義。在pytorch訓練出一個深度學習模型後,需要在TensorRT或者openvino部署,這時需要先把Pytorch模型轉換到onnx模型之後再做其它轉換。因此,在使用pytorch訓練深度學習模型完成後,在TensorRT或者openvino或者opencv和onnxruntime部署時,pytorch模型轉onnx這一步是必不可少的。
  • 手把手教你編寫一個上位機
    一、前言 大家好,我是ZhengN,本次來教大家編寫一個基於QT的簡單的上位機。學習一個新的東西我們都從最基礎地實例開始,比如學習C語言我們會從編寫一個hello程序開始、學習嵌入式我們從點燈開始。界面如:演示視頻:二、QT環境搭建 在開始編寫上位機之前我們先來一起搭建一下QT開發環境(不然就不是手把手了,哈哈)。
  • 手把手教你用 Flask,Docker 和 Kubernetes 部署Python機器學習模型(附代碼)
    將機器學習(ML)模型部署到生產環境中的一個常見模式是將這些模型作為 RESTful API 微服務公開,這些微服務從 Docker 容器中託管,例如使用 SciKit Learn 或 Keras 包訓練的 ML 模型,這些模型可以提供對新數據的預測。然後,可以將它們部署到雲環境中,以處理維護連續可用性所需的所有事情,例如容錯、自動縮放、負載平衡和滾動服務更新。
  • 手把手教你做MOD MagicaVoxel保姆級基礎教程
    表慌,這期教程將會手把手教你操作。本期MOD學習教程特別感謝熱心玩家@Android-KitKat,看了之後就連手殘如寶哥都會做MOD了呢!話不多說,快來一起GOOD GOOD STUDY 吧!寶藏世界的模型基本上都是體素(Voxel)模型。包括裝備,坐騎,寵物,船,礦車。
  • keras教程:手把手教你做聊天機器人(下)—— 快速搭建seq2seq模型
    搭建seq2seq模型3. 訓練模型,並預測聊天效果並且,使用「字典」和「語料」,我們已經完成了第1步準備的工作。感興趣的同學,可以戳這裡:《 keras教程:手把手教你做聊天機器人(上) 》這一期,我們來構建機器人最核心的部分:seq2seq模型下面,我們就開始啦~~如果我問你:「今天的心情如何?」你會如何回答我?
  • 開發者說丨手把手教你實現多傳感器融合技術
    這不僅是利用了多個傳感器相互協同操作的優勢,而且也綜合處理了其它信息源的數據來提高整個傳感器系統的智能化。雖然自動駕駛在全球範圍內已掀起浪潮,但是從技術方面而言依然存在挑戰。目前自動駕駛的痛點在於穩定可靠的感知及認知,包括清晰的視覺、優質的算法、多傳感器融合以及高效強大的運算能力。
  • 教你如何使用ASRT訓練中文語音識別模型
    ,本文將手把手按順序教你如何使用ASRT語音識別系統訓練一個中文語音識別模型。如果使用Visual StudioCode、Spider或者PyCharm等IDE,可以直接在代碼目錄下打開train_mspeech.py文件點擊運行按鈕。當模型訓練收斂的時候,可以直接按ctrl + C 或者在IDE裡點擊停止運行按鈕,以停止訓練模型,此時,應該已經在model_speech/ 目錄下對應的模型名稱裡保存了很多模型參數文件。
  • 3倍加速CPU上的BERT模型部署
    但是BERT模型的缺點是計算量過於龐大,對於推理部署有著很大的挑戰。最近我們看到越來越多的產品開始從一些輕量級的模型轉向使用BERT,所以BERT模型的部署性能變得越來越重要。最近,在學術界和工業界,大家都在加緊研發深度學習編譯器用於優化模型部署的性能,比如TVM [2], MLIR [3], 和Glow [4]。並且一些近期的成果[2,5] 也證明了深度學習編譯器可以有效地降低模型推理的時間。
  • 1.8M 超輕量目標檢測模型 NanoDet,比 YOLO 跑得快,上線兩天 Star 量超 200
    但是,在移動端目標檢測算法上,YOLO 系列和 SSD 等 Anchor-base 的模型一直佔據主導地位。近日,GitHub 上出現了一個項目 nanodet,它開源了一個移動端實時的 Anchor-free 檢測模型,希望能夠提供不亞於 YOLO 系列的性能,而且同樣方便訓練和移植。該項目上線僅兩天,Star 量已經超過 200。
  • 實用教程詳解:模型部署,用DNN模塊部署YOLOv5目標檢測(附原始碼)
    在典型的機器學習和深度學習項目中,我們通常從定義問題陳述開始,然後是數據收集和準備(數據預處理)和模型構建(模型訓練),對吧?但是,最後,我們希望我們的模型能夠提供給最終用戶,以便他們能夠利用它。模型部署是任何機器學習項目的最後階段之一,可能有點棘手。如何將機器學習模型傳遞給客戶/利益相關者?
  • 模型轉換實戰分享:OpenPose手部關鍵點檢測模型的遷移部署
    當你在開源平臺上看到一個優質的深度學習模型並想使用它時,很多時候會遇到一個棘手的問題,就是這個模型所使用的深度學習框架與你所熟悉的框架並不相同