Subscriber unable to read image from publisher

Viewed 328

I have been trying to get the subscriber to display an image with GUI by integrating opencv with pyqt5, but the image is not shown after I click the button. Before i integrate pyqt5 into the subscriber, it was able to show an image with just opencv itself.

-I am using Ubuntu 18.04.

My publisher code:

def main():
image_pub = rospy.Publisher("/image", Image, queue_size=1)
rate = rospy.Rate(50)
bridge = CvBridge()

while not rospy.is_shutdown():
      print("------------Mask detection processing one time!------------")

      image_path='/home/sk/catkin_ws/src/test/scripts/p_e.png'
      cv_image = cv2.imread(image_path)

      # read a image using opencv
      # change to your real image path    

      print(cv_image.shape)
      #cv2.imshow("Pikachu", cv_image)
      msg_image = bridge.cv2_to_imgmsg(cv_image, encoding = "bgr8") 

      stamp = rospy.Time.now()
      msg_image.header.frame_id = "camera_link"
      msg_image.header.stamp = stamp

      image_pub.publish(msg_image)
      rate.sleep()
if __name__ == '__main__':
try:
  rospy.init_node('image_pub_node', anonymous=True)
  main()
  rospy.spin()
except KeyboardInterrupt:
  print("Shutting down")
  sys.exit(0)
  pass

My subscriber code:

class ImageWidget(QtWidgets.QWidget):
#def callback(self,data):
def callback(self,rosdata):
  global cv_image      
  #self.bridge = CvBridge()
  self.bridge = CvBridge()
  #print("in callback")
  rate = rospy.Rate(10)
  rate.sleep() 
  try:
    cv_image = self.bridge.imgmsg_to_cv2(rosdata, "bgr8")
    #cv_image = self.bridge.imgmsg_to_cv2(data, "bgr8")    

  except CvBridgeError as e:                            
      print(e)


def __init__(self, parent=None):

    super(ImageWidget, self).__init__(parent)

    self.button = QtWidgets.QPushButton('Show picture')
    self.button.clicked.connect(self.main)
    self.image_frame = QtWidgets.QLabel()

    self.layout = QtWidgets.QVBoxLayout()
    self.layout.addWidget(self.button)
    self.layout.addWidget(self.image_frame)
    self.setLayout(self.layout)

def main(self):

    rospy.init_node('image_sub_node', anonymous=True)
    self.image_sub = rospy.Subscriber("/image", Image, self.callback, queue_size=1, buff_size=52428800)
    
    rate = rospy.Rate(50) #50Hz
    bridge = CvBridge()
  
    while not rospy.is_shutdown():
      print("------------Mask detection processing one time!------------")

      try:
        self.cv_image = cv2.imread('Image')
        self.cv_image = QtGui.QImage(self.cv_image.data, self.cv_image.shape[1], self.cv_image.shape[0], QtGui.QImage.Format_RGB888).rgbSwapped()
        self.image_frame.setPixmap(QtGui.QPixmap.fromImage(self.cv_image))
      except Exception:
        continue

        rate.sleep()
if __name__ == '__main__':
try:
  app = QtWidgets.QApplication(sys.argv)
  image_widget = ImageWidget()
  image_widget.show()
  sys.exit(app.exec_())
  rospy.init_node('image_sub_node', anonymous=True)
  main()
  rospy.spin()
except KeyboardInterrupt:
  pass
1 Answers

The exec_() method executes the eventloop so it will only finish executing after Qt finishes executing, and if you associate it with sys.exit then the subsequent code will never execute.

On the other hand you should not use while loops but you only need to use the eventloop, and send the information from the callback (which is executed in a secondary thread) to the window using signals.

import sys

from PyQt5 import QtCore, QtGui, QtWidgets
from sensor_msgs.msg import Image
from cv_bridge import CvBridge, CvBridgeError
import rospy
import cv2


class SubscriberManager(QtCore.QObject):
    imageChanged = QtCore.pyqtSignal(QtGui.QImage)

    def __init__(self, parent=None):
        super(SubscriberManager, self).__init__(parent)
        self.bridge = CvBridge()
        self.subscriber = None

    def start(self, name):
        self.stop()
        self.subscriber = rospy.Subscriber(
            name, Image, self._callback, queue_size=1, buff_size=52428800
        )

    def stop(self):
        if self.subscriber is not None:
            self.subscriber.unregister()
            self.subscriber = None

    def _callback(self, rosdata):
        try:
            cv_image = self.bridge.imgmsg_to_cv2(rosdata, "bgr8")
        except CvBridgeError as e:
            print(e)
        else:
            src = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
            h, w, ch = src.shape
            bytesPerLine = ch * w
            qImg = QtGui.QImage(
                src.data, w, h, bytesPerLine, QtGui.QImage.Format_RGB888
            )
            self.imageChanged.emit(qImg)


class ImageWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(ImageWidget, self).__init__(parent)

        self.button = QtWidgets.QPushButton("Show picture")
        self.image_frame = QtWidgets.QLabel()

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.button)
        lay.addWidget(self.image_frame)

        self.subscribe_manager = SubscriberManager()

        self.subscribe_manager.imageChanged.connect(self.on_image_changed)
        self.button.clicked.connect(self.start)

    def start(self):
        self.subscribe_manager.start("/image")

    @QtCore.pyqtSlot(QtGui.QImage)
    def on_image_changed(self, image):
        self.image_frame.setPixmap(QtGui.QPixmap.fromImage(image))


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    rospy.init_node("image_sub_node", anonymous=True)

    image_widget = ImageWidget()
    image_widget.show()
    sys.exit(app.exec_())
Related