天天看點

使用opencv實作執行個體分割,一學就會|附源碼

無論是從酒店房間接聽電話、在辦公裡樓工作,還是根本不想在家庭辦公室等情況,電話會議模糊功能都可以讓會議與會者專注于自己,這樣的功能對于在家工作并希望保護其家庭成員隐私的人特别有用。

為了實作這樣的功能,微軟利用計算機視覺、深度學習以及執行個體分割技術實作。

在之前的博文中,介紹了如何利用YOLO以及OpenCV實作目标檢測的功能 ,今天将采用Mask R-CNN來建構視訊模糊功能。

使用OpenCV進行執行個體分割

使用opencv實作執行個體分割,一學就會|附源碼
https://youtu.be/puSN8Dg-bdI

在本教程的第一部分中,将簡要介紹執行個體分割;之後将使用執行個體分割和OpenCV來實作:

  • 從視訊流中檢測出使用者并分割;
  • 模糊背景;
  • 将使用者添加回流本身;

什麼是執行個體分割?

使用opencv實作執行個體分割,一學就會|附源碼

圖1:對象檢測和執行個體分割之間的差別

如上圖所示,對于對象檢測(左圖,Object Detection)而言,在各個對象周圍繪制出一個框。 執行個體分割(右圖,Instance Segmentation)而言,是需要嘗試确定哪些像素屬于對應的對象。通過上圖,可以清楚地看到兩者之間的差異。

執行對象檢測時,是需要:

  • 計算每個對象的邊界框(x,y的)-坐标;
  • 然後将類标簽與每個邊界框相關聯;

從上可以看出,對象檢測并沒有告訴我們關于對象本身的形狀,而隻獲得了一組邊界框坐标。而另一方面,執行個體分割需要計算出一個逐像素掩模用于圖像中的每個對象。

即使對象具有相同的類标簽,例如上圖中的兩隻狗,我們的執行個體分割算法仍然報告總共三個獨特的對象:兩隻狗和一隻貓。

使用執行個體分割,可以更加細緻地了解圖像中的對象——比如知道對象存在于哪個(x,y)坐标中。此外,通過使用執行個體分割,可以輕松地從背景中分割前景對象。

本文使用Mask R-CNN進行執行個體分割。

項目結構

項目樹:

$ tree --dirsfirst
.
├── mask-rcnn-coco
│   ├── frozen_inference_graph.pb
│   ├── mask_rcnn_inception_v2_coco_2018_01_28.pbtxt
│   └── object_detection_classes_coco.txt
└── instance_segmentation.py

1 directory, 4 files           

項目包括一個目錄(由三個檔案組成)和一個Python腳本:

  • mask-rcnn-coco/

    :Mask R-CNN模型目錄包含三個檔案:
    • frozen_inference_graph .pb

      :Mask R-CNN模型的權重,這些權重是在COCO資料集上預先訓練所得到的;
    • mask_rcnn_inception_v2_coco_2018_01_28 .pbtxt

      :Mask R-CNN模型的配置檔案,如果你想在自己的資料集上建構及訓練自己的模型, 可以參閱網上的一些資源更改該配置檔案
    • object_detection_classes_coco.txt

      :此文本檔案中列出了資料集中包含的90個類,每行表示一個類别。
  • instance_segmentation .py

    :背景模糊腳本,本文的核心内容, 将詳細介紹該代碼并評估其算法性能。

使用OpenCV實作執行個體分割

下面開始使用OpenCV實作執行個體分割。首先打開

instance_segmentation .py

檔案并插入以下代碼:

# import the necessary packages
from imutils.video import VideoStream
import numpy as np
import argparse
import imutils
import time
import cv2
import os           

在開始編寫腳本時,首先需要導入必要的包,并且需要配置好編譯環境。本文使用的

OpenCV版本

為3.4.3。如果個人的計算機配置檔案不同,需要對其進行更新。強烈建議将此軟體放在隔離的虛拟環境中,推薦使用conda安裝。

下面解析

指令行參數

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--mask-rcnn", required=True,
    help="base path to mask-rcnn directory")
ap.add_argument("-c", "--confidence", type=float, default=0.5,
    help="minimum probability to filter weak detections")
ap.add_argument("-t", "--threshold", type=float, default=0.3,
    help="minimum threshold for pixel-wise mask segmentation")
ap.add_argument("-k", "--kernel", type=int, default=41,
    help="size of gaussian blur kernel")
args = vars(ap.parse_args())           

每個指令行參數的描述可以在下面找到:

  • mask-rcnn

    :Mask R-CNN目錄的基本路徑;
  • confidence

    :濾除弱檢測的最小機率,可以将此值的預設值設定為0.5,也可以通過指令行傳遞不同的值;
  • threshold

    :像素掩碼分割的最小門檻值,預設設定為 0.3;
  • kernel

    :高斯模糊核心的大小,預設設定41,這是通過實驗得到的經驗值;

下面加載資料集的标簽和OpenCV執行個體分割模型:

# load the COCO class labels our Mask R-CNN was trained on
labelsPath = os.path.sep.join([args["mask_rcnn"],
    "object_detection_classes_coco.txt"])
LABELS = open(labelsPath).read().strip().split("\n")
 
# derive the paths to the Mask R-CNN weights and model configuration
weightsPath = os.path.sep.join([args["mask_rcnn"],
    "frozen_inference_graph.pb"])
configPath = os.path.sep.join([args["mask_rcnn"],
    "mask_rcnn_inception_v2_coco_2018_01_28.pbtxt"])
 
# load our Mask R-CNN trained on the COCO dataset (90 classes)
# from disk
print("[INFO] loading Mask R-CNN from disk...")
net = cv2.dnn.readNetFromTensorflow(weightsPath, configPath)           

标簽檔案位于

mask-rcnn - coco

目錄,指定好路徑後就可以加載标簽檔案了。同樣地,

weightsPath

configPath

也執行類型的操作。

基于這兩個路徑,利用

dnn

子產品初始化神經網絡。在開始處理視訊幀之前,需要将Mask R-CNN加載到記憶體中(隻需要加載一次)。

下面構模組化糊核心并啟動網絡攝像頭視訊流:

# construct the kernel for the Gaussian blur and initialize whether
# or not we are in "privacy mode"
K = (args["kernel"], args["kernel"])
privacy = False
 
# initialize the video stream, then allow the camera sensor to warm up
print("[INFO] starting video stream...")
vs = VideoStream(src=0).start()
time.sleep(2.0)           

模糊核心元組也通過行指令設定。此外,項目有兩種模式:“正常模式”和“隐私模式”。是以, 布爾值

privacy

用于模式邏輯,上述代碼将其初始化為

False

網絡攝像頭視訊流用

VideoStream(src=0).start()

,首先暫停兩秒鐘以讓傳感器預熱。

初始化了所有變量和對象後,就可以從網絡攝像頭開始處理幀了:

# loop over frames from the video file stream
while True:
    # grab the frame from the threaded video stream
    frame = vs.read()
 
    # resize the frame to have a width of 600 pixels (while
    # maintaining the aspect ratio), and then grab the image
    # dimensions
    frame = imutils.resize(frame, width=600)
    (H, W) = frame.shape[:2]
 
    # construct a blob from the input image and then perform a
    # forward pass of the Mask R-CNN, giving us (1) the bounding
    # box coordinates of the objects in the image along with (2)
    # the pixel-wise segmentation for each specific object
    blob = cv2.dnn.blobFromImage(frame, swapRB=True, crop=False)
    net.setInput(blob)
    (boxes, masks) = net.forward(["detection_out_final",
        "detection_masks"])           

在每次疊代中,将抓取一幀并将其調整為設定的寬度,同時保持縱橫比。此外,為了之後的縮放操作,繼續并提取幀的尺寸。然後,建構一個

blob

并完成前向傳播網絡。

結果輸出是

boxes

masks

,雖然需要用到掩碼(mask),但還需要使用邊界框(boxes)中包含的資料。

下面對索引進行排序并初始化變量:

# sort the indexes of the bounding boxes in by their corresponding
    # prediction probability (in descending order)
    idxs = np.argsort(boxes[0, 0, :, 2])[::-1]
 
    # initialize the mask, ROI, and coordinates of the person for the
    # current frame
    mask = None
    roi = None
    coords = None           

通過其對應的預測機率對邊界框的索引進行排序,假設具有最大相應檢測機率的人是我們的使用者。然後初始化

mask

roi

以及邊界框的坐标。

周遊索引并過濾結果:

# loop over the indexes
    for i in idxs:
        # extract the class ID of the detection along with the
        # confidence (i.e., probability) associated with the
        # prediction
        classID = int(boxes[0, 0, i, 1])
        confidence = boxes[0, 0, i, 2]
 
        # if the detection is not the 'person' class, ignore it
        if LABELS[classID] != "person":
            continue
 
        # filter out weak predictions by ensuring the detected
        # probability is greater than the minimum probability
        if confidence > args["confidence"]:
            # scale the bounding box coordinates back relative to the
            # size of the image and then compute the width and the
            # height of the bounding box
            box = boxes[0, 0, i, 3:7] * np.array([W, H, W, H])
            (startX, startY, endX, endY) = box.astype("int")
            coords = (startX, startY, endX, endY)
            boxW = endX - startX
            boxH = endY - startY           

從idxs開始循環,然後,使用框和目前索引提取classID和 置信度。随後,執行第一個過濾器—— “人”。如果遇到任何其他對象類,繼續下一個索引。下一個過濾器確定預測的置信度超過通過指令行參數設定的門檻值。

如果通過了該測試,那麼将邊界框坐标縮放回圖像的相對尺寸,然後提取坐标和對象的寬度/高度。

計算掩膜并提取ROI:

# extract the pixel-wise segmentation for the object,
            # resize the mask such that it's the same dimensions of
            # the bounding box, and then finally threshold to create
            # a *binary* mask
            mask = masks[i, classID]
            mask = cv2.resize(mask, (boxW, boxH),
                interpolation=cv2.INTER_NEAREST)
            mask = (mask > args["threshold"])
 
            # extract the ROI and break from the loop (since we make
            # the assumption there is only *one* person in the frame
            # who is also the person with the highest prediction
            # confidence)
            roi = frame[startY:endY, startX:endX][mask]
            break           

上述代碼首先提取掩碼,并調整其大小,之後應用門檻值來建立二進制掩碼本身。示例如下圖所示:

使用opencv實作執行個體分割,一學就會|附源碼

圖2:使用OpenCV和執行個體分割在網絡攝像頭前通過執行個體分割計算的二進制掩碼

從上圖中可以看到,假設所有白色像素都是人(即前景),而所有黑色像素都是背景。使用掩碼後,通過NumPy陣列切片計算roi。之後循環斷開,這是因為你找到最大機率的人了。

如果處于“隐私模式”,需要進行初始化輸出幀并計算模糊:

# initialize our output frame
    output = frame.copy()
 
    # if the mask is not None *and* we are in privacy mode, then we
    # know we can apply the mask and ROI to the output image
    if mask is not None and privacy:
        # blur the output frame
        output = cv2.GaussianBlur(output, K, 0)
 
        # add the ROI to the output frame for only the masked region
        (startX, startY, endX, endY) = coords
        output[startY:endY, startX:endX][mask] = roi           

其輸出幀隻是原始幀的副本。

如果我們倆都:

  • 有一個非空的掩膜;
  • 處于“ 隐私模式”;
  • ... ...

然後将使用模糊背景并将掩碼應用于輸出幀。

下面顯示輸出以及圖像處理按鍵:

# show the output frame
    cv2.imshow("Video Call", output)
    key = cv2.waitKey(1) & 0xFF
 
    # if the `p` key was pressed, toggle privacy mode
    if key == ord("p"):
        privacy = not privacy
 
    # if the `q` key was pressed, break from the loop
    elif key == ord("q"):
        break
 
# do a bit of cleanup
cv2.destroyAllWindows()
vs.stop()           

keypresses

被擷取其值,有兩個值可供選擇,但會導緻不同的行為:

  • “p”:按下此鍵時, 打開或關閉“ 隐私模式”;
  • “q”:如果按下此鍵,将跳出循環并“退出”腳本;

每當退出時,上述代碼就會關閉打開的視窗并停止視訊流。

執行個體分割結果

現在已經實作了OpenCV執行個體分割算法,下面看看其實際應用!

打開一個終端并執行以下指令:

$ python instance_segmentation.py --mask-rcnn mask-rcnn-coco --kernel 41
[INFO] loading Mask R-CNN from disk...
[INFO] starting video stream...           

圖3:示範了一個用于網絡聊天的“隐私過濾器”

通過啟用“隐私模式”,可以:

  • 使用OpenCV執行個體分割查找具有最大相應機率的人物檢測(最可能是最接近相機的人);
  • 模糊視訊流的背景;
  • 将分割的、非模糊的人重疊到視訊流上;

下面列出一個視訊示範(需外網):

使用opencv實作執行個體分割,一學就會|附源碼

看完視訊會立即注意到,并沒有獲得真正的實時性能——每秒隻處理幾幀。為什麼是這樣?

要回答這些問題,請務必參考以下部分。

限制、缺點和潛在的改進

第一個限制是最明顯的——OpenCV執行個體分割的實作太慢而無法實時運作。在CPU上運作,每秒隻能處理幾幀。為了獲得真正的實時執行個體分割性能,需要利用到GPU。

但其中存在的問題是:

  • OpenCV對其

    dnn

    子產品的GPU支援相當有限;
  • 目前,它主要支援英特爾GPU;
  • NVIDIA CUDA GPU支援正在開發中,但目前尚未推出;

一旦OpenCV正式支援

dnn

子產品的NVIDIA GPU版本, 就能夠更輕松地建構實時(甚至超實時)的深度學習應用程式。但就目前而言,本文的執行個體分割教程隻作為示範:

此外,也可以做出的另一項改進與分割的人重疊在模糊的背景上有關。當将本文的實作與Microsoft的Office 365視訊模糊功能進行比較時,就會發現Microsoft會更加“流暢”。但也可以通過利用一些alpha混合來模仿這個功能。

對執行個體分割管道進行簡單而有效的更新可能是:

  • 使用形态學操作來增加蒙版的大小;
  • 在掩膜本身塗抹少量高斯模糊,幫助平滑掩碼;
  • 将掩碼值縮放到範圍[0,1];
  • 使用縮放蒙版建立alpha圖層;
  • 在模糊的背景上疊加平滑的掩膜+人;

或者,也可以計算掩膜本身的輪廓,然後應用掩膜近似來幫助建立“更平滑”的掩碼。

總結

看完本篇文章,你應該學習了如何使用OpenCV、Deep Learning和Python實作執行個體分割了吧。執行個體分割大體過程如下:

  • 檢測圖像中的每個對象;
  • 計算每個對象的逐像素掩碼;

注意,即使對象屬于同一類,執行個體分割也應為每個對象傳回唯一的掩碼;

作者資訊

Adrian Rosebrock ,機器學習,人工智能,圖像處理

本文由阿裡雲雲栖社群組織翻譯。 

文章原标題《Instance segmentation with OpenCV》,譯者:海棠,審校:Uncle_LLD。 

文章為簡譯,更為詳細的内容,

請檢視原文

繼續閱讀