当前位置:   article > 正文

Realsense D435i 通过YOLOv5、YOLOv8输出目标三维坐标_yolov5+d435i

yolov5+d435i

目录

效果图

2024.1.27 yolov8新思路

一、realsense 包

二、间隔拍照

三、YOLOv5封装

四、代码实现

效果图

手上有深度相机realsense,怎么去通过YOLO识别出目标,并获取三维坐标呢?怎么把目标三维坐标发给机械臂?

这里我只介绍下通过深度相机去获取三维坐标,因为每个人机械臂的通讯方式不同,所以第二个问题就不多介绍了(你都获取到了三维坐标,发给机械臂指令这还不简简单单)

平时在YOLO里使用相机,一般都是修改下detect.py中去修改source的参数,就能够实现视频流预测目标了

parser.add_argument('--source', type=str, default='0', help='file/dir/URL/glob/screen/0(webcam)')

但如果你想通过realsense深度相机去获取深度信息,从而获得三维坐标的话,上边这种方法是不可行的,即便你修改了source参数,深度相机也只会和普通相机一样,获得的是目标的二维坐标,也就是像素坐标。

2024.1.27 yolov8新思路

        之前好多小伙伴出现了ImportError: cannot import name 'MyLoadImages' from 'utils.dataloaders'报错,前段时间一直在忙,没再解决,现在放假手上又没相机,只能暂时搁置下了,等回学校再解决这个问题。这里我提供一个新的思路--使用YOLOv8,这样就不需要进行封装了,而是直接调用YOLOv8的函数就行,多说无益,直接上代码。

还得再多说一嘴,这里我只是提供了一个替换YOLOv5封装报错的方法,对于获取三维信息的代码还得需要小伙伴们结合下面的代码自己去整合一下,如果感兴趣的小伙伴多且不太明白如何改代码的话,评论区留言,后续我会再继续完善。

  1. import time
  2. import cv2
  3. import numpy as np
  4. import pyrealsense2 as rs
  5. from ultralytics import YOLO # 将YOLOv8导入到该py文件中
  6. ''' 深度相机 '''
  7. pipeline = rs.pipeline() # 定义流程pipeline,创建一个管道
  8. config = rs.config() # 定义配置config
  9. config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30) # 配置color流
  10. pipe_profile = pipeline.start(config) # streaming流开始
  11. align = rs.align(rs.stream.color)
  12. def get_aligned_images():
  13. frames = pipeline.wait_for_frames() # 等待获取图像帧,获取颜色和深度的框架集
  14. aligned_color_frame = frames.get_color_frame() # 获取对齐帧中的的color帧
  15. img_color = np.asanyarray(aligned_color_frame.get_data()) # RGB图
  16. return img_color
  17. if __name__ == '__main__':
  18. model = YOLO(r"E:\Deep learning\YOLOv8\yolov8n.pt") # 加载权重文件,如需要更换为自己训练好的权重best.pt即可
  19. # 设置计时器
  20. start_time = time.time()
  21. interval = 6 # 间隔时间(秒)
  22. try:
  23. while True:
  24. img_color = get_aligned_images() # 获取对齐图像与相机参数
  25. # 检查是否达到间隔时间
  26. if time.time() - start_time >= interval:
  27. start_time = time.time() # 重置计时器
  28. source = [img_color]
  29. # 调用YOLOv8中的推理,还是相当于把d435i中某一帧的图片进行detect推理
  30. results = model.predict(source,save=True,show_conf=False)
  31. key = cv2.waitKey(1)
  32. # Press esc or 'q' to close the image window
  33. if key & 0xFF == ord('q') or key == 27:
  34. cv2.destroyAllWindows()
  35. pipeline.stop()
  36. break
  37. finally:
  38. # Stop streaming
  39. pipeline.stop()

一、realsense 包

        在detect.py中不行,那就换种思路,首先你得了解如何使用python代码去打开深度相机,需要去了解下深度相机的SDK(也就是软件开发工具包),这里我用D435i为例(d415好像也一样适用),如何去使用代码去打开深度相机

安装SDK

pip install pyrealsense

怎么单独使用SDK包的代码打开深度相机,我就不多解释了,CSDN中一搜一堆

二、间隔拍照

        我们都知道视频流实际上就是一帧帧图片组成的,所以我们每隔几帧就拍一次照,有人可能会问,为什么要这么做?问的好!你想想,如果要给机械臂传目标的三维坐标时,你哐哐的一直给他实时的传,他反应的过来吗,所以才使用间隔拍照,为的就是让机械臂完成一次指令后,再去拍照。

三、YOLOv5封装

        为什么叫YOLOv5封装呢,其实目的就是咱通过间隔拍照得到了一张图片,这时只需要将这个图片传到YOLO的detect.py中,就好比用detect.py去预测一张图片,平时我们只需要修改下source参数就行,运行的话就能完成预测。现在呢,我们把YOLOv5封装成一个类,当建个拍完照后,将图片经过这个类去处理预测。不知道这样说能理解吗
 

四、代码实现

话不多说,直接上代码

这是主文件main.py,目的是实现D435i间隔拍照

  1. import time
  2. import cv2
  3. import torch
  4. import numpy as np
  5. import pyrealsense2 as rs
  6. import W_detectAPI
  7. pipeline = rs.pipeline() # 定义流程pipeline,创建一个管道
  8. config = rs.config() # 定义配置config
  9. # config.enable_stream(rs.stream.depth, 1280, 720, rs.format.z16, 6) # 配置depth流
  10. # config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 15) # 配置color流
  11. config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) # 配置depth流
  12. config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) # 配置color流
  13. pipe_profile = pipeline.start(config) # streaming流开始
  14. # device = pipe_profile.get_device()
  15. # device.hardware_reset()
  16. align = rs.align(rs.stream.color)
  17. def get_aligned_images():
  18. frames = pipeline.wait_for_frames() # 等待获取图像帧,获取颜色和深度的框架集
  19. aligned_frames = align.process(frames) # 获取对齐帧,将深度框与颜色框对齐
  20. aligned_depth_frame = aligned_frames.get_depth_frame() # 获取对齐帧中的的depth帧
  21. aligned_color_frame = aligned_frames.get_color_frame() # 获取对齐帧中的的color帧
  22. #### 获取相机参数 ####
  23. depth_intrin = aligned_depth_frame.profile.as_video_stream_profile().intrinsics # 获取深度参数(像素坐标系转相机坐标系会用到)
  24. color_intrin = aligned_color_frame.profile.as_video_stream_profile().intrinsics # 获取相机内参
  25. # 相机参数
  26. # 左右相机的基线是50mm,相机焦距为2.1mm
  27. # camera_parameters = {'fx': intr.fx, 'fy': intr.fy,
  28. # 'ppx': intr.ppx, 'ppy': intr.ppy,
  29. # 'height': intr.height, 'width': intr.width,
  30. # 'depth_scale': profile.get_device().first_depth_sensor().get_depth_scale()
  31. # }
  32. img_color = np.asanyarray(aligned_color_frame.get_data()) # RGB图
  33. img_depth = np.asanyarray(aligned_depth_frame.get_data()) # 深度图(默认16位)
  34. depth_colormap = cv2.applyColorMap \
  35. (cv2.convertScaleAbs(img_depth, alpha=0.008)
  36. , cv2.COLORMAP_JET)
  37. return color_intrin, depth_intrin, img_color, img_depth, aligned_depth_frame
  38. '''
  39. 获取随机点三维坐标
  40. '''
  41. def get_3d_camera_coordinate(depth_pixel, aligned_depth_frame, depth_intrin):
  42. x = depth_pixel[0]
  43. y = depth_pixel[1]
  44. dis = aligned_depth_frame.get_distance(x, y) # 获取该像素点对应的深度
  45. camera_coordinate = rs.rs2_deproject_pixel_to_point(depth_intrin, depth_pixel, dis)
  46. return dis, camera_coordinate
  47. if __name__ == '__main__': # 入口
  48. a = W_detectAPI.DetectAPI(weights='yolov5s.pt')
  49. # 设置计时器
  50. start_time = time.time()
  51. interval = 1 # 间隔时间(秒)
  52. try:
  53. while True:
  54. color_intrin, depth_intrin, img_color, img_depth, aligned_depth_frame = get_aligned_images() # 获取对齐图像与相机参数
  55. if not img_color.any() or not img_depth.any():
  56. continue
  57. depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(
  58. img_depth, alpha=0.03), cv2.COLORMAP_JET)
  59. images = np.hstack((img_color, depth_colormap))
  60. # 检查是否达到间隔时间
  61. if time.time() - start_time >= interval:
  62. start_time = time.time() # 重置计时器
  63. canvas, class_id_list, xyxy_list, conf_list = a.detect([img_color])
  64. camera_xyz_list = []
  65. if xyxy_list:
  66. for i in range(len(xyxy_list)):
  67. ux = int((xyxy_list[i][0] + xyxy_list[i][2]) / 2) # 计算像素坐标系的x
  68. uy = int((xyxy_list[i][1] + xyxy_list[i][3]) / 2) # 计算像素坐标系的y
  69. dis = aligned_depth_frame.get_distance(ux, uy)
  70. camera_xyz = rs.rs2_deproject_pixel_to_point(
  71. depth_intrin, (ux, uy), dis) # 计算相机坐标系的xyz
  72. camera_xyz = np.round(np.array(camera_xyz), 3) # 转成3位小数
  73. camera_xyz = np.array(list(camera_xyz)) * 1000
  74. camera_xyz = list(camera_xyz)
  75. cv2.circle(canvas, (ux, uy), 4, (255, 255, 255), 5) # 标出中心点
  76. cv2.putText(canvas, str(camera_xyz), (ux + 20, uy + 10), 0, 1,
  77. [225, 255, 255], thickness=1, lineType=cv2.LINE_AA) # 标出坐标
  78. # cv2.putText(canvas,"X1:" + str(camera_xyz[0]) + "m",(40,60), cv2.FONT_HERSHEY_SIMPLEX, 0.5,[255,0,0] )
  79. camera_xyz_list.append(camera_xyz)
  80. cv2.namedWindow('detection', flags=cv2.WINDOW_NORMAL |
  81. cv2.WINDOW_KEEPRATIO | cv2.WINDOW_GUI_EXPANDED)
  82. cv2.resizeWindow('detection',640,480)
  83. cv2.imshow('detection', canvas)
  84. cv2.waitKey(2000)
  85. key = cv2.waitKey(1)
  86. # Press esc or 'q' to close the image window
  87. if key & 0xFF == ord('q') or key == 27:
  88. cv2.destroyAllWindows()
  89. pipeline.stop()
  90. break
  91. finally:
  92. # Stop streaming
  93. pipeline.stop()

需要新建个 W_detactAPI.py 文件,目的是封装yolo为类

  1. import argparse
  2. import os
  3. import platform
  4. import random
  5. import sys
  6. from pathlib import Path
  7. import torch
  8. from torch.backends import cudnn
  9. FILE = Path(__file__).resolve()
  10. ROOT = FILE.parents[0] # YOLOv5 root directory
  11. if str(ROOT) not in sys.path:
  12. sys.path.append(str(ROOT)) # add ROOT to PATH
  13. ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
  14. from models.common import DetectMultiBackend
  15. from utils.dataloaders import IMG_FORMATS, VID_FORMATS, MyLoadImages, LoadScreenshots, LoadStreams
  16. from utils.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
  17. increment_path, non_max_suppression, print_args, scale_boxes, strip_optimizer, xyxy2xywh)
  18. from utils.plots import Annotator, colors, save_one_box
  19. from utils.torch_utils import select_device, smart_inference_mode, time_sync
  20. """
  21. 使用面向对象编程中的类来封装,需要去除掉原始 detect.py 中的结果保存方法,重写
  22. 保存方法将结果保存到一个 csv 文件中并打上视频的对应帧率
  23. """
  24. class YoloOpt:
  25. def __init__(self, weights='weights/last.pt',
  26. imgsz=(640, 640), conf_thres=0.25,
  27. iou_thres=0.45, device='cpu', view_img=False,
  28. classes=None, agnostic_nms=False,
  29. augment=False, update=False, exist_ok=False,
  30. project='/detect/result', name='result_exp',
  31. save_csv=True):
  32. self.weights = weights # 权重文件地址
  33. self.source = None # 待识别的图像
  34. if imgsz is None:
  35. self.imgsz = (640, 640)
  36. self.imgsz = imgsz # 输入图片的大小,默认 (640,640)
  37. self.conf_thres = conf_thres # object置信度阈值 默认0.25 用在nms中
  38. self.iou_thres = iou_thres # 做nms的iou阈值 默认0.45 用在nms中
  39. self.device = device # 执行代码的设备,由于项目只能用 CPU,这里只封装了 CPU 的方法
  40. self.view_img = view_img # 是否展示预测之后的图片或视频 默认False
  41. self.classes = classes # 只保留一部分的类别,默认是全部保留
  42. self.agnostic_nms = agnostic_nms # 进行NMS去除不同类别之间的框, 默认False
  43. self.augment = augment # augmented inference TTA测试时增强/多尺度预测,可以提分
  44. self.update = update # 如果为True,则对所有模型进行strip_optimizer操作,去除pt文件中的优化器等信息,默认为False
  45. self.exist_ok = exist_ok # 如果为True,则对所有模型进行strip_optimizer操作,去除pt文件中的优化器等信息,默认为False
  46. self.project = project # 保存测试日志的参数,本程序没有用到
  47. self.name = name # 每次实验的名称,本程序也没有用到
  48. self.save_csv = save_csv # 是否保存成 csv 文件,本程序目前也没有用到
  49. class DetectAPI:
  50. def __init__(self, weights, imgsz=640):
  51. # 初始化参数,加载模型
  52. self.opt = YoloOpt(weights=weights, imgsz=imgsz)
  53. weights = self.opt.weights # 传入的权重
  54. imgsz = self.opt.imgsz # 传入的图像尺寸
  55. # Initialize 初始化
  56. # 获取设备 CPU/CUDA
  57. self.device = select_device(self.opt.device)
  58. # 不使用半精度--半精度只支持CUDA
  59. self.half = self.device.type != 'cpu' # # FP16 supported on limited backends with CUDA
  60. # Load model 加载模型
  61. self.model = DetectMultiBackend(weights, self.device, dnn=False)
  62. self.stride = self.model.stride
  63. self.names = self.model.names
  64. self.pt = self.model.pt
  65. self.imgsz = check_img_size(imgsz, s=self.stride)
  66. # 不使用半精度
  67. if self.half:
  68. self.model.half() # switch to FP16
  69. # read names and colors
  70. # names是类别名称字典;colors是画框时用到的颜色
  71. self.names = self.model.module.names if hasattr(self.model, 'module') else self.model.names
  72. self.colors = [[random.randint(0, 255) for _ in range(3)] for _ in self.names]
  73. # 当自动拍照时会调用这个函数
  74. def detect(self, source):
  75. # 输入 detect([img])
  76. if type(source) != list:
  77. raise TypeError('source must a list and contain picture read by cv2')
  78. # DataLoader 加载数据
  79. # 直接从 source 加载数据
  80. dataset = MyLoadImages(source)
  81. # 源程序通过路径加载数据,现在 source 就是加载好的数据,因此 LoadImages 就要重写
  82. bs = 1 # set batch size
  83. # 保存的路径
  84. vid_path, vid_writer = [None] * bs, [None] * bs
  85. # Run inference
  86. result = []
  87. if self.device.type != 'cpu':
  88. self.model(torch.zeros(1, 3, self.imgsz, self.imgsz).to(self.device).type_as(
  89. next(self.model.parameters()))) # run once
  90. dt, seen = (Profile(), Profile(), Profile()), 0
  91. for im, im0s in dataset:
  92. with dt[0]:
  93. im = torch.from_numpy(im).to(self.model.device)
  94. im = im.half() if self.model.fp16 else im.float() # uint8 to fp16/32
  95. im /= 255 # 0 - 255 to 0.0 - 1.0
  96. if len(im.shape) == 3:
  97. im = im[None] # expand for batch dim
  98. # Inference
  99. pred = self.model(im, augment=self.opt.augment)[0]
  100. # NMS
  101. with dt[2]:
  102. # max_det=2 最多检测两个目标,如果图片中有3个目标,需要修改
  103. pred = non_max_suppression(pred, self.opt.conf_thres, self.opt.iou_thres, self.opt.classes, self.opt.agnostic_nms, max_det=2)
  104. # Process predictions
  105. # 处理每一张图片
  106. # 原来的情况是要保持图片,因此多了很多关于保存路径上的处理。
  107. # 另外,pred其实是个列表。元素个数为batch_size
  108. det = pred[0] # API 一次只处理一张图片,因此不需要 for 循环
  109. # copy 一个原图片的副本图片,不拷贝会对原图片造成影响
  110. im0 = im0s.copy()
  111. # 一张图片可能会有多个检测到的目标,标签也会有多个
  112. # result_txt = [] # 储存检测结果,每新检测出一个物品,长度就加一。
  113. # # 每一个元素是列表形式,储存着 类别,坐标,置信度
  114. xyxy_list = []
  115. conf_list = []
  116. class_id_list = []
  117. # 设置图片上绘制框的粗细,类别名称
  118. annotator = Annotator(im0, line_width=3, example=str(self.names))
  119. if len(det):
  120. # Rescale boxes from img_size to im0 size
  121. # 映射预测信息到原图
  122. det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()
  123. #
  124. for *xyxy, conf, cls in reversed(det):
  125. # 只输出dog的信息,不用了就删除这个if语句
  126. # if self.names[int(cls)] == "dog":
  127. class_id = int(cls)
  128. xyxy_list.append(xyxy)
  129. conf_list.append(conf)
  130. class_id_list.append(class_id)
  131. line = (int(cls.item()), [int(_.item()) for _ in xyxy], conf.item()) # label format
  132. # result_txt.append(line)
  133. label = f'{self.names[int(cls)]} {conf:.2f}'
  134. # 跳入Annotator函数中,打印目标坐标和机械臂坐标
  135. x0, y0 = annotator.box_label(xyxy, label, color=self.colors[int(cls)])
  136. return im0, class_id_list, xyxy_list, conf_list

这里需要提示下,你得在 annotator.box_label 添加上我这篇文章的代码,然后直接运行就ok了,有啥问题报错可以在评论区留言

如果有大佬有更好的方法,还请分享出来咱们一块交流交流!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/250710
推荐阅读
相关标签
  

闽ICP备14008679号