YOLO检测目标后实现距离测量
当我们使用YOLO检测器得到检测结果后,我们如何计算检测的物体距离相机的位置呢?
目前,有三种较为主流的距离计算方法,分别是单目测距、双目测距以及基于深度估计的测距方法。
单目测距
工作原理:单目测距使用一个摄像头来估计物体的距离。由于只有一个视角,无法直接计算深度信息,因此单目测距依赖于一些假设或先验知识,如物体的尺寸、颜色、纹理等特征。
优点: 简单且成本低。 易于部署和维护。
缺点: 准确度较低,因为缺乏直接的空间信息。 对环境和目标物体有特定要求,例如需要知道物体的实际大小。
应用场景:常用于简单场景下的距离估算,比如在已知背景环境中跟踪移动对象。
借助YOLO目标检测方法,计算检测到的物体距离:
from ultralytics import YOLO
import cv2
# 已知参数:不同类别的真实宽度(单位:米)
KNOWN_WIDTHS = {
0: 0.5, # 均压环
1: 0.5, # 复合绝缘子
2: 0.06, # 挂点金具
3: 0.5, # 玻璃绝缘子
4: 0.2, # 连接金具
5: 1, # '避雷器
6: 0.2, # 防震锤
}
# 不同类别的颜色(BGR 格式)
COLORS = {
0: (0, 255, 0), # 绿色
1: (255, 0, 0), # 蓝色
2: (0, 0, 255), # 红色
3: (0, 225, 255), # 红色
4: (0, 128, 255), # 红色
5: (128, 0, 255), # 红色
6: (3, 0, 128) # 红色
}
# 相机焦距(单位:像素)
FOCAL_LENGTH = 4032
def calculate_distance(known_width, focal_length, pixel_width):
"""
根据目标的真实宽度、相机焦距和目标在图像中的像素宽度,计算目标与相机的距离。
"""
return (known_width * focal_length) / pixel_width
def adjust_exposure(image):
"""
使用CLAHE技术调整图像的曝光度。
"""
# 将图像转换为Lab色彩空间,以便单独处理亮度通道
lab_image = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
l_channel, a_channel, b_channel = cv2.split(lab_image)
# 应用CLAHE到L通道
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
cl = clahe.apply(l_channel)
# 合并处理后的L通道和其他两个通道
updated_lab_image = cv2.merge((cl, a_channel, b_channel))
# 将Lab图像转换回BGR色彩空间
adjusted_image = cv2.cvtColor(updated_lab_image, cv2.COLOR_LAB2BGR)
return adjusted_image
def adjust_exposure(image):
"""
使用CLAHE技术调整图像的曝光度。
"""
# 将图像转换为Lab色彩空间,以便单独处理亮度通道
lab_image = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
l_channel, a_channel, b_channel = cv2.split(lab_image)
# 应用CLAHE到L通道
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
cl = clahe.apply(l_channel)
# 合并处理后的L通道和其他两个通道
updated_lab_image = cv2.merge((cl, a_channel, b_channel))
# 将Lab图像转换回BGR色彩空间
adjusted_image = cv2.cvtColor(updated_lab_image, cv2.COLOR_LAB2BGR)
return adjusted_image
# 加载模型
model = YOLO("D:\project_mine\detection/ultralytics/runs\detect/train2\weights/best.pt") # 预训练的 YOLO11n 模型
# 读取原始图像
original_img = cv2.imread("images/test2.JPG")
# 创建一个副本用于显示(调整曝光度)
adjusted_img = adjust_exposure(original_img.copy())
# 使用调整后的图像进行目标检测
results = model(adjusted_img) # 使用调整后的图像进行推理
# 处理结果
for result in results:
# 在原始图像上获取边界框坐标 (x1, y1, x2, y2)
boxes = result.boxes # 获取边界框对象
cls_list = result.boxes.cls # 获取类别 ID 列表
for box, cls in zip(boxes, cls_list):
cls = int(cls) # 转换为整数类别 ID
# 获取边界框坐标 (x1, y1, x2, y2)
x1, y1, x2, y2 = map(int, box.xyxy[0]) # 提取单个边界框的坐标
# 计算目标在原始图像中的宽度(像素)
pixel_width = x2 - x1
# 根据类别获取真实宽度
known_width = KNOWN_WIDTHS.get(cls, 0.1) # 默认宽度为 0.1 米(如果类别未定义)
# 计算目标与相机的距离
if pixel_width > 0: # 确保像素宽度有效
distance = calculate_distance(known_width, FOCAL_LENGTH, pixel_width)
# 根据类别获取颜色
color = COLORS.get(cls, (255, 255, 255)) # 默认颜色为白色
# 在调整后的图像上绘制检测框
cv2.rectangle(adjusted_img, (x1, y1), (x2, y2), color, 2)
# 构造标注文本
label = f"Class {cls}, Dist: {distance:.2f}m"
# 计算文字放置位置(置于框内中心位置偏上一点)
text_size, _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.9, 2)
text_x = x1 + (x2 - x1 - text_size[0]) // 2 # 文字X坐标:框内居中
text_y = y1 + (y2 - y1) // 2 # 文字Y坐标:框内垂直居中
# 在检测框中央添加标注信息
cv2.putText(adjusted_img, label, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.9, color, 2)
else:
print(f"无法计算距离:类别 {cls} 的目标宽度无效")
# 保存带有标注的图片
cv2.imwrite("调整曝光测距.jpg", adjusted_img)
双目测距
工作原理:双目测距模仿人类视觉系统,使用两个摄像头同时拍摄同一场景,通过比较两幅图像中的差异(视差)来计算每个点的深度信息。视差越大,物体越近;反之亦然。
优点: 相对准确,能够提供较为精确的深度信息。 不需要预先知道物体的具体信息。
缺点: 需要精确校准两个摄像头的位置关系。在低纹理区域或遮挡情况下性能下降。 成本较高,因为需要两个高质量的摄像头。
应用场景:适用于自动驾驶汽车、机器人导航等领域,其中精确的距离测量至关重要。
import cv2
import numpy as np
from ultralytics import YOLO
# 辅助函数:计算距离
def calculate_distance(disparity, focal_length, baseline):
"""根据视差计算目标距离"""
return (focal_length * baseline) / disparity
# 辅助函数:查找视差(简化实现)
def find_disparity(center_x, center_y, left_img, right_img):
"""通过左右图像匹配点计算视差"""
# 提取左图像和右图像的特征区域(简化实现:直接比较灰度值)
gray_left = cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY)
gray_right = cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY)
# 固定搜索范围(假设目标不会偏离太多)
search_range = 50 # 搜索范围为 50 像素
min_error = float('inf')
best_match_x = center_x
for offset in range(-search_range, search_range + 1):
match_x = center_x + offset
if 0 <= match_x < gray_right.shape[1]:
error = np.sum(np.abs(gray_left[center_y, center_x] - gray_right[center_y, match_x]))
if error < min_error:
min_error = error
best_match_x = match_x
# 视差为水平位移
disparity = abs(center_x - best_match_x)
return disparity
# 假设已知的参数
FOCAL_LENGTH =448 # 焦距(单位为像素)
BASELINE = 0.03 # 基线长度(单位为米)
model = YOLO("yolo11n.pt") # 预训练的 YOLO11n 模型
#model = YOLO("D:\project_mine\detection/ultralytics/runs\detect/train2\weights/best.pt")
# 读取左右图像
left_img = cv2.imread("images/right.jpg")
right_img = cv2.imread("images/left.jpg")
# 使用调整后的左图像进行目标检测
results = model(left_img) # 使用 YOLO 模型进行推理
# 处理结果
for result in results:
boxes = result.boxes # 获取边界框对象
for box in boxes:
# 获取边界框坐标 (x1, y1, x2, y2)
x1, y1, x2, y2 = map(int, box.xyxy[0]) # 提取单个边界框的坐标
# 计算目标在左图像中的中心点
center_x = (x1 + x2) // 2
center_y = (y1 + y2) // 2
# 在右图像中寻找对应的匹配点(简化实现:假设同一行)
disparity = find_disparity(center_x, center_y, left_img, right_img)
if disparity > 0: # 确保视差有效
# 计算目标与相机的距离
distance = calculate_distance(disparity, FOCAL_LENGTH, BASELINE)
# 在调整后的左图像上绘制检测框
color = (0, 255, 0) # 固定颜色为绿色
cv2.rectangle(left_img, (x1, y1), (x2, y2), color, 2)
# 构造标注文本
label = f"Dist: {distance:.2f}m"
# 计算文字放置位置(置于框内中心位置偏上一点)
text_size, _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.9, 2)
text_x = x1 + (x2 - x1 - text_size[0]) // 2 # 文字X坐标:框内居中
text_y = y1 + (y2 - y1) // 2 # 文字Y坐标:框内垂直居中
# 在检测框中央添加标注信息
cv2.putText(left_img, label, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.9, color, 2)
else:
print("无法计算距离:视差无效")
# 保存带有标注的图片
cv2.imwrite("left_with_annotations.jpg", left_img)
深度估计测距
工作原理:深度估计通常是基于深度学习的方法,利用神经网络从单一图像或立体图像中预测出每个像素对应的深度值。这种方法可以被视为一种高级形式的单目测距,但它不依赖于具体的物理测量,而是通过学习大量的标记数据来“理解”图像中的深度线索。
优点: 可以从单张图像中推断出深度信息,灵活性高。 随着模型训练数据量的增加和技术进步,准确性不断提高。
缺点:计算资源需求大,尤其是在实时应用中。 模型训练复杂,需要大量的标注数据。
应用场景:广泛应用于增强现实(AR)、虚拟现实(VR)、3D重建等需要高精度深度信息的领域。
下面这个是借助 torch
中自带的 MiDaS
深度估计模型进行距离测算。
import cv2
import torch
from ultralytics import YOLO
# 加载 YOLO11 模型
yolo_model_path = "yolo11n.pt" # 替换为您的 YOLO11 模型路径
yolo_model = YOLO(yolo_model_path)
# 加载深度估计模型(例如 MiDaS)
midas_model_type = "MiDaS_small" # 可选模型类型
midas = torch.hub.load("intel-isl/MiDaS", midas_model_type)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
midas.to(device)
midas.eval()
# 预处理变换
transform = torch.hub.load("intel-isl/MiDaS", "transforms")
if midas_model_type == "DPT_Large":
transform = transform.dpt_transform
else:
transform = transform.small_transform
# 标定参数(根据实验调整)
scale_factor = 100 # 根据标定得到的比例因子
offset = 2.0 # 根据标定得到的偏移量
# 打开视频流
cap = cv2.VideoCapture(0) # 使用摄像头,或者替换为视频文件路径
cv2.namedWindow("YOLO11 Depth Estimation")
while cap.isOpened():
ret, frame = cap.read()
if not ret:
print("无法读取视频帧!")
break
# 使用 YOLO11 进行目标检测
results = yolo_model(frame)
annotated_frame = results[0].plot() # 绘制检测结果
# 使用 MiDaS 进行深度估计
input_batch = transform(frame).to(device)
with torch.no_grad():
prediction = midas(input_batch)
prediction = torch.nn.functional.interpolate(
prediction.unsqueeze(1),
size=frame.shape[:2],
mode="bicubic",
align_corners=False,
).squeeze()
depth_map = prediction.cpu().numpy()
depth_map = cv2.normalize(depth_map, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# 对每个检测到的目标计算距离
for box in results[0].boxes:
x1, y1, x2, y2 = map(int, box.xyxy[0]) # 获取边界框坐标
center_x, center_y = (x1 + x2) // 2, (y1 + y2) // 2 # 计算中心点
depth_value = depth_map[center_y, center_x] # 获取中心点的深度值
distance = (scale_factor / (depth_value + offset)) # 计算实际距离
# 在图像上绘制距离信息
cv2.circle(annotated_frame, (center_x, center_y), 5, (0, 0, 255), -1) # 标记中心点
cv2.putText(annotated_frame, f"{distance:.2f}m", (x1, y1 - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 1)
# 显示结果
cv2.imshow("YOLO11 Depth Estimation", annotated_frame)
# 按下 'q' 键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
cap.release()
cv2.destroyAllWindows()
总结
相较而言,单目测距依赖与经验值,而深度估计测距则依赖模型性能,综上双目测距的方式无论是在速度方面还是精度方面都能有较好的表现。