ROS_Opencv-摄像头图像发布和订阅

图像读取

01.opencv读入数据
 摄像头    cv::  VideoCapture()
 视频      cv::  VideoCapture()
 读取视频时可以传入视频路径,而读取摄像头传入的是index索引号
 图像数据集cv::   cv2.imread()

02.摄像头挂载点
       ls /dev/video*
  或者 ls /dev  | grep video
     是否存在有效usb摄像头设备  dmesg | grep video
03.摄像头内核驱动与Linux内核
  就是通过总线与硬件设备进行通信,控制硬件进去各种工作状态,获取器件相关寄存器的值,从而得到设备的状态
     V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动
    linux上的camera都 是按照v4l2框架来设计,它的地址位于Linux kernel 
    V4L2的核心源码位于 drivers/media/v4l2-core  
    V4L2支持三类设备:视频输入输出设备、VBI设备和radio设备
   在 Linux中,硬件的驱动程序,都是由内核支持的
   Linux内核是Linux系统的核心程序,主要完成任务调度、内存管理、IO设备管理等等功能
03.摄像头硬件结构和工作原理
    摄像头引脚作用&硬件连接
    摄像头模组,全称CameraCompact Module,简写为CCM。
       CCM 包含四大件: 镜头(lens)、传感器(sensor)、软板(FPC)、图像处理芯片(DSP)
     sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,
              实现各种设备控制方法供上层调用并注册 v4l2_subdev
    接口: 
      USB接口 UVC,全称 USB Video Class 、是 USB-IF 定制的一套标准
      MIPI接口 MIPI(移动行业处理器接口)是Mobile Industry Processor Interface的

从Camera到ROS

通过用openCV来捕获视频流
在 cv_bridge::CvImage类中执行toImageMsg 将OpenCV图像转换为ROS消息的 
发布ros消息
 CvImage类中执行的OpenCV图像转换为ROS消息的成员函数为 toImageMsg
    cv::Mat frame;
    std_msgs::Header header;
    header.seq = cnt;
    header.stamp = ros::Time::now(); 
    sensor_msgs::Image img_msg ;
    cv_bridge::CvImage  img_bridge = cv_bridge::CvImage(header, sensor_msgs::image_encodings::BGR8, frame);
    img_bridge.toImageMsg(img_msg); 

从ROS中到opencv图像

cv_bridge中的
 Convert a sensor_msgs::Image message to an OpenCV-compatible CvImage, copying the image data.
    toCvCopy  返回值是 CvImagePtr
     toCvShare 返回值是 CvImageConstPtr
 //toCvCopy函数会从ROS消息中拷贝一个图像数据
 cv_bridge::CvImagePtr image_ptr = cv_bridge::toCvCopy(RGB_image, sensor_msgs::image_encodings::RGB8);
 cv::Mat RGB_image= image_ptr->image;  
其中
 typedef boost::shared_ptr<CvImage> CvImagePtr;
 typedef boost::shared_ptr<CvImage const> CvImageConstPtr;
 ROS中cv_bridge源码中执行转换的类为 CvImage,该类包含了OpenCV的Mat格式的图像、该消息的encoding以及ROS header
  CvImage
    std_msgs::Header header; //!< ROS header
    std::string encoding;    //!< Image encoding ("mono8", "bgr8", etc.)
    cv::Mat image;

发布和订阅

生产和消费 // 0.包含头文件 #include "ros/ros.h"
    //1.初始化 ROS 节点  ros::init(argc,argv,"talker"); //参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
    //2.实例化 ROS 句柄   ros::NodeHandle nh;    //该类封装了 ROS 中的一些常用功能

     //3.实例化 发布者 对象
     ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);
     std_msgs::String msg;  + 构造消息== 组织被发布的数据,并编写逻辑发布数据
     pub.publish(msg);

     
     //3.实例化 订阅者 对象
        ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
        void doMsg(const std_msgs::String::ConstPtr& msg_p){ } 定义回调函数-处理订阅的消息(回调函数)
       设置循环调用回调函数

   image_transport 被用在image订阅和发布上 image_transport 默认的传输,通过ROS传输sensor_msgs/Image
其他通信类型
    nh.advertiseService  nh.serviceClient

显示器

 opencv显示
   cv2.imshow()
 接口的物理标准和通讯协议
 计算机显示接口
  显示器的 VGA、DVI 、HDMI 和 DP 接口 USB Type-C接口-连接主机和显示器的外部显示接口
   HDMI High-Definition Multimedia Interface   基于 TMDS 技术
   DP(Display Port)接口是HDMI接口的竞争对手    基于 LVDS 技术
    DP口定义了两种接头,全尺寸(Full Size)和迷你DP(Mini),两种接头皆是20针
   VGA接口也称为D-Sub接口。在CRT显示器时代,VGA接口是必备的。因为CRT是模拟设备,而VGA采用的也是模拟协议
   USB 3.1的接口来说,有Type-A、Type-B以及Type-C 
     Type C接口的好处在于可以正反插拔,部分Type C接口也同样具有数据、电力、信号传输的功能
   电1和雷电2外形上采用的是Mini DP的外形,而雷电3采用的是USB Type-C的外形
   大多数人来说网线可能只是用来连接网络的。但是其实网线还可以用给设备供电,如果采用HDBaseT协议甚至还能传送高清影音
 家庭影音设备
      模拟信号:复合信号Composite Video
      模拟信号:分量信号Component Video
      数字信号 DVI Digital Visual Interface:

示例代码

// 一、创建功能包   catkin_create_pkg communication std_msgs roscpp
// 二、编写功能包的代码
#include<iostream>
#include<vector>
#include<string>
#include<ros/ros.h> //包含了ros当中常用的API,订阅,发布,日志的输出
#include<sensor_msgs/Image.h>
#include<image_transport/image_transport.h>
#include<cv_bridge/cv_bridge.h>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>

using namespace cv;

int main(int argc,char*argv[])
{
    const std::string package_name = "get_camera";
    ROS_INFO("Node read camera begin...");
   
   // ROS节点初始化  argc  argv和main函数保持一致   package_name 是节点名称
    ros::init(argc,argv,package_name);

  // 创建节点句柄
    ros::NodeHandle nh("~");
    std::cout<<"In main function"<<std::endl;
    // 1.首先用openCV来捕获视频流
    std::string video_name = "~/Data/show.mp4" ; 
    int camera_number = 0 ;   
    //VideoCapture 先实例化再初始化
    cv::VideoCapture capture;
    capture.open(video_name);
    if(!capture.isOpened())
    {
        ROS_INFO("read video %s failed,please check...",video_name.c_str());
        return -1;
    }

    //2.创建ROS发布者
    image_transport::ImageTransport it(nh);
     // 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String  1000是缓存队列长度
    // ros::Publisher chatter_pub = nh.advertise<std_msgs::String>("chatter", 1000);
    image_transport::Publisher img_pub_ =  it.advertise("/camera_compressed",1);
   // 设置循环的频率
    ros::Rate r(30);

    int cnt = 0;
    cv::Mat img;
    cv_bridge::CvImage img_bridge;
    sensor_msgs::Image img_msg;

    cv::Mat frame;
   //循环发布消息
    while( ros::ok() && capture.read(frame) )
    {
        
	// 循环等待回调函数 //查询一次回调函数是否有消息进来
        ros::spinOnce();
        ROS_INFO("img width = %d  img height=%d ",frame.cols,frame.rows);
        std_msgs::Header header;
        header.seq = cnt;
        header.stamp = ros::Time::now();  
         //通过CvImage类 在CvImage类中执行的OpenCV图像转换为ROS消息的成员函数为 toImageMsg()
        img_bridge = cv_bridge::CvImage(header, sensor_msgs::image_encodings::BGR8, frame);
        img_bridge.toImageMsg(img_msg); 
        //ROS发送图片(Image)数据类型的消息(message)
         // ros::Publisher pub_img = node.advertise<sensor_msgs::Image>("topic", queuesize);
        img_pub_.publish(img_msg);        

        cv::imshow("img",frame);
        cv::waitKey(1);
        r.sleep();
        ROS_INFO("publish  img  success frame = %d ",cnt);
        cnt += 1;
    }
    capture.release();
    return 0;
}

// 任何一个话题的发布和订阅,都会有其对应的发布缓存区和订阅缓存区
// ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter", 10, doMsg);
//一共有3个参数:订阅话题,回调函数队列长度,回调函数
// ros::spinOnce()时,会去话题订阅缓冲区中查看有没有回调函数,如果有则去处理回调函数,如果没有则继续往下执行
//ros::spin() 在调用后不会再返回,也就是你的主程序到这儿就不往下执行了  是一直调用;ros::spin()函数一般不会出现在循环中
// ros::spinOnce()是只调用一次, 在调用后还可以继续执行之后的程序 .如果还想再调用,就需要加上循环了 ros::spinOnce() 可以配合其它函数,一起放在while循环中处理
// 使用ros::spinOnce()而不是ros::spin()意味着你需要自己管理循环的控制和休眠。你需要根据实际情况调整循环的频率和休眠时间。
// catkin_make  source ./devel/setup.bash  rosrun 包名 C++节点

// 定义msg文件  2.编辑配置文件  编译后的中间文件查看: ++ 需要调用的中间文件(.../工作空间/devel/include/包名/xxx.h) 后续调用相关 msg 时,是从这些中间文件调用的
// 把消息发布与订阅封装成launch  roslaunch task_topic task_topic_7.launch
// ROS中给出的解决方案是:Nodelet,通过Nodelet可以将多个节点集成进一个进程

参考:

 cv_bridge用于ROS图像和OpenCV图像的转换 https://blog.csdn.net/bigdog_1027/article/details/79090571
posted @ 2021-09-27 17:56  辰令  阅读(1275)  评论(0编辑  收藏  举报