OpenVINO? 實現圖像實例分割

慧聰教育網 2021-06-15 09:05 來源:慧聰教育網

【慧聰教育網】實例分割概念

圖像實例分割是在對象檢測的基礎上進一步細化,分離對象的前景與背景,實現像素級別的對象分離。所以圖像實例分割是基于對象檢測的基礎上進一步提升。圖像實例分割在目標檢測、人臉檢測、表情識別、醫學圖像處理與疾病輔助診斷、視頻監控與對象跟蹤、零售場景的貨架空缺識別等場景下均有應用。很多人會把圖像語義分割跟實例分割搞混淆、其實圖像的語義分割(Semantic Segmentation)與圖像的實例分割(Instance Segmentation)是兩個不同的概念,看下圖:

OpenVINO? 實現圖像實例分割

圖-1(來自COCO數據集論文)

左側是圖像語義分割的結果,幾個不同的類別人、羊、狗、背景分別用不同的顏色表示;右側是圖像實例分割的結果,對每只羊都用不同的顏色表示,而且把每個對象從背景中分離出來。這個就是語義分割跟實例分割的區別,直白點可以說就是語義分割是對每個類別、實例分割是針對每個對象(多個對象可能屬于同一個類別)。

常見的實例分割網絡

1.   Mask-RCNN實例分割網絡

圖像實例分割是在對象檢測的基礎上再多出個基于ROI的分割分支,基于這樣思想的實例分割MasK-RCNN就是其經典代表,它的網絡結構如下:

OpenVINO? 實現圖像實例分割

圖-2(來自Mask-RCNN的論文)

Mask-RCNN可以簡單的認為是Faster-RCNN得基礎上加上一個實例分割分支。

2.   RetinaMask實例分割網絡

RetinaMask可以看出RetinaNet對象檢測網絡跟Mask-RCNN實例分割網絡的兩個優勢組合,基于特征金字塔實現了更好的Mask預測,網絡結構圖示如下:

OpenVINO? 實現圖像實例分割

圖-3(來自RetinaMask論文)

3.  PANet實例分割網絡

PANet主要工作是基于Mask-RCNN網絡上改進所得,作者通過改進Backbone部分提升了特征提取能力,通過自適應的池化操作得到更多融合特征,基于全鏈接融合產生mask,最終取得了比Mask-RCNN更好的實例分割效果,該模型的結構如下:

OpenVINO? 實現圖像實例分割

圖-4(來自PANet論文)

其中全鏈接特征融合mask分支如下圖:

OpenVINO? 實現圖像實例分割

圖-5(來自PANet論文)

4.  YOLACT實例分割網絡

該實例分割網絡也是基于RetinaNet對象檢測網絡的基礎上,添加一個Mask分支,不過在添加Mask分支的時候它的Mask分支設計跟RetinaMask有所不同,該網絡的結構圖示如下:

OpenVINO? 實現圖像實例分割

圖-6(來自YOLACT作者論文)

5.   CenterMask實例分割網絡

該實例網絡是基于FCOS對象檢測框架的基礎上,設計一個Mask分支輸出,該Mask分支被稱為空間注意力引導蒙板(Spatial Attention Guided Mask),該網絡的結構如下:

OpenVINO? 實現圖像實例分割

圖-7(來自CenterMask論文)

OpenVINO? 支持Mask-RCNN模型

OpenVINO? 中支持兩種實例分割模型分別是Mask-RCNN與YOLACT模型,其中Mask-RCNN模型支持來自intel官方庫文件、而YOLACT則來自公開的第三方提供。我們這里以官方的Mask-RCNN模型instance-segmentation-security-0050為例說明,該模型基于COCO數據集訓練,支持80個類別的實例分割,加上背景為81個類別。

OpenVINO? 支持部署Faster-RCNN與Mask-RCNN網絡時候輸入的解析都是基于兩個輸入層,它們分別是:

im_data : NCHW=[1x3x480x480]

im_info: 1x3 三個值分別是H、W、Scale=1.0

輸出有四個,名稱與輸出格式及解釋如下:

name: classes, shape: [100, ] 預測的100個類別可能性,值在[0~1]之間

name: scores: shape: [100, ] 預測的100個Box可能性,值在[0~1]之間

name: boxes, shape: [100, 4] 預測的100個Box坐標,左上角與右下角,基于輸入的480x480

name: raw_masks, shape: [100, 81, 28, 28] Box ROI區域的實例分割輸出,81表示類別(包含背景),28x28表示ROI大小,注意:此模型輸出大小為14x14

模型實例分割代碼演示

因為模型的加載與推理部分的代碼跟前面系列文章的非常相似,這里就不再給出。代碼演示部分重點在輸出的解析,為了簡化,我用了兩個for循環設置了輸入與輸出數據精度,然后直接通過hardcode的輸出層名稱來獲取推理之后各個輸出層對應的數據部分,首先獲取類別,根據類別ID與Box的索引,直接獲取實例分割mask,然后隨機生成顏色,基于mask實現與原圖BOX ROI的疊加,產生了實例分割之后的效果輸出。解析部分的代碼首先需要獲取推理以后的數據,獲取數據的代碼如下:

float w_rate = static_cast<float>(im_w) / 480.0;

    float h_rate = static_cast<float>(im_h) / 480.0;

 

    auto scores = infer_request.GetBlob("scores");

    auto boxes = infer_request.GetBlob("boxes");

    auto clazzes = infer_request.GetBlob("classes");

    auto raw_masks = infer_request.GetBlob("raw_masks");

    const float* score_data = static_cast<PrecisionTrait<Precision::FP32>::value_type*>(scores->buffer());

    const float* boxes_data = static_cast<PrecisionTrait<Precision::FP32>::value_type*>(boxes->buffer());

    const float* clazzes_data = static_cast<PrecisionTrait<Precision::FP32>::value_type*>(clazzes->buffer());

    const auto raw_masks_data = static_cast<PrecisionTrait<Precision::FP32>::value_type*>(raw_masks->buffer());

    const SizeVector scores_outputDims = scores->getTensorDesc().getDims();

    const SizeVector boxes_outputDims = boxes->getTensorDesc().getDims();

    const SizeVector mask_outputDims = raw_masks->getTensorDesc().getDims();

    const int max_count = scores_outputDims[0];

    const int object_size = boxes_outputDims[1];

    printf("mask NCHW=[%d, %d, %d, %d]\n", mask_outputDims[0], mask_outputDims[1], mask_outputDims[2], mask_outputDims[3]);

    int mask_h = mask_outputDims[2];

    int mask_w = mask_outputDims[3];

    size_t box_stride = mask_h * mask_w * mask_outputDims[1];

 

然后根據輸出數據格式開始解析Box框與Mask,這部分的代碼如下:

for (int n = 0; n < max_count; n++) {

        float confidence = score_data[n];

        float xmin = boxes_data[n*object_size] * w_rate;

        float ymin = boxes_data[n*object_size + 1] * h_rate;

        float xmax = boxes_data[n*object_size + 2] * w_rate;

        float ymax = boxes_data[n*object_size + 3] * h_rate;

        if (confidence > 0.5) {

            cv::Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));

            cv::Rect box;

            float x1 = std::min(std::max(0.0f, xmin), static_cast<float>(im_w));

            float y1 = std::min(std::max(0.0f, ymin), static_cast<float>(im_h));

            float x2 = std::min(std::max(0.0f, xmax), static_cast<float>(im_w));

            float y2 = std::min(std::max(0.0f, ymax), static_cast<float>(im_h));

            box.x = static_cast<int>(x1);

            box.y = static_cast<int>(y1);

            box.width = static_cast<int>(x2 - x1);

            box.height = static_cast<int>(y2 - y1);

            int label = static_cast<int>(clazzes_data[n]);

            std::cout << "confidence: " << confidence << " class name: " << coco_labels[label] << std::endl;

            // 解析mask

            float* mask_arr = raw_masks_data + box_stride * n + mask_h * mask_w * label;

            cv::Mat mask_mat(mask_h, mask_w, CV_32FC1, mask_arr);

            cv::Mat roi_img = src(box);

            cv::Mat resized_mask_mat(box.height, box.width, CV_32FC1);

            cv::resize(mask_mat, resized_mask_mat, cv::Size(box.width, box.height));

 

            cv::Mat uchar_resized_mask(box.height, box.width, CV_8UC3, color);

 

            roi_img.copyTo(uchar_resized_mask, resized_mask_mat <= 0.5);

            cv::addWeighted(uchar_resized_mask, 0.7, roi_img, 0.3, 0.0f, roi_img);

            cv::putText(src, coco_labels[label].c_str(), box.tl() + (box.br() - box.tl()) / 2, cv::FONT_HERSHEY_PLAIN, 1.0, cv::Scalar(0, 0, 255), 1, 8);

        }

    }

其中Mask部分的時候有個技巧的地方,首先獲取類別,然后根據類別,直接獲取Mask中對應的通道數據生成二值Mask圖像,添加上顏色,加權混合到ROI區域即可得到輸出結果。最終的代碼運行如下:

OpenVINO? 實現圖像實例分割

圖-8(實例分割)

如欲了解更多OpenVINO? 開發資料,

請掃描下方二維碼,

我們會把最新資料及時推送給您。

OpenVINO? 實現圖像實例分割

 

免責聲明:凡注明來源本網的所有作品,均為本網合法擁有版權或有權使用的作品,歡迎轉載,注明出處。非本網作品均來自互聯網,轉載目的在于傳遞更多信息,并不代表本網贊同其觀點和對其真實性負責。
百度 好搜 搜狗

警告:本站禁止未滿18周歲訪客瀏覽,如果當地法律禁止請自覺離開本站!收藏本站:請使用Ctrl+D進行收藏