ROS回调函数中cv2.destroyWindow()失效及图像不更新问题求助
问题分析与解决方案:OpenCV窗口无法销毁/更新的ROS节点问题
我在3D仿真中获取机器人的Lidar和相机数据,期望实现:当机器人检测到周围物体距离小于设定阈值时,用cv2显示相机图像;物体超出阈值后销毁显示窗口。但实际运行时,物体超出阈值后窗口停留在最后一帧无法销毁,再次检测到物体时也无法更新为新图像。
原代码如下:
#!/usr/bin/env python import rospy import cv2 from cv_bridge import CvBridge, CvBridgeError from sensor_msgs.msg import LaserScan from sensor_msgs.msg import Image bridge = CvBridge() threshold = 0.5 sub_camera = None window_name = "Camera preview" def scan_callback(scan): global sub_camera if(min(scan.ranges) < threshold): rospy.loginfo("Object detected") if(not sub_camera): sub_camera = rospy.Subscriber("/camera/rgb/image_raw", Image, image_callback) else: rospy.loginfo("No object detected") if(sub_camera): cv2.destroyWindow(window_name) sub_camera.unregister() sub_camera = None def image_callback(img): try: cv_image = bridge.imgmsg_to_cv2(img, "bgr8") except CvBridgeError as e: rospy.logerr("CvBridge Error: {0}".format(e)) cv2.namedWindow(window_name) cv2.imshow(window_name, cv_image) cv2.waitKey(1) rospy.init_node('camera_lidar_node', anonymous=False) sub_lidar = rospy.Subscriber("/scan", LaserScan, scan_callback) while not rospy.is_shutdown(): rospy.spin()
问题原因
- OpenCV事件循环未被正确处理:
cv2.waitKey(1)是处理窗口事件的关键,当相机订阅取消后,没有机会触发该函数,导致窗口销毁指令无法生效。 - Lidar数据未过滤无效值:部分Lidar会返回0表示无有效检测,直接取
min(scan.ranges)可能导致误判。 - 窗口状态未跟踪:重复调用
cv2.namedWindow可能导致窗口资源残留,且无法准确判断窗口是否存在。
修复后的代码
#!/usr/bin/env python import rospy import cv2 from cv_bridge import CvBridge, CvBridgeError from sensor_msgs.msg import LaserScan from sensor_msgs.msg import Image bridge = CvBridge() threshold = 0.5 sub_camera = None window_name = "Camera preview" window_exists = False # 跟踪窗口是否已创建 def scan_callback(scan): global sub_camera, window_exists # 过滤Lidar无效的0值,避免误判 valid_ranges = [r for r in scan.ranges if r > 0] if not valid_ranges: current_min_range = float('inf') else: current_min_range = min(valid_ranges) if current_min_range < threshold: rospy.loginfo("Object detected") if not sub_camera: sub_camera = rospy.Subscriber("/camera/rgb/image_raw", Image, image_callback) else: rospy.loginfo("No object detected") if sub_camera: sub_camera.unregister() sub_camera = None # 销毁窗口后必须处理事件循环,否则窗口残留 if window_exists: cv2.destroyWindow(window_name) cv2.waitKey(1) window_exists = False def image_callback(img): global window_exists try: cv_image = bridge.imgmsg_to_cv2(img, "bgr8") except CvBridgeError as e: rospy.logerr("CvBridge Error: {0}".format(e)) return # 仅在窗口未创建时执行创建操作 if not window_exists: cv2.namedWindow(window_name) window_exists = True cv2.imshow(window_name, cv_image) cv2.waitKey(1) rospy.init_node('camera_lidar_node', anonymous=False) sub_lidar = rospy.Subscriber("/scan", LaserScan, scan_callback) # 替换spin()为spin_once(),确保窗口事件能被持续处理 while not rospy.is_shutdown(): rospy.spin_once() if window_exists: cv2.waitKey(1) # 节点退出时清理所有窗口 cv2.destroyAllWindows()
关键修改说明
- 新增窗口状态跟踪:用
window_exists变量避免重复创建窗口,确保销毁逻辑准确。 - 过滤Lidar无效数据:排除0值,防止无效数据导致的误触发。
- 销毁窗口后强制处理事件:调用
cv2.destroyWindow后必须执行cv2.waitKey(1),让OpenCV完成窗口销毁操作。 - 主循环优化:用
rospy.spin_once()替代rospy.spin(),避免主线程阻塞,保证窗口事件循环持续运行。 - 退出时清理窗口:节点关闭前调用
cv2.destroyAllWindows(),彻底清理所有残留窗口。
内容的提问来源于stack exchange,提问作者A_Pumpkin
相关产品推荐
相关产品推荐

