1. 程式人生 > 實用技巧 >[Paddle學習筆記][12][基於YOLOv3的昆蟲檢測-模型預測]

[Paddle學習筆記][12][基於YOLOv3的昆蟲檢測-模型預測]

說明:

本例程使用YOLOv3進行昆蟲檢測。例程分為資料處理、模型設計、損失函式、訓練模型、模型預測和測試模型六個部分。本篇為第五部分,使用非極大值抑制來消除預測出的重疊面積過大的邊框,然後顯示預測結果影象。

實驗程式碼:

模型預測:

import paddle.fluid as fluid
from paddle.fluid.dygraph.base import to_variable

from source.data import single_test_reader, display_infer
from source.model import YOLOv3
from source.infer import
get_nms_infer num_classes = 7 # 類別數量 anchor_size = [10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326] # 錨框大小 anchor_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]] #
錨框掩碼 downsample_ratio = 32 # 下采樣率 image_path = './dataset/test/images/1872.jpeg' # 預測影象路徑 model_path = './output/darknet53-yolov3' # 網路權重路徑 sco_threshold = 0.70 # 預測得分閾值:根據測試的平均精度在準確率和召回率之間取一個平衡值 nms_threshold = 0.45 #
非極大值閾值 with fluid.dygraph.guard(): # 讀取影象 image, image_size = single_test_reader(image_path) # 讀取影象 image = to_variable(image) # 轉換格式 image_size = to_variable(image_size) # 轉換格式 # 載入模型 model = YOLOv3(num_classes=num_classes, anchor_mask=anchor_mask) # 載入模型 model_dict, _ = fluid.load_dygraph(model_path) # 載入權重 model.load_dict(model_dict) # 設定權重 model.eval() # 設定驗證 # 前向傳播 infer = model(image) # 獲取結果 infer = get_nms_infer(infer, image_size, num_classes, anchor_size, anchor_mask, downsample_ratio, sco_threshold, nms_threshold) # 顯示結果 print('image infer:', infer[0].shape[0]) # 顯示影象預測結果數量 display_infer(infer[0], image_path) # 顯示一張影象預測結果

結果:

image infer: 6

infer.py檔案

import numpy as np

def sigmoid(x):
    """
    功能:
        計算sigmoid函式
    輸入:
        x - 輸入數值
    輸出:
        y - 輸出數值
    """
    return 0.5 * (1.0 + np.tanh(0.5 * x))

# def sigmoid(x):
#     return 1.0 / (1.0 + np.exp(-x))

def get_infer(infer, image_size, num_classes, anchor_size, anchor_mask, downsample_ratio):
    """
    功能:
        計算每個特徵影象的預測邊框和得分
    輸入:
        infer            - 特徵影象
        image_size       - 影象高寬
        num_classes      - 類別數量
        anchor_size      - 錨框大小
        anchor_mask      - 錨框掩碼
        downsample_ratio - 下采樣率
    輸出:
        pdbox            - 預測邊框
        pdsco            - 預測得分
    """
    # 調整特徵形狀
    batch_size = infer.shape[0]   # 特徵批數
    num_rows   = infer.shape[2]   # 特徵行數
    num_cols   = infer.shape[3]   # 特徵列數
    num_anchor = len(anchor_mask) # 錨框數量
    
    infer = infer.numpy()
    infer = infer.reshape([-1, num_anchor, 5 + num_classes, num_rows, num_cols]) # 轉換特徵形狀
    
    # 計算預測邊框
    pdloc = infer[:, :, 0:4, :, :]           # 獲取預測位置:[b,c,4,n,m]
    pdbox = np.zeros(pdloc.shape)            # 預測邊框陣列:[b,c,4,n,m]
    image_h = num_rows * downsample_ratio    # 預測影象高度
    image_w = num_cols * downsample_ratio    # 預測影象寬度
    
    for m in range(batch_size): # 遍歷影象
        for i in range(num_rows): # 遍歷行數
            for j in range(num_cols): # 遍歷列數
                for k in range(num_anchor): # 遍歷錨框
                    # 獲取邊框大小
                    anchor_w = anchor_size[2 * anchor_mask[k]]     # 錨框寬度
                    anchor_h = anchor_size[2 * anchor_mask[k] + 1] # 錨框高度
                    
                    # 設定預測邊框
                    pdbox[m, k, 0, i, j] = j        # 預測邊框cx
                    pdbox[m, k, 1, i, j] = i        # 預測邊框cy
                    pdbox[m, k, 2, i, j] = anchor_w # 預測邊框pw
                    pdbox[m, k, 3, i, j] = anchor_h # 預測邊框ph
                    
    pdbox[:, :, 0, :, :] = (pdbox[:, :, 0, :, :] + sigmoid(pdloc[:, :, 0, :, :])) / num_cols # 預測邊框x=cx + dx
    pdbox[:, :, 1, :, :] = (pdbox[:, :, 1, :, :] + sigmoid(pdloc[:, :, 1, :, :])) / num_rows # 預測邊框y=cy + dy
    pdbox[:, :, 2, :, :] = (pdbox[:, :, 2, :, :] * np.exp(pdloc[:, :, 2, :, :])) / image_w   # 預測邊框w=pw * exp(tw)
    pdbox[:, :, 3, :, :] = (pdbox[:, :, 3, :, :] * np.exp(pdloc[:, :, 3, :, :])) / image_h   # 預測邊框h=ph * exp(th)
    pdbox = np.clip(pdbox, 0.0, 1.0) # 限制預測邊框範圍為[0,1]
    
    pdbox = pdbox.transpose((0, 1, 3, 4, 2))                     # 調整資料維度:[b,c,n,m,4]
    pdbox = pdbox.reshape((pdbox.shape[0], -1, pdbox.shape[-1])) # 調整資料形狀:[b,c*n*m,4]
    
    # 調整座標格式
    pdbox[:, :, 0] = pdbox[:, :, 0] - pdbox[:, :, 2] / 2.0 # 預測邊框x1
    pdbox[:, :, 1] = pdbox[:, :, 1] - pdbox[:, :, 3] / 2.0 # 預測邊框y1
    pdbox[:, :, 2] = pdbox[:, :, 0] + pdbox[:, :, 2]       # 預測邊框x2
    pdbox[:, :, 3] = pdbox[:, :, 1] + pdbox[:, :, 3]       # 預測邊框y2
    
    # 計算原圖座標
    scale = image_size.numpy() # 原圖高寬
    for m in range(batch_size):
        pdbox[m, :, 0] = pdbox[m, :, 0] * scale[m, 1] # 預測邊框x1
        pdbox[m, :, 1] = pdbox[m, :, 1] * scale[m, 0] # 預測邊框y1
        pdbox[m, :, 2] = pdbox[m, :, 2] * scale[m, 1] # 預測邊框x2
        pdbox[m, :, 3] = pdbox[m, :, 3] * scale[m, 0] # 預測邊框y2
    
    # 計算預測得分
    pdobj = sigmoid(infer[:, :, 4, :, :])               # 預測物體概率:[b,c,n,m],對損失函式計算結果求sigmoid
    pdcls = sigmoid(infer[:, :, 5:5+num_classes, :, :]) # 預測類別概率:[b,c,7,n,m],對損失函式計算結果求sigmoid
    
    pdobj = np.expand_dims(pdobj, axis=2)                        # 新增資料維度:[b,c,1,n,m]
    pdsco = pdobj * pdcls                                        # 計算預測得分:[b,c,7,n,m]
    
    pdsco = pdsco.transpose((0, 1, 3, 4, 2))                     # 調整資料維度:[b,c,n,m,7]
    pdsco = pdsco.reshape((pdsco.shape[0], -1, pdsco.shape[-1])) # 調整資料形狀:[b,c*n*m,7]
    pdsco = pdsco.transpose((0, 2, 1))                           # 調整資料維度:[b,7,c*n*m]
    
    return pdbox, pdsco

# def get_infer(infer, image_size, num_classes, anchor_size, anchor_mask, downsample_ratio):
#     # 獲取錨框大小
#     anchor_list = [] # 錨框列表
#     for i in anchor_mask: # 遍歷錨框
#         anchor_list.append(anchor_size[2 * i])     # 錨框寬度
#         anchor_list.append(anchor_size[2 * i + 1]) # 錨框高度
    
#     # 計算預測結果
#     pdbox, pdsco = fluid.layers.yolo_box(
#         x=infer,
#         img_size=image_size,
#         class_num=num_classes,
#         anchors=anchor_list,
#         conf_thresh=0.01,
#         downsample_ratio=downsample_ratio)
    
#     pdsco = fluid.layers.transpose(pdsco, perm=[0, 2, 1])
    
#     return pdbox.numpy(), pdsco.numpy()

def get_sum_infer(infer, image_size, num_classes, anchor_size, anchor_mask, downsample_ratio):
    """
    功能:
        計算三個輸出的預測結果的邊框和得分
    輸入:
        infer            - 特徵列表
        image_size       - 影象高寬
        num_classes      - 類別數量
        anchor_size      - 錨框大小
        anchor_mask      - 錨框掩碼
        downsample_ratio - 下采樣率
    輸出:
        pdbox            - 預測邊框
        pdsco            - 預測得分
    """
    # 計算預測結果
    pdbox_list = [] # 預測邊框列表
    pdsco_list = [] # 預測得分列表
    for i in range(len(infer)):  # 遍歷特徵列表
        pdbox, pdsco = get_infer(infer[i], image_size, num_classes, anchor_size, anchor_mask[i], downsample_ratio)
        
        pdbox_list.append(pdbox) # 新增邊框列表
        pdsco_list.append(pdsco) # 新增得分列表
        
        # 減小下采樣率
        downsample_ratio //= 2   # 減小下采樣率
    
    # 合併預測結果
    pdbox = np.concatenate(pdbox_list, axis=1) # 連線預測邊框列表第一維
    pdsco = np.concatenate(pdsco_list, axis=2) # 連線預測得分列表第二維
    
    return pdbox, pdsco

##############################################################################################################

def get_box_iou_xyxy(box1, box2):
    """
    功能: 
        計算邊框交併比值
    輸入: 
        box1 - 邊界框1
        box2 - 邊界框2
    輸出:
        iou  - 交併比值
    """
    # 計算交集面積
    x1_min, y1_min, x1_max, y1_max = box1[0], box1[1], box1[2], box1[3]
    x2_min, y2_min, x2_max, y2_max = box2[0], box2[1], box2[2], box2[3]
    
    x_min = np.maximum(x1_min, x2_min)
    y_min = np.maximum(y1_min, y2_min)
    x_max = np.minimum(x1_max, x2_max)
    y_max = np.minimum(y1_max, y2_max)
    
    w = np.maximum(x_max - x_min + 1.0, 0)
    h = np.maximum(y_max - y_min + 1.0, 0)
    
    intersection = w * h # 交集面積
    
    # 計算並集面積
    s1 = (y1_max - y1_min + 1.0) * (x1_max - x1_min + 1.0)
    s2 = (y2_max - y2_min + 1.0) * (x2_max - x2_min + 1.0)
    
    union = s1 + s2 - intersection # 並集面積
    
    # 計算交併比
    iou = intersection / union
    
    return iou

def get_nms_index(pdbox, pdsco, sco_threshold, nms_threshold):
    """
    功能:
        獲取非極大值抑制預測索引
    輸入:
        pdbox         - 預測邊框
        pdsco         - 預測得分
        sco_threshold - 預測得分閾值
        nms_threshold - 非極大值閾值
    輸出:
        nms_index     - 預測索引
    """
    # 獲取得分索引
    sco_index = np.argsort(pdsco)[::-1] # 對得分逆向排序,獲取預測得分索引
    
    # 非極大值抑制
    nms_index = [] # 預測索引列表
    while(len(sco_index) > 0): # 如果剩餘得分索引數量大於0,則進行非極大值抑制
        # 獲取最大得分
        max_index = sco_index[0]     # 獲取最大得分索引
        max_score = pdsco[max_index] # 獲取最大得分
        
        if max_score < sco_threshold: # 如果最大得分小於預測得分閾值,則不處理剩餘得分索引
            break
        
        # 設定保留標識
        keep_flag = True # 保留標識為真
        for i in nms_index: # 遍歷保留索引
            # 計算交併比值
            box1 = pdbox[max_index] # 第一個邊框座標
            box2 = pdbox[i]         # 保留的邊框座標
            
            iou = get_box_iou_xyxy(box1, box2) # 計算交併比值
            if iou > nms_threshold: # 如果交併比值大於非極大值閾值,則不處理剩餘保留索引
                keep_flag = False # 保留標識為假
                break
        
        # 新增保留索引
        if keep_flag: # 如果保留標識為真,則新增預測索引
            nms_index.append(max_index) # 新增預測索引列表
        
        # 獲取剩餘索引
        sco_index = sco_index[1:]
    
    # 轉換資料格式
    nms_index = np.array(nms_index)
    
    return nms_index

def get_nms_class(pdbox, pdsco, sco_threshold, nms_threshold):
    """
    功能:
        獲取非極大值抑制的預測結果
    輸入:
        pdbox         - 預測邊框
        pdsco         - 預測得分
        sco_threshold - 預測得分閾值
        nms_threshold - 非極大值閾值
    輸出:
        infer_list    - 預測結果列表
    """
    # 獲取批次結果
    batch_size = pdbox.shape[0] # 預測批數數量
    class_numb = pdsco.shape[1] # 總的類別數量
    infer_list = []             # 預測結果列表
    
    for i in range(batch_size): # 遍歷批次
        # 獲取預測結果
        infer = [] # 每批預測列表
        for c in range(class_numb): # 遍歷類別
            # 獲取預測索引
            nms_index = get_nms_index(pdbox[i], pdsco[i][c], sco_threshold, nms_threshold)
            if len(nms_index) < 1: # 如果預測索引為0,則計算下一個類別索引
                continue
                
            # 設定預測結果
            nms_pdsco = pdsco[i][c][nms_index]            # 預測得分
            nms_pdbox = pdbox[i][nms_index]               # 預測邊框
            nms_infer = np.zeros([nms_pdsco.shape[0], 6]) # 預測結果
            
            nms_infer[:, 0] = c                 # 設定預測類別
            nms_infer[:, 1] = nms_pdsco[:]      # 設定預測得分
            nms_infer[:, 2:6] = nms_pdbox[:, :] # 設定預測邊框
            
            infer.append(nms_infer)             # 新增每類結果
        
        # 新增預測列表        
        if len(infer) > 0:
            infer = np.concatenate(infer, axis=0) # 合併各批預測結果
            infer_list.append(infer)              # 新增預測結果列表
        else:
            infer_list.append(infer)              # 新增空的預測結果
        
    return infer_list

##############################################################################################################

def get_nms_infer(infer, image_size, num_classes, anchor_size, anchor_mask, downsample_ratio, 
                  sco_threshold, nms_threshold):
    """
    功能:
        獲取三個輸出的非極大值抑制的預測結果
    輸入:
        infer            - 特徵列表
        image_size       - 原圖高寬
        num_classes      - 類別數量
        anchor_size      - 錨框大小
        anchor_mask      - 錨框掩碼
        downsample_ratio - 下采樣率
        sco_threshold    - 預測得分閾值
        nms_threshold    - 非極大值閾值
    輸出:
        infer            - 預測結果
    """
    # 計算預測結果
    pdbox, pdsco = get_sum_infer(infer, image_size, num_classes, anchor_size, anchor_mask, downsample_ratio)
    
    # 非極大值抑制
    infer = get_nms_class(pdbox, pdsco, sco_threshold, nms_threshold)
    
    return infer

參考資料:

https://blog.csdn.net/litt1e/article/details/88814417

https://blog.csdn.net/litt1e/article/details/88852745

https://blog.csdn.net/litt1e/article/details/88907542

https://aistudio.baidu.com/aistudio/projectdetail/742781

https://aistudio.baidu.com/aistudio/projectdetail/672017

https://aistudio.baidu.com/aistudio/projectdetail/868589

https://aistudio.baidu.com/aistudio/projectdetail/122277