ROS中OpenCV的使用——人脸检测

http://xiaoyatec.com/2015/10/11/ros%E4%B8%ADopencv%E7%9A%84%E4%BD%BF%E7%94%A8/

ROS中OpenCV的使用——人脸检测

10-11 ROS视觉相关 Jason.Q 9,926 views

ROS中OpenCV的使用——人脸检测_第1张图片

环境:

Ubuntu12.04

ROS Hydro version

OpenCV 2.4.9

 

特别标注:

这篇文章首发于“北京小芽科技有限公司”的技术博客(xiaoyatec.com)上,所有版权归属于北京小芽科技有限公司。更多相关信息请关注北京小芽科技、小芽机器人。 

 

正文:

ROS中,OpenCV的使用,简单一句话概括就是:用ROS获取图像,然后转换成OpenCV的图像格式,后面就是OpenCV图像处理的事情了。

从规范的学习角度来说,大家应该先看看ROS中的vision_opencv功能包集,地址为:

http://wiki.ros.org/vision_opencv?distro=indigo

然后再学习里面的功能包cv_bridge,地址为:(C++版本 和 python版本,自选)

http://wiki.ros.org/cv_bridge/Tutorials

如果是简单滴学习使用方式的话,到这里就OK了,如何还想了解ros跟opencv的几何处理或者其他处理方式的话,可以分别了解以下的功能包:

image_geometry、image_common、image_pipeline、image_transport等等

 

这篇文章主要说明的是简单使用opencv的流程和原理,里面配备了人脸检测的例子。

 

首先,如果之前没有使用过opencv的话,建议在通用系统里边先装个opencv来玩玩里面的例子,待对opencv有个大体概念时,再转战到ros系统来。

对于ros系统,原理性的东西我就不多说了,这里所说的内容也是以节点为运行单位的。要完成这个流程,需要两个节点:usb_cam设备驱动程序节点 和 图像显示和图像处理节点,同时需要一个主题,即这两个节点的桥梁。

对于usb_cam设备驱动节点来说,是用来从ROS系统里获取摄像头图像的,在我的另外一篇文章《ROS中usb摄像头的使用_(usb_cam)》里边已经做过介绍,这里就不再描述,其实《ROS中usb摄像头的使用_(usb_cam)》里已经使用了opencv,但由于介绍内容的目标不一样,所以这里另外起一篇文章单独进行介绍opencv。

在讲另外一个节点前,先说说两个节点通信的桥梁:主题。其实主题就是一些变量或者结构体的集合,节点可以使用这些变量或结构体作为存储空间(即发布主题),也可以读取它们作为信息处理(订阅主题)。【这里说得比较白话文,目的在于大家好理解,主题里面涉及的内容还很多,可以参阅ros官方文献】

再来看看另外一个节点:图像显示和图像处理节点。这个节点主要就运用了opencv的内容,所以这里就重点描述一下这个节点处理的流程。

在详述之前,先来个图来描述一下上面的内容:
ROS中OpenCV的使用——人脸检测_第2张图片

 

 

这里边主要的问题在于,ROS图像与opencv图像之间的互相转换。主要涉及到ROS的功能包cv_bridge。这里主要讲讲C++版本的,python版本的原理和C++的一样。

(和官方文档说得差不多,英文好的可以直接看原文

http://wiki.ros.org/cv_bridge/Tutorials/UsingCvBridgeToConvertBetweenROSImagesAndOpenCVImages

首先来理解一些概念,ROS用sensor_msgs/Image消息格式来传输图像,这个和opencv图像格式不同,这样就需要有一个图像转换的过程。而CvBridge提供了ROS和opencv之间的接口。原理如图所示:

ROS中OpenCV的使用——人脸检测_第3张图片

 

 

 

我们再来看看cv_bridge命名空间:

ROS中OpenCV的使用——人脸检测_第4张图片

 

 

里面的cv::Mat变量即为OpenCV格式图像。也就是我们要把ROS的图像sensor_msgs::Image图像类型转换为cv_bridge图像类型,然后抽取cv_bridge的image变量即为opencv的图像类型数据。

图像的转换,可以使用以下的其中一个函数(根据自己的需求选取,具体使用方式请参阅官方文献)

ROS中OpenCV的使用——人脸检测_第5张图片

 

 

 

图像格式转换过来之后,剩下的就是OpenCV图像处理的事情了。图像处理请参阅OpenCV官网的资料和例程。

 

下来就举一个人脸检测的例子来对上面的内容进行一个实例化的补充说明。

1、 打开终端运行如下两条命令:(启动ROS环境)

 

 

2、 usb_cam设备驱动节点跟《ROS中usb摄像头的使用_(usb_cam)》的一样,直接运行即可,运行之后,图像会不断地发布到/camera/image_raw主题上:

 

 

3、 图像显示和图像处理节点cvbridge,框架跟《ROS中usb摄像头的使用_(usb_cam)》的一样,只是在里面稍微做些修改即可,这里吧它的源码全部贴出来,里面带了注释,这里我就不多说了。编译之后,可以用以下命令直接运行

编译过程:

ROS中OpenCV的使用——人脸检测_第6张图片

 

 

编译成功之后,可执行文件节点生成于 “~/catkin_ws_cvbridge/devel/lib/cvbridge”文件夹中

 

 

至此,图像接收和显示节点已经构建完成。

在运行程序前,要确保连个模型文件的存在和存放位置:

haarcascade_frontalface_alt.xml 和 haarcascade_eye_tree_eyeglasses.xml

可以到opencv源码目录里去下载:

https://github.com/Itseez/opencv/tree/master/data/haarcascades

保存位置,就看你代码怎么写了,如果是绝对路径或者相对路径,就把它们放在对应的路径里边,如果是运行环境路径,就把它们放在指令运行的当前目录里边,比如下面的情况,我是在catkin_ws_cvbridge文件夹里运行rosrun命令的,那我就得把这两个文件放在catkin_ws_cvbridge文件夹里边。

运行以下两个命令来执行节点:(图像接收显示并对图像进行处理)

 

 

 

具体代码如下:

 

#include <ros/ros.h> //rosç³»ç»å¤´æ件éå #include <image_transport/image_transport.h> #include <cv_bridge/cv_bridge.h> #include <sensor_msgs/image_encodings.h> #include <opencv2/objdetect/objdetect.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> #include <stdio.h> using namespace std; using namespace cv; //ç¨äºäººè¸æ£æµç模åæ件ï¼å¦éï¼ const std::string face_cascade_name = "haarcascade_frontalface_alt.xml"; //ç¨äºç¼çæ£æµç模åæ件ï¼å¦éï¼ const std::string eyes_cascade_name = "haarcascade_eye_tree_eyeglasses.xml"; cv::CascadeClassifier face_cascade; cv::CascadeClassifier eyes_cascade; //å¾åæ¾ç¤ºçªå£çæ é¢å称 static const std::string OPENCV_WINDOW = "Image window"; class ImageConverter { //èç¹å¥æ ros::NodeHandle nh_; //ç¨æ¥åå¸å订éROSç³»ç»çå¾å image_transport::ImageTransport it_; //订é主é¢çåé image_transport::Subscriber image_sub_; //åå¸ä¸»é¢çåé image_transport::Publisher image_pub_; public: ImageConverter(): it_(nh_) { // Subscrive to input video feed and publish output video feed image_sub_ = it_.subscribe("/camera/image_raw", 1, &ImageConverter::imageCb2, this); //"/camera/rgb/image_color "为Kinectè¾åºå½©è²å¾åçTopic //åå¸ä¸»é¢ image_pub_ = it_.advertise("/image_converter/output_video", 1); //å è½½çº§èåç±»å¨æ件 if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading\n"); }; if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error loading\n"); }; //å½åçªå£ cv::namedWindow(OPENCV_WINDOW); } ~ImageConverter() { cv::destroyWindow(OPENCV_WINDOW); } //订é主é¢çåè°å½æ° void imageCb2(const sensor_msgs::ImageConstPtr& msg)//Callback2 { cv_bridge::CvImagePtr cv_ptr; try { //å°sensor_msgs::Imageæ°æ®ç±»å转æ¢ä¸ºcv::Matç±»åï¼å³opencvçIplImageæ ¼å¼ï¼ 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; } //è³æ­¤ï¼ROSå¾ååOpenCVå¾å转æ¢å®æ¯ if(!cv_ptr->image.empty()) { //å©ç¨OpenCV对å¾åè¿è¡å¤ç detectAndDisplay(cv_ptr->image); } else { printf(" No captured frame. Break"); } cv::waitKey(3); // Output modified video stream image_pub_.publish(cv_ptr->toImageMsg()); } //å¾åå¤çè¿ç¨ï¼äººè¸æ£æµåç¼çæ£æµï¼ void detectAndDisplay( Mat frame ) { std::vector<Rect> faces; cv::Mat frame_gray; cvtColor( frame, frame_gray, CV_BGR2GRAY ); equalizeHist( frame_gray, frame_gray ); //-- å¤å°ºå¯¸æ£æµäººè¸ face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) ); for( int i = 0; i < faces.size(); i++ ) { Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 ); ellipse( frame, center, Size( faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar ( 255, 0, 255 ), 4, 8, 0 ); Mat faceROI = frame_gray( faces[i] ); std::vector<Rect> eyes; //-- å¨æ¯å¼ äººè¸ä¸æ£æµåç¼ eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CV_HAAR_SCALE_IMAGE, Size(30, 30) ); for( int j = 0; j < eyes.size(); j++ ) { Point center( faces[i].x + eyes[j].x + eyes[j].width*0.5, faces[i].y + eyes[j].y + eyes[j].height*0.5 ); int radius = cvRound( (eyes[j].width + eyes[i].height)*0.25 ); circle( frame, center, radius, Scalar( 255, 0, 0 ), 4, 8, 0 ); } } //-- æ¾ç¤ºç»æå¾å imshow( OPENCV_WINDOW, frame ); } }; //主å½æ° int main(int argc, char** argv) { //rosèç¹çåå§å ros::init(argc, argv, "image_converter"); //å¾åè·åå¤ç并æ¾ç¤ºçç±»ï¼åå§åå®ä¾çè¿éå³ä¸ºå¤ççè¿ç¨ ImageConverter ic; //ros::spin()è¿å¥èªå¾ªç¯ï¼å½ros::ok()è¿åFALSEæ¶ï¼ros::spin()å°±ç«å»è·³åºå¾ªç¯ //è¿å¾å¯è½æ¯ros::shutdown()被è°ç¨ï¼æèç¨æ·æä¸äºCtrl+Céåºç»åé® ros::spin(); return 0; }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

#include <ros/ros.h>    //ros系统头文件集合

#include <image_transport/image_transport.h>

#include <cv_bridge/cv_bridge.h>

#include <sensor_msgs/image_encodings.h>

#include <opencv2/objdetect/objdetect.hpp>

#include <opencv2/imgproc/imgproc.hpp>

#include <opencv2/highgui/highgui.hpp>

 

#include <iostream>

#include <stdio.h>

 

using namespace std;

using namespace cv;

 

//用于人脸检测的模型文件(另附)

const std::string face_cascade_name = "haarcascade_frontalface_alt.xml";

//用于眼睛检测的模型文件(另附)

const std::string eyes_cascade_name = "haarcascade_eye_tree_eyeglasses.xml";

cv::CascadeClassifier face_cascade;

cv::CascadeClassifier eyes_cascade;

 

//图像显示窗口的标题名称

static const std::string OPENCV_WINDOW = "Image window";

 

class ImageConverter

{

  //节点句柄

  ros::NodeHandle nh_;

  //用来发布和订阅ROS系统的图像

  image_transport::ImageTransport it_;

  //订阅主题的变量

  image_transport::Subscriber image_sub_;

  //发布主题的变量

  image_transport::Publisher image_pub_;

 

public:

  ImageConverter(): it_(nh_)

  {

    // Subscrive to input video feed and publish output video feed

    image_sub_ = it_.subscribe("/camera/image_raw", 1, &ImageConverter::imageCb2, this);

    //"/camera/rgb/image_color "为Kinect输出彩色图像的Topic

 

    //发布主题

    image_pub_ = it_.advertise("/image_converter/output_video", 1);

    

    //加载级联分类器文件

    if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading\n"); };

    if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error loading\n"); };

    

    //命名窗口

    cv::namedWindow(OPENCV_WINDOW);

  }

 

  ~ImageConverter()

  {

    cv::destroyWindow(OPENCV_WINDOW);

  }

  

  //订阅主题的回调函数

  void imageCb2(const sensor_msgs::ImageConstPtr& msg)//Callback2

  {

    cv_bridge::CvImagePtr cv_ptr;

    try

    {

      //将sensor_msgs::Image数据类型转换为cv::Mat类型(即opencv的IplImage格式)

      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;

    }

    

    //至此,ROS图像和OpenCV图像转换完毕

    if(!cv_ptr->image.empty())

    {

      //利用OpenCV对图像进行处理

      detectAndDisplay(cv_ptr->image);

    }

    else

    {

      printf(" No captured frame. Break");

    }

    

    cv::waitKey(3);

    

    // Output modified video stream

    image_pub_.publish(cv_ptr->toImageMsg());

  }

  

  //图像处理过程(人脸检测和眼睛检测)

  void detectAndDisplay( Mat frame )

  {

    std::vector<Rect> faces;

    cv::Mat frame_gray;

 

    cvtColor( frame, frame_gray, CV_BGR2GRAY );

    equalizeHist( frame_gray, frame_gray );

 

    //-- 多尺寸检测人脸

    face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) );

 

    for( int i = 0; i < faces.size(); i++ )

    {

        Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 );

        ellipse( frame, center, Size( faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar  ( 255, 0, 255 ), 4, 8, 0 );

 

        Mat faceROI = frame_gray( faces[i] );

        std::vector<Rect> eyes;

 

        //-- 在每张人脸上检测双眼

        eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CV_HAAR_SCALE_IMAGE, Size(30, 30) );

 

        for( int j = 0; j < eyes.size(); j++ )

        {

            Point center( faces[i].x + eyes[j].x + eyes[j].width*0.5, faces[i].y + eyes[j].y + eyes[j].height*0.5 );

            int radius = cvRound( (eyes[j].width + eyes[i].height)*0.25 );

            circle( frame, center, radius, Scalar( 255, 0, 0 ), 4, 8, 0 );

        }

    }

    

    //-- 显示结果图像

    imshow( OPENCV_WINDOW, frame );

  }

};

 

//主函数

int main(int argc, char** argv)

{

  //ros节点的初始化

  ros::init(argc, argv, "image_converter");

  

  //图像获取处理并显示的类,初始化实例的过错即为处理的过程

  ImageConverter ic;

  

  //ros::spin()进入自循环,当ros::ok()返回FALSE时,ros::spin()就立刻跳出循环

  //这很可能是ros::shutdown()被调用,或者用户按下了Ctrl+C退出组合键

  ros::spin();

  

  return 0;

}

 

 

 

 

成功运行之后,显示如下:(摄像头图像不是很流畅)

ROS中OpenCV的使用——人脸检测_第7张图片

 

参考文献:

1、Converting between ROS images and OpenCV images (C++)

链接地址为:

http://wiki.ros.org/cv_bridge/Tutorials/UsingCvBridgeToConvertBetweenROSImagesAndOpenCVImages

2、《OpenCV源码》

链接地址为:

https://github.com/Itseez/opencv

3、Cascade Classifier:OpenCV人脸检测例程》

链接地址为:

http://docs.opencv.org/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.html?highlight=haarcascade_frontalface_alt

你可能感兴趣的:(opencv,Ros,学习)