一、项目目录结构
总览
1.1 .github文件夹
可以不用管
1.2 datesets
存放自己的数据集,笔者此处定名为qrcode_public,分为images和labels。同时分别包含train 和 val。
1.3 data
训练自己的数据集,需要修改yaml文件
1.4 models
models文件夹,网络构建的配置文件和函数,分别是s、m、l、x。检测速度分别是快到慢,精度是低到高。
训练自己的数据集,需要修改
1.5 runs
runs是运行train.py与detect.py的时候产生的文件所在文件夹
1.6 utils
工具文件夹,存放工具类函数 ,loss ,metrics等函数
1.7 其他文件
参考
二、detect.py
可预测图片或者视频,或者整个文件夹或网络流,不修改参数则默认检测data/images文件夹下的两张图片。
2.1 导入模块与一些库
'''====================1.导入安装好的python库======================='''
import argparse # 解析命令行参数的库
import os # 与操作系统进行交互的文件库 包含文件路径操作与解析
import sys # sys模块包含了与python解释器和它的环境有关的函数。
from pathlib import Path # Path能够更加方便得对字符串路径进行处理
import cv2 # sys模块包含了与python解释器和它的环境有关的函数。
import torch #pytorch 深度学习库
import torch.backends.cudnn as cudnn #让内置的cudnn的 auto-tuner 自动寻找最适合当前配置的高效算法,来达到优化运行效率的问题
2.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环境可以运行的路径,假如当前项目不在该路径中,就无法运行其中的模块,所以就需要加载路径
sys.path.append(str(ROOT)) # add ROOT to PATH 把ROOT添加到运行路径上
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative ROOT设置为相对路径
2.3 加载自定义模块
from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
from utils.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
increment_path, non_max_suppression, print_args, scale_boxes, strip_optimizer, xyxy2xywh)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import select_device, smart_inference_mode
from A import B 从A引入B函数或模块
2.4 main
def main(opt):
check_requirements(exclude=('tensorboard', 'thop')) # 检查运行所需的依赖包是否已安装,排除了'tensorboard'和'thop'
run(**vars(opt)) # 运行主函数,传入命令行参数的变量
if __name__ == "__main__":
opt = parse_opt() # 解析命令行参数
main(opt) # 调用主函数,传入解析后的参数
if __name__ == "__main__":
这个语句通常用于Python脚本,表示如果当前脚本被直接执行(而不是被导入到其他模块中),则执行其中的代码块。
2.5 opt参数
def parse_opt():
parser = argparse.ArgumentParser()
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'runs/train/exp12/weights/best.pt', help='model path or triton URL')
parser.add_argument('--source', type=str, default=ROOT / 'mydata/images/Qrcode', help='file/dir/URL/glob/screen/0(webcam)')
parser.add_argument('--data', type=str, default=ROOT / 'data/Qrcode0.yaml', help='(optional) dataset.yaml path')
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.25, 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')
parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride')
opt = parser.parse_args()
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
print_args(vars(opt))
return opt
定义了一个函数parse_opt()
,用于解析命令行参数并返回相应的参数对象。具体的参数包括:
--weights
: 模型权重的路径或Triton URL,默认为预训练模型的路径。--source
: 输入数据的路径,可以是文件、目录、URL、通配符或摄像头输入,默认为QR码图像的路径。--data
: (可选)数据集配置文件的路径,默认为QR码数据集的配置文件路径。--imgsz
: 推断图像的大小,以高度和宽度的形式给出,默认为640。--conf-thres
: 置信度阈值,默认为0.25。--iou-thres
: 非极大值抑制(NMS)的IoU阈值,默认为0.45。--max-det
: 每张图像的最大检测数,默认为1000。--device
: 使用的设备,可以是CUDA设备编号(如0或0,1,2,3)或cpu,默认为空。--view-img
: 是否显示结果。--save-txt
: 是否将结果保存为*.txt文件。--save-conf
: 是否将置信度保存在保存的txt标签中。--save-crop
: 是否保存裁剪的预测框。--nosave
: 是否不保存图像/视频。--classes
: 按类别过滤,默认为空。--agnostic-nms
: 是否使用类别无关的NMS。--augment
: 是否进行增强推理。--visualize
: 是否可视化特征。--update
: 是否更新所有模型。--project
: 结果保存的项目路径,默认为runs/detect。--name
: 结果保存的项目名称,默认为exp。--exist-ok
: 是否允许已存在的项目/名称,不递增。--line-thickness
: 边界框的线条粗细,默认为3像素。--hide-labels
: 是否隐藏标签。--hide-conf
: 是否隐藏置信度。--half
: 是否使用FP16半精度推理。--dnn
: 是否使用OpenCV DNN进行ONNX推理。--vid-stride
: 视频帧速率步长,默认为1。
函数还会根据imgsz
的长度进行扩展。最后,函数会打印解析的参数,并返回参数对象opt
。
三、detect.run
3.1 载入参数
def run(
weights=ROOT / 'yolov5s.pt', # model path or triton URL
source=ROOT / 'data/images', # file/dir/URL/glob/screen/0(webcam)
data=ROOT / 'data/coco128.yaml', # dataset.yaml path
imgsz=(640, 640), # inference size (height, width)
conf_thres=0.25, # confidence threshold
iou_thres=0.45, # NMS IOU threshold
max_det=1000, # maximum detections per image
device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu
view_img=False, # show results
save_txt=False, # save results to *.txt
save_conf=False, # save confidences in --save-txt labels
save_crop=False, # save cropped prediction boxes
nosave=False, # do not save images/videos
classes=None, # filter by class: --class 0, or --class 0 2 3
agnostic_nms=False, # class-agnostic NMS
augment=False, # augmented inference
visualize=False, # visualize features
update=False, # update all models
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
line_thickness=3, # bounding box thickness (pixels)
hide_labels=False, # hide labels
hide_conf=False, # hide confidences
half=False, # use FP16 half-precision inference
dnn=False, # use OpenCV DNN for ONNX inference
vid_stride=1, # video frame-rate stride
):
定义了一个函数run()
,用于运行目标检测模型。该函数包含了许多参数,这些参数控制着模型的各种行为和设置。具体参数及其默认值包括:
weights
: 模型权重的路径或Triton URL,默认为预训练模型的路径。source
: 输入数据的路径,可以是文件、目录、URL、通配符或摄像头输入,默认为图像数据集的路径。data
: 数据集配置文件的路径,默认为数据集的配置文件路径。imgsz
: 推断图像的大小,以元组的形式给出,默认为(640, 640)。conf_thres
: 置信度阈值,默认为0.25。iou_thres
: 非极大值抑制(NMS)的IoU阈值,默认为0.45。max_det
: 每张图像的最大检测数,默认为1000。device
: 使用的设备,可以是CUDA设备编号(如0或0,1,2,3)或cpu,默认为空。view_img
: 是否显示结果,默认为False。save_txt
: 是否将结果保存为*.txt文件,默认为False。save_conf
: 是否将置信度保存在保存的txt标签中,默认为False。save_crop
: 是否保存裁剪的预测框,默认为False。nosave
: 是否不保存图像/视频,默认为False。classes
: 按类别过滤,默认为空。agnostic_nms
: 是否使用类别无关的NMS,默认为False。augment
: 是否进行增强推理,默认为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。vid_stride
: 视频帧速率步长,默认为1。
该函数可根据传入的参数执行目标检测,并输出相应的结果。
3.2 初始化
source = str(source)
save_img = not nosave and not source.endswith('.txt') # save inference images
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
screenshot = source.lower().startswith('screen')
if is_url and is_file:
source = check_file(source) # download
-
source = str(source)
:将source
转换为字符串类型,以确保后续操作的一致性。 -
save_img = not nosave and not source.endswith('.txt')
:这一行创建了一个布尔值save_img
,它的值取决于两个条件:nosave
为假(即nosave
为False
),且source
不以.txt
结尾。这个条件可能用于确定是否保存推理图像。 -
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
:这一行检查source
是否是文件,它使用了Path
对象来获取文件的后缀,并检查后缀是否在图片格式或视频格式列表中。这可能用于区分来源是文件还是其他类型的数据流。 -
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
:这一行检查source
是否是一个URL,它检查source
是否以特定的协议开头,如rtsp://
、rtmp://
、http://
或https://
。 -
webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
:这一行确定了是否是来自摄像头的输入。它的逻辑是:如果source
是数字字符串(通过isnumeric()
检查)、以.txt
结尾或者是URL但不是文件,则认为是来自摄像头。 -
screenshot = source.lower().startswith('screen')
:这一行检查source
是否以'screen'
开头,如果是,可能表示要截取屏幕。
3.3 保存结果
# Directories
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
-
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)
:这一行创建了一个保存结果的目录路径。它使用了Path
对象来合并project
和name
,然后调用了increment_path
函数来确保目录的唯一性。exist_ok
参数表示如果目录已经存在,是否允许覆盖或忽略,默认情况下可能是True
。 -
(save_dir / 'labels' if save_txt else save_dir)
:这一行是一个条件表达式,它根据条件save_txt
的值选择要创建目录的路径。如果save_txt
为真,则在save_dir
路径下创建一个名为'labels'
的子目录,否则直接使用save_dir
路径。 -
.mkdir(parents=True, exist_ok=True)
:这一行调用了mkdir
方法来创建目录。parents=True
表示会创建所有必要的父目录,如果它们不存在的话。exist_ok=True
表示如果目录已经存在,不会抛出异常。
3.4 加载数据
bs = 1 # batch_size
if webcam:
view_img = check_imshow(warn=True)
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
bs = len(dataset)
elif screenshot:
dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
else:
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
vid_path, vid_writer = [None] * bs, [None] * bs
-
bs = 1
:这一行设置了批处理大小(batch_size),默认为1。 -
if webcam:
:这一行检查是否使用摄像头作为数据源。 -
view_img = check_imshow(warn=True)
:这一行调用了check_imshow
函数,可能用于检查是否应该显示图像,同时设置了一个警告标志(warn=True
)。 -
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
:如果使用摄像头,就会创建一个LoadStreams
数据集对象,它可能是用于加载视频流。参数包括图像大小(img_size
)、步长(stride
)、是否自动调整模型(auto
)以及视频步长(vid_stride
)等。 -
bs = len(dataset)
:如果是从摄像头加载数据,批处理大小(bs
)会根据数据集的长度来设置。 -
elif screenshot:
:如果不是从摄像头加载数据,这一行检查是否加载屏幕截图。 -
dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
:如果加载屏幕截图,就会创建一个LoadScreenshots
数据集对象,它可能是用于加载屏幕截图数据。参数类似于前面的设置。 -
else:
:如果既不是从摄像头加载数据,也不是加载屏幕截图,就会执行这一行。 -
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
:这一行创建了一个LoadImages
数据集对象,它用于加载图像数据。参数类似于前面的设置。 -
vid_path, vid_writer = [None] * bs, [None] * bs
:这一行初始化了两个列表,分别存储视频路径(vid_path
)和视频写入器(vid_writer
)。列表的长度为批处理大小(bs
),每个元素初始化为None
。
3.5 推理
model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz)) # warmup
seen, windows, dt = 0, [], (Profile(), Profile(), Profile())
-
model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz))
:这一行调用了模型的warmup
方法,用于模型预热。预热是指在实际运行之前对模型进行一些初始化,以提高性能和稳定性。参数imgsz
是一个元组,表示输入图像的大小。其中的条件表达式1 if pt or model.triton else bs
根据条件选择批处理大小,如果模型使用了PT或者Triton,则批处理大小为1,否则使用之前设置的批处理大小bs
。 -
seen, windows, dt = 0, [], (Profile(), Profile(), Profile())
:这一行初始化了三个变量seen
、windows
和dt
。其中:seen
可能是用于记录已经处理过的图像数目。windows
是一个空列表,可能用于存储检测到的窗口(bounding boxes)信息。dt
是一个元组,包含了三个Profile()
对象的实例,可能是用于记录代码的性能统计信息,比如运行时间等。
for path, im, im0s, vid_cap, s in dataset:
with dt[0]:
im = torch.from_numpy(im).to(model.device)
im = im.half() if model.fp16 else im.float() # uint8 to fp16/32
im /= 255 # 0 - 255 to 0.0 - 1.0
if len(im.shape) == 3:
im = im[None] # expand for batch dim
# Inference
with dt[1]:
visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
pred = model(im, augment=augment, visualize=visualize)
# NMS
with dt[2]:
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
-
for path, im, im0s, vid_cap, s in dataset:
:这是一个迭代器,遍历数据集中的每个元素,其中:path
是图像或视频的路径。im
是预处理后的图像数据。im0s
是原始图像数据。vid_cap
是视频捕获对象,可能为 None。s
是一个字符串,可能是用于记录一些信息。
-
在
with dt[0]:
块中,对图像进行预处理:- 将图像转换为 PyTorch 张量并移到模型设备上。
- 如果模型使用了 fp16,则将图像转换为半精度浮点数。
- 将图像像素值缩放到 [0, 1] 的范围内。
- 如果图像是 3 维的,则添加一个批处理维度。
-
在
with dt[1]:
块中进行推理:- 可能会根据
visualize
参数选择是否保存可视化结果。 - 调用模型进行推理,得到预测结果
pred
。
- 可能会根据
-
在
with dt[2]:
块中进行非最大抑制(NMS)处理:- 使用
non_max_suppression
函数对预测结果进行非最大抑制,过滤掉重叠度高的框。
- 使用
-
在注释的部分,可能是对检测结果进行进一步处理,包括第二阶段分类器(如果有)、结果写入文件、结果可视化、结果保存等操作。
-
打印推理时间以及一些其他信息,包括检测到的对象数量、推理时间等。
# Process predictions
for i, det in enumerate(pred): # per image
seen += 1
if webcam: # batch_size >= 1
p, im0, frame = path[i], im0s[i].copy(), dataset.count
s += f'{i}: '
else:
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
p = Path(p) # to Path
save_path = str(save_dir / p.name) # im.jpg
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # im.txt
s += '%gx%g ' % im.shape[2:] # print string
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
imc = im0.copy() if save_crop else im0 # for save_crop
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
处理模型的预测结果:
-
for i, det in enumerate(pred):
:这是一个循环,对每个图像的预测结果进行处理。i
是图像的索引。det
是模型对当前图像的预测结果,可能包含多个检测到的对象。
-
seen += 1
:增加seen
变量的计数器,用于记录已处理的图像数量。 -
if webcam:
:这是一个条件判断,判断是否为摄像头输入模式。- 如果是,表示批处理大小大于等于1,因此对于每个图像,需要分别处理摄像头输入。
- 在这种情况下,从
path
、im0s
中获取当前图像的相关信息,并更新s
字符串。 - 否则,表示单张图像输入模式,直接从
path
中获取图像信息。
-
p = Path(p)
:将图像路径转换为Path
对象。 -
save_path = str(save_dir / p.name)
:构建保存图像的路径,使用了保存目录save_dir
和图像文件名p.name
。 -
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')
:构建保存标签文件的路径,使用了保存目录save_dir
、'labels' 文件夹和图像文件名的基本部分p.stem
。如果数据集模式为 'image',则只保存一个.txt
文件;否则,在文件名末尾添加当前帧的索引。 -
s += '%gx%g ' % im.shape[2:]
:将图像的尺寸信息添加到输出字符串s
中。 -
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]
:计算图像的归一化增益(normalization gain),用于将检测结果的边界框坐标从图像坐标系转换为归一化坐标系。 -
imc = im0.copy() if save_crop else im0
:如果需要保存剪裁区域,则创建原始图像的副本imc
;否则直接使用原始图像im0
。 -
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
:创建一个注释器(Annotator)对象,用于向图像中添加边界框和标签。
if len(det):
# Rescale boxes from img_size to im0 size
det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()
# Print results
for c in det[:, 5].unique():
n = (det[:, 5] == c).sum() # detections per class
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
-
if len(det):
:检查当前图像是否有检测结果。- 如果检测结果不为空,则执行下面的操作。
-
det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()
:将检测到的边界框坐标从模型输出的图像尺寸(im.shape[2:]
)转换为原始图像尺寸(im0.shape
),并四舍五入为整数。这个操作使用了scale_boxes
函数。 -
for c in det[:, 5].unique():
:对每个类别执行操作。det[:, 5]
是检测结果中每个边界框对应的类别索引。.unique()
方法返回唯一的类别索引。- 循环遍历每个唯一的类别索引。
-
n = (det[:, 5] == c).sum()
:统计当前类别出现的次数,即检测到的当前类别的对象数量。 -
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "
:将类别名称、对象数量添加到输出字符串s
中。names[int(c)]
获取类别名称。{n} {names[int(c)]}
格式化为 "数量 类别名" 的形式。's' * (n > 1)
根据对象数量是否大于1,添加's'
字符串,用于正确显示单复数形式。s +=
将每个类别的信息添加到输出字符串中。
# Write results
for *xyxy, conf, cls in reversed(det):
if save_txt: # Write to file
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
with open(f'{txt_path}.txt', 'a') as f:
f.write(('%g ' * len(line)).rstrip() % line + '\n')
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))
if save_crop:
save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)
-
for *xyxy, conf, cls in reversed(det):
:对每个检测到的对象执行操作。xyxy
包含了对象的四个坐标值(左上角和右下角),conf
是检测置信度,cls
是对象的类别。 -
if save_txt:
:如果设置了保存文本的选项,则执行下面的操作。xyxy2xywh
函数将边界框坐标转换为中心点坐标和宽高形式,并将其归一化。将类别、归一化坐标、置信度组成一行数据。将数据写入标签文件中,文件名为txt_path.txt
。 -
获取对象的类别索引if save_img or save_crop or view_img:
:如果设置了保存图像、保存剪裁区域或查看图像的选项,则执行下面的操作。c
。根据需要隐藏标签或置信度,构建要显示的标签。在图像上绘制边界框和标签。 -
将边界框对应的区域剪裁下来,并保存到指定路径。if save_crop:
:如果设置了保存剪裁区域的选项,则执行下面的操作。
# Stream results
im0 = annotator.result()
if view_img:
if platform.system() == 'Linux' and p not in windows:
windows.append(p)
cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO) # allow window resize (Linux)
cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
cv2.imshow(str(p), im0)
cv2.waitKey(1) # 1 millisecond
用于流式传输(streaming)检测结果并显示图像(如果需要)。让我来解释:
-
im0 = annotator.result()
:获取注释器(Annotator)对象的结果,即已经在图像上绘制了边界框和标签的图像。 -
如果需要,执行下面的操作。if view_img:
:检查是否需要显示图像。 -
如果满足条件,执行下面的操作。if platform.system() == 'Linux' and p not in windows:
:检查当前操作系统是否为 Linux,并且当前图像路径p
不在已经打开的窗口列表windows
中。 -
windows.append(p)
:将当前图像路径添加到已打开窗口列表中。 -
cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO)
:创建一个窗口,窗口名称为当前图像路径的字符串表示,设置窗口属性为可调整大小并保持宽高比。 -
cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
:调整窗口大小,使其与当前图像的大小相匹配。 -
cv2.imshow(str(p), im0)
:在指定的窗口中显示图像。 -
cv2.waitKey(1)
:等待用户按键输入,参数表示等待的毫秒数,这里设置为 1 毫秒,表示每次循环等待 1 毫秒,然后继续执行下一次循环。
# Save results (image with detections)
if save_img:
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
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)
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 = str(Path(save_path).with_suffix('.mp4')) # force *.mp4 suffix on results videos
vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
vid_writer[i].write(im0)
# Print time (inference-only)
LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms")
用于保存带有检测结果的图像或视频,并打印推理时间信息。让我来解释:
-
如果需要,执行下面的操作。if save_img:
:检查是否需要保存图像。 -
如果是,表示处理的是单张图像,直接使用if dataset.mode == 'image':
:检查数据集模式是否为图像模式。cv2.imwrite()
函数将图像保存到指定路径save_path
。else: # 'video' or 'stream'
:如果不是图像模式,则处理视频或流数据。 if vid_path[i] != save_path:
:检查当前帧的保存路径是否与前一帧相同,如果不同,则表示开始保存新的视频。如果是新的视频,更新当前视频的保存路径和视频编码器。vid_writer[i].write(im0)
:将带有检测结果的图像帧写入视频文件。LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms")
:打印推理时间信息。s
是一个字符串,包含了图像的一些信息,例如尺寸等。 如果没有检测到任何对象,输出 "(no detections)"。dt[1].dt * 1E3
表示推理时间,乘以 1000 转换为毫秒单位。
这段代码的作用是根据需要保存带有检测结果的图像或视频,并打印推理时间信息。
# Print results
t = tuple(x.t / 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[0]) # update model (to fix SourceChangeWarning)
-
t = tuple(x.t / seen * 1E3 for x in dt)
:这一行计算了预处理、推理和非最大抑制(NMS)的平均时间,并将结果存储在元组t
中。这些时间是每张图像的平均时间(以毫秒为单位)。 -
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
:这一行打印了预处理、推理和NMS的平均时间信息,以及输入图像的形状。这些信息对于评估模型性能和优化代码非常有用。 -
如果设置了保存文本或保存图像的选项
save_txt
或save_img
,则会执行以下操作:- 如果保存文本 (
save_txt
),则打印已保存标签文件的数量和保存路径。 - 打印结果保存的路径。
- 如果保存文本 (
-
如果设置了更新选项
update
,则会执行以下操作:- 调用
strip_optimizer(weights[0])
函数来更新模型,可能是为了修复一些警告或错误。
- 调用
这段代码主要用于输出结果信息、保存结果文件和更新模型(如果需要)。
对推理过程这一部分代码做一个总结:
这一段代码是一个目标检测算法中的推理过程,通过对一张或多张图片中的物体进行检测,输出检测结果,并将检测结果保存到文件或显示在窗口中。以下是每个步骤的详细说明:
对于每个输入图片,将其路径、原始图像和当前帧数(如果存在)分别赋值给p、im0和frame变量;
如果webcam为True,则将输出信息字符串s初始化为空,否则将其初始化为该数据集的"frame”属性;
将p转换为Path类型,并生成保存检测结果的路径save path和文本文件路径txtpath;
将im0大小与目标检测的输入大小匹配,将检测结果det中的边界框坐标从img size缩放到im0大小,然后将结果打印在输出字符串s中;
如果save tx为为True,则将结果写入文本文件中;
如果save_img、save_crop或view_img中任意一个为True,则将检测结果添加到图像中,并在窗口中显示结果,
如果save img为True,则保存结果图像:
如果是视频数据集,则将结果写入视频文件中;
最后,打印每个图片的检测时间。