OpenCV(opencv_apps)在ROS中的视频图像的应用(重点讲解哈里斯角点的检测)

1、引言

通过opencv_apps,你可以在ROS中以最简单的方式运行OpenCV提供的许多功能,也就是说,运行一个与功能相对应的launch启动文件,就可以跳过为OpenCV的许多功能编写OpenCV应用程序代码,非常的方便。
对于想熟悉每个细节的伙伴们,可以去看源码,对于熟悉视觉操作很有帮助。
官方说明:http://wiki.ros.org/opencv_apps
github源码:https://github.com/ros-perception/opencv_apps

2、启动操作

2.1、opencv_apps.launch 

先来启动摄像头等相关操作,需要启动opencv_apps.launch文件,我们先来了解下: 

gedit /home/jetson/workspace/catkin_ws/src/jetbot_ros/launch/opencv_apps.launch
<launch>
    <arg name="img_flip" default="False"/>
    <arg name="img_transform" default="True"/>
    <group if="$(arg img_transform)">
        <arg name="img_topic" default="/csi_cam_0/image_raw"/>
        <include file="$(find jetson_nano_csi_cam)/launch/jetson_csi_cam.launch"/>
        <node name="img_transform" pkg="jetbot_ros" type="img_transform.py" output="screen">
            <param name="img_flip" type="bool" value="$(arg img_flip)"/>
            <param name="img_topic" type="string" value="$(arg img_topic)"/>
        </node>
    </group>
</launch>

这里<include>节点包含一个jetson_csi_cam.launch启动文件以及一个名为img_transform的节点
其中jetson_csi_cam.launch我们查看下里面的内容:

cat /home/jetson/workspace/catkin_ws/src/jetson_nano_csi_cam_ros/launch/jetson_csi_cam.launch

里面是一些摄像头的参数设置和启动摄像头,通过GSCAM开源包将GStreamer图像流引入到ROS中,转换成sensor_msgs/Image类型的Image话题,发布到ROS中,供其他节点使用。

2.2、img_transform.py

然后就是做这些操作的节点,我们来看下它的源码: 

gedit /home/jetson/workspace/catkin_ws/src/jetbot_ros/scripts/img_transform.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy
import cv2 as cv
from cv_bridge import CvBridge
from sensor_msgs.msg import Image

def topic(msg):
    if not isinstance(msg, Image):
        return
    bridge = CvBridge()
    frame = bridge.imgmsg_to_cv2(msg, "bgr8")
    # Canonical input image size
    frame = cv.resize(frame, (640, 480))
    if img_flip == True: frame = cv.flip(frame, 1)
    # opencv mat ->  ros msg
    msg = bridge.cv2_to_imgmsg(frame, "bgr8")
    pub_img.publish(msg)

if __name__ == '__main__':
    rospy.init_node("pub_img", anonymous=False)
    img_topic = rospy.get_param("~img_topic", "/csi_cam_0/image_raw")
    img_flip = rospy.get_param("~img_flip", False)
    sub_img = rospy.Subscriber(img_topic, Image, topic)
    pub_img = rospy.Publisher("/image", Image, queue_size=10)
    rate = rospy.Rate(2)
    rospy.spin()

这里就是定义一个pub_img节点,订阅CSI摄像头相关的话题,然后通过Image话题进行发布,供其余节点使用。其中获取参数:rospy.get_param("~img_topic", "/csi_cam_0/image_raw"),如果没有在~img_topic获取到,就使用/csi_cam_0/image_raw默认值。同样的rospy.get_param("~img_flip", False)如果没有获取到~img_flip的值,就默认为False

2.3、打开相机

熟悉摄像头的相关操作之后,我们就来做一些准备工作,开启相机:
roslaunch jetbot_ros opencv_apps.launch
查看方式:rqt_image_view,如下图:

或者网页查看:rosrun web_video_server web_video_server
然后使用IP+端口就可以查看了,如下图:

然后点击里面的话题,就可以看到摄像视频了,如下图:

我们来看下开启了哪些话题:rostopic list 

/csi_cam_0/camera_info
/csi_cam_0/image_raw
/csi_cam_0/image_raw/compressed
/csi_cam_0/image_raw/compressed/parameter_descriptions
/csi_cam_0/image_raw/compressed/parameter_updates
/csi_cam_0/image_raw/compressedDepth
/csi_cam_0/image_raw/compressedDepth/parameter_descriptions
/csi_cam_0/image_raw/compressedDepth/parameter_updates
/csi_cam_0/image_raw/theora
/csi_cam_0/image_raw/theora/parameter_descriptions
/csi_cam_0/image_raw/theora/parameter_updates
/image
/rosout
/rosout_agg 

是一些关于csi摄像头相关的话题,接下来我们对里面大量的示例做一个演示 

3、哈里斯角点

因为我们都是在opencv_apps包里面,所以先来这个所在工作区间的这个包的launch目录里面。
查看下有哪些启动文件:ls /home/jetson/workspace/catkin_ws/src/opencv_apps/launch/

 adding_images.launch               hough_circles.launch
camshift.launch                    hough_lines.launch
contour_moments.launch             hsv_color_filter.launch
convex_hull.launch                 lk_flow.launch
corner_harris.launch               people_detect.launch
discrete_fourier_transform.launch  phase_corr.launch
edge_detection.launch              pyramids.launch
face_detection.launch              rgb_color_filter.launch
face_recognition.launch            segment_objects.launch
fback_flow.launch                  simple_flow.launch
find_contours.launch               smoothing.launch
general_contours.launch            threshold.launch
goodfeature_track.launch           watershed_segmentation.launch
hls_color_filter.launch

可以看到对图像操作的功能还是挺多的,有霍夫圆、霍夫直线、轮廓矩、LK光流、哈里斯角点、边缘、人物、面部识别等等

3.1、corner_harris.launch

判断某个点是图像中的角点,这里的对角点的检测,我们花多点时间重点来说明下,后面的节点基本就是展示为主。
roslaunch opencv_apps corner_harris.launch
启动如下,使用一张棋盘格式的图片让它识别:


可以看到图片中的角点,有很多的小圆圈给标注着,这里的角点检测的多少和准确度,取决于上面的threshold阈值大小,可以自行调节,调小了,角点数量就会多,相对而言准确度也在下降,调大阈值,角点数量就相应减少,准确度要高。 

我们来看下这个启动文件里面的内容:

cat  /home/jetson/workspace/catkin_ws/src/opencv_apps/launch/corner_harris.launch
<launch>
  <arg name="node_name" default="corner_harris" />

  <arg name="image" default="image" doc="The image topic. Should be remapped to the name of the real image topic." />

  <arg name="use_camera_info" default="false" doc="Indicates that the camera_info topic should be subscribed to to get the default input_frame_id. Otherwise the frame from the image message will be used." />
  <arg name="debug_view" default="true" doc="Specify whether the node displays a window to show image" />
  <arg name="queue_size" default="3" doc="Specigy queue_size of input image subscribers" />

  <arg name="threshold" default="200" doc="Threshold value of a circle around corners's norm" />

  <!-- corner_harris.cpp  -->
  <node name="$(arg node_name)" pkg="opencv_apps" type="corner_harris" >
    <remap from="image" to="$(arg image)" />
    <param name="use_camera_info" value="$(arg use_camera_info)" />
    <param name="debug_view" value="$(arg debug_view)" />
    <param name="queue_size" value="$(arg queue_size)" />
  </node>
</launch>

3.2、corner_harris.cpp

前面是一些参数设置,后面是一个node节点部分,其中type="corner_harris"可以知道使用的是C++写的,因为如果是Python写的,就是type="corner_harris.py"这样的形式,当然那里也注释说明了是corner_harris.cpp
其中C++代码文件的地址:ls /home/jetson/workspace/catkin_ws/build/opencv_apps
我们来看下这个哈里斯角点的实际代码:
gedit /home/jetson/workspace/catkin_ws/build/opencv_apps/corner_harris.cpp 

/*********************************************************************
* Software License Agreement (BSD License)
*
*  Copyright (c) 2016, Kentaro Wada.
*  All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*   * Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above
*     copyright notice, this list of conditions and the following
*     disclaimer in the documentation and/or other materials provided
*     with the distribution.
*   * Neither the name of the Kentaro Wada nor the names of its
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
*  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
*  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
*  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
*  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
*  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*  POSSIBILITY OF SUCH DAMAGE.
*********************************************************************/

#include <ros/ros.h>
#include <nodelet/loader.h>

int main(int argc, char **argv)
{
    ros::init(argc, argv, "corner_harris", ros::init_options::AnonymousName);
    if (ros::names::remap("image") == "image") {
        ROS_WARN("Topic 'image' has not been remapped! Typical command-line usage:\n"
                 "\t$ rosrun image_rotate image_rotate image:=<image topic> [transport]");
    }

    // need to use 1 worker thread to prevent freezing in imshow, calling imshow from mutiple threads
    //nodelet::Loader manager(false);
    ros::param::set("~num_worker_threads", 1); // need to call   Loader(bool provide_ros_api = true);
    nodelet::Loader manager(true);
    nodelet::M_string remappings;
    nodelet::V_string my_argv(argv + 1, argv + argc);
    my_argv.push_back("--shutdown-on-close"); // Internal

    manager.load(ros::this_node::getName(), "opencv_apps/corner_harris", remappings, my_argv);

    ros::spin();
    return 0;
}

这段代码就是初始化corner_harris节点,image话题的重映射等,然后用圆圈来标注角点,这个操作会用到接下来要讲的一个插件。

3.3、corner_harris_nodelet.cpp

上面的核心代码,会用到Nodelet,关于这个Nodelet插件的解释:
允许用户在ROS节点中添加自定义功能,Nodelet使得开发人员能够将复杂的代码封装到可重用的插件中,这些插件可以像其他ROS节点一样进行部署和通信,开发人员可以编写更加模块化和可维护的代码,提高ROS系统的可扩展性和可重用性。
更关键的是效率问题,Nodelet提供一种方法,可以让多个算法程序在一个进程中,使用共享指针shared_ptr来实现零拷贝通信,以降低因为拷贝传输大数据(比如图像流,点云等)而延迟的问题,换句话说就是将多个node捆绑在一起管理,使得同一个manager里面的话题的数据传输更快,因为不会在进程内传递消息时进行复制而产生的效率下降。 

查看插件文件:ls /home/jetson/workspace/catkin_ws/src/opencv_apps/src/nodelet
我们打开哈里斯角点的插件代码来看下:

gedit /home/jetson/workspace/catkin_ws/src/opencv_apps/src/nodelet/corner_harris_nodelet.cpp

 

// -*- coding:utf-8-unix; mode: c++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
/*********************************************************************
* Software License Agreement (BSD License)
*
*  Copyright (c) 2016, JSK Lab.
*  All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*   * Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above
*     copyright notice, this list of conditions and the following
*     disclaimer in the documentation and/or other materials provided
*     with the distribution.
*   * Neither the name of the Kei Okada nor the names of its
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
*  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
*  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
*  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
*  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
*  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*  POSSIBILITY OF SUCH DAMAGE.
*********************************************************************/
#include <ros/ros.h>
#include "opencv_apps/nodelet.h"
#include <image_transport/image_transport.h>
#include <sensor_msgs/image_encodings.h>
#include <cv_bridge/cv_bridge.h>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <dynamic_reconfigure/server.h>
#include "opencv_apps/CornerHarrisConfig.h"

// https://github.com/opencv/opencv/blob/master/samples/cpp/tutorial_code/TrackingMotion/cornerHarris_Demo.cpp
/**
 * @function cornerHarris_Demo.cpp
 * @brief Demo code for detecting corners using Harris-Stephens method
 * @author OpenCV team
 */

namespace opencv_apps
{
class CornerHarrisNodelet : public opencv_apps::Nodelet
{
  image_transport::Publisher img_pub_;
  image_transport::Subscriber img_sub_;
  image_transport::CameraSubscriber cam_sub_;
  ros::Publisher msg_pub_;

  boost::shared_ptr<image_transport::ImageTransport> it_;

  typedef opencv_apps::CornerHarrisConfig Config;
  typedef dynamic_reconfigure::Server<Config> ReconfigureServer;
  Config config_;
  boost::shared_ptr<ReconfigureServer> reconfigure_server_;

  int queue_size_;
  bool debug_view_;

  std::string window_name_;
  static bool need_config_update_;

  int threshold_;

  void reconfigureCallback(Config& new_config, uint32_t level)
  {
    config_ = new_config;
    threshold_ = config_.threshold;
  }

  const std::string& frameWithDefault(const std::string& frame, const std::string& image_frame)
  {
    if (frame.empty())
      return image_frame;
    return frame;
  }

  void imageCallbackWithInfo(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& cam_info)
  {
    doWork(msg, cam_info->header.frame_id);
  }

  void imageCallback(const sensor_msgs::ImageConstPtr& msg)
  {
    doWork(msg, msg->header.frame_id);
  }

  static void trackbarCallback(int /*unused*/, void* /*unused*/)
  {
    need_config_update_ = true;
  }

  void doWork(const sensor_msgs::ImageConstPtr& image_msg, const std::string& input_frame_from_msg)
  {
    // Work on the image.
    try
    {
      // Convert the image into something opencv can handle.
      cv::Mat frame = cv_bridge::toCvShare(image_msg, sensor_msgs::image_encodings::BGR8)->image;

      // Do the work
      cv::Mat dst, dst_norm, dst_norm_scaled;
      dst = cv::Mat::zeros(frame.size(), CV_32FC1);

      cv::Mat src_gray;

      if (frame.channels() > 1)
      {
        cv::cvtColor(frame, src_gray, cv::COLOR_BGR2GRAY);
      }
      else
      {
        src_gray = frame;
        cv::cvtColor(src_gray, frame, cv::COLOR_GRAY2BGR);
      }

      /// Detector parameters
      int block_size = 2;
      int aperture_size = 3;
      double k = 0.04;

      /// Detecting corners
      cv::cornerHarris(src_gray, dst, block_size, aperture_size, k, cv::BORDER_DEFAULT);

      /// Normalizing
      cv::normalize(dst, dst_norm, 0, 255, cv::NORM_MINMAX, CV_32FC1, cv::Mat());
      cv::convertScaleAbs(dst_norm, dst_norm_scaled);

      /// Drawing a circle around corners
      for (int j = 0; j < dst_norm.rows; j++)
      {
        for (int i = 0; i < dst_norm.cols; i++)
        {
          if ((int)dst_norm.at<float>(j, i) > threshold_)
          {
            cv::circle(frame, cv::Point(i, j), 5, cv::Scalar(0), 2, 8, 0);
          }
        }
      }

      /// Create window
      if (debug_view_)
      {
        cv::namedWindow(window_name_, cv::WINDOW_AUTOSIZE);
        const int max_threshold = 255;
        if (need_config_update_)
        {
          config_.threshold = threshold_;
          reconfigure_server_->updateConfig(config_);
          need_config_update_ = false;
        }
        cv::createTrackbar("Threshold:", window_name_, &threshold_, max_threshold, trackbarCallback);
      }

      if (debug_view_)
      {
        cv::imshow(window_name_, frame);
        int c = cv::waitKey(1);
      }

      // Publish the image.
      sensor_msgs::Image::Ptr out_img =
          cv_bridge::CvImage(image_msg->header, sensor_msgs::image_encodings::BGR8, frame).toImageMsg();
      img_pub_.publish(out_img);
    }
    catch (cv::Exception& e)
    {
      NODELET_ERROR("Image processing error: %s %s %s %i", e.err.c_str(), e.func.c_str(), e.file.c_str(), e.line);
    }
  }

  void subscribe()  // NOLINT(modernize-use-override)
  {
    NODELET_DEBUG("Subscribing to image topic.");
    if (config_.use_camera_info)
      cam_sub_ = it_->subscribeCamera("image", queue_size_, &CornerHarrisNodelet::imageCallbackWithInfo, this);
    else
      img_sub_ = it_->subscribe("image", queue_size_, &CornerHarrisNodelet::imageCallback, this);
  }

  void unsubscribe()  // NOLINT(modernize-use-override)
  {
    NODELET_DEBUG("Unsubscribing from image topic.");
    img_sub_.shutdown();
    cam_sub_.shutdown();
  }

public:
  virtual void onInit()  // NOLINT(modernize-use-override)
  {
    Nodelet::onInit();
    it_ = boost::shared_ptr<image_transport::ImageTransport>(new image_transport::ImageTransport(*nh_));

    pnh_->param("queue_size", queue_size_, 3);
    pnh_->param("debug_view", debug_view_, false);

    if (debug_view_)
    {
      always_subscribe_ = true;
    }

    window_name_ = "CornerHarris Demo";

    reconfigure_server_ = boost::make_shared<dynamic_reconfigure::Server<Config> >(*pnh_);
    dynamic_reconfigure::Server<Config>::CallbackType f =
        boost::bind(&CornerHarrisNodelet::reconfigureCallback, this, _1, _2);
    reconfigure_server_->setCallback(f);

    img_pub_ = advertiseImage(*pnh_, "image", 1);

    onInitPostProcess();
  }
};
bool CornerHarrisNodelet::need_config_update_ = false;
}  // namespace opencv_apps

namespace corner_harris
{
class CornerHarrisNodelet : public opencv_apps::CornerHarrisNodelet
{
public:
  virtual void onInit()  // NOLINT(modernize-use-override)
  {
    ROS_WARN("DeprecationWarning: Nodelet corner_harris/corner_harris is deprecated, "
             "and renamed to opencv_apps/corner_harris.");
    opencv_apps::CornerHarrisNodelet::onInit();
  }
};
}  // namespace corner_harris

#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(opencv_apps::CornerHarrisNodelet, nodelet::Nodelet);
PLUGINLIB_EXPORT_CLASS(corner_harris::CornerHarrisNodelet, nodelet::Nodelet);

主要关注其中检测角点的方法:

cv::cornerHarris(src_gray, dst, block_size, aperture_size, k, cv::BORDER_DEFAULT);

参数说明如下:

src_gray:输入的灰度Mat矩阵或浮点图像
dst:存储着哈里斯角点检测的结果,跟源图的尺寸和类型一样
block_size:邻域的大小
aperture_size:Sobel边缘检测滤波器大小
k:Harris中间参数,经验值0.04~0.06
cv::BORDER_DEFAULT:插值类型

我们也可以看到在发布和订阅时,用到的就是指针
发布时:

sensor_msgs::Image::Ptr out_img =cv_bridge::CvImage(image_msg->header, sensor_msgs::image_encodings::BGR8, frame).toImageMsg();
img_pub_.publish(out_img);

订阅时:

Nodelet::onInit();
it_ = boost::shared_ptr<image_transport::ImageTransport>(new image_transport::ImageTransport(*nh_));

if (config_.use_camera_info)
      cam_sub_ = it_->subscribeCamera("image", queue_size_, &CornerHarrisNodelet::imageCallbackWithInfo, this);
    else
      img_sub_ = it_->subscribe("image", queue_size_, &CornerHarrisNodelet::imageCallback, this);

这样我们在消息传递时,就只需要传指针了,当然这里是针对同一台设备的进程间通信,如果是不同设备那还是需要解引用传输实际内容,因为我们知道ROS的分布节点进行通信的协议是XML-RPC,本质也是HTTP协议,只不过编码格式是XML类型,它们之间的传输还得是拷贝内容进行通信。

查看节点关系:rqt_graph,我这里将调试节点隐藏,显得更清晰点,如下图:

可以看到CSI摄像头图像获取需要经过转换之后最终通过/Image话题发布给哈里斯角点算法处理。 

识别角点的原理,简单来说就是在特征窗口里面,如果灰度发生了较大的变化,就认为这里是一个角点,有兴趣的可以查阅:harris.cpp

4、霍夫直线检测

接下来的部分,就没有上述那么去分析了,只是熟悉下常用的几个功能。
霍夫直线的检测在计算机视觉和图像处理中用途广泛,可以用于边缘检测、直线检测等。
实际场景中,可以通过从图像中检测出边缘,然后通过识别直线或曲线,将这些边缘连接起来,形成完整的物体。
另外无人驾驶的发展,对于自动化检测道路、车道线等应用也有着广泛的应用。
启动launch文件:roslaunch opencv_apps hough_lines.launch
很好的检测到了我身上的衣服以及上面的“中国”文字,如下图:

5、图像轮廓矩

contour_moments.launch是启动识别图像中轮廓的矩函数,这里的轮廓矩也可以理解成轮廓的特征,它也有着很广泛的应用:
目标识别:提取图像中物体的轮廓特征,可以对目标进行识别和分类。
目标检测:通过检测物体的形状和轮廓,来确定目标的位置。
图像分割:因为可以对不同的区域进行目标的识别,所以也可以帮助其进行图像的分割。
医学领域:可以用来识别图像中的组织器官和患病部位,从而提取特征,进行医学的诊断。
启动:roslaunch opencv_apps contour_moments.launch,如下图:

6、LK光流 

LK光流是描述目标运动的方法,利用LK光流可以实现对目标的追踪,从而知道目标的位姿。
LK光流法的前提条件如下:
亮度恒定:一个像素点随着时间的变化,其亮度值(像素灰度值)是不能变化的。
小运动:时间的变化不会引起位置的剧烈变化。这样才能利用相邻帧之间的位置变化引起的灰度值变化,去求取灰度对位置的偏导数。
空间一致:前一帧的相邻像素点在后一帧也是相邻的,因为为了求解x,y方向的速度,需要建立方程组,而空间一致假设就可以利用邻域n个像素点来建立n个方程。
启动:roslaunch opencv_apps lk_flow.launch,如下图:

7、相机相位位移

检测相机移动的快慢,或者里面目标的运动快慢
启动:roslaunch opencv_apps phase_corr.launch,如下图:

移动越快,圆就越大

大概的介绍就先到这儿吧,另外一些关于OpenCV的文章,有兴趣的可以查阅:
OpenCV自带的HAAR级联分类器对脸部(人脸、猫脸等)的检测识别
OpenCV的HSV颜色空间在无人车中颜色识别的应用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/124599.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# Onnx DirectMHP 全范围角度2D多人头部姿势估计

效果 项目 代码 using Microsoft.ML.OnnxRuntime.Tensors; using Microsoft.ML.OnnxRuntime; using OpenCvSharp; using System; using System.Collections.Generic; using System.Windows.Forms; using System.Linq; using System.Numerics;namespace Onnx_Demo {public part…

MySQL进阶_1.逻辑架构和SQL执行流程

文章目录 第一节、逻辑架构剖析1.1、服务器处理客户端请求1.2、Connectors1.3、第1层&#xff1a;连接层1.4、第2层&#xff1a;服务层1.5、 第3层&#xff1a;引擎层1.6、 存储层1.7、小结 第二节、SQL执行流程2.1、查询缓存2.2、解析器2.3、优化器2.4、执行器 第三节、数据库…

redisTemplate不支持zpopmax,解决方案使用reverseRangeWithScore

在redis客户端可以使用zpopmax redisTemplate不支持zpopmax 解决方案 使用reverseRangeWithScore 接下来我们进行测试 我们要返回最大的value&#xff0c;应该是c import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.a…

编译过程 学习 CMake 文档的前置知识

OHHHH&#xff0c;发现自己的基础知识真他妈的是呼呼漏风&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c; 尴尬得意识到&#xff0c;不仅是英语水平有问题&#xff0c;他码的基础知识…

Linux学习第37天:Linux I2C 驱动实验:哥俩好

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 世界上的很多事物都是成双成对出现的。也包括在驱动开发的过程中&#xff0c;比如I2C中其实就是数据线和时钟线的相互配合才能完成的。 I2C常用于连接各种外设、…

debian/ubuntu/windows配置wiregurad内网服务器(包含掉线自启动)

文章目录 前言一、服务器配置安装wireguard软件生成私钥公钥配置服务器参数配置服务器sysctl参数启动、停止服务端 二、用户端配置安装wireguard软件生成私钥公钥配置客户端参数启动、停止客户端配置服务开机启动 三、服务器添加、删除客户四、配置掉线自启动配置掉线自启动脚本…

【不正经操作】百度深度学习框架paddlepaddle本地运行-Python环境配置笔记

百度深度学习框架PaddlePaddle 百度深度学习框架PaddlePaddle是一个支持深度学习和机器学习的开源框架。它由百度公司于2016年开发并发布&#xff0c;现在已经成为中国最受欢迎的深度学习框架之一&#xff0c;并且在国际上也获得了不少关注。 特点与功能 易于使用 PaddlePa…

二分查找(二分法)

核心代码&#xff08;循环&#xff09;; int f -1;while(left<right){mid(leftright)/2;if(a[mid]key){fmid;break;}if(key<a[mid]) rmid-1;if(key>a[mid]) lmid1;}if(f-1) cout<<"没找到";else cout <<f<<endl;核心代码(递归): int bins…

Unity之NetCode多人网络游戏联机对战教程(6)--NetworkTransform组件

文章目录 前言NetworkTransform是什么玩家移动脚本NetworkTransform字段讲解Synchronizing ("Syncing")ThresholdsLocal spaceInterpolationSlerp PositionUse Quaternion SynchronizationUse Quaternion CompressionUse Half Float PrecisionAuthority modesServer …

0基础学习VR全景平台篇第118篇:利用动作录制器功能避免重复操作 - PS教程

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 嗨&#xff0c;大家好。欢迎收看蛙色VR系列教程之PS利用动作记录器节约补地时间。 大家拍摄在补地的时候&#xff0c;利用插件选择输入输出选项的时候&#xff0c;每次重复操作…

Transformer的最简洁pytorch实现

目录 前言 1. 数据预处理 2. 模型参数 3. Positional Encoding 4. Pad Mask 5. Subsequence Mask 6. ScaledDotProductAttention 7. MultiHeadAttention 8. FeedForward Networks 9. Encoder Layer 10. Encoder 11. Decoder Layer 12. Decoder 13. Transformer 1…

什么是 HwameiStor?

HwameiStor 是一款 Kubernetes 原生的容器附加存储 (CAS) 解决方案&#xff0c;将 HDD、SSD 和 NVMe 磁盘形成本地存储资源池进行统一管理&#xff0c; 使用 CSI 架构提供分布式的本地数据卷服务&#xff0c;为有状态的云原生应用或组件提供数据持久化能力。 具体的功能特性如下…

GoLong的学习之路(二十)进阶,语法之反射(reflect包)

这个是为了接上之前的语法篇的。按照我的学习计划&#xff0c;这里此时应该有一个小项目来做一个统合。但是吧&#xff0c;突然觉得&#xff0c;似乎也没必要。能学go的大部分肯定都是有其他语言的基础的。 接下来说反射 文章目录 反射介绍reflect包TypeOftype name和type kin…

基于springboot的二次元商品销售网站的设计与开发

大家好我是玥沐春风&#xff0c;今天分享一个基于springboot的二次元商品销售网站的设计与开发&#xff0c;项目源码以及部署相关请联系我&#xff0c;文末附上联系信息 。 开发工具及技术 2.3.1 Spring Boot框架 SpringBoot是一个全新的开源的轻量级框架。简化了Spring应用的…

一张数学地图带你尽览数学分支

我们在学校学习的数学可能也只是数学领域的冰山一角&#xff0c;作为庞大而多样的学科&#xff0c;我今天将通过一张数学地图带你尽览数学分支。 本数学地图对应的视频讲解地址如下&#xff1a; https://www.youtube.com/watch?vOmJ-4B-mS-Y 另外&#xff0c;由于图片较大&a…

React进阶之路(一)-- JSX基础、组件基础

文章目录 React介绍React开发环境搭建项目目录说明以及相关调整 JSX基础JSX介绍JSX中使用js表达式JSX列表渲染JSX条件渲染JSX样式处理JSX注意事项 组件基础组件的概念函数组件类组件事件绑定如何绑定事件获取事件对象传递额外参数 组件状态状态不可变表单处理受控表单组件非受控…

Triples of Cows

题目传送门 引 模拟赛 T 4 T4 T4 , 变换挺妙的, 而且感觉转换后问题就迎刃而解了 解法 强行模拟拆点重连边显然不行,会让图的边数达到 n 2 n^2 n2 级别的 —————————————————————————————————————————————————— 考虑转…

python爬虫怎么翻页 ?

首先&#xff0c;你需要安装相关的库。在你的命令行窗口中&#xff0c;输入以下命令来安装所需的库&#xff1a; pip install requests beautifulsoup4然后&#xff0c;你可以使用以下代码来爬取网页内容并翻页&#xff1a; package mainimport ("fmt""net/htt…

米软科技 | 推进医院智慧管理分级评估体系建立、提升评级

国家卫生健康委办公厅于2021年3月15日发布了“关于印发医院智慧管理分级评估标准体系&#xff08;试行&#xff09;的通知”&#xff08;国卫办医函〔2021〕86 号&#xff09;&#xff0c;该评估体系用于指导医疗机构科学、规范开展智慧医院建设&#xff0c;提升医院管理精细化…

[黑马程序员Pandas教程]——Pandas缺失值处理

目录&#xff1a; 学习目标空值和缺失值查看缺失值 加载数据并通过info函数初步查看缺失值情况df.isnull().sum()空值数量统计Missingno库对缺失值的情况进行可视化探查 安装missingno库missingno.bar(df)缺失值数量可视化missingno.matrix(df)缺失值位置的可视化missingno.he…