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