ROS机械臂开发:机器视觉应用

一、ROS图像接口

摄像头驱动安装

i5@i5-ThinkPad-T460p:~$ sudo apt install ros-kinetic-usb-cam

编写摄像头启动usb-cam.launch文件

<launch>

  <node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen" >
    <param name="video_device" value="/dev/video0" />
    <param name="image_width" value="640" />
    <param name="image_height" value="480" />
    <param name="pixel_format" value="yuyv" />
    <param name="camera_frame_id" value="usb_cam" />
    <param name="io_method" value="mmap"/>
  </node>

</launch>

usb_cam功能包中的参数

参数 类型 默认值 描述
~video_device string "/dev/video0" 摄像头设备号
~image_width integer 640 横向分辨率
~image_height integer 480 纵向分辨率
~pixel_format string "mjpeg" 像素编码
~io_method string "mmap" IO通道
~camera_frame_id string "head_camera" 摄像头坐标系
~framerate integer 30 帧率
~contrast integer 32 对比度,0-255
~brightness integer 32 亮度,0-255
~saturation integer 32 饱和度,0-255
~sharpness integer 22 清晰度,0-255
~autofocus boolean false 自动对焦
~focus integer 51 焦点(非自动对焦状态下有效)
~camera_info_url string "" 摄像头校准文件路径
~camera_name string "head_camera" 摄像头名称

usb_cam功能包中的话题

名称 类型 描述
~<camera_name>/image sensor_msgs/Image 发布图像数据
i5@i5-ThinkPad-T460p:~$ rosmsg show sensor_msgs/Image 
std_msgs/Header header
  uint32 seq
  time stamp
  string frame_id
uint32 height
uint32 width
string encoding
uint8 is_bigendian
uint32 step
uint8[] data

消息中各个域的含义如下:

  • header
    消息头,包含消息序号,时间戳,绑定坐标系
  • height
    图像纵向分辨率
  • width
    图像横向分辨率
  • encoding
    图像编码格式,包含RGB、YUV等常用格式,不涉及图像压缩编码
  • is_bigendian
    图像数据的大小端存储模式
  • step
    一行图像数据的字节数量,作为数据的步长参数
  • data
    存储图像数据的数组,大小为step*height个字节

启动摄像头

i5@i5-ThinkPad-T460p:~$ roslaunch vision_application usb_cam.launch 

如果使用的是带内置USB摄像头的笔记本,此时摄像头应当已经启动了:


摄像头启动

查看摄像头图像

i5@i5-ThinkPad-T460p:~$ rqt_image_view
rqt_image_view

二、摄像头内参标定

内参属于摄像头自身参数,外参是指和机械臂之间的位置关系。标定内参是为了消除图像的畸变,再做后面的识别。ROS提供了标定功能包,直接命令行安装:

i5@pop-os:~$ sudo apt install ros-melodic-camera-calibration

安装后启动摄像头和标定功能程序:

i5@pop-os:~$ rosrun camera_calibration cameracalibrator.py --size 8x6 --square 0.024 image:=/usb_cam/image_raw camera:=/usb_cam

将标定板摆放不同的位置和角度,直到CALIBRATE变为绿色,点击,即可完成摄像头内参标定。

内参标定
采样点
标定Ready

点击SAVE,程序将标定文件写入磁盘保存。


保存标定参数

解压后会得到一系列标定过程中获得的图片和一个yaml标定文件,这个标定文件即包含了摄像头的内参,可直接在usb_cam功能包中使用。


标定文件

得到标定文件后,在开头的usb_cam.lauch文件中加入tag:

<param name="camera_info_url" type="string" value="file://$(find ur5_vision)/ost.yaml"/>

再次启动摄像头后获得的图像即为标定后的图像了。如果启动过程中有警告:does not match name narrow_stereo in file,可尝试将yaml文件中的camera_name修改为head_camera后再重新启动。

三、ROS+OpenCV物体识别

在ROS中使用OpenCV,可以通过CvBridge功能包来实现ROS图像消息和OpenCV图像数据结构间的转换:


cv_bridge

在ROS中进行OpenCV物体识别开发一般经过如下的流程:

  • ROS驱动摄像头,发布图像消息
  • 将ROS图像消息转换成OpenCV图像数据
  • OpenCV图像处理
  • OpenCV图像转换成ROS消息
    下面给出在ROS中使用OpenCV人脸识别API编写的程序示例:
头文件
#ifndef FACE_DETECTOR_HPP_
#define FACE_DETECTOR_HPP_

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect.hpp>
#include "opencv2/highgui/highgui.hpp"

#include <ros/ros.h>
#include <cv_bridge/cv_bridge.h>
#include <image_transport/image_transport.h>

////定义7种颜色,用于标记人脸
cv::Scalar colors[] =
{
    // 红橙黄绿青蓝紫
   CV_RGB(255,0,0),
   CV_RGB(255, 97, 0),
   CV_RGB(255, 255, 0),
   CV_RGB(0, 255, 0),
   CV_RGB(255, 97, 0),
   CV_RGB(0, 0, 255),
   CV_RGB(160, 32, 240),
};

class FaceDetector
{
public:
    FaceDetector(ros::NodeHandle& nh);

    ~FaceDetector();

private:
    void detectFace(const sensor_msgs::ImageConstPtr &msg);
    void drawFace(cv::Mat &image, const std::vector<cv::Rect> &rect);
    void imageCb(const sensor_msgs::ImageConstPtr &msg);

    image_transport::ImageTransport it_;
    image_transport::Subscriber image_sub_;
    image_transport::Publisher image_pub_;
    cv_bridge::CvImagePtr cv_ptr_;
    cv::Mat gray_img_;
    cv::Mat process_img_;
    cv::CascadeClassifier cascade_;
    std::vector<cv::Rect> rect_;
};

#endif /* FACE_DETECTOR_HPP_ */

cpp文件
#include "face_detector.hpp"

FaceDetector::FaceDetector(ros::NodeHandle& nh) : it_(nh)
{
    // 加载人脸分类器
    cascade_.load("/usr/share/opencv/haarcascades/haarcascade_frontalface_alt2.xml");
    image_sub_ = it_.subscribe("/usb_cam/image_raw", 1, &FaceDetector::imageCb, this);
    image_pub_ = it_.advertise("/face_detect", 1);
}

FaceDetector::~FaceDetector()
{
    ROS_INFO("Bye bye\n");
}

void FaceDetector::drawFace(cv::Mat &image, const std::vector<cv::Rect> &rect)
{
    cv::Point center;
    int radius;
    for(int i = 0; i < rect.size(); i++)
    {
        center.x = cvRound((rect[i].x + rect[i].width * 0.5));
        center.y = cvRound((rect[i].y + rect[i].height * 0.5));
        radius = cvRound((rect[i].width + rect[i].height) *0.25);
        cv::circle(image, center, radius, colors[i % 7], 2);
    }
}

void FaceDetector::detectFace(const sensor_msgs::ImageConstPtr &msg)
{
    try
    {
        cv_ptr_ = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
    }
    catch (cv_bridge::Exception &e)
    {
        ROS_ERROR("cv_bridge exception: %s", e.what());
        return;
    }
    cv::Mat &image = cv_ptr_->image;
    process_img_ = image.clone();
    cv::cvtColor(image, gray_img_, CV_BGR2GRAY);
    cascade_.detectMultiScale(gray_img_, rect_, 1.3, 4, 0);
    ROS_INFO("%d face detected\n", static_cast<int>(rect_.size()));
    drawFace(process_img_, rect_);
    cv_bridge::CvImage out_image;
    out_image.encoding = cv_ptr_->encoding;
    out_image.header = cv_ptr_->header;
    out_image.image = process_img_;
    image_pub_.publish(out_image.toImageMsg());
}

void FaceDetector::imageCb(const sensor_msgs::ImageConstPtr &msg)
{
    detectFace(msg);
}

int main(int argc, char** argv )
{
    ros::init(argc, argv, "simple_face_vision_detection");
    ros::NodeHandle n_;

    ros::WallDuration(0.1).sleep();

    FaceDetector fd(n_);

    while (ros::ok())
    {
        // Process image callback
        ros::spinOnce();

        ros::WallDuration(0.1).sleep();
    }
    return 0;
}
launch文件
<launch>
    <include file="$(find ur5_vision)/launch/usb_cam.launch" />
    <node name="face_detector" pkg="ur5_vision" type="face_detector" output="screen" />
    <node name="image_viewer" pkg="rqt_image_view" type="rqt_image_view" output="screen" >
        <param name="image" value="/face_detect" />
    </node>
</launch>

启动后将看到标注人脸(如果有)的视频流,终端会打印检测到人脸的数目:


face detection
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,719评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,337评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,887评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,488评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,313评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,284评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,672评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,346评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,644评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,700评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,457评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,316评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,706评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,261评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,648评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,859评论 2 335

推荐阅读更多精彩内容