訂閱
糾錯(cuò)
加入自媒體

非最大抑制算法是如何工作的?

簡(jiǎn)介

你曾經(jīng)使用過(guò)物體檢測(cè)算法嗎?如果是,則很有可能你已經(jīng)使用了非最大抑制算法。也許這是你使用的深度學(xué)習(xí)模式的一部分,你甚至沒(méi)有注意到。因?yàn)榧词故欠浅?fù)雜的算法也會(huì)面臨這個(gè)問(wèn)題,它們會(huì)多次識(shí)別同一個(gè)對(duì)象。今天,想向你展示非最大抑制算法是如何工作的,并提供一個(gè)python實(shí)現(xiàn)。首先向你展示,邊界框是包圍圖像中檢測(cè)到的對(duì)象的矩形。然后我將介紹非最大抑制的代碼。該算法逐個(gè)刪除冗余的邊界框。它通過(guò)移除重疊大于閾值的框來(lái)實(shí)現(xiàn)。邊界框我們使用邊界框來(lái)標(biāo)記圖像中已識(shí)別出感興趣對(duì)象的部分。

在本例中,要識(shí)別的對(duì)象是方塊A中的大方塊。邊界框始終是垂直的矩形。因此,我們只需要存儲(chǔ)所有邊界框的左上角和右下角。

當(dāng)使用目標(biāo)檢測(cè)方法時(shí),同一個(gè)目標(biāo)在稍有不同的區(qū)域被多次檢測(cè)到的情況經(jīng)常發(fā)生。

大多數(shù)情況下,我們只想檢測(cè)一次對(duì)象。為了實(shí)現(xiàn)這一點(diǎn),我們通過(guò)應(yīng)用非最大值抑制來(lái)刪除冗余的邊界框。非最大抑制現(xiàn)在,向你展示了執(zhí)行非最大抑制的完整功能代碼,這樣你就有了一個(gè)概覽。但別擔(dān)心,我會(huì)帶你看一下代碼。

def NMS(boxes, overlapThresh = 0.4):
            # 返回一個(gè)空列表,如果沒(méi)有給出框

      if len(boxes) == 0:
                   return []
             x1 = boxes[:, 0]  # x左上角的坐標(biāo)
             y1 = boxes[:, 1]  # y左上角的坐標(biāo)
             x2 = boxes[:, 2]  # x右下角的坐標(biāo)
             y2 = boxes[:, 3]  # y右下角的坐標(biāo)

# 計(jì)算邊界框的面積,并對(duì)邊界進(jìn)行排序

# 邊框的右下角y坐標(biāo)

areas = (x2 - x1 + 1) * (y2 - y1 + 1) # 需要加1

# 開(kāi)始時(shí)所有框的索引。

indices = np.a(chǎn)range(len(x1))

for i,box in enumerate(boxes):
            # 創(chuàng)建臨時(shí)索引
             temp_indices = indices[indices!=i]
            # 找出相交方塊的坐標(biāo)
             xx1 = np.maximum(box[0], boxes[temp_indices,0])
             yy1 = np.maximum(box[1], boxes[temp_indices,1])
             xx2 = np.minimum(box[2], boxes[temp_indices,2])
             yy2 = np.minimum(box[3], boxes[temp_indices,3])
            # 找出交叉框的寬度和高度
             w = np.maximum(0, xx2 - xx1 + 1)
             h = np.maximum(0, yy2 - yy1 + 1)
            # 計(jì)算重疊的比例
             overlap = (w * h) / areas[temp_indices]
            # 如果實(shí)際的邊界框與其他框的重疊部分大于閾值,刪除它的索引
             if np.a(chǎn)ny(overlap) > treshold:
                 indices = indices[indices 。 i]
           # 只返回其余索引的方框
            return boxes[indices].a(chǎn)stype(int)

非最大抑制(NMS)函數(shù)接收一組框,閾值默認(rèn)值0.4。

def NMS(boxes, overlapThresh = 0.4):

框的數(shù)組必須進(jìn)行組織,以便每行包含不同的邊界框。

如果它們重疊更多,則兩個(gè)中的一個(gè)將被丟棄。重疊樹(shù)閾值為0.4意味著兩個(gè)矩形可以共享其40%的面積。矩形的面積是用它的寬度乘以它的高度來(lái)計(jì)算的。我們?cè)黾?,因?yàn)檫吔缈蛟谄瘘c(diǎn)坐標(biāo)和終點(diǎn)坐標(biāo)上都有一個(gè)像素。

areas = (x2 - x1 + 1) * (y2 - y1 + 1)

然后,我們?yōu)樗锌騽?chuàng)建索引。稍后,我們將逐個(gè)刪除索引,直到只有對(duì)應(yīng)于非重疊框的索引。

indices = np.a(chǎn)range(len(x1))

在循環(huán)中,我們迭代所有框。對(duì)于每個(gè)框,我們檢查它與任何其他框的重疊是否大于閾值。如果是這樣,我們將從索引列表中刪除該框的索引。

我們創(chuàng)建包含方框索引的索引,其中不包含box[i]的索引。

temp_indices = indices[indices。絠]

為了計(jì)算重疊,我們首先計(jì)算相交框的坐標(biāo)。這段代碼是矢量化的,以加快速度,我們計(jì)算長(zhǎng)方體[i]與其他長(zhǎng)方體的交點(diǎn)。        

xx1 = np.maximum(box[0], boxes[temp_indices,0])
       yy1 = np.maximum(box[1], boxes[temp_indices,1])
       xx2 = np.minimum(box[2], boxes[temp_indices,2])
       yy2 = np.minimum(box[3], boxes[temp_indices,3])

這可能有點(diǎn)混亂,但零點(diǎn)在左上角。因此,我們通過(guò)選擇????1及????1的最小值,????2及????2的最大值來(lái)獲得相交框的坐標(biāo)。

然后計(jì)算相交框的寬度和高度。我們?nèi)∽畲笾?和計(jì)算的寬度和高度,因?yàn)樨?fù)的寬度和高度會(huì)擾亂重疊的計(jì)算。        

w = np.maximum(0, xx2 - xx1 + 1)
       h = np.maximum(0, yy2 - yy1 + 1)

重疊就是相交框的面積除以邊界框的面積。在我們的例子中,所有邊界框的大小都相同,但該算法也適用于大小不同的情況。        

overlap = (w * h) / areas[temp_indices]

然后,如果box[i]與任何其他框的重疊大于treshold,則我們從剩余的索引中排除索引i。        

if np.a(chǎn)ny(overlap) > treshold:
       indices = indices[indices 。 i]

然后,我們返回帶有未刪除索引的框。像素坐標(biāo)必須是整數(shù),所以我們轉(zhuǎn)換它們只是為了安全。    

return boxes[indices].a(chǎn)stype(int)

基于模板匹配的目標(biāo)檢測(cè)你可能會(huì)問(wèn)自己,我最初是如何得到這些邊界框的。我使用了一種叫做模板匹配的簡(jiǎn)單技術(shù)。你只需要一個(gè)圖像和一個(gè)模板,即你要搜索的對(duì)象。我們的形象將是方塊A。

我們的模板將是圖像中間的方塊。

請(qǐng)注意,模板的方向和大。ㄒ韵袼貫閱挝唬┍仨毰c要在圖像中檢測(cè)的對(duì)象大致相同。

我們需要opencv。如果你還沒(méi)有,可以在終端中安裝。

pip install opencv-python

我們導(dǎo)入cv2。

import cv2

要執(zhí)行模板匹配并從中生成邊界框,我們可以使用以下函數(shù)。

def bounding_boxes(image, template):
          (tH, tW) = template.shape[:2]             # 獲取模板的高度和寬度
            imageGray = cv2.cvtColor(image, 0)        # 將圖像轉(zhuǎn)換為灰度
            templateGray = cv2.cvtColor(template, 0)  # 將模板轉(zhuǎn)換為灰度


            result = cv2.matchTemplate(imageGray, templateGray, cv2.TM_CCOEFF_NORMED)  # 模板匹配返回相關(guān)性
          (y1, x1) = np.where(result >= treshold)  # 對(duì)象被檢測(cè)到,其中相關(guān)性高于閾值
            boxes = np.zeros((len(y1), 4))      # 構(gòu)造一個(gè)零數(shù)組
            x2 = x1 + tW                       # 用模板的寬度計(jì)算x2
            y2 = y1 + tH                       # 計(jì)算y2與模板的高度
           # 填充邊框數(shù)組
            boxes[:, 0] = x1                
            boxes[:, 1] = y1
            boxes[:, 2] = x2
            boxes[:, 3] = y2
            return boxes.a(chǎn)stype(int)

cv2.matchTemplate函數(shù)返回圖像不同部分與模板的相關(guān)性。

然后,我們選擇圖像的部分,其中相關(guān)性在閾值之上。

(y1, x1) = np.where(result >= treshold)

我們還需要一個(gè)函數(shù)將邊界框繪制到圖像上。

def draw_bounding_boxes(image,boxes):

    for box in boxes:
              image = cv2.rectangle(copy.deepcopy(image),box[:2], box[2:], (255,0,0), 3)
           return image

完整代碼

import cv2

import pyautogui

import pyautogui

import cv2

import numpy as np

import os

import time

import matplotlib.pyplot as plt

import copy




def NMS(boxes, overlapThresh = 0.4):

      # 返回一個(gè)空列表,如果沒(méi)有給出框

       if len(boxes) == 0:

           return []

       x1 = boxes[:, 0]  # x左上角的坐標(biāo)

       y1 = boxes[:, 1]  # y左上角的坐標(biāo)

       x2 = boxes[:, 2]  # x右下角的坐標(biāo)

       y2 = boxes[:, 3]  # y右下角的坐標(biāo)

      # 計(jì)算邊界框的面積,并對(duì)邊界進(jìn)行排序

      # 邊框的右下角y坐標(biāo)

       areas = (x2 - x1 + 1) * (y2 - y1 + 1) # 需要加1

      # 開(kāi)始時(shí)所有框的索引。

       indices = np.a(chǎn)range(len(x1))

       for i,box in enumerate(boxes):
                  # 創(chuàng)建臨時(shí)索引
                   temp_indices = indices[indices。絠]
                  # 找出相交方塊的坐標(biāo)
                   xx1 = np.maximum(box[0], boxes[temp_indices,0])
                   yy1 = np.maximum(box[1], boxes[temp_indices,1])
                   xx2 = np.minimum(box[2], boxes[temp_indices,2])
                   yy2 = np.minimum(box[3], boxes[temp_indices,3])
                  # 找出交叉框的寬度和高度
                   w = np.maximum(0, xx2 - xx1 + 1)
                   h = np.maximum(0, yy2 - yy1 + 1)
                  # 計(jì)算重疊的比例
                   overlap = (w * h) / areas[temp_indices]
                  # 如果實(shí)際的邊界框與其他框的重疊部分大于閾值,刪除它的索引
                   if np.a(chǎn)ny(overlap) > treshold:
                       indices = indices[indices 。 i]

      # 只返回其余索引的方框

      return boxes[indices].a(chǎn)stype(int)


def bounding_boxes(image, template):

   (tH, tW) = template.shape[:2]             # 獲取模板的高度和寬度
            imageGray = cv2.cvtColor(image, 0)        # 將圖像轉(zhuǎn)換為灰度
            templateGray = cv2.cvtColor(template, 0)  # 將模板轉(zhuǎn)換為灰度
   

     result = cv2.matchTemplate(imageGray, templateGray, cv2.TM_CCOEFF_NORMED)  # 模板匹配返回相關(guān)性
          (y1, x1) = np.where(result >= treshold)  # 對(duì)象被檢測(cè)到,其中相關(guān)性高于閾值
            boxes = np.zeros((len(y1), 4))      # 構(gòu)造一個(gè)零數(shù)組
            x2 = x1 + tW                       # 用模板的寬度計(jì)算x2
            y2 = y1 + tH                       # 計(jì)算y2與模板的高度
           # 填充邊框數(shù)組
            boxes[:, 0] = x1                
            boxes[:, 1] = y1
            boxes[:, 2] = x2
            boxes[:, 3] = y2
            return boxes.a(chǎn)stype(int)


def draw_bounding_boxes(image,boxes):
            for box in boxes:
                image = cv2.rectangle(copy.deepcopy(image),box[:2], box[2:], (255,0,0), 3)
            return image


if __name__ == "__main__":

      time.sleep(2)

      treshold = 0.8837 # 關(guān)聯(lián)閾值,以便識(shí)別一個(gè)對(duì)象

      template_diamonds = plt.imread(r"templates/ace_diamonds_plant_template.jpg")
             ace_diamonds_rotated = plt.imread(r"images/ace_diamonds_table_rotated.jpg")
             boxes_redundant = bounding_boxes(ace_diamonds_rotated, template_diamonds) # 計(jì)算邊界盒
             boxes = NMS(boxes_redundant)                                            # 刪除多余的包圍框
             overlapping_BB_image = draw_bounding_boxes(ace_diamonds_rotated,
                                              boxes_redundant)  # 使用所有多余的邊框繪制圖像
             segmented_image = draw_bounding_boxes(ace_diamonds_rotated,boxes)           # 在圖像上繪制邊界框
             plt.imshow(overlapping_BB_image)
             plt.show()
             plt.imshow(segmented_image)
             plt.show()

結(jié)論

我們可以使用非最大值抑制來(lái)刪除冗余的邊界框。它們是多余的,因?yàn)樗鼈兌啻螛?biāo)記同一對(duì)象。

NMS算法利用相交三角形的面積計(jì)算三角形之間的重疊。如果邊界框與任何其他邊界框的重疊高于閾值,則將刪除該邊界框。


    ?原文標(biāo)題:非最大抑制?

聲明: 本文由入駐維科號(hào)的作者撰寫(xiě),觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號(hào)
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯(cuò)
x
*文字標(biāo)題:
*糾錯(cuò)內(nèi)容:
聯(lián)系郵箱:
*驗(yàn) 證 碼:

粵公網(wǎng)安備 44030502002758號(hào)