You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()

问题原因

  1. OpenCV事件循环未被正确处理:cv2.waitKey(1)是处理窗口事件的关键,当相机订阅取消后,没有机会触发该函数,导致窗口销毁指令无法生效。
  2. Lidar数据未过滤无效值:部分Lidar会返回0表示无有效检测,直接取min(scan.ranges)可能导致误判。
  3. 窗口状态未跟踪:重复调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 07:22:36