0
$\begingroup$

Rosanswers logo

Hi,

I have been working on a Python class that will allow me to spawn an object in gazebo, take an image of the object, and then delete that object, for a given number of objects that I provide. I am able to spawn the objects, and have written a subscriber that is able to take images using a sensor. However, the images that are saved are not being taken in the order I am asking them to. Starting the script, I seem to be getting images out of order. For example, if I set it to spawn 5 objects without deleting for clarity, and take an image each time a new object is spawned, I could have 1 image without any objects, 3 images with 2 objects present, and the last image with 3 objects present. Sometimes I even get images with objects in positions that were set the last time I ran the script.

I thought this was because my publisher and subscriber were working at different rates, so looking at the answers on this forum I added queue_size=1 and buff_size = 2***30, to make sure that it was taking the last image that was sent. However, the problem is still there and nothing has changed. I have also tried increasing the time delay which has not helped, and was trying to find a way to delete the subscriber queue each time so that the latest message was definitely the one being read, but I have been unable to do so. I am not sure how to solve this and was wondering if anyone had a solution to this or an idea of why this was happening? I have attached my code below. Thanks!

import numpy as np
import rospy, tf, time
from gazebo_msgs.srv import SpawnModel, DeleteModel, GetModelState
from geometry_msgs.msg import *
from take_photo import ImageNode
# ROS Image message
from sensor_msgs.msg import Image
# ROS Image message -> OpenCV2 image converter
from cv_bridge import CvBridge, CvBridgeError
# OpenCV2 for saving an image
import cv2
import os

class ImageCollection():
    def __init__(self, number_spawn, model_path, time_delay=3.0):
        print("Waiting for gazebo services...")
        #rospy.init_node("spawn_products_in_bins")
        rospy.wait_for_service("gazebo/delete_model")
        rospy.wait_for_service("gazebo/spawn_sdf_model")
        rospy.wait_for_service("gazebo/get_model_state")
        print("Got it.")
        self.delete_model = rospy.ServiceProxy("gazebo/delete_model", DeleteModel)
        self.spawn_model = rospy.ServiceProxy("gazebo/spawn_sdf_model", SpawnModel)
        self.model_coordinates = rospy.ServiceProxy("gazebo/get_model_state", GetModelState)
        
        # Instantiate CvBridge
        self.bridge = CvBridge()
        self.image_number = 0
        self.number_spawn = number_spawn
        self.time_delay = time_delay
        self.model_path = model_path

        # Create a directory to store images
        self.folder_path = "Gazebo_Images"
        try:
            os.mkdir(self.folder_path)
        except:
            print("Folder already exists, appending to that folder...")

        # Initialize the node
        rospy.init_node('image_listener')
        # Define your image topic
        self.image_topic = "/wamv/sensors/cameras/front_left_camera/image_raw"
        # Set up your subscriber and define its callback
        # rospy.Subscriber(image_topic, Image, self.image_callback)
        # rospy.spin()
    
    def spawn(self):

        with open(self.model_path, "r") as f:
            product_xml = f.read()

        # Determine a random position for the buoy (function has not been shown here but is just a random generator)
        bin_x, bin_y = self.generate_random_position()
        self.item_name   =   "croc_{0}".format(self.image_number)
        print("Spawning model: %s" % self.item_name)

        # Want to give the object a random yaw
        random_yaw = np.random.random()
        orient = Quaternion(*tf.transformations.quaternion_from_euler(0,0,random_yaw*np.pi))

        item_pose = Pose(Point(x=bin_x, y=bin_y, z=0), orient)
        self.spawn_model(self.item_name, product_xml, "", item_pose, "world")

    def start_taking_images(self):
        # Start image taking node
        rospy.Subscriber(self.image_topic, Image, self.image_callback, queue_size = 1, buff_size = 2**30)
        rospy.spin()
    
    def image_callback(self, msg):
        # Spawn objects
        self.spawn()

        print("Received an image!")
        try:
            # Convert your ROS Image message to OpenCV2
            cv2_img = self.bridge.imgmsg_to_cv2(msg, "bgr8")
        except CvBridgeError as e:
            print(e)
        # Save your OpenCV2 image as a png 
        time.sleep(self.time_delay)
        cv2.imwrite(self.folder_path+'/croc_{}'.format(self.image_number)+'.png', cv2_img)
        print("Saved Image!")
        
        #print("Deleting model: %s" % self.item_name)
        #self.delete_model(self.item_name)

        self.image_number += 1
        if self.image_number == self.number_spawn:
            self.shutdown()
        
    def shutdown(self):
        # shutdown the ROS node
        print("Shutting down ImageNode after taking {} image(s)...".format(self.image_number))
        rospy.signal_shutdown("ROS is shutting down ImageNode")


if __name__ == "__main__":
    # Number of objects to spawn
    number = 2

    # Spawning crocodile
    spawner = ImageCollection(number, "crocodile_buoy/model.sdf", time_delay=3.0)
    spawner.start_taking_images()

Originally posted by jdastoor3 on ROS Answers with karma: 13 on 2021-12-10

Post score: 0


Original comments

Comment by osilva on 2021-12-10:
Hi @jdastoor3 your question is clear. Unfortunately I don’t know the answer as I’ll need time to study it more. However why not use a rosbag to save your pictures instead of trying to handle in the node?

Comment by jdastoor3 on 2021-12-10:
I was unaware that was a possibility. Would that avoid the issues I have been having? I found this script, would that be a good place to start? Sorry, I am new to ROS.

Comment by osilva on 2021-12-10:
Yes a good place and search for ‘rosbag tutorials’. It will be a lot faster

Comment by osilva on 2021-12-10:
Take a look at this thread, hope it helps: https://stackoverflow.com/questions/38700271/python-converting-image-time-series-to-ros-bag

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

Your code is doing many things which lead to bad performance.

  1. You should not be doing any rospy calls until you have done the rospy.init_node().

  2. If you want good performance, you can not do slow stuff inside a subscribe callback: don't sleep, don't call services, don't wait for services, don't write a lot of data to files, don't convert the image data into some other format.

  3. One design strategy many have used successfully is to simply have the callback save the msg and then return. Then in your main loop, you keep checking to see if a msg is available, then process it when one shows up. In the main loop it is OK to do all the slow things I listed in item (2).

  4. If fact, you must sleep in main loop because you want to limit how many times per second the main loop executes. Typically a rospy.Rate object is used to implement this.

  5. If you sleep in main loop, use rospy.sleep(), not time.sleep().


Originally posted by Mike Scheutzow with karma: 4903 on 2021-12-11

This answer was ACCEPTED on the original site

Post score: 1


Original comments

Comment by jdastoor3 on 2021-12-11:
So essentially I should just save the msg that callback receives, and create a main function which has the code currently in callback? Where would I put the rospy.Rate object? Is that right after I start the subscriber? And I don't need the rospy.spin() right after I call the subscriber right?

Comment by Mike Scheutzow on 2021-12-11:
Here is example of how to use rospy.Rate() in a main loop. Yes, the while loop replaces the rospy.spin() statement.

http://wiki.ros.org/rospy/Overview/Time#Sleeping_and_Rates

And you should rethink that 3 second sleep that you have in there.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.