1. 程式人生 > >pytorch實現yolo-v3 (原始碼閱讀和復現) -- 005

pytorch實現yolo-v3 (原始碼閱讀和復現) -- 005

前面已經搭建了YOLO V3的網路, 只需要在此基礎上提供輸入,並對預測結果過濾,就可以輸出了,完整工程程式碼已分享.

1. 影象預處理部分preprocess.py

給定影象路徑以及期望的尺寸, 進行整體縮放後, 居中放置在畫布上,並返回,
返回值值包括 : 處理後圖像, 原圖, 原圖尺寸

def letterbox_image(img, input_dim):
    """
    調整影象尺寸
    將影象按照比例進行縮放,此時 某個邊正好可以等於目標長度,另一邊小於等於目標長度
    將縮放後的資料拷貝到畫布中心,返回完成縮放
    """
    ori_w, ori_h = img.shape[1
], img.shape[0] dst_w, dst_h = input_dim new_w = int(ori_w * min(dst_w/ori_w, dst_h/ori_h)) # 保證較長的邊縮放後正好等於目標長度 new_h = int(ori_h * min(dst_w/ori_w, dst_h/ori_h)) resized_image = cv2.resize(src=img, dsize=(new_w, new_h), interpolation=cv2.INTER_CUBIC) # 雙三次插值 # 建立一個畫布, 將resized_image資料 拷貝到畫布中心
canvas = np.full(shape=(dst_w, dst_h, 3), fill_value=128) canvas[(dst_h-new_h)//2: (dst_h-new_h)//2 + new_h, (dst_w-new_w)//2: (dst_w-new_w)//2 + new_w, :] = resized_image return canvas # 引數:原圖路徑,目標大小 # 將原圖resize到 inp_dim*inp_dim大小 # 即 (3,416,416) # 返回值 (處理後圖像,原圖,原圖尺寸) def preprocess_image(img, input_dim)
:
""" 為神經網路準備輸入影象資料 返回值: 處理後圖像, 原圖, 原圖尺寸 """ ori_img = cv2.imread(img) dim = ori_img.shape[1], ori_img.shape[0] # h,w, c img = letterbox_image(ori_img, (input_dim, input_dim)) # img = letterbox_image_(ori_img, (input_dim, input_dim)) img_ = img[:, :, ::-1].transpose((2,0,1)).copy() img_ = torch.from_numpy(img_).float().div(255.0).unsqueeze(0) return img_, ori_img, dim

2. 檢測指令碼

detect.py

from __future__ import division
import time
import torch
from torch.autograd import Variable
import cv2
from util import *
import argparse
import os
import os.path as osp
from darknet import Darknet
from preprocess import preprocess_image, inp_to_image
import random
import pickle as pkl

def arg_parse():
    """
    檢測模組的引數轉換
    """
    # 格式: 引數名, 目標引數(dest是字典的key),幫助資訊,型別, 預設值
    parser = argparse.ArgumentParser(description="Yolo v3 檢測模型")
    parser.add_argument("--images", dest="images", help="待檢測影象目錄", type=str, default="imgs")
    parser.add_argument("--dets", dest="dets", help="檢測結果儲存目錄", type=str, default="det")
    parser.add_argument("--bs", dest="bs", help="Batch 大小, 預設為 1", default=1)
    parser.add_argument("--confidence", dest="confidence", help="目標檢測結果置信度閾值", default=0.5)
    parser.add_argument("--nms_thresh", dest="nms_thresh", help="NMS非極大值抑制閾值", default=0.4)

    parser.add_argument("--cfg", dest="cfg", help="配置檔案", default="cfg/yolov3.cfg", type=str)
    parser.add_argument("--weights", dest="weights", help="模型權重", default="yolov3.weights", type=str)

    parser.add_argument("--resize", dest="resize", help="網路輸入解析度. 解析度越高,則準確率越高; 反之亦然.", \
                        default="416", type=str)
    parser.add_argument("--scales", dest="scales", help="縮放尺度用於檢測", default="1,2,3", type=str)

    # 返回轉換好的結果
    return parser.parse_args()

if __name__ == '__main__':
    args = arg_parse() # 型別: argparse.Namespace, 可以像easydict一樣使用
    print(args)

    scales = args.scales
    images = args.images
    batch_size = args.bs
    confidence = args.confidence
    nms_thresh = args.nms_thresh

    # GPU環境是否可用
    CUDA = torch.cuda.is_available()

    # coco 資料集
    num_class, classes = load_classes("data/coco.names")

    # 建立神經網路
    print("載入神經網路...")
    model = Darknet(args.cfg)
    model.load_weights(args.weights)
    print("模型載入成功.")

    # 網路輸入資料大小
    model.net_info["height"] = int(args.resize)
    input_dim = model.net_info["height"]
    assert input_dim > 0 and input_dim % 32 == 0

    # 如果GPU可用, 模型切換到cuda中執行
    if CUDA:
        model.cuda()
    # 評估模式
    model.eval()

    # 載入待檢測影象列表
    try:
        imlist = [os.path.join(images, img) for img in os.listdir(images) if os.path.splitext(img)[1] in [".png", ".jpg", ".jpeg"]]
    except NotADirectoryError:
        imlist = []
        imlist.append(os.path.join(images))
    except FileNotFoundError:
        print("%s 不是有效的目錄或者檔案" % (images,))
        exit()

    # 儲存結果目錄
    if not os.path.exists(args.dets):
        os.mkdir(args.dets)

    load_batch = time.time()
    # 載入全部待檢測影象
    # map: 轉換函式prep_image, 兩個陣列 imlist [input_dim for x in imlist] 為其提供引數
    batches = list(map(preprocess_image, imlist, [input_dim for x in imlist]))
    ptt_images = [x[0] for x in batches] # ptt: pytorch_tensor
    ori_images = [x[1] for x in batches]
    ori_images_dim_list = [x[2] for x in batches]
    # repeat(*size), 沿著指定維度複製資料
    # 注: size維度必須和資料本身維度要一致
    ori_images_dim_list = torch.FloatTensor(ori_images_dim_list).repeat(1, 2) # (11,4) 原始影象尺寸

    if CUDA:
        ori_images_dim_list = ori_images_dim_list.cuda()

    # 所有檢測結果
    # objs = []
    # 第i個影象批次
    i = 0
    #  批處理 ...
    write = False
    # batch >1 支援實現
    if batch_size>1:
        num_batches = int(len(imlist)/batch_size + 0.5)
        ptt_images = [torch.cat(
                        (ptt_images[ i * batch_size: min((i + 1) * batch_size, len(ptt_images)) ]) )
                      for i in range(num_batches)]

    # 暫未支援batch>1
    for batch in ptt_images:
        start = time.time()
        if CUDA:
            batch = batch.cuda()

        with torch.no_grad():
            predictions = model(Variable(batch), CUDA)
        # 結果過濾
        predictions = write_results(predictions, confidence, num_class, nms=True, nms_thresh=nms_thresh)

        if type(predictions) == int:
            i += 1
            continue
        end = time.time()

        print(end - start, predictions.shape) # 單位 秒

        predictions[:, 0] += i * batch_size # [0]表示影象索引

        if  not write:
            output = predictions
            write = True
        else:
            output = torch.cat((output, predictions))

        for im_num, image in enumerate(imlist[i*batch_size: min((i+1)*batch_size, len(imlist))]):
            im_ind =  i*batch_size + im_num # 影象編號
            objs = [ classes[int(x[-1])] for x in output if int(x[0]) == im_ind] # 輸出第im_ind影象結果
            print("{0:20s} predicted in {1:6.3f} seconds".format(osp.split(image)[-1], (end-start)/batch_size))
            print("{0:20s} {1:s}".format("Objects detected", " ".join(objs)))
            print("----------------------------------------------------------")
        i += 1


    # 對所有的輸入的檢測結果
    try:
        output
    except:
        print("沒有檢測到任何目標")
        exit()

    # 0: 影象索引 1-4: 座標(在縮放後圖像中的位置) 5:score 6: ??? 7(-1):類別
    # print(output)

    # 對output結果按照dim=0維度分組?
    ori_images_dim_list = torch.index_select(ori_images_dim_list, 0, output[:, 0].long()) # pytorch 切片torch.index_select(data, dim, indices)
    scaling_factor = torch.min(input_dim/ori_images_dim_list, 1)[0].view(-1, 1)

    # 座標換算,將居中的位置座標轉換為以(0,0)為起點的座標 x-x'soffset, y-y'soffset
    output[:, [1,3]] -= (input_dim - scaling_factor*ori_images_dim_list[:, 0].view(-1,1))/2 # x416 - (縮放後x<=416長度/2 )
    output[:, [2,4]] -= (input_dim - scaling_factor*ori_images_dim_list[:, 1].view(-1,1))/2

    output[:, 1:5] /= scaling_factor # 縮放至原圖大小尺寸

    # 繪圖
    colors = pkl.load(open("pallete", "rb"))
    def draw(x, batch, results):
        # batch 轉換後的影象, 沒用到這裡
        c1 = tuple(x[1:3].int()) # x1,y1
        c2 = tuple(x[3:5].int()) # x2,y2
        img = results[int(x[0])] # 影象索引
        cls = int(x[-1])
        label = "%s" % classes[cls]
        color = random.choice(colors) # 隨機選擇顏色
        # 繪圖(繪製一條結果)
        cv2.rectangle(img, c1, c2, color, 1)
        t_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_PLAIN, 1, 1)[0]
        c2 = c1[0] + t_size[0] + 3, c1[1] + t_size[1] + 4
        cv2.rectangle(img, c1, c2, color, -1)
        cv2.putText(img, label, (c1[0], c1[1] + t_size[1] + 4), cv2.FONT_HERSHEY_PLAIN, 1, [225, 255, 255], 1)
        return img
    # 開始逐條繪製output中結果
    list(map(lambda x: draw(x, ptt_images, ori_images), output))
    # 儲存檔案路徑
    det_names = ["{}/det_{}".format(args.dets, osp.split(x)[-1]) for x in imlist]
    list(map(cv2.imwrite, det_names, ori_images))

主時鐘對程式碼已經做了很細緻的解釋, 下面只陳述下主要過程:

  • 搭建網路,並初始化權重

  • 預處理輸入影象,並依據batch size引數調整, 預處理函式的輸出是batch為1的tensor

  • 分批次進行預測, 將預測結果進行nms過濾, 並剔除掉置信度不夠的區域, 列印最終的檢測結果到控制檯

  • output中儲存所有的檢測結果, 利用其中資訊,展開視覺化,將結果儲存在了det目錄下.

檢測結果示例如圖:
這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述