10.5 ROS в OpenCV: пакет cv_bridge

Когда драйверы камер запущены и работают, нам нужен способ обработки видеопотоков ROS с использованием OpenCV. ROS предоставляет пакет cv_bridge для преобразования между ROS и OpenCV и форматами изображений. Скрипт Python cv_bridge.demo.py в каталоге rbx1_vision / node демонстрирует, как использовать cv_bridge. Прежде чем мы посмотрим на код, вы можете попробовать его следующим образом.

Если у вас Kinect или Xtion, убедитесь, что вы сначала запустили соответствующий драйвер, если он еще не запущен.

Для Microsoft Kinect:

$ roslaunch freenect_launch freenect-registered-xyzrgb.launch

Для камер Asus Xtion, Xtion Pro или Primesense 1.08 / 1.09:

$ roslaunch openni2_launch openni2.launch depth_registration:=true

или для веб-камеры:

 $ roslaunch rbx1_vision usb_cam.launch video_device:=/dev/video0

(Сменить видеоустройство необходимо.)

Теперь запустите узел cv_bridge_demo.py:

$ rosrun rbx1_vision cv_bridge_demo.py

После небольшой задержки вы увидите два окна с изображениями. Окно в верхней части показывает живое видео после того, как оно было преобразовано в оттенки серого, а затем отправлено через OpenCV размытие и краевые фильтры. Окно внизу показывает изображение глубины серого, где белые пиксели находятся дальше, а темно-серые пиксели ближе к камере. (Это окно останется пустым, если вы используете обычную веб-камеру.) Чтобы выйти из демонстрации, либо введите букву «q» с помощью мыши над одним из окон, либо нажмите Ctrl-C в терминале, где вы запустили демонстрационный скрипт.

Давайте теперь посмотрим на код, чтобы увидеть, как он работает.

Ссылка на источник: cv_bridge_demo.py

#!/usr/bin/env python
import rospy
import sys
import cv2
import cv2.cv as cv
from sensor_msgs.msg import Image, CameraInfo
from cv_bridge import CvBridge, CvBridgeError
import numpy as np
class cvBridgeDemo():
def __init__(self):
self.node_name = "cv_bridge_demo"
rospy.init_node(self.node_name) 16.
# What we do during shutdown
rospy.on_shutdown(self.cleanup)
# Create the OpenCV display window for the RGB image
self.cv_window_name = self.node_name
cv.NamedWindow(self.cv_window_name, cv.CV_WINDOW_NORMAL)
cv.MoveWindow(self.cv_window_name, 25, 75)
# And one for the depth image
cv.NamedWindow("Depth Image", cv.CV_WINDOW_NORMAL)
cv.MoveWindow("Depth Image", 25, 350)
# Create the cv_bridge object
self.bridge = CvBridge()
# Subscribe to the camera image and depth topics and set
# the appropriate callbacks
self.image_sub = rospy.Subscriber("/camera/rgb/image_color",
Image, self.image_callback
self.depth_sub =
rospy.Subscriber("/camera/depth_registered/image_rect",
Image, self.depth_callback) rospy.loginfo("Waiting for image topics...")
def image_callback(self, ros_image):
# Use cv_bridge() to convert the ROS image to OpenCV format try:
frame = self.bridge.imgmsg_to_cv2(ros_image, "bgr8") except CvBridgeError, e:
print e
# Convert the image to a Numpy array since most cv2 functions # require Numpy arrays.
frame = np.array(frame, dtype=np.uint8)
    # Process the frame using the process_image() function
display_image = self.process_image(frame) # Display the image.
cv2.imshow(self.node_name, display_image)
    # Process any keyboard commands
self.keystroke = cv.WaitKey(5)
if 32 <= self.keystroke and self.keystroke < 128:
cc = chr(self.keystroke).lower()
if cc == 'q':
# The user has press the q key, so exit rospy.signal_shutdown("User hit q key to quit.")
def depth_callback(self, ros_image):
# Use cv_bridge() to convert the ROS image to OpenCV format try:
            # Use the default passthrough encoding for the depth image
depth_image = self.bridge.imgmsg_to_cv2(ros_image, except CvBridgeError, e:
            print e
        # Convert the depth image to a Numpy array
depth_array = np.array(depth_image, dtype=np.float32)
        # Normalize the depth image to fall between 0 and 1
cv2.normalize(depth_array, depth_array, 0, 1, cv2.NORM_MINMAX) # Process the depth image
depth_display_image = self.process_depth_image(depth_array) # Display the result
cv2.imshow("Depth Image", depth_display_image)
def process_image(self, frame):
# Convert to grayscale
grey = cv2.cvtColor(frame, cv.CV_BGR2GRAY)
        # Blur the image
grey = cv2.blur(grey, (7, 7))
# Compute edges using the Canny edge filter
edges = cv2.Canny(grey, 15.0, 30.0)
return edges
def process_depth_image(self, frame):
# Just return the raw image for this demo return frame
def cleanup(self):
print "Shutting down vision node." cv2.destroyAllWindows()
def main(args): try:
        cvBridgeDemo()
rospy.spin()
except KeyboardInterrupt:
print "Shutting down vision node." cv.DestroyAllWindows()
if __name__ == '__main__': main(sys.argv)

Давайте посмотрим на ключевые строки в этом скрипте.

import cv2
import cv2.cv as cv
from sensor_msgs.msg import Image, CameraInfo 
from cv_bridge import CvBridge, CvBridgeError 
import numpy as np

Все наши скрипты OpenCV будут импортировать библиотеку cv2, а также более старые функции pre-cv2, найденные в cv2.cv. Нам также обычно понадобятся типы сообщений ROS Image и CamInInofromthesensor_msgspackage. Для этого необходимо преобразовать формат изображения ROS в OpenCV, нам нужны классы CvBridge и CvBridgeError из пакета ROS cv_bridge. Наконец, OpenCV выполняет большую часть своей обработки изображений с использованием массивов Numpy, поэтому нам почти всегда нужен доступ к модулю Python numpy.

# Create the OpenCV display window for the RGB image
self.cv_window_name = self.node_name
cv.NamedWindow(self.cv_window_name, cv.CV_WINDOW_NORMAL)
cv.MoveWindow(self.cv_window_name, 25, 75)
# And one for the depth image
cv.NamedWindow("Depth Image", cv.CV_WINDOW_NORMAL)
cv.MoveWindow("Depth Image", 25, 350)

Если вы уже знакомы с OpenCV, вы узнаете эти операторы, которые создают именованные окна отображения для мониторинга видеопотоков; в этом случае одно окно для обычного видео RGB и одно для изображения глубины, если мы используем камеру RGB-D. Мы также перемещаем окна так, чтобы окно глубины находилось ниже окна RGB.

self.bridge = CvBridge()

Вот как мы создаем объект CvBridge для последующего преобразования изображений ROS в Формат OpenCV.

self.image_sub = rospy.Subscriber("/camera/rgb/image_color", Image, self.image_callback)
self.depth_sub = rospy.Subscriber("/camera/depth_registered/image_rect",
Image, self.depth_callback)

Это два ключевых подписчика, один для потока изображений RGB и один для изображения глубины. Обычно мы не будем жестко кодировать названия тем, чтобы их можно было переназначить в соответствующий файл запуска, но для демонстрации мы будем использовать имена тем по умолчанию, используемые узлом openni. Как и для всех подписчиков, мы назначаем функции обратного вызова для выполнения реальной работы с изображениями.

def image_callback(self, ros_image):
# Use cv_bridge() to convert the ROS image to OpenCV format try
frame = self.bridge.imgmsg_to_cv2(ros_image, "bgr8") except CvBridgeError, e:
print e

Это начало нашей функции обратного вызова для изображения RGB. ROS предоставляет изображение в качестве первого звена, которое мы назвалиros_image. Thetry-exclyblock использует функцию imgmsg_to_cv2 для преобразования изображения в формат OpenCV, предполагая сине-зеленое / красное 8-битное преобразование.

frame = np.array(frame, dtype=np.uint8) 51.
# Process the frame using the process_image() function
display_image = self.process_image(frame)
# Display the image.
cv2.imshow(self.node_name, display_image)

Большинство функций OpenCV требуют преобразования изображения в массив Numpy, поэтому мы выполняем преобразование здесь. Затем мы отправляем массив изображений в функцию process_image (), которую мы опишем ниже. Результатом является новое изображение с именем display_image, которое отображается в нашем ранее созданном окне отображения с помощью функции OpenCV imshow ().

# Process any keyboard commands
self.keystroke = cv.WaitKey(5)
if 32 <= self.keystroke and self.keystroke < 128:
cc = chr(self.keystroke).lower() if cc == 'q':
        # The user has press the q key, so exit
rospy.signal_shutdown("User hit q key to quit.")

Наконец, мы ищем ввод с клавиатуры от пользователя. В этом случае мы ищем только нажатие клавиши «q», которая сигнализирует о том, что мы хотим завершить сценарий.

def depth_callback(self, ros_image):
# Use cv_bridge() to convert the ROS image to OpenCV format try:
            # Use the default passthrough encoding for the depth image
depth_image = self.bridge.imgmsg_to_cv2(ros_image, "passthrough")
        except CvBridgeError, e:
            print e

Обратный вызов глубины начинается аналогично обратному вызову изображения, описанному ранее. Первое отличие, которое мы замечаем, состоит в том, что мы используем кодировку «passthrough» по умолчанию вместо 3-канального 8-битного преобразования цвета, используемого для изображения RGB.

depth_array = np.array(depth_image, dtype=np.float32)
# Normalize the depth image to fall between 0 and 1
cv2.normalize(depth_array, depth_array, 0, 1, cv2.NORM_MINMAX)

Поскольку OpenCV хранит изображения в виде массивов Numpy, мы преобразуем необработанное изображение глубины в массив float32. Затем мы нормализуем массив к интервалу [0, 1], поскольку функция OpenCV imshow () может отображать изображения в градациях серого, когда значения пикселей лежат между 0 и 1.

depth_display_image = self.process_depth_image(depth_array)
# Display the result
cv2.imshow("Depth Image", depth_display_image)

Массив глубины отправляется в функцию process_depth_image () для дальнейшей обработки (при желании), и результат отображается в окне изображения глубины, созданном ранее.

def process_image(self, frame):
# Convert to grayscale
grey = cv2.cvtColor(frame, cv.CV_BGR2GRAY)
# Blur the image
grey = cv2.blur(grey, (7, 7))
# Compute edges using the Canny edge filter
edges = cv2.Canny(grey, 15.0, 30.0)
return edges

Напомним, что функция обратного вызова изображения, в свою очередь, вызвала функцию process_image () на тот случай, если мы хотим манипулировать изображением перед его отображением пользователю. Для целей этой демонстрации здесь мы конвертируем изображение в градации серого, размываем его, используя гауссов фильтр, с дисперсией 7 пикселей в измерениях x и y, а затем вычисляем края Канни по результату. Изображение края возвращается к обратному вызову, где оно отображается пользователю.

def process_depth_image(self, frame):
# Just return the raw image for this demo return frame

В этой демонстрации мы ничего не делаем с изображением глубины и просто возвращаем исходный кадр. Не стесняйтесь добавлять свой собственный набор фильтров OpenCV здесь.

Last updated