前言
前面簡單介紹了YOLOv5的項目目錄結構(直通車:YOLOv5源碼逐行超詳細注釋與解讀(1)——項目目錄結構解析),對項目整體有了大致了解。
今天要學習的是detect.py。通常這個文件是用來預測一張圖片或者一個視頻的,也可以預測一個圖片文件夾或者是一些網(wǎng)絡流。下載后直接運行默認是對date/images文件夾下的兩張照片進行檢測識別。
文章代碼逐行手打注釋,每個模塊都有對應講解,一文幫你梳理整個代碼邏輯!
友情提示:全文近4萬字,可以先點再慢慢看哦~
???本人YOLOv5源碼詳解系列:??
YOLOv5源碼逐行超詳細注釋與解讀(1)——項目目錄結構解析
YOLOv5源碼逐行超詳細注釋與解讀(2)——推理部分detect.py
YOLOv5源碼逐行超詳細注釋與解讀(3)——訓練部分train.py
YOLOv5源碼逐行超詳細注釋與解讀(4)——驗證部分val(test).py
YOLOv5源碼逐行超詳細注釋與解讀(5)——配置文件yolov5s.yaml
YOLOv5源碼逐行超詳細注釋與解讀(6)——網(wǎng)絡結構(1)yolo.py
YOLOv5源碼逐行超詳細注釋與解讀(7)——網(wǎng)絡結構(2)common.py
目錄
前言
??一、導包和基本配置
1.1 導入安裝好的python庫等
1.2 定義一些文件路徑
1.3 加載自定義模塊
??二、執(zhí)行main函數(shù)
??三、設置opt參數(shù)
??四、執(zhí)行run函數(shù)
4.1?載入?yún)?shù)
4.2 初始化配置
4.3?保存結果
4.4?加載模型
4.5?加載數(shù)據(jù)
4.6?推理部分
4.6.1 熱身部分
4.6.2 對每張圖片/視頻進行前向推理
4.6.3 NMS除去多余的框
4.6.4 預測過程
4.6.5? 打印目標檢測結果
4.6.6? 在窗口中實時查看檢測結果
4.6.7? 設置保存結果
4.7?在終端里打印出運行的結果
??五、detect.py代碼全部注釋
??一、導包和基本配置
1.1 導入安裝好的python庫等
'''====================1.導入安裝好的python庫======================='''
import argparse # 解析命令行參數(shù)的庫
import os # 與操作系統(tǒng)進行交互的文件庫 包含文件路徑操作與解析
import sys # sys模塊包含了與python解釋器和它的環(huán)境有關的函數(shù)。
from pathlib import Path # Path能夠更加方便得對字符串路徑進行處理
import cv2 # sys模塊包含了與python解釋器和它的環(huán)境有關的函數(shù)。
import torch #pytorch 深度學習庫
import torch.backends.cudnn as cudnn #讓內置的cudnn的 auto-tuner 自動尋找最適合當前配置的高效算法,來達到優(yōu)化運行效率的問題
首先,導入一下常用的python庫:
- argparse:??它是一個用于命令項選項與參數(shù)解析的模塊,通過在程序中定義好我們需要的參數(shù),argparse 將會從 sys.argv 中解析出這些參數(shù),并自動生成幫助和使用信息
- os:?它提供了多種操作系統(tǒng)的接口。通過os模塊提供的操作系統(tǒng)接口,我們可以對操作系統(tǒng)里文件、終端、進程等進行操作
- sys:?它是與python解釋器交互的一個接口,該模塊提供對解釋器使用或維護的一些變量的訪問和獲取,它提供了許多函數(shù)和變量來處理 Python 運行時環(huán)境的不同部分
- pathlib:??這個庫提供了一種面向對象的方式來與文件系統(tǒng)交互,可以讓代碼更簡潔、更易讀
- torch:??這是主要的Pytorch庫。它提供了構建、訓練和評估神經網(wǎng)絡的工具
-
torch.backends.?cudnn:??它提供了一個接口,用于使用cuDNN庫,在NVIDIA GPU上高效地進行深度學習。cudnn模塊是一個Pytorch庫的擴展
?1.2 定義一些文件路徑
'''=====================2.獲取當前文件的絕對路徑=============================='''
FILE = Path(__file__).resolve() # __file__指的是當前文件(即detect.py),FILE最終保存著當前文件的絕對路徑,比如D://yolov5/detect.py
ROOT = FILE.parents[0] # YOLOv5 root directory ROOT保存著當前項目的父目錄,比如 D://yolov5
if str(ROOT) not in sys.path: # sys.path即當前python環(huán)境可以運行的路徑,假如當前項目不在該路徑中,就無法運行其中的模塊,所以就需要加載路徑
sys.path.append(str(ROOT)) # add ROOT to PATH 把ROOT添加到運行路徑上
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative ROOT設置為相對路徑
這段代碼會獲取當前文件的絕對路徑,并使用Path庫將其轉換為Path對象。
這一部分的主要作用有兩個:
- 將當前項目添加到系統(tǒng)路徑上,以使得項目中的模塊可以調用。
- 將當前項目的相對路徑保存在ROOT中,便于尋找項目中的文件。
1.3 加載自定義模塊
'''=====================3..加載自定義模塊============================='''
from models.common import DetectMultiBackend
from utils.datasets import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr,
increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import select_device, time_sync
這些都是用戶自定義的庫,由于上一步已經把路徑加載上了,所以現(xiàn)在可以導入,這個順序不可以調換。具體來說,代碼從如下幾個文件中導入了部分函數(shù)和類:
- models.common.py:? 這個文件定義了一些通用的函數(shù)和類,比如圖像的處理、非極大值抑制等等。
- utils.dataloaders.py:?這個文件定義了兩個類,LoadImages和LoadStreams,它們可以加載圖像或視頻幀,并對它們進行一些預處理,以便進行物體檢測或識別。
- utils.general.py:??這個文件定義了一些常用的工具函數(shù),比如檢查文件是否存在、檢查圖像大小是否符合要求、打印命令行參數(shù)等等。
- utils.plots.py:? 這個文件定義了Annotator類,可以在圖像上繪制矩形框和標注信息。
- utils.torch_utils.py:? 這個文件定義了一些與PyTorch有關的工具函數(shù),比如選擇設備、同步時間等等。
通過導入這些模塊,可以更方便地進行目標檢測的相關任務,并且減少了代碼的復雜度和冗余。
??二、執(zhí)行main函數(shù)
'''=======================二、設置main函數(shù)==================================='''
def main(opt):
# 檢查環(huán)境/打印參數(shù),主要是requrement.txt的包是否安裝,用彩色顯示設置的參數(shù)
check_requirements(exclude=('tensorboard', 'thop'))
# 執(zhí)行run()函數(shù)
run(**vars(opt))
# 命令使用
# python detect.py --weights runs/train/exp_yolov5s/weights/best.pt --source data/images/fishman.jpg # webcam
if __name__ == "__main__":
opt = parse_opt() # 解析參數(shù)
main(opt) # 執(zhí)行主函數(shù)
這是程序的主函數(shù)。它調用了 check_requirements() 函數(shù)和 run() 函數(shù),并將命令行參數(shù) opt 轉換為字典作為參數(shù)傳遞給 run() 函數(shù)。以下是對該代碼的一些解釋:
- check_requirements(exclude=('tensorboard', 'thop')):? ?檢查程序所需的依賴項是否已安裝。
- run(**vars(opt)) :? 將 opt 變量的屬性和屬性值作為關鍵字參數(shù)傳遞給 run() 函數(shù)。
if name == ‘main’:的作用:
一個python文件通常有兩種使用方法,第一是作為腳本直接執(zhí)行,第二是 import 到其他的 python 腳本中被調用(模塊重用)執(zhí)行。因此 if name == ‘main’:的作用就是控制這兩種情況執(zhí)行代碼的過程,在 if name == ‘main’: 下的代碼只有在第一種情況下(即文件作為腳本直接執(zhí)行)才會被執(zhí)行,而 import 到其他腳本中是不會被執(zhí)行的。
- opt = parse_opt():? 解析命令行傳進的參數(shù)。該段代碼分為三部分,第一部分定義了一些可以傳導的參數(shù)類型,第二部分對于imgsize部分進行了額外的判斷(640*640),第三部分打印所有參數(shù)信息,opt變量存儲所有的參數(shù)信息,并返回。
- main(opt):??執(zhí)行命令行參數(shù)。該段代碼分為兩部分,第一部分首先完成對于requirements.txt的檢查,檢測這些依賴包有沒有安裝;第二部分,將opt變量參數(shù)傳入,執(zhí)行run函數(shù)。
??三、設置opt參數(shù)
'''=================三、Parse_opt()用來設置輸入?yún)?shù)的子函數(shù)==============================='''
def parse_opt():
parser = argparse.ArgumentParser()
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)')
parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam')
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
parser.add_argument('--conf-thres', type=float, default=0.5, help='confidence threshold')
parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--view-img', action='store_true', help='show results')
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
parser.add_argument('--augment', action='store_true', help='augmented inference')
parser.add_argument('--visualize', action='store_true', help='visualize features')
parser.add_argument('--update', action='store_true', help='update all models')
parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name')
parser.add_argument('--name', default='exp', help='save results to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
opt = parser.parse_args() # 擴充維度
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
print_args(FILE.stem, opt) # 打印所有參數(shù)信息
return opt
這段代碼是一個 Python 腳本中的一個函數(shù),用于解析命令行參數(shù)并返回這些參數(shù)的值。
主要功能是為模型進行推理時提供參數(shù)。下面是每個參數(shù)的作用和默認值:
- --weights:??訓練的權重路徑,可以使用自己訓練的權重,也可以使用官網(wǎng)提供的權重。默認官網(wǎng)的權重yolov5s.pt(yolov5n.pt/yolov5s.pt/yolov5m.pt/yolov5l.pt/yolov5x.pt/區(qū)別在于網(wǎng)絡的寬度和深度以此增加)
- --source:? 測試數(shù)據(jù),可以是圖片/視頻路徑,也可以是'0'(電腦自帶攝像頭),也可以是rtsp等視頻流, 默認data/images
- --data:? 配置數(shù)據(jù)文件路徑,包括image/label/classes等信息,訓練自己的文件,需要作相應更改,可以不用管
- --imgsz:? 預測時網(wǎng)絡輸入圖片的尺寸,默認值為 [640]
- --conf-thres:? 置信度閾值,默認為 0.50
- --iou-thres:? 非極大抑制時的 IoU 閾值,默認為 0.45
- --max-det:? 保留的最大檢測框數(shù)量,每張圖片中檢測目標的個數(shù)最多為1000類
- --device:? 使用的設備,可以是 cuda 設備的 ID(例如 0、0,1,2,3)或者是 'cpu',默認為 '0'
- --view-img:? 是否展示預測之后的圖片/視頻,默認False
- --save-txt:? 是否將預測的框坐標以txt文件形式保存,默認False,使用--save-txt 在路徑runs/detect/exp*/labels/*.txt下生成每張圖片預測的txt文件
- --save-conf:? 是否保存檢測結果的置信度到 txt文件,默認為 False
- --save-crop:? 是否保存裁剪預測框圖片,默認為False,使用--save-crop 在runs/detect/exp*/crop/剪切類別文件夾/ 路徑下會保存每個接下來的目標
- --nosave:? 不保存圖片、視頻,要保存圖片,不設置--nosave 在runs/detect/exp*/會出現(xiàn)預測的結果
- --classes:? 僅檢測指定類別,默認為 None
- --agnostic-nms:? 是否使用類別不敏感的非極大抑制(即不考慮類別信息),默認為 False
- --augment:? 是否使用數(shù)據(jù)增強進行推理,默認為 False
- --visualize:? 是否可視化特征圖,默認為 False
- --update:? 如果為True,則對所有模型進行strip_optimizer操作,去除pt文件中的優(yōu)化器等信息,默認為False
- --project:? 結果保存的項目目錄路徑,默認為 'ROOT/runs/detect'
- --name:? 結果保存的子目錄名稱,默認為 'exp'
- --exist-ok:? 是否覆蓋已有結果,默認為 False
- --line-thickness:? 畫 bounding box 時的線條寬度,默認為 3
- --hide-labels:? 是否隱藏標簽信息,默認為 False
- --hide-conf:? 是否隱藏置信度信息,默認為 False
- --half:? 是否使用 FP16 半精度進行推理,默認為 False
- --dnn:? 是否使用 OpenCV DNN 進行 ONNX 推理,默認為 False
(關于這一部分詳解及調參,推薦大家看這篇神文:手把手帶你調參YOLOv5 (v5.0-v7.0)(推理))
??四、執(zhí)行run函數(shù)
run()函數(shù)按照邏輯順序可以分為載入?yún)?shù)、初始化配置、保存結果、加載模型、加載數(shù)據(jù)、推理部分、在終端里打印出運行的結果,這七個主要部分。
4.1?載入?yún)?shù)
'''===================1.載入?yún)?shù)======================='''
@torch.no_grad() # 該標注使得方法中所有計算得出的tensor的requires_grad都自動設置為False,也就是說不進行梯度的計算(當然也就沒辦法反向傳播了), 節(jié)約顯存和算
def run(weights=ROOT / 'yolov5s.pt', # model.pt path(s) 事先訓練完成的權重文件,比如yolov5s.pt,默認 weights/,假如使用官方訓練好的文件(比如yolov5s),則會自動下載
source=ROOT / 'data/images', # file/dir/URL/glob, 0 for webcam 預測時的輸入數(shù)據(jù),可以是文件/路徑/URL/glob, 輸入是0的話調用攝像頭作為輸入,默認data/images/
# data=ROOT / 'data/coco128.yaml', # dataset.yaml path, data文件路徑,包括類別/圖片/標簽等信息
imgsz=(640, 640), # inference size (pixels) 預測時的放縮后圖片大小(因為YOLO算法需要預先放縮圖片), 兩個值分別是height, width。默認640*640
conf_thres=0.25, # confidence threshold 置信度閾值, 高于此值的bounding_box才會被保留。默認0.25,用在nms中
iou_thres=0.45, # NMS IOU threshold IOU閾值,高于此值的bounding_box才會被保留。默認0.45,用在nms中
max_det=1000, # maximum detections per image 一張圖片上檢測的最大目標數(shù)量,用在nms中
device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu 所使用的GPU編號,如果使用CPU就寫cpu
view_img=False, # show results 是否展示預測之后的圖片或視頻,默認False
save_txt=False, # save results to *.txt 是否將預測的框坐標以txt文件形式保存, 默認False, 使用--save-txt 在路徑runs/detect/exp*/labels/*.txt下生成每張圖片預測的txt文件
save_conf=False, # save confidences in --save-txt labels 是否將結果中的置信度保存在txt文件中,默認False
save_crop=False, # save cropped prediction boxes 是否保存裁剪后的預測框,默認為False, 使用--save-crop 在runs/detect/exp*/crop/剪切類別文件夾/ 路徑下會保存每個接下來的目標
nosave=False, # do not save images/videos 不保存圖片、視頻, 要保存圖片,不設置--nosave 在runs/detect/exp*/會出現(xiàn)預測的結果
classes=None, # filter by class: --class 0, or --class 0 2 3 過濾指定類的預測結果
agnostic_nms=False, # class-agnostic NMS 進行NMS去除不同類別之間的框, 默認False
augment=False, # augmented inference TTA測試時增強/多尺度預測,可以提分
visualize=False, # visualize features 是否可視化網(wǎng)絡層輸出特征
update=False, # update all models 如果為True,則對所有模型進行strip_optimizer操作,去除pt文件中的優(yōu)化器等信息,默認為False
project=ROOT / 'runs/detect', # save results to project/name 預測結果保存的路徑
name='exp', # save results to project/name 結果保存文件夾的命名前綴
exist_ok=False, # existing project/name ok, do not increment True: 推理結果覆蓋之前的結果 False: 推理結果新建文件夾保存,文件夾名遞增
line_thickness=3, # bounding box thickness (pixels) 繪制Bounding_box的線寬度
hide_labels=False, # hide labels 若為True: 隱藏標簽
hide_conf=False, # hide confidences 若為True: 隱藏置信度
half=False, # use FP16 half-precision inference 是否使用半精度推理(節(jié)約顯存)
dnn=False, # use OpenCV DNN for ONNX inference 是否使用OpenCV DNN預測
):
這段代碼定義了run()函數(shù),并設置了一系列參數(shù),用于指定物體檢測或識別的相關參數(shù)。
這些參數(shù)包括:
- weights:? 模型權重文件的路徑,默認為YOLOv5s的權重文件路徑。
- source:? 輸入圖像或視頻的路徑或URL,或者使用數(shù)字0指代攝像頭,默認為YOLOv5自帶的測試圖像文件夾。
- data:? 數(shù)據(jù)集文件的路徑,默認為COCO128數(shù)據(jù)集的配置文件路徑。
- imgsz:? 輸入圖像的大小,默認為640x640。
- conf_thres:? 置信度閾值,默認為0.25。
- iou_thres:? 非極大值抑制的IoU閾值,默認為0.45。
- max_det:? 每張圖像的最大檢測框數(shù),默認為1000。
- device:? 使用的設備類型,默認為空,表示自動選擇最合適的設備。
- view_img:? 是否在屏幕上顯示檢測結果,默認為False。
- save_txt:? 是否將檢測結果保存為文本文件,默認為False。
- save_conf:? 是否在保存的文本文件中包含置信度信息,默認為False。
- save_crop:? 是否將檢測出的目標區(qū)域保存為圖像文件,默認為False。
- nosave:? 是否不保存檢測結果的圖像或視頻,默認為False。
- classes:? 指定要檢測的目標類別,默認為None,表示檢測所有類別。
- agnostic_nms: 是否使用類別無關的非極大值抑制,默認為False。
- augment:? 是否使用數(shù)據(jù)增強的方式進行檢測,默認為False。
- visualize:? 是否可視化模型中的特征圖,默認為False。
- update:? 是否自動更新模型權重文件,默認為False。
- project:? 結果保存的項目文件夾路徑,默認為“runs/detect”。
- name:? 結果保存的文件名,默認為“exp”。
- exist_ok:? 如果結果保存的文件夾已存在,是否覆蓋,默認為False,即不覆蓋。
- line_thickness:? 檢測框的線條寬度,默認為3。
- hide_labels:? 是否隱藏標簽信息,默認為False,即顯示標簽信息。
- hide_conf:? 是否隱藏置信度信息,默認為False,即顯示置信度信息。
- half:? 是否使用FP16的半精度推理模式,默認為False。
- dnn:? 是否使用OpenCV DNN作為ONNX推理的后端,默認為False。
4.2 初始化配置
'''=========================2.初始化配置==========================='''
# 輸入的路徑變?yōu)樽址? source = str(source)
# 是否保存圖片和txt文件,如果nosave(傳入的參數(shù))為false且source的結尾不是txt則保存圖片
save_img = not nosave and not source.endswith('.txt') # save inference images
# 判斷source是不是視頻/圖像文件路徑
# Path()提取文件名。suffix:最后一個組件的文件擴展名。若source是"D://YOLOv5/data/1.jpg", 則Path(source).suffix是".jpg", Path(source).suffix[1:]是"jpg"
# 而IMG_FORMATS 和 VID_FORMATS兩個變量保存的是所有的視頻和圖片的格式后綴。
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
# 判斷source是否是鏈接
# .lower()轉化成小寫 .upper()轉化成大寫 .title()首字符轉化成大寫,其余為小寫, .startswith('http://')返回True or Flase
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
# 判斷是source是否是攝像頭
# .isnumeric()是否是由數(shù)字組成,返回True or False
webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
if is_url and is_file:
# 返回文件。如果source是一個指向圖片/視頻的鏈接,則下載輸入數(shù)據(jù)
source = check_file(source) # download
這段代碼主要用于處理輸入來源。定義了一些布爾值區(qū)分輸入是圖片、視頻、網(wǎng)絡流還是攝像頭。
首先將source轉換為字符串類型,然后判斷是否需要保存輸出結果。如果nosave和source的后綴不是.txt,則會保存輸出結果。
接著根據(jù)source的類型,確定輸入數(shù)據(jù)的類型:
- 如果source的后綴是圖像或視頻格式之一,那么將is_file設置為True;
- 如果source以rtsp://、rtmp://、http://、https://開頭,那么將is_url設置為True;
- 如果source是數(shù)字或以.txt結尾或是一個URL,那么將webcam設置為True;
- 如果source既是文件又是URL,那么會調用check_file函數(shù)下載文件。
4.3?保存結果
'''========================3.保存結果======================'''
# Directories
# save_dir是保存運行結果的文件夾名,是通過遞增的方式來命名的。第一次運行時路徑是“runs\detect\exp”,第二次運行時路徑是“runs\detect\exp1”
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)
# 根據(jù)前面生成的路徑創(chuàng)建文件夾
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
這段代碼主要是用于創(chuàng)建保存輸出結果的目錄。創(chuàng)建一個新的文件夾exp(在runs文件夾下)來保存運行的結果。
首先代碼中的 project 指 run 函數(shù)中的 project,對應的是 runs/detect 的目錄,name 對應 run 函數(shù)中的“name=exp”,然后進行拼接操作。使用increment_path函數(shù)來確保目錄不存在,如果存在,則在名稱后面添加遞增的數(shù)字。
然后判斷 save_txt 是否為 true,save_txt 在 run 函數(shù)以及 parse_opt() 函數(shù)中都有相應操作,如果傳入save_txt,新建 “l(fā)abels” 文件夾存儲結果
這個過程中,如果目錄已經存在,而exist_ok為False,那么會拋出一個異常,指示目錄已存在。如果exist_ok為True,則不會拋出異常,而是直接使用已經存在的目錄。
4.4?加載模型
'''=======================4.加載模型=========================='''
# Load model 加載模型
# 獲取設備 CPU/CUDA
device = select_device(device)
# DetectMultiBackend定義在models.common模塊中,是我們要加載的網(wǎng)絡,其中weights參數(shù)就是輸入時指定的權重文件(比如yolov5s.pt)
model = DetectMultiBackend(weights, device=device, dnn=dnn)
stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx
'''
stride:推理時所用到的步長,默認為32, 大步長適合于大目標,小步長適合于小目標
names:保存推理結果名的列表,比如默認模型的值是['person', 'bicycle', 'car', ...]
pt: 加載的是否是pytorch模型(也就是pt格式的文件)
jit:當某段代碼即將第一次被執(zhí)行時進行編譯,因而叫“即時編譯”
onnx:利用Pytorch我們可以將model.pt轉化為model.onnx格式的權重,在這里onnx充當一個后綴名稱,
model.onnx就代表ONNX格式的權重文件,這個權重文件不僅包含了權重值,也包含了神經網(wǎng)絡的網(wǎng)絡流動信息以及每一層網(wǎng)絡的輸入輸出信息和一些其他的輔助信息。
'''
# 確保輸入圖片的尺寸imgsz能整除stride=32 如果不能則調整為能被整除并返回
imgsz = check_img_size(imgsz, s=stride) # check image size
# Half
# 如果不是CPU,使用半進度(圖片半精度/模型半精度)
half &= pt and device.type != 'cpu' # half precision only supported by PyTorch on CUDA
if pt:
model.model.half() if half else model.model.float()
這段代碼主要是用于選擇設備、初始化模型和檢查圖像大小。
首先調用select_device函數(shù)選擇設備,如果device為空,則使用默認設備。
然后使用DetectMultiBackend類來初始化模型,其中
- weights? ?指模型的權重路徑
- device? 指設備
- dnn? 指是否使用OpenCV DNN
- data? 指數(shù)據(jù)集配置文件的路徑
- fp16? 指是否使用半精度浮點數(shù)進行推理
接著從模型中獲取stride、names和pt等參數(shù),其中
- stride? 指下采樣率
- names? ?指模型預測的類別名稱
- pt? ?是Pytorch模型對象
最后調用check_img_size函數(shù)檢查圖像大小是否符合要求,如果不符合則進行調整。
4.5?加載數(shù)據(jù)
'''=======================5.加載數(shù)據(jù)========================'''
# Dataloader
# 通過不同的輸入源來設置不同的數(shù)據(jù)加載方式
if webcam: # 使用攝像頭作為輸入
view_img = check_imshow() # 檢測cv2.imshow()方法是否可以執(zhí)行,不能執(zhí)行則拋出異常
cudnn.benchmark = True # set True to speed up constant image size inference 該設置可以加速預測
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit)# 加載輸入數(shù)據(jù)流
'''
source:輸入數(shù)據(jù)源;image_size 圖片識別前被放縮的大?。籹tride:識別時的步長,
auto的作用可以看utils.augmentations.letterbox方法,它決定了是否需要將圖片填充為正方形,如果auto=True則不需要
'''
bs = len(dataset) # batch_size 批大小
else: # 直接從source文件下讀取圖片
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit)
bs = 1 # batch_size
# 保存視頻的路徑
vid_path, vid_writer = [None] * bs, [None] * bs # 前者是視頻路徑,后者是一個cv2.VideoWriter對象
這段代碼是根據(jù)輸入的 source 參數(shù)來判斷是否是通過 webcam 攝像頭捕捉視頻流
- 如果是,則使用 LoadStreams 加載視頻流
- 否則,使用 LoadImages 加載圖像
如果是 webcam 模式,則設置 cudnn.benchmark = True 以加速常量圖像大小的推理。bs?表示 batch_size(批量大小),這里是 1 或視頻流中的幀數(shù)。vid_path 和 vid_writer?分別是視頻路徑和視頻編寫器,初始化為長度為 batch_size 的空列表。
4.6?推理部分
推理部分是整個算法的核心部分。通過for循環(huán)對加載的數(shù)據(jù)進行遍歷,一幀一幀地推理,進行NMS非極大值抑制、繪制bounding box、預測類別。
4.6.1 熱身部分
# Run inference
if pt and device.type != 'cpu':
# 使用空白圖片(零矩陣)預先用GPU跑一遍預測流程,可以加速預測
model(torch.zeros(1, 3, *imgsz).to(device).type_as(next(model.model.parameters()))) # warmup
dt, seen = [0.0, 0.0, 0.0], 0
'''
dt: 存儲每一步驟的耗時
seen: 計數(shù)功能,已經處理完了多少幀圖片
'''
# 去遍歷圖片,進行計數(shù),
for path, im, im0s, vid_cap, s in dataset:
'''
在dataset中,每次迭代的返回值是self.sources, img, img0, None, ''
path:文件路徑(即source)
im: resize后的圖片(經過了放縮操作)
im0s: 原始圖片
vid_cap=none
s: 圖片的基本信息,比如路徑,大小
'''
# ===以下部分是做預處理===#
t1 = time_sync() # 獲取當前時間
im = torch.from_numpy(im).to(device) # 將圖片放到指定設備(如GPU)上識別。#torch.size=[3,640,480]
im = im.half() if half else im.float() # uint8 to fp16/32 # 把輸入從整型轉化為半精度/全精度浮點數(shù)。
im /= 255 # 0 - 255 to 0.0 - 1.0 歸一化,所有像素點除以255
if len(im.shape) == 3:
im = im[None] # expand for batch dim 添加一個第0維。缺少batch這個尺寸,所以將它擴充一下,變成[1,3,640,480]
t2 = time_sync() # 獲取當前時間
dt[0] += t2 - t1 # 記錄該階段耗時
這段代碼進行了模型的熱身(warmup)操作,即對模型進行一些預處理以加速后續(xù)的推理過程。
首先定義了一些變量,包括seen、windows和dt,分別表示已處理的圖片數(shù)量、窗口列表和時間消耗列表。遍歷 dataset ,整理圖片信息;進行預測,根據(jù) run 函數(shù)里面的置信度以及IOU參數(shù),進行信息過濾;對檢測框進行后續(xù)處理,畫框選擇,坐標映射(640*640坐標映射為原圖坐標),是否保存繪畫結果。
接著對數(shù)據(jù)集中的每張圖片進行預處理:
- 首先將圖片轉換為Tensor格式,并根據(jù)需要將其轉換為FP16或FP32格式。
- 然后將像素值從0-255轉換為0.0-1.0歸一化,并為批處理增加一維。
- 最后記錄時間消耗并更新dt列表。
4.6.2 對每張圖片/視頻進行前向推理
# Inference
# 可視化文件路徑。如果為True則保留推理過程中的特征圖,保存在runs文件夾中
visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
# 推理結果,pred保存的是所有的bound_box的信息,
pred = model(im, augment=augment, visualize=visualize) #模型預測出來的所有檢測框,torch.size=[1,18900,85]
t3 = time_sync()
dt[1] += t3 - t2
這段代碼對每張圖片/視頻進行前向推理
第一行代碼,創(chuàng)建了一個名為“visualize”的變量,如果需要可視化,則將其設置為保存可視化結果的路徑,否則將其設置為False。使用increment_path函數(shù)創(chuàng)建路徑,如果文件名已存在,則將數(shù)字附加到文件名后面以避免覆蓋已有文件。
第二行代碼,使用model函數(shù)對圖像im進行預測,augment和visualize參數(shù)用于指示是否應該在預測時使用數(shù)據(jù)增強和可視化。
第三行代碼,記錄了當前時間,并計算從上一個時間點到這個時間點的時間差,然后將這個時間差加到一個名為dt的時間差列表中的第二個元素上。
4.6.3NMS除去多余的框
# NMS
# 執(zhí)行非極大值抑制,返回值為過濾后的預測框
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
'''
pred: 網(wǎng)絡的輸出結果
conf_thres: 置信度閾值
iou_thres: iou閾值
classes: 是否只保留特定的類別 默認為None
agnostic_nms: 進行nms是否也去除不同類別之間的框
max_det: 檢測框結果的最大數(shù)量 默認1000
'''
# 預測+NMS的時間
dt[2] += time_sync() - t3
這段代碼是執(zhí)行非最大值抑制(NMS)的步驟,用于篩選預測結果。
non_max_suppression函數(shù)的輸入?yún)?shù)包括預測結果pred、置信度閾值conf_thres、IOU(交并比)閾值iou_thres、類別classes、是否進行類別無關的NMSagnostic_nms,以及最大檢測數(shù)max_det。該函數(shù)的輸出是經過NMS篩選后的預測結果。
第二行代碼更新了計時器,記錄了NMS操作所用的時間。
4.6.4 預測過程
# Process predictions
# 把所有的檢測框畫到原圖中
for i, det in enumerate(pred): # per image 每次迭代處理一張圖片
'''
i:每個batch的信息
det:表示5個檢測框的信息
'''
seen += 1 #seen是一個計數(shù)的功能
if webcam: # batch_size >= 1
# 如果輸入源是webcam則batch_size>=1 取出dataset中的一張圖片
p, im0, frame = path[i], im0s[i].copy(), dataset.count
s += f'{i}: ' # s后面拼接一個字符串i
else:
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
'''
大部分我們一般都是從LoadImages流讀取本都文件中的照片或者視頻 所以batch_size=1
p: 當前圖片/視頻的絕對路徑 如 F:\yolo_v5\yolov5-U\data\images\bus.jpg
s: 輸出信息 初始為 ''
im0: 原始圖片 letterbox + pad 之前的圖片
frame: 視頻流,此次取的是第幾張圖片
'''
這段代碼使用了一個循環(huán)來遍歷檢測結果列表中的每個物體,并對每個物體進行處理。
循環(huán)中的變量“i”是一個索引變量,表示當前正在處理第幾個物體,而變量"det"則表示當前物體的檢測結果。循環(huán)體中的第一行代碼 "seen += 1" 用于增加一個計數(shù)器,記錄已處理的物體數(shù)量。
接下來,根據(jù)是否使用網(wǎng)絡攝像頭來判斷處理單張圖像還是批量圖像。
如果使用的是網(wǎng)絡攝像頭,則代碼會遍歷每個圖像并復制一份備份到變量"im0"中,同時將當前圖像的路徑和計數(shù)器記錄到變量"p"和"frame"中。最后,將當前處理的物體索引和相關信息記錄到字符串變量"s"中。
如果沒有使用網(wǎng)絡攝像頭,則會直接使用"im0s"變量中的圖像,將圖像路徑和計數(shù)器記錄到變量"p"和"frame"中。同時,還會檢查數(shù)據(jù)集中是否有"frame"屬性,如果有,則將其值記錄到變量"frame"中。
p = Path(p) # to Path
# 圖片/視頻的保存路徑save_path 如 runs\\detect\\exp8\\fire.jpg
save_path = str(save_dir / p.name) # im.jpg
# 設置保存框坐標的txt文件路徑,每張圖片對應一個框坐標信息
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # im.txt
# 設置輸出圖片信息。圖片shape (w, h)
s += '%gx%g ' % im.shape[2:] # print string
# 得到原圖的寬和高
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
# 保存截圖。如果save_crop的值為true,則將檢測到的bounding_box單獨保存成一張圖片。
imc = im0.copy() if save_crop else im0 # for save_crop
# 得到一個繪圖的類,類中預先存儲了原圖、線條寬度、類名
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
首先將圖像路徑轉換為"Path"對象。
接下來,使用"save_dir"變量中的路徑和圖像文件名來構建保存檢測結果圖像的完整路徑,并將其保存在變量"save_path"中。根據(jù)數(shù)據(jù)集的模式("image"或"video")來構建保存檢測結果標簽的文件路徑,并將其保存在變量"txt_path"中。
在處理圖像路徑和文件路徑之后,將圖像的尺寸信息添加到字符串變量"s"中,以便于打印。接著,計算歸一化增益"gn",并將其保存在變量中,以便后續(xù)使用。
然后,根據(jù)是否需要保存截取圖像的標志"save_crop"來選擇是否要對原始圖像進行復制,以備保存截取圖像時使用。
最后,創(chuàng)建了一個"Annotator"對象,以便于在圖像上繪制檢測結果。
# 判斷有沒有框
if len(det):
# Rescale boxes from img_size to im0 size
# 將預測信息映射到原圖
# 將標注的bounding_box大小調整為和原圖一致(因為訓練時原圖經過了放縮)此時坐標格式為xyxy
det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round() #scale_coords:坐標映射功能
# Print results
# 打印檢測到的類別數(shù)量
for c in det[:, -1].unique():
n = (det[:, -1] == c).sum() # detections per class
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
這段代碼會判斷有沒有框。如果檢測結果列表中存在物體,則代碼會執(zhí)行一些操作。
首先,將檢測結果中的物體坐標從縮放后的圖像大小還原回原始圖像的大小。這里使用了一個名為"scale_coords"的函數(shù)來進行縮放,該函數(shù)的作用是將物體坐標從縮放前的大小變換到縮放后的大小。
接著,遍歷每個物體,將其類別和數(shù)量添加到字符串變量"s"中。具體來說,計算當前類別下檢測到的物體數(shù)量"n",然后根據(jù)數(shù)量和類別名字構建一段字符串,并將其添加到變量"s"中。代碼中的"names"變量包含了數(shù)據(jù)集中所有類別的名稱。
最后,返回字符串變量"s",并結束當前代碼塊。
4.6.5? 打印目標檢測結果
# Write results
# 保存預測結果:txt/圖片畫框/crop-image
for *xyxy, conf, cls in reversed(det):
# 將每個圖片的預測信息分別存入save_dir/labels下的xxx.txt中 每行: class_id + score + xywh
if save_txt: # Write to file 保存txt文件
# 將xyxy(左上角+右下角)格式轉為xywh(中心點+寬長)格式,并歸一化,轉化為列表再保存
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
# line的形式是: ”類別 x y w h“,若save_conf為true,則line的形式是:”類別 x y w h 置信度“
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
with open(txt_path + '.txt', 'a') as f:
# 寫入對應的文件夾里,路徑默認為“runs\detect\exp*\labels”
f.write(('%g ' * len(line)).rstrip() % line + '\n')
# 在原圖上畫框+將預測到的目標剪切出來保存成圖片,保存在save_dir/crops下,在原圖像畫圖或者保存結果
if save_img or save_crop or view_img: # Add bbox to image
c = int(cls) # integer class # 類別標號
label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}') # 類別名
annotator.box_label(xyxy, label, color=colors(c, True)) #繪制邊框
# 在原圖上畫框+將預測到的目標剪切出來保存成圖片,保存在save_dir/crops下(單獨保存)
if save_crop:
save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)
這段代碼是打印目標檢測結果的一些操作
如果存在目標檢測結果,則代碼會執(zhí)行下一步操作,這里是將檢測結果寫入文件或在圖像上添加框并保存。
- 如果需要將檢測結果寫入文件,則將檢測結果中的物體坐標轉換為相對于原始圖像的歸一化坐標,并將其寫入到以圖像文件名命名的".txt"文件中。在寫入文件時,代碼將包含類別、位置和可選置信度等信息。文件的保存路徑是變量"txt_path"。
- 如果需要保存檢測結果圖像或者在圖像上繪制框,每個物體添加一個邊界框,并將其標記在圖像上。具體來說,將邊界框選擇一個顏色,并在邊界框周圍添加標簽(可選)。
- 如果需要將邊界框截取出來保存,則調用名為"save_one_box"的函數(shù),將邊界框從圖像中截取出來,并將其保存到特定的文件夾中。
這些操作都是基于一些設置變量(如"save_txt"、"save_img"等)來控制的,這些變量決定了檢測結果是否應該寫入文件或圖像。
最后,如果需要在窗口中查看檢測結果,則代碼會在圖像上繪制邊界框并顯示圖像。
?4.6.6? 在窗口中實時查看檢測結果
# Print time (inference-only)
# 打印耗時
LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')
# Stream results
# 如果設置展示,則show圖片 / 視頻
im0 = annotator.result() # im0是繪制好的圖片
# 顯示圖片
if view_img:
cv2.imshow(str(p), im0)
cv2.waitKey(1) # 暫停 1 millisecond
這段代碼是實現(xiàn)在輸出窗口實時查看檢測結果
如果需要在窗口中實時查看檢測結果,則會使用OpenCV庫中的函數(shù)將圖像顯示在窗口中,并等待1毫秒以便繼續(xù)下一幀的檢測。
代碼會檢查是否已經為當前圖像創(chuàng)建了窗口(if p not in windows),并在必要時創(chuàng)建窗口,并使用圖像名稱來命名該窗口。窗口的名稱是由變量"p"指定的圖像路徑名。
- 如果檢測到圖像尚未在窗口中打開,則代碼會創(chuàng)建一個新窗口并將圖像顯示在窗口中。
- 如果圖像已經在窗口中打開,則代碼會直接更新窗口中的圖像。
?4.6.7? 設置保存結果
# Save results (image with detections)
# 設置保存圖片/視頻
if save_img: # 如果save_img為true,則保存繪制完的圖片
if dataset.mode == 'image': # 如果是圖片,則保存
cv2.imwrite(save_path, im0)
else: # 'video' or 'stream' 如果是視頻或者"流"
if vid_path[i] != save_path: # new video vid_path[i] != save_path,說明這張圖片屬于一段新的視頻,需要重新創(chuàng)建視頻文件
vid_path[i] = save_path
# 以下的部分是保存視頻文件
if isinstance(vid_writer[i], cv2.VideoWriter):
vid_writer[i].release() # release previous video writer
if vid_cap: # video
fps = vid_cap.get(cv2.CAP_PROP_FPS) # 視頻幀速率 FPS
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 獲取視頻幀寬度
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 獲取視頻幀高度
else: # stream
fps, w, h = 30, im0.shape[1], im0.shape[0]
save_path += '.mp4'
vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
vid_writer[i].write(im0)
這段代碼是設置保存圖片和視頻
首先“save_img”判斷是否是圖片,如果是則保存路徑和圖片;如果是視頻或流,需要重新創(chuàng)建視頻文件。
保存視頻文件中,
- 如果是視頻“vid_cap”,則使用python-opencv讀取視頻,計算視頻幀速率FPS以及視頻幀寬度和高度。
- 如果是流,則保存路徑后綴加上'.mp4'
(hu~終于給這一塊講完了)
我們來對推理過程這一部分代碼做一個總結:
這一段代碼是一個目標檢測算法中的推理過程,通過對一張或多張圖片中的物體進行檢測,輸出檢測結果,并將檢測結果保存到文件或顯示在窗口中。以下是每個步驟的詳細說明:
- 對于每個輸入圖片,將其路徑、原始圖像和當前幀數(shù)(如果存在)分別賦值給p、im0和frame變量;
- 如果webcam為True,則將輸出信息字符串s初始化為空,否則將其初始化為該數(shù)據(jù)集的“frame”屬性;
- 將p轉換為Path類型,并生成保存檢測結果的路徑save_path和文本文件路徑txt_path;
- 將im0大小與目標檢測的輸入大小匹配,將檢測結果det中的邊界框坐標從img_size縮放到im0大小,然后將結果打印在輸出字符串s中;
- 如果save_txt為True,則將結果寫入文本文件中;
- 如果save_img、save_crop或view_img中任意一個為True,則將檢測結果添加到圖像中,并在窗口中顯示結果;
- 如果save_img為True,則保存結果圖像;
- 如果是視頻數(shù)據(jù)集,則將結果寫入視頻文件中;
- 最后,打印每個圖片的檢測時間。
4.7?在終端里打印出運行的結果
'''================7.在終端里打印出運行的結果============================'''
# Print results
t = tuple(x / seen * 1E3 for x in dt) # speeds per image 平均每張圖片所耗費時間
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
if save_txt or save_img:
s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '' # 標簽保存的路徑
LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
if update:
strip_optimizer(weights) # update model (to fix SourceChangeWarning)
這部分代碼用于打印結果,記錄了一些總共的耗時,以及信息保存。
輸出結果包括每張圖片的預處理、推理和NMS時間,以及結果保存的路徑。
如果update為True,則將模型更新,以修復SourceChangeWarning。
??五、detect.py代碼全部注釋
# YOLOv5 ?? by Ultralytics, GPL-3.0 license
"""
Run inference on images, videos, directories, streams, etc.
Usage:
$ python path/to/detect.py --weights yolov5s.pt --source 0 # webcam
img.jpg # image
vid.mp4 # video
path/ # directory
path/*.jpg # glob
'https://youtu.be/Zgi9g1ksQHc' # YouTube
'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP stream
"""
'''===============================================一、導入包==================================================='''
'''====================================1.導入安裝好的python庫========================================'''
import argparse # 解析命令行參數(shù)的庫
import os # 與操作系統(tǒng)進行交互的文件庫 包含文件路徑操作與解析
import sys # sys模塊包含了與python解釋器和它的環(huán)境有關的函數(shù)。
from pathlib import Path # Path能夠更加方便得對字符串路徑進行處理
import cv2 # sys模塊包含了與python解釋器和它的環(huán)境有關的函數(shù)。
import torch #pytorch 深度學習庫
import torch.backends.cudnn as cudnn #讓內置的cudnn的 auto-tuner 自動尋找最適合當前配置的高效算法,來達到優(yōu)化運行效率的問題
'''==================================================2.獲取當前文件的絕對路徑===================================================='''
FILE = Path(__file__).resolve() # __file__指的是當前文件(即detect.py),FILE最終保存著當前文件的絕對路徑,比如D://yolov5/detect.py
ROOT = FILE.parents[0] # YOLOv5 root directory ROOT保存著當前項目的父目錄,比如 D://yolov5
if str(ROOT) not in sys.path: # sys.path即當前python環(huán)境可以運行的路徑,假如當前項目不在該路徑中,就無法運行其中的模塊,所以就需要加載路徑
sys.path.append(str(ROOT)) # add ROOT to PATH 把ROOT添加到運行路徑上
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative ROOT設置為相對路徑
'''==================================================3..加載自定義模塊===================================================='''
from models.common import DetectMultiBackend
from utils.datasets import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr,
increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import select_device, time_sync
'''==================================================二、run函數(shù)——傳入?yún)?shù)===================================================='''
'''====================================1.載入?yún)?shù)========================================'''
@torch.no_grad() # 該標注使得方法中所有計算得出的tensor的requires_grad都自動設置為False,也就是說不進行梯度的計算(當然也就沒辦法反向傳播了), 節(jié)約顯存和算
def run(weights=ROOT / 'yolov5s.pt', # model.pt path(s) 事先訓練完成的權重文件,比如yolov5s.pt,默認 weights/,假如使用官方訓練好的文件(比如yolov5s),則會自動下載
source=ROOT / 'data/images', # file/dir/URL/glob, 0 for webcam 預測時的輸入數(shù)據(jù),可以是文件/路徑/URL/glob, 輸入是0的話調用攝像頭作為輸入,默認data/images/
# data=ROOT / 'data/coco128.yaml', # dataset.yaml path, data文件路徑,包括類別/圖片/標簽等信息
imgsz=(640, 640), # inference size (pixels) 預測時的放縮后圖片大小(因為YOLO算法需要預先放縮圖片), 兩個值分別是height, width。默認640*640
conf_thres=0.25, # confidence threshold 置信度閾值, 高于此值的bounding_box才會被保留。默認0.25,用在nms中
iou_thres=0.45, # NMS IOU threshold IOU閾值,高于此值的bounding_box才會被保留。默認0.45,用在nms中
max_det=1000, # maximum detections per image 一張圖片上檢測的最大目標數(shù)量,用在nms中
device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu 所使用的GPU編號,如果使用CPU就寫cpu
view_img=False, # show results 是否展示預測之后的圖片或視頻,默認False
save_txt=False, # save results to *.txt 是否將預測的框坐標以txt文件形式保存, 默認False, 使用--save-txt 在路徑runs/detect/exp*/labels/*.txt下生成每張圖片預測的txt文件
save_conf=False, # save confidences in --save-txt labels 是否將結果中的置信度保存在txt文件中,默認False
save_crop=False, # save cropped prediction boxes 是否保存裁剪后的預測框,默認為False, 使用--save-crop 在runs/detect/exp*/crop/剪切類別文件夾/ 路徑下會保存每個接下來的目標
nosave=False, # do not save images/videos 不保存圖片、視頻, 要保存圖片,不設置--nosave 在runs/detect/exp*/會出現(xiàn)預測的結果
classes=None, # filter by class: --class 0, or --class 0 2 3 過濾指定類的預測結果
agnostic_nms=False, # class-agnostic NMS 進行NMS去除不同類別之間的框, 默認False
augment=False, # augmented inference TTA測試時增強/多尺度預測,可以提分
visualize=False, # visualize features 是否可視化網(wǎng)絡層輸出特征
update=False, # update all models 如果為True,則對所有模型進行strip_optimizer操作,去除pt文件中的優(yōu)化器等信息,默認為False
project=ROOT / 'runs/detect', # save results to project/name 預測結果保存的路徑
name='exp', # save results to project/name 結果保存文件夾的命名前綴
exist_ok=False, # existing project/name ok, do not increment True: 推理結果覆蓋之前的結果 False: 推理結果新建文件夾保存,文件夾名遞增
line_thickness=3, # bounding box thickness (pixels) 繪制Bounding_box的線寬度
hide_labels=False, # hide labels 若為True: 隱藏標簽
hide_conf=False, # hide confidences 若為True: 隱藏置信度
half=False, # use FP16 half-precision inference 是否使用半精度推理(節(jié)約顯存)
dnn=False, # use OpenCV DNN for ONNX inference 是否使用OpenCV DNN預測
):
'''====================================2.初始化配置========================================'''
# 輸入的路徑變?yōu)樽址? source = str(source)
# 是否保存圖片和txt文件,如果nosave(傳入的參數(shù))為false且source的結尾不是txt則保存圖片
save_img = not nosave and not source.endswith('.txt') # save inference images
# 判斷source是不是視頻/圖像文件路徑
# Path()提取文件名。suffix:最后一個組件的文件擴展名。若source是"D://YOLOv5/data/1.jpg", 則Path(source).suffix是".jpg", Path(source).suffix[1:]是"jpg"
# 而IMG_FORMATS 和 VID_FORMATS兩個變量保存的是所有的視頻和圖片的格式后綴。
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
# 判斷source是否是鏈接
# .lower()轉化成小寫 .upper()轉化成大寫 .title()首字符轉化成大寫,其余為小寫, .startswith('http://')返回True or Flase
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
# 判斷是source是否是攝像頭
# .isnumeric()是否是由數(shù)字組成,返回True or False
webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
if is_url and is_file:
# 返回文件。如果source是一個指向圖片/視頻的鏈接,則下載輸入數(shù)據(jù)
source = check_file(source) # download
'''====================================3.保存結果========================================'''
# Directories
# save_dir是保存運行結果的文件夾名,是通過遞增的方式來命名的。第一次運行時路徑是“runs\detect\exp”,第二次運行時路徑是“runs\detect\exp1”
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)
# 根據(jù)前面生成的路徑創(chuàng)建文件夾
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
'''====================================4.加載模型========================================'''
# Load model 加載模型
# 獲取設備 CPU/CUDA
device = select_device(device)
# DetectMultiBackend定義在models.common模塊中,是我們要加載的網(wǎng)絡,其中weights參數(shù)就是輸入時指定的權重文件(比如yolov5s.pt)
model = DetectMultiBackend(weights, device=device, dnn=dnn)
stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx
'''
stride:推理時所用到的步長,默認為32, 大步長適合于大目標,小步長適合于小目標
names:保存推理結果名的列表,比如默認模型的值是['person', 'bicycle', 'car', ...]
pt: 加載的是否是pytorch模型(也就是pt格式的文件)
jit:當某段代碼即將第一次被執(zhí)行時進行編譯,因而叫“即時編譯”
onnx:利用Pytorch我們可以將model.pt轉化為model.onnx格式的權重,在這里onnx充當一個后綴名稱,
model.onnx就代表ONNX格式的權重文件,這個權重文件不僅包含了權重值,也包含了神經網(wǎng)絡的網(wǎng)絡流動信息以及每一層網(wǎng)絡的輸入輸出信息和一些其他的輔助信息。
'''
# 確保輸入圖片的尺寸imgsz能整除stride=32 如果不能則調整為能被整除并返回
imgsz = check_img_size(imgsz, s=stride) # check image size
# Half
# 如果不是CPU,使用半進度(圖片半精度/模型半精度)
half &= pt and device.type != 'cpu' # half precision only supported by PyTorch on CUDA
if pt:
model.model.half() if half else model.model.float()
'''====================================5.加載數(shù)據(jù)========================================'''
# Dataloader
# 通過不同的輸入源來設置不同的數(shù)據(jù)加載方式
if webcam: # 使用攝像頭作為輸入
view_img = check_imshow() # 檢測cv2.imshow()方法是否可以執(zhí)行,不能執(zhí)行則拋出異常
cudnn.benchmark = True # set True to speed up constant image size inference 該設置可以加速預測
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit)# 加載輸入數(shù)據(jù)流
'''
source:輸入數(shù)據(jù)源;image_size 圖片識別前被放縮的大??;stride:識別時的步長,
auto的作用可以看utils.augmentations.letterbox方法,它決定了是否需要將圖片填充為正方形,如果auto=True則不需要
'''
bs = len(dataset) # batch_size 批大小
else: # 直接從source文件下讀取圖片
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit)
bs = 1 # batch_size
# 保存視頻的路徑
vid_path, vid_writer = [None] * bs, [None] * bs # 前者是視頻路徑,后者是一個cv2.VideoWriter對象
'''====================================6.推理部分========================================'''
# Run inference
if pt and device.type != 'cpu':
# 使用空白圖片(零矩陣)預先用GPU跑一遍預測流程,可以加速預測
model(torch.zeros(1, 3, *imgsz).to(device).type_as(next(model.model.parameters()))) # warmup
dt, seen = [0.0, 0.0, 0.0], 0
'''
dt: 存儲每一步驟的耗時
seen: 計數(shù)功能,已經處理完了多少幀圖片
'''
# 去遍歷圖片,進行計數(shù),
for path, im, im0s, vid_cap, s in dataset:
'''
在dataset中,每次迭代的返回值是self.sources, img, img0, None, ''
path:文件路徑(即source)
im: resize后的圖片(經過了放縮操作)
im0s: 原始圖片
vid_cap=none
s: 圖片的基本信息,比如路徑,大小
'''
# ===以下部分是做預處理===#
t1 = time_sync() # 獲取當前時間
im = torch.from_numpy(im).to(device) # 將圖片放到指定設備(如GPU)上識別。#torch.size=[3,640,480]
im = im.half() if half else im.float() # uint8 to fp16/32 # 把輸入從整型轉化為半精度/全精度浮點數(shù)。
im /= 255 # 0 - 255 to 0.0 - 1.0 歸一化,所有像素點除以255
if len(im.shape) == 3:
im = im[None] # expand for batch dim 添加一個第0維。缺少batch這個尺寸,所以將它擴充一下,變成[1,3,640,480]
t2 = time_sync() # 獲取當前時間
dt[0] += t2 - t1 # 記錄該階段耗時
# Inference
# 可視化文件路徑。如果為True則保留推理過程中的特征圖,保存在runs文件夾中
visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
# 推理結果,pred保存的是所有的bound_box的信息,
pred = model(im, augment=augment, visualize=visualize) #模型預測出來的所有檢測框,torch.size=[1,18900,85]
t3 = time_sync()
dt[1] += t3 - t2
# NMS
# 執(zhí)行非極大值抑制,返回值為過濾后的預測框
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
'''
pred: 網(wǎng)絡的輸出結果
conf_thres: 置信度閾值
iou_thres: iou閾值
classes: 是否只保留特定的類別 默認為None
agnostic_nms: 進行nms是否也去除不同類別之間的框
max_det: 檢測框結果的最大數(shù)量 默認1000
'''
# 預測+NMS的時間
dt[2] += time_sync() - t3
# Second-stage classifier (optional) 設置第二次分類,默認不使用
# pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)
# Process predictions
# 把所有的檢測框畫到原圖中
for i, det in enumerate(pred): # per image 每次迭代處理一張圖片
'''
i:每個batch的信息
det:表示5個檢測框的信息
'''
seen += 1 #seen是一個計數(shù)的功能
if webcam: # batch_size >= 1
# 如果輸入源是webcam則batch_size>=1 取出dataset中的一張圖片
p, im0, frame = path[i], im0s[i].copy(), dataset.count
s += f'{i}: ' # s后面拼接一個字符串i
else:
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
'''
大部分我們一般都是從LoadImages流讀取本都文件中的照片或者視頻 所以batch_size=1
p: 當前圖片/視頻的絕對路徑 如 F:\yolo_v5\yolov5-U\data\images\bus.jpg
s: 輸出信息 初始為 ''
im0: 原始圖片 letterbox + pad 之前的圖片
frame: 視頻流,此次取的是第幾張圖片
'''
# 當前路徑y(tǒng)olov5/data/images/
p = Path(p) # to Path
# 圖片/視頻的保存路徑save_path 如 runs\\detect\\exp8\\fire.jpg
save_path = str(save_dir / p.name) # im.jpg
# 設置保存框坐標的txt文件路徑,每張圖片對應一個框坐標信息
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # im.txt
# 設置輸出圖片信息。圖片shape (w, h)
s += '%gx%g ' % im.shape[2:] # print string
# 得到原圖的寬和高
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
# 保存截圖。如果save_crop的值為true,則將檢測到的bounding_box單獨保存成一張圖片。
imc = im0.copy() if save_crop else im0 # for save_crop
# 得到一個繪圖的類,類中預先存儲了原圖、線條寬度、類名
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
# 判斷有沒有框
if len(det):
# Rescale boxes from img_size to im0 size
# 將預測信息映射到原圖
# 將標注的bounding_box大小調整為和原圖一致(因為訓練時原圖經過了放縮)此時坐標格式為xyxy
det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round() #scale_coords:坐標映射功能
# Print results
# 打印檢測到的類別數(shù)量
for c in det[:, -1].unique():
n = (det[:, -1] == c).sum() # detections per class
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
# Write results
# 保存預測結果:txt/圖片畫框/crop-image
for *xyxy, conf, cls in reversed(det):
# 將每個圖片的預測信息分別存入save_dir/labels下的xxx.txt中 每行: class_id + score + xywh
if save_txt: # Write to file 保存txt文件
# 將xyxy(左上角+右下角)格式轉為xywh(中心點+寬長)格式,并歸一化,轉化為列表再保存
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
# line的形式是: ”類別 x y w h“,若save_conf為true,則line的形式是:”類別 x y w h 置信度“
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
with open(txt_path + '.txt', 'a') as f:
# 寫入對應的文件夾里,路徑默認為“runs\detect\exp*\labels”
f.write(('%g ' * len(line)).rstrip() % line + '\n')
# 在原圖上畫框+將預測到的目標剪切出來保存成圖片,保存在save_dir/crops下,在原圖像畫圖或者保存結果
if save_img or save_crop or view_img: # Add bbox to image
c = int(cls) # integer class # 類別標號
label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}') # 類別名
annotator.box_label(xyxy, label, color=colors(c, True)) #繪制邊框
# 在原圖上畫框+將預測到的目標剪切出來保存成圖片,保存在save_dir/crops下(單獨保存)
if save_crop:
save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)
# Print time (inference-only)
# 打印耗時
LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')
# Stream results
# 如果設置展示,則show圖片 / 視頻
im0 = annotator.result() # im0是繪制好的圖片
# 顯示圖片
if view_img:
cv2.imshow(str(p), im0)
cv2.waitKey(1) # 暫停 1 millisecond
# Save results (image with detections)
# 設置保存圖片/視頻
if save_img: # 如果save_img為true,則保存繪制完的圖片
if dataset.mode == 'image': # 如果是圖片,則保存
cv2.imwrite(save_path, im0)
else: # 'video' or 'stream' 如果是視頻或者"流"
if vid_path[i] != save_path: # new video vid_path[i] != save_path,說明這張圖片屬于一段新的視頻,需要重新創(chuàng)建視頻文件
vid_path[i] = save_path
# 以下的部分是保存視頻文件
if isinstance(vid_writer[i], cv2.VideoWriter):
vid_writer[i].release() # release previous video writer
if vid_cap: # video
fps = vid_cap.get(cv2.CAP_PROP_FPS) # 視頻幀速率 FPS
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 獲取視頻幀寬度
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 獲取視頻幀高度
else: # stream
fps, w, h = 30, im0.shape[1], im0.shape[0]
save_path += '.mp4'
vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
vid_writer[i].write(im0)
'''====================================7.在終端里打印出運行的結果========================================'''
# Print results
t = tuple(x / seen * 1E3 for x in dt) # speeds per image 平均每張圖片所耗費時間
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
if save_txt or save_img:
s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '' # 標簽保存的路徑
LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
if update:
strip_optimizer(weights) # update model (to fix SourceChangeWarning)
'''==================================================三、Parse_opt()用來設置輸入?yún)?shù)的子函數(shù)===================================================='''
def parse_opt():
"""
?? weights: 訓練的權重路徑,可以使用自己訓練的權重,也可以使用官網(wǎng)提供的權重
默認官網(wǎng)的權重yolov5s.pt(yolov5n.pt/yolov5s.pt/yolov5m.pt/yolov5l.pt/yolov5x.pt/區(qū)別在于網(wǎng)絡的寬度和深度以此增加)
??source: 測試數(shù)據(jù),可以是圖片/視頻路徑,也可以是'0'(電腦自帶攝像頭),也可以是rtsp等視頻流, 默認data/images
?? data: 配置數(shù)據(jù)文件路徑, 包括image/label/classes等信息, 訓練自己的文件, 需要作相應更改, 可以不用管
如果設置了只顯示個別類別即使用了--classes = 0 或二者1, 2, 3等, 則需要設置該文件,數(shù)字和類別相對應才能只檢測某一個類
??imgsz: 網(wǎng)絡輸入圖片大小, 默認的大小是640
??conf-thres: 置信度閾值, 默認為0.25
??iou-thres: 做nms的iou閾值, 默認為0.45
??max-det: 保留的最大檢測框數(shù)量, 每張圖片中檢測目標的個數(shù)最多為1000類
??device: 設置設備CPU/CUDA, 可以不用設置
??view-img: 是否展示預測之后的圖片/視頻, 默認False, --view-img 電腦界面出現(xiàn)圖片或者視頻檢測結果
??save-txt: 是否將預測的框坐標以txt文件形式保存, 默認False, 使用--save-txt 在路徑runs/detect/exp*/labels/*.txt下生成每張圖片預測的txt文件
??save-conf: 是否將置信度conf也保存到txt中, 默認False
??save-crop: 是否保存裁剪預測框圖片, 默認為False, 使用--save-crop 在runs/detect/exp*/crop/剪切類別文件夾/ 路徑下會保存每個接下來的目標
??nosave: 不保存圖片、視頻, 要保存圖片,不設置--nosave 在runs/detect/exp*/會出現(xiàn)預測的結果
??classes: 設置只保留某一部分類別, 形如0或者0 2 3, 使用--classes = n, 則在路徑runs/detect/exp*/下保存的圖片為n所對應的類別, 此時需要設置data
??agnostic-nms: 進行NMS去除不同類別之間的框, 默認False
??augment: TTA測試時增強/多尺度預測
??visualize: 是否可視化網(wǎng)絡層輸出特征
??update: 如果為True,則對所有模型進行strip_optimizer操作,去除pt文件中的優(yōu)化器等信息,默認為False
??project:保存測試日志的文件夾路徑
??name:保存測試日志文件夾的名字, 所以最終是保存在project/name中
??exist_ok: 是否重新創(chuàng)建日志文件, False時重新創(chuàng)建文件
??line-thickness: 畫框的線條粗細
??hide-labels: 可視化時隱藏預測類別
??hide-conf: 可視化時隱藏置信度
??half: 是否使用F16精度推理, 半進度提高檢測速度
??dnn: 用OpenCV DNN預測
"""
parser = argparse.ArgumentParser()
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)')
parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam')
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
parser.add_argument('--conf-thres', type=float, default=0.5, help='confidence threshold')
parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--view-img', action='store_true', help='show results')
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
parser.add_argument('--augment', action='store_true', help='augmented inference')
parser.add_argument('--visualize', action='store_true', help='visualize features')
parser.add_argument('--update', action='store_true', help='update all models')
parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name')
parser.add_argument('--name', default='exp', help='save results to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
opt = parser.parse_args() # 擴充維度
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
print_args(FILE.stem, opt) # 打印所有參數(shù)信息
return opt
'''==================================================四、設置main函數(shù)===================================================='''
def main(opt):
# 檢查環(huán)境/打印參數(shù),主要是requrement.txt的包是否安裝,用彩色顯示設置的參數(shù)
check_requirements(exclude=('tensorboard', 'thop'))
# 執(zhí)行run()函數(shù)
run(**vars(opt))
# 命令使用
# python detect.py --weights runs/train/exp_yolov5s/weights/best.pt --source data/images/fishman.jpg # webcam
if __name__ == "__main__":
opt = parse_opt() # 解析參數(shù)
main(opt) # 執(zhí)行主函數(shù)
本文參考:文章來源:http://www.zghlxwxcb.cn/news/detail-417545.html
【帶你一行行讀懂yolov5代碼,yolov5源碼】文章來源地址http://www.zghlxwxcb.cn/news/detail-417545.html
到了這里,關于YOLOv5源碼逐行超詳細注釋與解讀(2)——推理部分detect.py的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!