ROS高效进阶第四章 -- 机器视觉处理之ros集成zbar实现二维码检测

机器视觉处理之ros集成zbar实现二维码检测

1 资料

本文是机器视觉处理系列的第五篇,属于临时决定加的一篇,我们将使用 zbar 库,在 ros 上实现二维码检测。二维码是我们生活中熟悉的陌生人,各种手机支付,微信加好友,地铁进站扫码等等等,而关于二维码的由来,以及生成和检测原理,相信大多数人是不知道的。
本文依然基于本系列第一篇 ROS高效进阶第四章 – 机器视觉处理之图像格式,usb_cam,摄像头标定,opencv和cv_bridge引入 的 robot_vision 进行扩充,再次强调下本文的测试环境是ubuntu20.04 + ros noetic。
本文参考资料如下:
(0)二维码的秘密
(1)二维码原理与使用
(2)二维码的原理是什么?
(3)二维码的生成细节和原理
(4)ISO二维码标准文档
(5)二维码扫描之zxing与zbar的优劣
(6)Zbar算法官网
(7)Zbar算法流程介绍

2 正文

2.1 二维码引入

(1)二维码英文为QR Code,全称为Quick Response Code,即快速响应码。在二维码之前有条形码,但是信息容量太小,于是日本人原昌宏(Denso-Wave公司)搞出来了二维码,用来容纳更多信息。更多资料参考:二维码的秘密
(2)发展至今,二维码已经有40多个版本,每个版本有固定的码元数,1码元也就是一个小格子,存储1bit,也就是0(白色)或1(黑色),每个版本横向和纵向各自以4码元为单位递增,以此类推。
在这里插入图片描述
(3)一张二维码图片是分成各个区域的,并不是所有区域都装数据,通常用下面幅图说明,具体的解释看:二维码原理与使用 中的QR码的基本结构
在这里插入图片描述
(4)陈皓的酷壳里有一篇广为流传的二维码讲解博客:二维码的生成细节和原理 ,这里讲了一个二维码是怎么绘制出来的,大家可以参考。不过文章里有处错误,这里指出下。
在这里插入图片描述
另外有一篇文档二维码的原理是什么? ,介绍了如何把字符串:cxd1301 存进二维码。这篇文章相比陈皓的文章风格比较简介,也可以参考。当然,如果是你搞二维码的专业工程师,最好去读ISO的二维码标准文档:ISO二维码标准文档

2.2 zbar算法

(1)当下有两种常用的二维码开源解析库,zbar和zxing。zbar是C代码,zxing是C++,都能很好的识别二维码,而zbar的效率更高,也来自日本,官网为:Zbar算法官网 。zbar和zxing的比较请参考:二维码扫描之zxing与zbar的优劣
(2)本文在ubuntu 20.04和ros noetic上,安装并集成zbar,实时检测usb_cam中的二维码。本文不讲解zbar的解析原理,具体可以参考:Zbar算法流程介绍

2.3 qr_detector样例

(1)nodelet 和 image_transport 的引入
nodelet 是ros提供的一种特殊的组件,支持多节点同一进程启动,主要用于大量数据交互的情况下。此时节点之间的topic数据是通过指针直接传递的,不需要socket通道,避免拷贝数据占用大量性能。
使用nodelet必须继承nodelet::Nodelet的类,并实现其onInit()方法,然后launch文件中使用nodelet管理器启动。本文的qr_detector将尝试使用这种方式,编写ros节点。
image_transport 是一个ROS库,它用于在ROS节点之间高效地传输图像信息。相比普通的topic订阅和发送,他提供更灵活的使用方式,本文的qr_detector将尝试使用这个库,下面是简单的使用样例。

ros::NodeHandle nh;
image_transport::ImageTransport it(nh);
image_transport::Subscriber sub = it.subscribe("camera/image", 1, imageCallback);
...
sub.shutdown()

(2)安装zbar,以及在 robot_vision 中创建 qr_detector 样例

sudo apt install libzbar-dev
cd ~/catkin_ws/src/robot_vision 
mkdir include/qr_detector
touch include/qr_detector/qr_detector_nodelet.h src/qr_detector_nodelet.cpp 
touch launch/qr_detector.launch
// 必须使用nodelets.xml文件,指定和描述创建的Nodelet节点
touch nodelets.xml

qr_detector_nodelet.h

#pragma once
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
#include <ros/ros.h>
#include <nodelet/nodelet.h>
#include <cv_bridge/cv_bridge.h>
#include <image_transport/image_transport.h>
#include <zbar.h>

namespace qr_detector {
//自定义QrTag 以及QrTags 
struct QrTag {
  std::string qr_msg;
  std::vector<cv::Point> square_apex_vector;
};
using QrTags = std::vector<QrTag>;
// QrDetector 是zbar的封装类,对外提供detect()方法,用于检测传入的cv 图像里的二维码
class QrDetector {
public:
  QrDetector() {
    // 设置zbar参数,ZBAR_NONE表示无差别识别所有二维码类型
    // zbar::ZBAR_CFG_ENABLE和1,表示启用二维码识别
    scanner_.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
  }

  QrTags detect(const cv::Mat& image) {
    cv::Mat gray_img;
    // 将cv图像转为灰度图,颜色对于二维码没有意义
    cv::cvtColor(image, gray_img, CV_BGR2GRAY);
	// 实例化zbar图像对象,Y800表示这是8位灰度图像
    const uint64_t width = image.cols;
    const uint64_t height = image.rows;
    zbar::Image img(width, height, "Y800", gray_img.data, width*height);
    // 使用zbar扫描器扫描图像,数据存在img里
    scanner_.scan(img);

    QrTags tags;
    //如果图像内有多个二维码,这里就有多个,一般都是一个
    for (auto s = img.symbol_begin(); s != img.symbol_end(); ++s) {
      QrTag tag;
      // 这里是获取二维码的字符串信息,有些是明文,有些是加密文,还需要后台二次处理
      tag.qr_msg = s->get_data();
      // 每个二维码都有四个角点,这里是获取角点信息,不过不输出
      for (int i = 0; i < s->get_location_size(); i++) {
        tag.square_apex_vector.push_back(cv::Point(s->get_location_x(i), s->get_location_y(i)));
      }
      tags.push_back(tag);
    }
    return tags;
  }

private:
  // zbar扫描器
  zbar::ImageScanner scanner_;
};
// QrDetectorNodelet 必须继承nodelet::Nodelet,并重载onInit()
class QrDetectorNodelet : public nodelet::Nodelet {
public:
  QrDetectorNodelet();
  virtual ~QrDetectorNodelet();
  void onInit() override;

private:
  void DownstreamConnectCb();
  void DownstreamDisconnectCb();
  void ImageCb(const sensor_msgs::ImageConstPtr& image);

private:
  ros::NodeHandle nh_;
  image_transport::ImageTransport it_;
  // 订阅图像的句柄
  image_transport::Subscriber img_sub_;
  ros::Publisher qr_pub_;
  QrDetector detector_;
};
}

qr_detector_nodelet.cpp

#include "qr_detector/qr_detector_nodelet.h"
#include <pluginlib/class_list_macros.h>
#include <cv_bridge/cv_bridge.h>
#include <sensor_msgs/image_encodings.h>
#include <std_msgs/String.h>
// 使用 PLUGINLIB_EXPORT_CLASS 注册 QrDetectorNodelet 类为 Nodelet 插件。
PLUGINLIB_EXPORT_CLASS(qr_detector::QrDetectorNodelet, nodelet::Nodelet);

namespace qr_detector {
// 初始化化image_transport::ImageTransport it_;
QrDetectorNodelet::QrDetectorNodelet() : it_(nh_) {
    ROS_INFO("QrDetectorNodelet Constructor");
}
// 如果QrDetectorNodelet销毁,则销毁img_sub_句柄
QrDetectorNodelet::~QrDetectorNodelet() {
    img_sub_.shutdown();
    ROS_INFO("QrDetectorNodelet Destructor");
}

void QrDetectorNodelet::onInit() {
    // Nodelet节点的node handle句柄不能自己实例化,因为是大家共享同一个,因此必须使用getNodeHandle()从Nodelet节点管理器那里获取
    nh_ = getNodeHandle();
    // 创建二维码广播器,创建时塞了两个回调
    // 当下游订阅了二维码topic,则执行DownstreamConnectCb回调
    // 当下游取消订阅二维码topic,则执行DownstreamDisconnectCb
    qr_pub_ = nh_.advertise<std_msgs::String>("/qr_codes", 10, 
                                            std::bind(&QrDetectorNodelet::DownstreamConnectCb, this),
                                            std::bind(&QrDetectorNodelet::DownstreamDisconnectCb, this));
    ROS_INFO("init qr detector nodelet, nodehand name %s", nh_.getNamespace().c_str());
}

void QrDetectorNodelet::DownstreamConnectCb() {
    // 如果二维码topic被订阅,且img_sub_ 没有实例化,则实例化img_sub_ ,并注册回调函数ImageCb
    if (!img_sub_ && qr_pub_.getNumSubscribers() > 0) {
        img_sub_ = it_.subscribe("image", 1, &QrDetectorNodelet::ImageCb, this);
        ROS_INFO("subscribe image topic from usb_cam");
    }
}

void QrDetectorNodelet::DownstreamDisconnectCb() {
    // 如果没有节点订阅二维码topic,则QrDetectorNodelet也不订阅图像,节省开销
    if (qr_pub_.getNumSubscribers() == 0) {
        img_sub_.shutdown();
        ROS_INFO("unsubscibe image topic");
    }
}

void QrDetectorNodelet::ImageCb(const sensor_msgs::ImageConstPtr& image) {
    cv_bridge::CvImageConstPtr cv_image;

    try {
        // 使用cv_bridge将原始图像转为cv图像
        cv_image = cv_bridge::toCvShare(image, sensor_msgs::image_encodings::BGR8);
    } catch (cv_bridge::Exception& e) {
        ROS_ERROR("cv_bridge exception %s", e.what());
        return;
    }
	// 检测图像,获取信息后发布二维码topic
    QrTags qr_tags = detector_.detect(cv_image->image);
    for (auto& qr_tag : qr_tags) {
        std_msgs::String msg;
        msg.data = qr_tag.qr_msg;
        qr_pub_.publish(msg);
    }
}
}

qr_detector.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>

    <node pkg="nodelet"
          type="nodelet"
          name="qr_detector"
          output="screen"
          // nodelet支持节点合并进程启动,也支持standalone模式
          args="standalone robot_vision/qr_detector_nodelet">
        <remap from="image" to="usb_cam/image_raw"/>
    </node>

    <node
        pkg="rqt_image_view"
        type="rqt_image_view"
        name="rqt_image_view"
        output="screen"
    />    
</launch>

nodelets.xml

<library path="lib/libqr_detector_nodelet">

    <class name="robot_vision/qr_detector_nodelet"
           type="qr_detector::QrDetectorNodelet"
           base_class_type="nodelet::Nodelet">
        <description>
            QR detector nodelet.
        </description>
    </class>
</library>

package.xml添加nodelets.xml的什么,${prefix}是robot_vision包的路径

  <export>
    <nodelet plugin="${prefix}/nodelets.xml"/>
  </export>

CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)
project(robot_vision)
set(CMAKE_CXX_FLAGS "-std=c++11 -Wall ${CMAKE_CXX_FLAGS}")
find_package(catkin REQUIRED COMPONENTS
  cv_bridge
  image_transport
  roscpp
  nodelet
  cv_bridge
  image_transport
  rospy
  sensor_msgs
  std_msgs
  geometry_msgs
  message_generation
)
add_message_files(
  FILES
  BoundingBox.msg
  BoundingBoxes.msg
)
generate_messages(
  DEPENDENCIES
  std_msgs
)
catkin_package(
  INCLUDE_DIRS include
  LIBRARIES qr_detector_nodelet
  CATKIN_DEPENDS roscpp nodelet sensor_msgs cv_bridge image_transport
)
find_library(ZBAR_LIBRARIES NAMES zbar)
include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)
add_library(qr_detector_nodelet src/qr_detector_nodelet.cpp)
target_link_libraries(qr_detector_nodelet zbar ${catkin_LIBRARIES})
catkin_install_python(PROGRAMS
  scripts/cv_bridge_test.py scripts/face_detector.py scripts/motion_detector.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

(3)编译并运行

cd ~/catkin_ws/
catkin_make --source src/robot_vision
source devel/setup.bash
roslaunch robot_vision qr_detector.launch
//再开一个窗口
rostopic echo /qr_codes

扫描支付宝付款码:
在这里插入图片描述
扫描微信个人码:
在这里插入图片描述
扫描条形码(准确度不好)
在这里插入图片描述

3 总结

本文的代码托管在本人的github上:robot_vision
另外本人还研究了ros的ar_track_alvar 包,可以在ros noetic上跑起来,这里不对他进行讲解,代码也在本人的github上:ar_track_alvar

  • 5
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值