将launch文件转换成关系图-----roslaunch_to_dot

本文介绍了一款名为roslaunch_to_dot的工具,该工具可以从ROS启动文件中创建Dot图文件,便于开发者直观地查看ROS节点之间的依赖关系。通过简单的命令行操作,可以将启动文件转换为Dot图,并支持自动导出为PNG、SVG或PDF格式。

github地址:https://github.com/bponsler/roslaunch_to_dot

从github上面clone下来即可使用

git clone https://github.com/screamlsj/roslaunch_to_dot.git

进入下好的文件夹

运行.py文件,如下

./roslaunch-to-dot.py --png ~/Documents/ros/src/freenect_stack/freenect_launch/launch/kinect-xyz.launch kxyz.dot

效果如下:




更多使用方法见:

 usage: roslaunch-to-dot.py [-h] [--landscape] [--aspect-ratio ASPECTRATIO]
                               [--png] [--svg] [--pdf]
                               [--disable-groups]
                               [--show-node-type]
                               [--show-rosparam-nodes]
                               launchFile outputFile [arg [arg ...]]

    Create a dot graph file from a ROS launch file.

    positional arguments:
      launchFile            path to the desired launch file
      outputFile            the output dot file to save
      arg                   override an arg specified anywhere in the launch file
                            tree

    optional arguments:
      -h, --help            show this help message and exit
      --landscape           display the nodes from left to right instead of top to
                            bottom
      --aspect-ratio ASPECTRATIO
                            the approximate aspect ratio desired (default =
                            8.5/11)
      --png                 automatically convert the dot file to a PNG
      --svg                 automatically convert the dot file to a SVG
      --pdf                 automatically convert the dot file to a PDF
      --disable-groups      don't group nodes/launch files based on their package
      --show-node-type      label ROS nodes with their type in addition to their
                            name
      --show-rosparam-nodes
                            display nodes and connections for all rosparam files
                            used

#! /usr/bin/env python3 import cv2 as cv import numpy as np import torch import message_filters import rospy import sys import time from sensor_msgs.msg import Image from cv_bridge import CvBridge from rospkg import RosPack # get abs path from os import path # get home path from gazebo_msgs.msg import ModelStates from geometry_msgs.msg import * from pyquaternion import Quaternion as PyQuaternion # Global variables path_yolo = path.join(path.expanduser('~'), 'yolov5') path_vision = RosPack().get_path('vision') path_weigths = path.join(path_vision, 'weigths') cam_point = (-0.44, -0.5, 1.58) height_tavolo = 0.74 dist_tavolo = None origin = None model = None model_orientation = None legoClasses = ['X1-Y1-Z2', 'X1-Y2-Z1', 'X1-Y2-Z2', 'X1-Y2-Z2-CHAMFER', 'X1-Y2-Z2-TWINFILLET', 'X1-Y3-Z2', 'X1-Y3-Z2-FILLET', 'X1-Y4-Z1', 'X1-Y4-Z2', 'X2-Y2-Z2', 'X2-Y2-Z2-FILLET'] argv = sys.argv a_show = '-show' in argv # Utility Functions def get_dist_tavolo(depth, hsv, img_draw): global dist_tavolo #color = (120,1,190) #mask = get_lego_mask(color, hsv, (5, 5, 5)) #dist_tavolo = depth[mask].max() #if dist_tavolo > 1: dist_tavolo -= height_tavolo dist_tavolo = np.nanmax(depth) def get_origin(img): global origin origin = np.array(img.shape[1::-1]) // 2 def get_lego_distance(depth): return depth.min() def get_lego_color(center, rgb): return rgb[center].tolist() def get_lego_mask(color, hsv, toll = (20, 20, 255)): thresh = np.array(color) mintoll = thresh - np.array([toll[0], toll[1], min(thresh[2]-1, toll[2])]) maxtoll = thresh + np.array(toll) return cv.inRange(hsv, mintoll, maxtoll) def getDepthAxis(height, lego): X, Y, Z = (int(x) for x in lego[1:8:3]) #Z = (0.038, 0.057) X = (0.031, 0.063) Y = (0.031, 0.063, 0.095, 0.127) rapZ = height / 0.019 - 1 pinZ = round(rapZ) rapXY = height / 0.032 pinXY = round(rapXY) errZ = abs(pinZ - rapZ) + max(pinZ - 2, 0) errXY = abs(pinXY - rapXY) + max(pinXY - 4, 0) if errZ < errXY: return pinZ, 2, pinZ == Z # pin, is ax Z, match else: if pinXY == Y: return pinXY, 1, True else: return pinXY, 0, pinXY == X def point_distorption(point, height, origin): p = dist_tavolo / (dist_tavolo - height) point = point - origin return p * point + origin def point_inverse_distortption(point, height): p = dist_tavolo / (dist_tavolo - height) point = point - origin return point / p + origin def myimshow(title, img): def mouseCB(event,x,y,a,b): print(x, y, img[y, x], "\r",end='',flush=True) print("\033[K", end='') cv.imshow(title, img) cv.setMouseCallback(title, mouseCB) cv.waitKey() # ----------------- LOCALIZATION ----------------- # def process_item(imgs, item): #images rgb, hsv, depth, img_draw = imgs #obtaining Yolo informations (class, coordinates, center) x1, y1, x2, y2, cn, cl, nm = item.values() mar = 15 x1, y1 = max(mar, x1), max(mar, y1) x2, y2 = min(rgb.shape[1]-mar, x2), min(rgb.shape[0]-mar, y2) boxMin = np.array((x1-mar, y1-mar)) x1, y1, x2, y2 = np.int0((x1, y1, x2, y2)) boxCenter = (y2 + y1) // 2, (x2 + x1) // 2 color = get_lego_color(boxCenter, rgb) hsvcolor = get_lego_color(boxCenter, hsv) sliceBox = slice(y1-mar, y2+mar), slice(x1-mar, x2+mar) #crop img with coordinate bounding box; computing all imgs l_rgb = rgb[sliceBox] l_hsv = hsv[sliceBox] if a_show: cv.rectangle(img_draw, (x1,y1),(x2,y2), color, 2) l_depth = depth[sliceBox] l_mask = get_lego_mask(hsvcolor, l_hsv) # filter mask by color l_mask = np.where(l_depth < dist_tavolo, l_mask, 0) l_depth = np.where(l_mask != 0, l_depth, dist_tavolo) #myimshow("asda", hsv) #getting lego height from camera and table l_dist = get_lego_distance(l_depth) l_height = dist_tavolo - l_dist #masking l_top_mask = cv.inRange(l_depth, l_dist-0.002, l_dist+0.002) #cv.bitwise_xor(img_draw,img_draw,img_draw, mask=cv.inRange(depth, l_dist-0.002, l_dist+0.002)) #myimshow("hmask", l_top_mask) # model detect orientation depth_borded = np.zeros(depth.shape, dtype=np.float32) depth_borded[sliceBox] = l_depth depth_image = cv.normalize( depth_borded, None, alpha=0, beta=255, norm_type=cv.NORM_MINMAX, dtype=cv.CV_8U ) depth_image = cv.cvtColor(depth_image, cv.COLOR_GRAY2RGB).astype(np.uint8) #yolo in order to keep te orientation #print("Model orientation: Start...", end='\r') model_orientation.conf = 0.7 results = model_orientation(depth_image) pandino = [] pandino = results.pandas().xyxy[0].to_dict(orient="records") n = len(pandino) #print("Model orientation: Finish", n) # Adjust prediction pinN, ax, isCorrect = getDepthAxis(l_height, nm) if not isCorrect and ax == 2: if cl in (1,2,3) and pinN in (1, 2): # X1-Y2-Z*, *1,2,2-CHAMFER cl = 1 if pinN == 1 else 2 # -> Z'pinN' elif cl in (7, 8) and pinN in (1, 2): # X1-Y4-Z*, *1,2 cl = 7 if pinN == 1 else 8 # -> Z'pinN' elif pinN == -1: nm = "{} -> {}".format(nm, "Target") else: print("[Warning] Error in classification") elif not isCorrect: ax = 1 if cl in (0, 2, 5, 8) and pinN <= 4: # X1-Y*-Z2, *1,2,3,4 cl = (2, 5, 8)[pinN-2] # -> Y'pinN' elif cl in (1, 7) and pinN in (2, 4): # X1-Y*-Z1, *2,4 cl = 1 if pinN == 2 else 7 # -> Y'pinN' elif cl == 9 and pinN == 1: # X2-Y2-Z2 cl = 2 # -> X1 ax = 0 else: print("[Warning] Error in classification") nm = legoClasses[cl] if n != 1: print("[Warning] Classification not found") or_cn, or_cl, or_nm = ['?']*3 or_nm = ('lato', 'lato', 'sopra/sotto')[ax] else: print() #obtaining Yolo informations (class, coordinates, center) or_item = pandino[0] or_cn, or_cl, or_nm = or_item['confidence'], or_item['class'], or_item['name'] if or_nm == 'sotto': ax = 2 if or_nm == 'lato' and ax == 2: ax = 1 #--- #creating silouette top surface lego contours, hierarchy = cv.findContours(l_top_mask, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) top_center = np.zeros(2) for cnt in contours: tmp_center, top_size, top_angle = cv.minAreaRect(cnt) top_center += np.array(tmp_center) top_center = boxMin + top_center / len(contours) #creating silouette lego contours, hierarchy = cv.findContours(l_mask, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) if len(contours) == 0: return None cnt = contours[0] l_center, l_size, l_angle = cv.minAreaRect(cnt) l_center += boxMin if ax != 2: l_angle = top_angle l_box = cv.boxPoints((l_center, l_size, l_angle)) if l_size[0] <=3 or l_size[1] <=3: cv.drawContours(img_draw, np.int0([l_box]), 0, (0,0,0), 2) return None # filter out artifacts if a_show: cv.drawContours(img_draw, np.int0([l_box]), 0, color, 2) # silouette distorption # get vertexs distance from origin top_box = l_box.copy() vertexs_norm = [(i, np.linalg.norm(vec - origin)) for vec, i in zip(l_box, range(4))] vertexs_norm.sort(key=lambda tup: tup[1]) # get closest vertex iver = vertexs_norm[0][0] vec = l_box[iver] # distorping closest vertex if or_nm == 'sopra': l_height -= 0.019 top_box[iver] = point_distorption(l_box[iver], l_height, origin) v0 = top_box[iver] - vec # adapt adiacents veretx v1 = l_box[iver - 3] - vec # i - 3 = i+1 % 4 v2 = l_box[iver - 1] - vec top_box[iver - 3] += np.dot(v0, v2) / np.dot(v2, v2) * v2 top_box[iver - 1] += np.dot(v0, v1) / np.dot(v1, v1) * v1 l_center = (top_box[0] + top_box[2]) / 2 if a_show: cv.drawContours(img_draw, np.int0([top_box]), 0, (5,5,5), 2) cv.circle(img_draw, np.int0(top_box[iver]),1, (0,0,255),1,cv.LINE_AA) #rotation and axis drawing if or_nm in ('sopra', 'sotto', 'sopra/sotto', '?'): # fin x, y directions (dirX, dirY) dirZ = np.array((0,0,1)) if or_nm == 'sotto': dirZ = np.array((0,0,-1)) projdir = l_center - top_center if np.linalg.norm(projdir) < l_size[0] / 10: dirY = top_box[0] - top_box[1] dirX = top_box[0] - top_box[-1] if np.linalg.norm(dirY) < np.linalg.norm(dirX): dirX, dirY = dirY, dirX projdir = dirY * np.dot(dirY, projdir) edgeFar = [ver for ver in top_box if np.dot(ver - l_center, projdir) >= 0][:2] dirY = (edgeFar[0] + edgeFar[1]) / 2 - l_center dirY /= np.linalg.norm(dirY) dirY = np.array((*dirY, 0)) dirX = np.cross(dirZ, dirY) elif or_nm == "lato": # find pin direction (dirZ) edgePin = [ver for ver in top_box if np.dot(ver - l_center, l_center - top_center) >= 0][:2] dirZ = (edgePin[0] + edgePin[1]) / 2 - l_center dirZ /= np.linalg.norm(dirZ) dirZ = np.array((*dirZ, 0)) if cl == 10: if top_size[1] > top_size[0]: top_size = top_size[::-1] if top_size[0] / top_size[1] < 1.7: ax = 0 if ax == 0: vx,vy,x,y = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01) dir = np.array((vx, vy)) vertexs_distance = [abs(np.dot(ver - l_center, dir)) for ver in edgePin] iverFar = np.array(vertexs_distance).argmin() dirY = edgePin[iverFar] - edgePin[iverFar-1] dirY /= np.linalg.norm(dirY) dirY = np.array((*dirY, 0)) dirX = np.cross(dirZ, dirY) if a_show: cv.circle(img_draw, np.int0(edgePin[iverFar]), 5, (70,10,50), 1) #cv.line(img_draw, np.int0(l_center), np.int0(l_center+np.array([int(vx*100),int(vy*100)])),(0,0,255), 3) if ax == 1: dirY = np.array((0,0,1)) dirX = np.cross(dirZ, dirY) if a_show: cv.line(img_draw, *np.int0(edgePin), (255,255,0), 2) l_center = point_inverse_distortption(l_center, l_height) # post rotation extra theta = 0 if cl == 1 and ax == 1: theta = 1.715224 - np.pi / 2 if cl == 3 and or_nm == 'sotto': theta = 2.359515 - np.pi if cl == 4 and ax == 1: theta = 2.145295 - np.pi if cl == 6 and or_nm == 'sotto': theta = 2.645291 - np.pi if cl == 10 and or_nm == 'sotto': theta = 2.496793 - np.pi rotX = PyQuaternion(axis=dirX, angle=theta) dirY = rotX.rotate(dirY) dirZ = rotX.rotate(dirZ) if a_show: # draw frame lenFrame = 50 unit_z = 0.031 unit_x = 22 * 0.8039 / dist_tavolo x_to_z = lenFrame * unit_z/unit_x center = np.int0(l_center) origin_from_top = origin - l_center endX = point_distorption(lenFrame * dirX[:2], x_to_z * dirX[2], origin_from_top) frameX = (center, center + np.int0(endX)) endY = point_distorption(lenFrame * dirY[:2], x_to_z * dirY[2], origin_from_top) frameY = (center, center + np.int0(endY)) endZ = point_distorption(lenFrame * dirZ[:2], x_to_z * dirZ[2], origin_from_top) frameZ = (center, center + np.int0(endZ)) cv.line(img_draw, *frameX, (0,0,255), 2) cv.line(img_draw, *frameY, (0,255,0), 2) cv.line(img_draw, *frameZ, (255,0,0), 2) # --- # draw text if or_cl != '?': or_cn = ['SIDE', 'UP', 'DOWN'][or_cl] text = "{} {:.2f} {}".format(nm, cn, or_cn) (text_width, text_height) = cv.getTextSize(text, cv.FONT_HERSHEY_DUPLEX, 0.4, 1)[0] text_offset_x = boxCenter[1] - text_width // 2 text_offset_y = y1 - text_height box_coords = ((text_offset_x - 1, text_offset_y + 1), (text_offset_x + text_width + 1, text_offset_y - text_height - 1)) cv.rectangle(img_draw, box_coords[0], box_coords[1], (210,210,10), cv.FILLED) cv.putText(img_draw, text, (text_offset_x, text_offset_y), cv.FONT_HERSHEY_DUPLEX, 0.4, (255, 255, 255), 1) def getAngle(vec, ax): vec = np.array(vec) if not vec.any(): return 0 vec = vec / np.linalg.norm(vec) wise = 1 if vec[-1] >= 0 else -1 dotclamp = max(-1, min(1, np.dot(vec, np.array(ax)))) return wise * np.arccos(dotclamp) msg = ModelStates() msg.name = nm #fov = 1.047198 #rap = np.tan(fov) #print("rap: ", rap) xyz = np.array((l_center[0], l_center[1], l_height / 2 + height_tavolo)) xyz[:2] /= rgb.shape[1], rgb.shape[0] xyz[:2] -= 0.5 xyz[:2] *= (-0.968, 0.691) xyz[:2] *= dist_tavolo / 0.84 xyz[:2] += cam_point[:2] rdirX, rdirY, rdirZ = dirX, dirY, dirZ rdirX[0] *= -1 rdirY[0] *= -1 rdirZ[0] *= -1 qz1 = PyQuaternion(axis=(0,0,1), angle=-getAngle(dirZ[:2], (1,0))) rdirZ = qz1.rotate(dirZ) qy2 = PyQuaternion(axis=(0,1,0), angle=-getAngle((rdirZ[2],rdirZ[0]), (1,0))) rdirX = qy2.rotate(qz1.rotate(rdirX)) qz3 = PyQuaternion(axis=(0,0,1), angle=-getAngle(rdirX[:2], (1,0))) rot = qz3 * qy2 * qz1 rot = rot.inverse msg.pose = Pose(Point(*xyz), Quaternion(x=rot.x,y=rot.y,z=rot.z,w=rot.w)) #pub.publish(msg) #print(msg) return msg #image processing def process_image(rgb, depth): img_draw = rgb.copy() hsv = cv.cvtColor(rgb, cv.COLOR_BGR2HSV) get_dist_tavolo(depth, hsv, img_draw) get_origin(rgb) #results collecting localization #print("Model localization: Start...",end='\r') model.conf = 0.6 results = model(rgb) pandino = results.pandas().xyxy[0].to_dict(orient="records") #print("Model localization: Finish ") # ---- if depth is not None: imgs = (rgb, hsv, depth, img_draw) results = [process_item(imgs, item) for item in pandino] # ---- msg = ModelStates() for point in results: if point is not None: msg.name.append(point.name) msg.pose.append(point.pose) pub.publish(msg) if a_show: cv.imshow("vision-results.png", img_draw) cv.waitKey() pass def process_CB(image_rgb, image_depth): t_start = time.time() #from standard message image to opencv image rgb = CvBridge().imgmsg_to_cv2(image_rgb, "bgr8") depth = CvBridge().imgmsg_to_cv2(image_depth, "32FC1") process_image(rgb, depth) print("Time:", time.time() - t_start) rospy.signal_shutdown(0) pass #init node function def start_node(): global pub print("Starting Node Vision 1.0") rospy.init_node('vision') print("Subscribing to camera images") #topics subscription rgb = message_filters.Subscriber("/camera/color/image_raw", Image) depth = message_filters.Subscriber("/camera/depth/image_raw", Image) #publisher results pub=rospy.Publisher("lego_detections", ModelStates, queue_size=1) print("Localization is starting.. ") print("(Waiting for images..)", end='\r'), print(end='\033[K') #images synchronization syncro = message_filters.TimeSynchronizer([rgb, depth], 1, reset=True) syncro.registerCallback(process_CB) #keep node always alive rospy.spin() pass def load_models(): global model, model_orientation #yolo model and weights classification print("Loading model best.pt") weight = path.join(path_weigths, 'best.pt') model = torch.hub.load(path_yolo,'custom',path=weight, source='local') #yolo model and weights orientation print("Loading model orientation.pt") weight = path.join(path_weigths, 'depth.pt') model_orientation = torch.hub.load(path_yolo,'custom',path=weight, source='local') pass if __name__ == '__main__': load_models() try: start_node() except rospy.ROSInterruptException: pass 请在此代码基础上按照上述要求修改,不要基于其他代码框架,如果没找到对应修改点请说明
10-22
<think>我们有一个ROS节点,原本是通过触发拍照(可能是通过服务调用)来获取一张图片进行识别。现在要改为使用Kinect摄像头实时视频流进行识别。 原始代码中,我们通过`message_filters.TimeSynchronizer`同步订阅了RGB和深度图像,并在回调函数`process_CB`中处理图像。但是,原来的回调函数在处理一次后就会关闭节点(`rospy.signal_shutdown(0)`)。我们需要将其改为持续处理,即每次收到图像都进行识别,并且不关闭节点。 修改点: 1. 移除`rospy.signal_shutdown(0)`,使节点持续运行。 2. 由于实时处理可能对性能要求较高,考虑加入帧率控制(比如每秒处理5-10帧),避免处理每一帧图像导致计算资源紧张。 3. 确保深度图像和RGB图像的时间同步仍然有效。 另外,注意原始代码中每次处理图像都会重新加载模型(`load_models`),这显然是不必要的。我们已经将模型加载放在全局,只需一次。 具体修改步骤: - 在`process_CB`函数中,移除`rospy.signal_shutdown(0)`这一行,这样回调函数每次被调用时都会处理图像,而不会关闭节点。 - 但注意,原始代码中`process_CB`函数在处理完一次图像后就会关闭节点,这是因为设计为触发一次。现在我们希望持续处理,所以去掉关闭。 - 另外,原始代码中使用了`TimeSynchronizer`来确保同时收到RGB和深度图像,这已经适合实时流,所以我们不需要改变同步方式。 - 为了避免处理每一帧图像,我们可以在回调函数中加入一个简单的节流机制(例如,记录上一次处理的时间,如果时间间隔小于某个阈值则跳过当前帧)。 但是,注意`message_filters.TimeSynchronizer`已经会按照两个话题中较慢的频率来触发回调。如果我们希望进一步降低处理频率,可以在回调内部进行控制。 改进:添加一个简单的节流控制(throttling),例如每秒最多处理5帧。 修改后的`process_CB`函数可能如下: ```python def process_CB(image_rgb, image_depth): global last_time current_time = time.time() # 节流控制:例如每秒最多处理5帧,即0.2秒一帧 if current_time - last_time < 0.2: return last_time = current_time # 转换图像 rgb = CvBridge().imgmsg_to_cv2(image_rgb, "bgr8") depth = CvBridge().imgmsg_to_cv2(image_depth, "32FC1") process_image(rgb, depth) # 注意:移除了rospy.signal_shutdown(0) ``` 在节点初始化时需要初始化`last_time`。 但是,这样直接在回调函数中做节流可能会丢失同步信息(因为深度和RGB图像是同步的,但下一组同步图像到来时可能因为节流被跳过,而再下一组可能不同步)。因此,另一种做法是使用`message_filters.ApproximateTimeSynchronizer`并设置较小的时间间隔,然后通过队列大小控制频率?但原始代码使用的是`TimeSynchronizer`,要求严格同步。 考虑到实时性,我们可以在回调函数中不做节流,而是通过控制发布频率(例如,识别结果发布频率)来实现,但识别本身消耗资源,所以节流是必要的。 建议:在回调函数内部使用时间节流,同时使用全局变量记录上一次处理时间。 另外,在`process_image`函数中,我们每次都会调用模型进行识别,注意模型推理可能比较耗时,所以节流是必要的。 此外,原始代码中,在`start_node`函数中,我们使用了`rospy.spin()`来保持节点运行。修改后,节点会持续运行直到手动关闭。 我们还需要注意:在`process_image`函数中,我们发布消息的方式是每次处理一张图片就发布一次。改为实时后,我们会频繁发布消息,需要确保下游节点可以处理。 修改步骤总结: 1. 在全局范围添加一个变量`last_time`,初始化为0。 2. 修改`process_CB`函数,加入节流控制(跳过太近的帧),并移除关闭节点的代码。 3. 修改`start_node`函数,不再需要调用`rospy.signal_shutdown`,所以移除相关的逻辑(原本在`process_CB`中关闭,现在移除了)。 注意:由于深度图像和RGB图像是同步的,我们跳过的是一组图像(RGB+深度),所以不会造同步问题。 修改后的代码框架如下: 在全局部分添加: ```python last_time = 0 ``` 修改`process_CB`: ```python def process_CB(image_rgb, image_depth): global last_time t_start = time.time() # 节流:如果距离上一次处理时间不足0.2秒(5Hz),则跳过 if t_start - last_time < 0.2: return last_time = t_start rgb = CvBridge().imgmsg_to_cv2(image_rgb, "bgr8") depth = CvBridge().imgmsg_to_cv2(image_depth, "32FC1") process_image(rgb, depth) print("Time:", time.time() - t_start) # 打印处理时间,用于性能监控 ``` 注意:我们保留了打印处理时间,这样可以帮助我们了解处理一帧需要多久,从而调整节流时间。 另外,原始代码中,在`process_image`函数中,我们调用了`get_dist_tavolo`和`get_origin`,这些函数每次处理一张图像都会调用,这是正确的。 但是,在`process_image`函数中,我们每次都会发布一个`ModelStates`消息,这个消息可能包含多个检测到的物体。改为实时后,我们会频繁发布这个消息。 最后,我们需要确保在节点启动时初始化`last_time`。可以在`start_node`函数中初始化,也可以在全局初始化(如上面用0,但第一次时间间隔会很长,所以可以在第一次回调时不跳过)。或者,将`last_time`初始化为当前时间减去节律间隔,这样第一帧就会处理。 修改全局变量初始化: ```python last_time = time.time() - 1 # 初始化为1秒前,保证第一帧会被处理 ``` 另外,注意原始代码中`a_show`用于控制是否显示图像。在实时处理中,如果每一帧都显示,可能会影响性能。建议在不需要调试时关闭显示(即运行时不带`-show`参数)。 由于我们修改了节点的行为,使其持续运行,所以在启动节点后,它会一直处理Kinect发来的图像,直到节点被终止。 完整修改后的代码(只显示修改部分): 在导入库之后,添加全局变量: ```python # ... 原有全局变量 last_time = time.time() - 1 # 用于节流 ``` 修改`process_CB`函数: ```python def process_CB(image_rgb, image_depth): global last_time t_start = time.time() # 节流控制:例如5Hz if t_start - last_time < 0.2: # 0.2秒,即5Hz return last_time = t_start # 转换图像 rgb = CvBridge().imgmsg_to_cv2(image_rgb, "bgr8") depth = CvBridge().imgmsg_to_cv2(image_depth, "32FC1") process_image(rgb, depth) print("Time:", time.time() - t_start) # 打印处理时间 # 注意:移除了rospy.signal_shutdown(0) ``` 这样修改后,节点将会以大约5Hz的频率处理图像。 注意:如果处理时间超过0.2秒,那么实际频率会更低。因此,节流参数需要根据实际处理能力调整。 另外,由于原始代码中使用了`TimeSynchronizer`,它会缓存消息直到两个话题都有新消息。在节流的情况下,我们跳过了某些消息,那么这些消息会被丢弃,下一组消息到来时再处理。这是可以接受的。 但是,如果处理速度跟不上,可能会导致消息堆积,ROS会默认保留最后一条消息(队列大小为1)。在`message_filters.Subscriber`中,我们可以在创建订阅者时设置队列大小。原始代码没有设置,默认是None(可能为无限队列)。为了避免堆积,我们可以设置队列大小为1: ```python rgb = message_filters.Subscriber("/camera/color/image_raw", Image, queue_size=1) depth = message_filters.Subscriber("/camera/depth/image_raw", Image, queue_size=1) ``` 这样,当处理速度跟不上时,只保留最新的图像,丢弃旧的。 因此,我们还需要修改`start_node`函数中的订阅部分,设置队列大小: ```python rgb = message_filters.Subscriber("/camera/color/image_raw", Image, queue_size=1) depth = message_filters.Subscriber("/camera/depth/image_raw", Image, queue_size=1) ``` 最后,由于节点现在持续运行,我们需要确保在需要时能够正常退出(比如Ctrl+C)。 总结:我们通过以上修改,将原本只处理一次图像的节点,改为了持续处理Kinect实时图像流的节点,并加入了节流控制以避免过载。 请根据实际运行情况调整节流时间(0.2秒)以平衡实时性和计算负载。 </think>以下是针对您需求的代码修改方案,将原项目中的单次拍照识别改为Kinect实时识别: ### 核心修改方案 ```python #! /usr/bin/env python3 import cv2 as cv import numpy as np import torch import message_filters import rospy import sys import time # ... (保留原有导入) # 新增全局变量 last_process_time = 0 processing_fps = 5 # 设置实时处理帧率(5-10Hz为宜) def process_image(rgb, depth): # ... (保留原有函数内容) # 移除 rospy.signal_shutdown(0) 调用 pass # === 核心修改:实时处理函数 === def realtime_process_CB(image_rgb, image_depth): global last_process_time # 帧率控制:避免处理每一帧造资源过载 current_time = time.time() if current_time - last_process_time < 1.0/processing_fps: return last_process_time = current_time try: # 图像格式转换 rgb = CvBridge().imgmsg_to_cv2(image_rgb, "bgr8") depth = CvBridge().imgmsg_to_cv2(image_depth, "32FC1") # 处理当前帧 process_image(rgb, depth) except Exception as e: rospy.logerr(f"实时处理错误: {str(e)}") # === 修改节点初始化函数 === def start_node(): global pub print("Starting Node Vision 1.0 (Real-time Kinect Mode)") rospy.init_node('vision_realtime') # 修改为Kinect话题 print("订阅Kinect实时图像流") rgb_sub = message_filters.Subscriber("/kinect/color/image_raw", Image) # Kinect RGB话题 depth_sub = message_filters.Subscriber("/kinect/depth/image_raw", Image) # Kinect深度话题 # 创建Publisher pub = rospy.Publisher("lego_detections", ModelStates, queue_size=10) # 使用ApproximateTimeSynchronizer实现同步 sync = message_filters.ApproximateTimeSynchronizer( [rgb_sub, depth_sub], queue_size=5, slop=0.1 # 允许0.1秒的时间差 ) sync.registerCallback(realtime_process_CB) print("实时识别已启动...") rospy.spin() # ... (保留main函数和其他部分) ``` ### 关键修改说明 1. **实时处理架构重构** - 新增`realtime_process_CB`函数替代原`process_CB` - 添加帧率控制逻辑 (`processing_fps`变量) - 使用`try-except`捕获异常保证系统稳定性 2. **Kinect话题配置** ```python # Kinect V2默认话题 rgb_sub = message_filters.Subscriber("/kinect/color/image_raw", Image) depth_sub = message_filters.Subscriber("/kinect/depth/image_raw", Image) # 若使用Azure Kinect: # rgb_sub = message_filters.Subscriber("/rgb/image_raw", Image) # depth_sub = message_filters.Subscriber("/depth_to_rgb/image_raw", Image) ``` 3. **精确时间同步改进** ```python # 使用近似时间同步器,容许0.1秒时间差 sync = message_filters.ApproximateTimeSynchronizer( [rgb_sub, depth_sub], queue_size=5, slop=0.1 ) ``` 4. **性能优化措施** - 发布队列大小增加到10 (`queue_size=10`) - 帧率默认设为5FPS,平衡精度和实时性 - 添加异常处理防止节点崩溃 ### 坐标转换增强(推荐添加) 在`process_item`函数处理姿态后,添加机械臂基座标系转换: ```python def process_item(imgs, item): # ... (原有处理逻辑) # === 新增:转换到机械臂基坐标系 === try: from tf2_geometry_msgs import do_transform_pose from tf2_ros import Buffer, TransformListener # 初始化TF监听器 tf_buffer = Buffer() tf_listener = TransformListener(tf_buffer) # 获取从相机到机械臂基座的变换 transform = tf_buffer.lookup_transform( "arm_base", # 目标坐标系 (机械臂基座) "kinect_link", # 源坐标系 (Kinect) rospy.Time(0), # 获取最新可用变换 rospy.Duration(1.0) # 超时1秒 ) # 应用坐标变换 transformed_pose = do_transform_pose(msg.pose, transform) msg.pose = transformed_pose except Exception as e: rospy.logwarn(f"坐标转换失败: {str(e)}") return msg ``` ### 部署步骤 1. **配置Kinect驱动程序** ```bash # Kinect V2 roslaunch kinect2_bridge kinect2_bridge.launch # Azure Kinect roslaunch azure_kinect_ros_driver driver.launch ``` 2. **启动修改后的识别节点** ```bash python3 modified_vision_node.py ``` 3. **验证数据流** ```bash # 查看识别结果 rostopic echo /lego_detections # 查看图像流 rqt_image_view /kinect/color/image_raw ``` ### 性能优化建议 1. **GPU加速推理** ```python # 加载模型时启用GPU model = torch.hub.load(path_yolo,'custom',path=weight, source='local').cuda() ``` 2. **图像降采样处理** ```python def realtime_process_CB(image_rgb, image_depth): # ... rgb = cv.resize(rgb, (640, 480)) # 降采样到640x480 # ... ``` 3. **动态帧率调整** ```python # 根据系统负载动态调整帧率 if processing_time > 0.2: # 单帧处理超200ms processing_fps = max(2, processing_fps - 1) # 降帧率 elif processing_time < 0.1: processing_fps = min(10, processing_fps + 1) # 升帧率 ``` ### 相关问题 1. **如何验证Kinect与机械臂的坐标转换准确性?** 可通过`rviz`可视化工具加载TF坐标系,使用标记物验证相对位置[^3]。 2. **实时处理中出现图像延迟如何处理?** 可减小`ApproximateTimeSynchronizer`的`slop`值,或使用`message_filters.TimeSynchronizer`严格同步[^1]。 3. **如何优化YOLO模型实现更高帧率?** 考虑使用TensorRT加速或量化模型,可将推理速度提升3-5倍[^4]。 4. **深度图像出现空洞如何处理?** 添加深度修复滤波: ```python depth = cv2.inpaint(depth, mask, 3, cv2.INPAINT_NS) ``` 5. **如何评估实时识别系统性能?** 使用`rostopic hz /lego_detections`监控输出频率,`rqt_graph`查看节点负载[^3]。 这些修改将使系统能够利用Kinect实时视频流进行物体识别,同时保持与机械臂控制系统的集完整性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值