一、线程安全推理
在多线程环境中运行YOLO 模型需要仔细考虑,以确保线程安全。Python's threading
模块允许您同时运行多个线程,但在这些线程中使用YOLO 模型时,需要注意一些重要的安全问题。本页将指导您创建线程安全的YOLO 模型推理。
1.1、了解Python 线程
Python 线程是一种并行形式,允许程序同时运行多个操作。不过,Python 的全局解释器锁(GIL)意味着一次只能有一个线程执行Python 字节码。
虽然这听起来像是一种限制,但线程仍然可以提供并发性,尤其是在 I/O 绑定操作或使用释放 GIL 的操作(如由YOLO 的底层 C 库执行的操作)时。
1.2、共享模型实例的危险
在线程外实例化YOLO 模型并在多个线程间共享该实例可能会导致竞赛条件,即由于并发访问,模型的内部状态会被不一致地修改。如果模型或其组件所持有的状态在设计上不是线程安全的,那么问题就会特别严重。
非线程安全示例:单个模型实例
# Unsafe: Sharing a single model instance across threads #
from threading import Thread # 导入线程模块
from ultralytics import YOLO # 导入YOLO类
# Instantiate the model outside the thread #
shared_model = YOLO("yolov8n.pt") # 在线程外实例化YOLO模型。这是不安全的做法!
def predict(image_path):
"""Predicts objects in an image using a preloaded YOLO model,
take path string to image as argument.
使用预加载的YOLO模型预测图像中的对象,
并将图像路径字符串作为参数。
"""
results = shared_model.predict(
image_path
) # 多个线程共享同一个模型实例进行预测,可能导致线程冲突
# Process results #
# 处理结果(例如,保存、显示等),这里省略具体代码 #
# Starting threads that share the same model instance #
# 创建并启动多个线程,这些线程共享同一个模型实例 #
Thread(target=predict, args=("image1.jpg",)).start()
Thread(target=predict, args=("image2.jpg",)).start()
# 这段代码的问题在于,多个线程同时使用`shared_model`这个模型实例,
# 由于YOLO模型内部的操作可能不是线程安全的,这会导致:
# - 竞态条件(Race Conditions):多个线程不确定性地访问和修改模型的内部状态,导致结果不可预测。
# - 数据损坏(Data Corruption):模型的权重或其他内部数据可能被破坏。
# - 程序崩溃(Crashes):在某些情况下,可能会导致程序崩溃。
# 因此,**绝对不要**在多个线程之间共享同一个YOLO模型实例。
在上面的例子中 shared_model
被多个线程使用,这可能导致不可预测的结果,因为 predict
可由多个线程同时执行。
非线程安全示例:多个模型实例
# Unsafe: Sharing multiple model instances across threads can still lead to issues #
# 不安全:即使使用多个模型实例,在线程间共享仍然可能导致问题 #
from threading import Thread # 导入线程模块
from ultralytics import YOLO # 导入YOLO类
# Instantiate multiple models outside the thread #
# 在线程外实例化多个模型 #
shared_model_1 = YOLO("yolov8n_1.pt") # 创建第一个YOLO模型实例
shared_model_2 = YOLO("yolov8n_2.pt") # 创建第二个YOLO模型实例
def predict(model, image_path):
"""Runs prediction on an image using a specified YOLO model, returning the results.
使用指定的YOLO模型对图像进行预测,并返回结果。
"""
results = model.predict(image_path) # 使用传入的模型进行预测
# Process results #
# 处理结果(例如,保存、显示等),这里省略具体代码 #
# Starting threads with individual model instances #
# 创建并启动线程,每个线程使用不同的模型实例 #
Thread(
target=predict, args=(shared_model_1, "image1.jpg")
).start() # 创建并启动第一个线程,使用shared_model_1
Thread(
target=predict, args=(shared_model_2, "image2.jpg")
).start() # 创建并启动第二个线程,使用shared_model_2
# 虽然这里每个线程都有自己的模型实例(shared_model_1, shared_model_2),
# 但仍然可能存在线程安全问题。原因在于:
# 1. **底层资源共享**:YOLO模型可能依赖于一些底层的、非线程安全的库或资源。
# 即使模型实例不同,如果它们共享这些底层资源,仍然可能发生冲突。
# 2. **全局状态**:某些库或系统可能有全局状态,这些状态可能被YOLO模型修改,
# 从而影响其他线程。
# 3. **硬件限制**:例如,GPU资源是有限的。如果多个线程同时进行大量的GPU计算,
# 可能会导致性能下降或不稳定的行为。
#
# 因此,为了确保真正的线程安全,**最佳实践是在每个线程内部创建并销毁YOLO模型实例**,
# 而不是在线程外部创建然后分发给线程。
1.3、 线程安全推理
要执行线程安全推理,应在每个线程中实例化一个单独的YOLO 模型。这样可以确保每个线程都有自己独立的模型实例,从而消除出现竞赛条件的风险。
线程安全示例
# Safe: Instantiating a single model inside each thread
from threading import Thread # 导入线程模块
from ultralytics import YOLO # 导入YOLO类
def thread_safe_predict(image_path):
"""Predict on an image using a new YOLO model instance in a thread-safe manner; takes image path as input.
在一个线程安全的方式下,使用一个新的YOLO模型实例对图像进行预测;接受图像路径作为输入。
"""
local_model = YOLO("yolov8n.pt") # 在每个线程内部实例化一个新的YOLO模型。这是线程安全的首选方法。
results = local_model.predict(image_path) # 使用当前线程的模型实例进行预测
# Process results
# 处理结果(例如,保存、显示等),这里省略具体代码
# Starting threads that each have their own model instance
# 创建并启动多个线程,每个线程都拥有自己的模型实例
Thread(target=thread_safe_predict, args=("image1.jpg",)).start()
Thread(target=thread_safe_predict, args=("image2.jpg",)).start()
# 这种方式是线程安全的,因为每个线程都创建并使用自己独立的YOLO模型实例。
# 这避免了多个线程同时访问和修改同一个模型,从而消除了潜在的竞态条件和数据损坏。
# 每个线程在执行完毕后,其模型实例也会被销毁,不会影响其他线程。
1.4、使用 ThreadingLocked 装饰器
Ultralytics 提供了 ThreadingLocked
装饰器,可用于确保函数的线程安全执行。该装饰器使用锁来确保一次只能有一个线程执行被装饰的函数。
from ultralytics import YOLO # 导入YOLO类
from ultralytics.utils import ThreadingLocked # 导入ThreadingLocked装饰器
# Create a model instance #
model = YOLO("yolov8n.pt") # 创建一个YOLO模型实例
# Decorate the predict method to make it thread-safe #
# 使用 @ThreadingLocked 装饰器使 predict 方法线程安全 #
@ThreadingLocked()
def thread_safe_predict(image_path):
"""Thread-safe prediction using a shared model instance.
使用共享模型实例进行线程安全预测。
"""
results = model.predict(image_path) # 使用共享的模型进行预测
return results # 返回预测结果
# Now you can safely call this function from multiple threads #
# 现在可以安全地从多个线程调用这个函数了 #
# 这段代码使用了 `ThreadingLocked` 装饰器来确保对 `model.predict` 方法的线程安全访问。
# `ThreadingLocked` 装饰器会在方法调用前后自动加锁和解锁,
# 从而避免了多个线程同时调用 `model.predict` 导致的竞态条件。
# **需要注意的是**,虽然这种方法可以实现线程安全,但仍然建议在每个线程内部创建自己的模型实例,
# 因为这样可以提供更好的隔离性和避免潜在的资源竞争。
"(《世界人权宣言》) ThreadingLocked
装饰器在需要跨线程共享模型实例,但又想确保每次只有一个线程可以访问它时特别有用。与为每个线程创建一个新的模型实例相比,这种方法可以节省内存,但可能会降低并发性,因为线程需要等待锁被释放。
1.5、结论
当使用YOLO 型号与Python'时 threading
为了确保线程安全,我们总是在使用模型的线程中实例化模型。这种做法可以避免竞赛条件,确保推理任务可靠运行。
对于更高级的应用场景和进一步优化多线程推理性能,可以考虑使用基于进程的多进程并行或利用带有专用工作进程的任务队列。
在多线程Python 环境中使用Ultralytics YOLO 模型时,为防止出现竞赛条件,应在每个线程中实例化一个单独的YOLO 模型。这样可以确保每个线程都有自己独立的模型实例,避免并发修改模型状态。
from threading import Thread # 导入线程模块 [cite: 53, 54]
from ultralytics import YOLO # 导入YOLO类 [cite: 53, 54]
def thread_safe_predict(image_path):
"""Predict on an image in a thread-safe manner."""
local_model = YOLO("yolov8n.pt") # 在每个线程中创建一个新的YOLO模型实例 [cite: 53, 54]
results = local_model.predict(image_path) # 使用线程内的模型实例进行预测 [cite: 53, 54]
# Process results #
# 处理预测结果(例如,保存或显示),这里省略具体代码 #
# Starting threads that each have their own model instance #
# 创建并启动两个线程,每个线程都有自己的YOLO模型实例 #
Thread(target=thread_safe_predict, args=("image1.jpg",)).start()
Thread(target=thread_safe_predict, args=("image2.jpg",)).start()
# 这段代码展示了线程安全地使用YOLO进行预测的方法。
# 关键在于,每个线程内部都会创建一个独立的YOLO模型实例,
# 从而避免了多个线程同时访问和修改同一个模型实例可能导致的问题。
二、流媒体源:for-循环
import cv2
from ultralytics import YOLO
#加载YOLOV8模型
model=YOLO("yolov8n.pt")
# 打开视频文件
video_path = 'WH038.mp4'
# cap = cv2.VideoCapture(0)#调用摄像头
cap = cv2.VideoCapture(video_path)
# 遍历视频帧
while cap.isOpened():
# 从视频中读取一帧
success, frame= cap.read()
if success:
# 在帧上运行YOLOV8推理
results = model(frame)
# 在帧上可视化推理结果
annotated_frame = results[0].plot()
# 显示标注后的帧
cv2.imshow("YOLOV8推理结果", annotated_frame)
# 如果按下'q'键则退出循环
if cv2.waitKey(1) & 0xFF == ord("q"):
break
else:
# 如果视频播放完毕,则退出循环
break
# 释放视频捕获对象并关闭显示窗口
cap.release()
cv2.destroyWindow()