Search code examples
pythonopencvros

Frozen black image and trackbar while using OpenCV and ROS


I am trying to create a simple ROS node that subscribes to an image topic, then displays the image with trackbars to allow the user to determine the correct HSV values needed to threshold an image.

The problem is that the window appears without an image. Like this:

screenshot of window without image:

screenshot of window without image

An interesting behavior is placing cv2.waitKey(30) in main right before rospy.spin(). Then I get the window, with the trackbars, without the image, and nothing is clickable. Like this:

screenshot of window with unclickable features and no image:

screenshot of window with unclickable features and no image

I have read a lot about this issue, and it seems to get fixed when people use cv2.waitKey(delay), but this is not the case for me no matter how I fiddle with the code.

My system is Ubuntu GNOME 16.04 and it is running ROS Kinetic.

#!/usr/bin/env python

"""Allows the user to calibrate for HSV filtering later."""

# Standard libraries
from argparse import ArgumentParser

# ROS libraries
import rospy
from cv_bridge import CvBridge
from sensor_msgs.msg import Image

# Other libraries
import cv2
import numpy as np


def nothing():
    """Does nothing."""

    pass


def calibrator(msg, args):
    """Updates the calibration window"""

    bridge = args
    raw = bridge.imgmsg_to_cv2(msg, desired_encoding="bgr8")
    hsv = cv2.cvtColor(raw, cv2.COLOR_BGR2HSV)

    # get info from track bar and appy to result
    h_low = cv2.getTrackbarPos('H_low', 'HSV Calibrator')
    s_low = cv2.getTrackbarPos('S_low', 'HSV Calibrator')
    v_low = cv2.getTrackbarPos('V_low', 'HSV Calibrator')
    h_high = cv2.getTrackbarPos('H_high', 'HSV Calibrator')
    s_high = cv2.getTrackbarPos('S_high', 'HSV Calibrator')
    v_high = cv2.getTrackbarPos('V_high', 'HSV Calibrator')

    # Normal masking algorithm
    lower = np.array([h_low, s_low, v_low])
    upper = np.array([h_high, s_high, v_high])

    mask = cv2.inRange(hsv, lower, upper)

    result = cv2.bitwise_and(raw, raw, mask=mask)

    cv2.imshow('HSV Calibrator', result)
    cv2.waitKey(30)


def main(node, subscriber):
    """Creates a camera calibration node and keeps it running."""

    # Initialize node
    rospy.init_node(node)

    # Initialize CV Bridge
    bridge = CvBridge()

    # Create a named window to calibrate HSV values in
    cv2.namedWindow('HSV Calibrator')

    # Creating track bar
    cv2.createTrackbar('H_low', 'HSV Calibrator', 0, 179, nothing)
    cv2.createTrackbar('S_low', 'HSV Calibrator', 0, 255, nothing)
    cv2.createTrackbar('V_low', 'HSV Calibrator', 0, 255, nothing)

    cv2.createTrackbar('H_high', 'HSV Calibrator', 50, 179, nothing)
    cv2.createTrackbar('S_high', 'HSV Calibrator', 100, 255, nothing)
    cv2.createTrackbar('V_high', 'HSV Calibrator', 100, 255, nothing)

    # Subscribe to the specified ROS topic and process it continuously
    rospy.Subscriber(subscriber, Image, calibrator, callback_args=(bridge))

    rospy.spin()


if __name__ == "__main__":
    PARSER = ArgumentParser()
    PARSER.add_argument("--subscribe", "-s",
                        default="/cameras/lmy_cam",
                        help="ROS topic to subcribe to (str)", type=str)
    PARSER.add_argument("--node", "-n", default="CameraCalibrator",
                        help="Node name (str)", type=str)
    ARGS = PARSER.parse_args()

    main(ARGS.node, ARGS.subscribe)

Solution

  • The issue only arises when we use cv2.imshow(winname, mat) inside a subscriber's callback function; other OpenCV functions work normally, but this seems to be a bug.

    One way to overcome this is by analyzing each frame like this:

    while not rospy.is_shutdown():
        data = rospy.wait_for_message(topic, topic_type)
        # do stuff
        cv2.imshow(winname, mat)
        cv2.waitKey(delay)
    

    instead of this:

    def callback(data):
        # do stuff
        cv2.imshow(winname, mat)
        cv2.waitKey(delay)
    
    rospy.subscribe(name, data_class, callback=callback)
    rospy.spin()