当前位置: 首页 > news >正文

SLAM从入门到精通(构建自己的slam包)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        我们学习了很多的开源包,比如hector、gmapping。但其实我们也可以自己编写一个slam包。这么做最大的好处,主要还是可以帮助自己更好地去了解slam、掌握slam以及用好slam。就像学习rtos一样,使用好别人提供的api是一回事,自己会写rtos又是另外一回事。一旦我们自己会写rtos之后,那么其他所有的实时操作系统都是很容易掌握的。slam也是一样。

        前面我们也知道,怎么构建一个slam包了?一般来说,它就是画图-》定位-》画图循环迭代的过程。今天可以做的简单一点。直接从cmd_vel-》laser-》画图,虽然内容简单了一点,但是效果出来的时候,还是很有成就感的。另外本文代码参考了现有的ros书籍,再次表示感谢。

1、编写slam_tfpub文件

        代码的主要功能就是接收到cmd_vel消息之后,将自己的tf信息发送出去。头文件slam_tfpub.h如下所示,

#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <tf/transform_broadcaster.h>
#include <tf/tf.h>#define pi 3.1415926class TfMove
{public:TfMove(ros::NodeHandle& nh, ros::Rate& r);void VelCallback(const geometry_msgs::TwistPtr& vel);void init_sub();private:ros::NodeHandle& nh_;ros::Subscriber sub_;tf::TransformBroadcaster tfbrd_;ros::Rate rate;double x,y,z,roll,pit,yaw;
};

        而源文件slam_tfpub.cpp如下所示,

#include "slam_tfpub.h"TfMove::TfMove(ros::NodeHandle& nh, ros::Rate& r):nh_(nh), rate(r)
{x = 0;y = 0;z = 0;roll = 0;pit = 0;yaw = 0;init_sub();
}void TfMove::VelCallback(const geometry_msgs::TwistPtr& vel)
{x += vel->linear.x;y += vel->linear.y;z += vel->linear.z;roll += vel->angular.x/pi * 180;pit += vel->angular.y/pi * 180;yaw += vel->angular.z/pi * 180;tf::Transform trans;trans.setOrigin(tf::Vector3(x,y,z));tf::Quaternion q;q.setRPY(roll, pit, yaw);trans.setRotation(q);tfbrd_.sendTransform(tf::StampedTransform(trans, ros::Time::now(), "map", "base_link"));rate.sleep();
}void TfMove::init_sub()
{sub_ = nh_.subscribe("cmd_vel", 1, &TfMove::VelCallback, this);ros::spin();
}int main(int argc, char* argv[])
{ros::init(argc, argv, "myslam_tfpub");ros::NodeHandle nh;ros::Rate rate(10);TfMove tfmove(nh, rate);return 0;}

2、编写slam_laser文件

        slam_laser主要是模拟lidar的传感器数据。它的头文件slam_laser.h是这样的,

#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>class LaserScanPub
{public:LaserScanPub(ros::NodeHandle& nh, double minAngle, double maxAngle, double scanTime,double minRange, double maxRange, double scanNums);~LaserScanPub();void scanpub_init();void laserdata_init();private:ros::NodeHandle nh_;ros::Publisher scanpub_;sensor_msgs::LaserScan laserdata_;double minAngle;double maxAngle;double minRange;double maxRange;double scanTime;double scanNums;
};

        而源文件slam_laser.cpp是这样的,

#include "slam_laser.h"LaserScanPub::LaserScanPub(ros::NodeHandle& nh, double min_angle, double maxAngle,double scanTime, double minRange, double maxRange, double scanNums):nh_(nh),minAngle(minAngle), maxAngle(maxAngle),minRange(minRange),maxRange(maxRange), scanNums(scanNums), scanTime(scanTime)
{scanpub_init();
}LaserScanPub::~LaserScanPub()
{
}void LaserScanPub::laserdata_init()
{ros::Time scantime = ros::Time::now();laserdata_.header.stamp = scantime;laserdata_.header.frame_id = "base_link";laserdata_.range_min = minRange;laserdata_.range_max = maxRange;laserdata_.scan_time = scanTime;laserdata_.angle_increment = (maxAngle - minAngle)/scanNums; // angle resolutionlaserdata_.time_increment = scanTime/scanNums; // time resolutionlaserdata_.ranges.resize(scanNums);laserdata_.intensities.resize(scanNums);for(int i = 0; i < scanNums; i++){laserdata_.ranges[i] = 5;laserdata_.intensities[i] = 100;}
}void LaserScanPub::scanpub_init()
{scanpub_ = nh_.advertise<sensor_msgs::LaserScan>("scan", 100);ros::Rate rate(10);while(nh_.ok()){laserdata_init();scanpub_.publish(laserdata_);rate.sleep();}
}int main(int argc, char* argv[])
{ros::init(argc, argv, "myslam_laser");ros::NodeHandle nh;LaserScanPub scanpub(nh, 0, 1.57, 0.01, 0, 10, 100);return 0;
}

3、编写book_myslam文件

        前面准备好了tf和laser,接下来就是最终要的制图工作的。它的基本原理就是在laser触发回调的时候,利用tf信息,计算出lidar坐标在地图上的实际位置。中间绘制的方法使用到了bresenham算法,这个前面也提及过。book_myslam.h头文件是这样的,

#include <ros/ros.h>
#include <nav_msgs/OccupancyGrid.h>
#include <nav_msgs/MapMetaData.h>
#include <sensor_msgs/LaserScan.h>
#include <tf/transform_listener.h>#include <tf/tf.h>
#include <vector>
#include <fstream>
#include <math.h>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>using namespace std;struct MapPoint
{int x;int y;MapPoint(){x = 0;y = 0;}MapPoint(int x0, int y0){x = x0;y = y0;}
};class MySlam
{public:MySlam(ros::NodeHandle& nh, double mapreso, double mposx, double mposy,double mposz, double morientx, double morienty, double orientz,double morientw, int mwidth, int mheight);~MySlam();void mappub_init();void lasersub_init();void lasercallback(const sensor_msgs::LaserScanConstPtr& laserdata);void mapdata_init();vector<MapPoint> bresenham(int x0, int y0, int x1, int y1);private:ros::NodeHandle nh_;ros::Subscriber lasersub_;ros::Publisher mappub_;tf::TransformListener tflistener_;nav_msgs::OccupancyGrid mapdata_;double mapreso;double mposx;double mposy;double mposz;double morientx;double morienty;double morientz;double morientw;int mwidth;int mheight;vector<MapPoint> endpoints;MapPoint endpoint;vector<MapPoint> mappoints;tf::StampedTransform base2map;tf::Quaternion quat;double theta;tf::Vector3 trans_base2map;double tx, ty;int basex0, basey0;double basex, basey;double mapx, mapy;double beamsAngle;int mapxn, mapyn;int laserNum;int nx,ny;int idx;ofstream fopen;int scan_count;int scan_reso;boost::mutex map_mutex;	
};

        它的实现文件book_myslam.cpp是这样的,

#include "book_myslam.h"MySlam::MySlam(ros::NodeHandle& nh, double mapreso, double mposx, double mposy,double mposz, double morientx, double morienty, double morientz,double morientw, int mwidth, int mheight):nh_(nh), mapreso(mapreso),mposx(mposx), mposy(mposy), mposz(mposz), morientx(morientx),morienty(morienty), morientz(morientz), morientw(morientw),mwidth(mwidth), mheight(mheight)
{mapdata_init();mappub_init();lasersub_init();
}MySlam::~MySlam()
{
}void MySlam::lasercallback(const sensor_msgs::LaserScanConstPtr& laserdata)
{if(scan_count % scan_reso == 0){try {tflistener_.waitForTransform("map", "base_link", ros::Time(0), ros::Duration(3.0));tflistener_.lookupTransform("map", "base_link", ros::Time(0), base2map);}catch(tf::TransformException& ex){ROS_INFO("%s", ex.what());ros::Duration(1.0).sleep();}boost::mutex::scoped_lock map_lock(map_mutex);quat = base2map.getRotation();theta = quat.getAngle();trans_base2map = base2map.getOrigin();tx = trans_base2map.getX();ty = trans_base2map.getY();basex0 = int(tx/mapreso);basey0 = int(ty/mapreso);laserNum = laserdata->ranges.size();fopen.open("data.txt", ios::app);if(fopen.is_open()){cout << "open file successful!" << endl;}else{cout << "open file fail" << endl;}for(int i = 0; i < laserNum; i++){beamsAngle = laserdata->angle_min + i * laserdata->angle_increment;basex = laserdata->ranges[i] * cos(beamsAngle);basey = laserdata->ranges[i] * sin(beamsAngle);mapx = basex * cos(theta) + basey * sin(theta) + tx;mapy = basey * cos(theta) - basex * sin(theta) + ty;nx = int(mapx/mapreso);ny = int(mapy/mapreso);mapxn = nx + 1;mapyn = ny + 1;endpoint.x = mapxn;endpoint.y = mapyn;fopen << endpoint.x << " " << endpoint.y << std::endl;endpoints.push_back(endpoint);}fopen.close();for(vector<MapPoint>::iterator iter = endpoints.begin(); iter != endpoints.end(); iter++){mappoints = MySlam::bresenham(basex0, basey0, (*iter).x, (*iter).y);cout << "scan numbers: " << endpoints.size() << endl;cout << "bresenham point nums are: " << mappoints.size() << endl;cout << "x0, y0 is " << basex0 << " " << basey0 << std::endl;cout << "angle is " << theta << std::endl;for(vector<MapPoint>::iterator iter1 = mappoints.begin(); iter1 != mappoints.end(); iter1 ++){idx = mwidth * (*iter1).y + (*iter1).x;cout << "idx is " << (*iter1).x << " " << (*iter1).y << std::endl;mapdata_.data[idx] = 0;}mappoints.clear();}endpoints.clear();mappub_.publish(mapdata_);}scan_count ++;
}vector<MapPoint> MySlam::bresenham(int x0, int y0, int x1, int y1)
{vector<MapPoint> pp;MapPoint p;int dx, dy, h, a, b, x, y, flag, t;dx = abs(x1-x0);dy = abs(y1-y0);if(x1 > x0) a = 1; else a = -1;if(y1 > y0) b = 1; else b = -1;x = x0;y = y0;if(dx >= dy){flag = 0;}else{t = dx;dx = dy;dy = t;flag = 1;}h = 2 * dy - dx;for(int i = 1; i <= dx; ++i){p.x = x, p.y = y;pp.push_back(p);if(h >= 0){if(flag == 0) y = y+b;else x = x+a;h =h - 2*dx;}if(flag ==0) x = x+a;else y = y+b;h = h + 2*dy;}return pp;
}void MySlam::mappub_init()
{mappub_ = nh_.advertise<nav_msgs::OccupancyGrid>("map", 100);
}void MySlam::lasersub_init()
{lasersub_ = nh_.subscribe("scan", 1, &MySlam::lasercallback, this);
}void MySlam::mapdata_init()
{scan_count = 0;scan_reso = 1;ros::Time currtime = ros::Time::now();mapdata_.header.stamp = currtime;mapdata_.header.frame_id = "map";mapdata_.info.resolution = mapreso;mapdata_.info.width = mwidth;mapdata_.info.height = mheight;mapdata_.info.origin.position.x = mposx;mapdata_.info.origin.position.y = mposy;mapdata_.info.origin.position.z = mposz;mapdata_.info.origin.orientation.x = morientx;mapdata_.info.origin.orientation.y = morienty;mapdata_.info.origin.orientation.z = morientz;mapdata_.info.origin.orientation.w = morientw;int datasize = mwidth * mheight;mapdata_.data.resize(datasize);for(int i = 0;  i < datasize; i++){mapdata_.data[i] = -1;}
}int main(int argc, char* argv[])
{int debug_flag = 0;//while(debug_flag == 0) sleep(10);ros::init(argc, argv, "MySlam");ros::NodeHandle nh;double mapreso = 0.05;double mposx = 0;double mposy = 0;double mposz = 0;double morientx = 0;double morienty = 0;double morientz= 0;double morientw= 1;int mwidth = 300;int mheight = 300;MySlam myslam(nh, mapreso, mposx, mposy, mposz, morientx, morienty, morientz, morientw, mwidth, mheight);ros::spin();return 0;
}

4、准备编译脚本

        上面3个文件其实就是3个程序,所以我们在CMakeLists.txt里面做好三个程序的编译脚本就可以了。需要调试的话,可以添加上-Wall -g选项。

add_executable(slam_tfpub src/slam_tfpub.cpp)
target_link_libraries(slam_tfpub ${catkin_LIBRARIES})
add_dependencies(slam_tfpub beginner_tutorials_generate_messages_cpp)add_executable(slam_laser src/slam_laser.cpp)
target_link_libraries(slam_laser ${catkin_LIBRARIES})
add_dependencies(slam_laser beginner_tutorials_generate_messages_cpp)add_definitions("-Wall -g")add_executable(book_myslam src/book_myslam.cpp)
target_link_libraries(book_myslam ${catkin_LIBRARIES})
add_dependencies(book_myslam beginner_tutorials_generate_messages_cpp)

5、编译

        编译就很简单了,直接输入catkin_make即可。

6、构建launch文件

        因为启动的程序比较多,这里可以编写一个myslam.launch文件,使用起来方便一点。脚本文件注意放在launch目录下面。

<launch><node pkg="beginner_tutorials" type="slam_tfpub" name="tf_pub"/><node pkg="beginner_tutorials" type="slam_laser" name="laser_pub"/><node pkg="beginner_tutorials" type="book_myslam" name="myslam"/>
</launch>

7、实验步骤

        实验步骤稍微复杂一点,主要分成四步。第一,打开roscore;第二,用rostopic发送cmd_vel信息,

rostopic pub -r 10 /cmd_vel geometry_msgs/Twist '[0.003, 0.0, 0.0]' '[0.0, 0.0, 0.0]'

        第三,启动myslam.launch文件,

roslaunch beginner_tutorials myslam.launch

        第四,就是输入rosrun rviz rviz命令,创建map,选中map之后进一步查看建图效果。这四个步骤需要严格按顺序执行,不然缺少了某个步骤,很有可能程序会发生闪退,主要是book_myslam这个程序。这样,不出意外的话,我们就可以在rviz上面看到这样的建图效果了,

相关文章:

SLAM从入门到精通(构建自己的slam包)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 我们学习了很多的开源包&#xff0c;比如hector、gmapping。但其实我们也可以自己编写一个slam包。这么做最大的好处&#xff0c;主要还是可以帮助…...

全球二氧化碳排放数据1deg产品(ODIAC)数据

简介 全球二氧化碳排放数据1deg产品(ODIAC)是一个空间分辨率为1deg*1deg的全球化石燃料燃烧产生的二氧化碳空间分布产品。它率先将基于空间的夜间灯光数据与单个发电厂的排放/位置相结合来估计化石燃料二氧化碳的排放。该产品被国际研究界广泛用于各种研究应用&#xff08;例如…...

Element-UI 日期选择器--禁用未来日期

在做项目的时候经常会遇到一些报表需要填写日期&#xff0c;一般是填写当日及当日以前&#xff0c;这时候我们的日期选择器就需要进行一些限制&#xff0c;比如&#xff1a; 这样之后&#xff0c;就不会误填写到明天啦&#xff0c;下面让我们看一下代码实现 html页面代码 这里…...

终端常用脚本命令

Mac编写shell脚本文件 Rvm切换Ruby Mac系统指定更新 Mac应用安装&#xff1a;允许任何来源 Mac终端常用命令与Vim常用命令 Mac退出VIM模式 git协议实现管理(三个步骤) GIT 常用命令 .gitignore git工具常用操作指令 prettier前端本地格式化工具 SourceTree撤销Commit提交 pod i…...

百度翻译很方便,几点注意事项

前几天修改资源&#xff0c;就想翻译一些字串。用了一下百度&#xff0c;还是很方便的。 昨天开通了开发者账号&#xff0c;试了一下批量翻译。也发现了一些问题&#xff1a; 有的语言不支持&#xff0c;如ben/tr/jav好像没有区分地区。也可能是我还不熟悉。使用太多会欠费。比…...

阿里云安装 redis

1、在opt目录下面安装redis https://download.redis.io/redis-stable.tar.gz redis的最新稳定版本。更多版本可见 redis cd /opt wget https://download.redis.io/redis-stable.tar.gz2、解压tar包&#xff0c;会生成redis-stable文件夹 tar -xzvf redis-stable.tar.gz3、安装…...

解释什么是异步非阻塞?

在IO和网络编程中&#xff0c;我们经常看到几个概念&#xff1a;同步、异步、阻塞、非阻塞。 同步和异步   同步和异步是针对应用程序和内核的交互而言的&#xff0c;同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪&#xff0c;而异步是指用户进程触发…...

1024程序节特辑:一文读懂小程序支付流程

小程序支付流程 概述前置准备登录流程调用wx.login()向微信服务器发送请求 支付流程调用wx.requestPayment()部分后台处理逻辑支付功能要求 支付流程面试题 主页传送门&#xff1a;&#x1f4c0; 传送 概述 小程序支付是由微信支付推出的一种便捷支付方式&#xff0c;通过扫码…...

C- 使用原子变量实现信号量

信号量 信号量&#xff08;Semaphore&#xff09;是并发编程中的一个核心同步原语&#xff0c;它在多进程和多线程环境下被设计用来协调不同的执行单元&#xff0c;确保它们在对共享资源的访问上达到同步和互斥。信号量内部维护一个计数器&#xff0c;该计数器的初始值可以被视…...

Pytorch与Onnx的转换与推理

Open Neural Network Exchange&#xff08;ONNX&#xff0c;开放神经网络交换&#xff09;格式&#xff0c;是一个用于表示深度学习模型的标准&#xff0c;可使模型在不同框架之间进行转移。 一、pytorch模型保存/加载 有两种方式可用于保存/加载pytorch模型 1&#xff09;文件…...

Linux权限详解

文章目录 1. shell命令及运行原理2. Linux权限的概念&#xff08;1&#xff09;用户种类&#xff08;2&#xff09;切换用户&#xff08;3&#xff09;命令提权 3. Linux权限管理&#xff08;1&#xff09;文件访问者的分类&#xff08;人&#xff09;&#xff08;2&#xff09…...

基于react18+arco+zustand通用后台管理系统React18Admin

React-Arco-Admin轻量级后台管理系统解决方案 基于vite4构建react18后台项目ReactAdmin。使用了reactarco-designzustandbizcharts等技术架构非凡后台管理框架。支持 dark/light主题、i18n国际化、动态路由鉴权、3种经典布局、tabs路由标签 等功能。 技术框架 编辑器&#xff…...

BAT031:按列表名单将路径a下的文件夹批量剪切到路径b

引言&#xff1a;编写批处理程序&#xff0c;实现按列表名单将路径a下的文件夹批量剪切到路径b。 一、新建Windows批处理文件 参考博客&#xff1a; CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件&#xff0c;点击…...

随机专享记录第一话 -- RustDesk的自我搭建和使用

1.介绍 RustDesk是继TeamView、向日葵等远程桌面软件后的新起之秀,最主要的是开源的可自己搭建中继服务。相比于公共服务器,连接一次等待的时间要多久,用过TeamView的都知道,而且还是免费的,不像某些远程搞各种个人证书,各种登录设备限制! 先看看软件图,这是待连接界…...

【数据库】拼接字段 使用别名

拼接字段 使用别名 e . g . e.g. e.g. Vendors 表包含供应商名和电话信息&#xff0c;name 和 mobile&#xff1b;需要输出这两个属性的值的组合作为供应商的基本信息组合。 SELECT concat(name, _, mobile) FROM Vendors; -- 语句通过 MySQL 环境下测试&#xff0c;其他 DBMS…...

Golang设计22种模式

什么是设计模式 设计模式是面向对象软件的设计经验,是通常设计问题的解决方案。每一种设计模式系统的命名、解释和评价了面向对象中一个重要的和重复出现的设计。 设计模式的分类 创建模式 - 用来帮助我们创建对象的 工厂模式 (Factory Pattern)抽象工厂模式 (Abstract F…...

MMKV(3)

使用时遇到的问题 在项目的构建配置文件&#xff08;如 Gradle 或 Maven&#xff09;中添加相应的依赖项。 MMKV 是一个键值存储库&#xff0c;它存储的是原始的字节数组数据。需要存储和检索复杂的对象或数据结构&#xff0c;需要自行进行序列化和反序列化操作。可以使用任何…...

vivado报错警告之[Vivado 12-1017] Problems encountered:

文章目录 方法一方法二方法三&#xff08;作者最终解决&#xff09; 我们对vivado 的程序进行综合(Run Synthesis)时&#xff0c;可能会出现[Vivado 12-1017] Problems encountered: 1. Failed to delete one or more files in run directory的一个警告信息&#xff0c;导致我们…...

基于springboot汽车租赁系统

功能如下图所示 摘要 Spring Boot汽车租赁系统的设计旨在满足不断增长的租车市场需求&#xff0c;并通过简化开发和部署流程来提供方便的租车解决方案。系统采用了现代化的架构&#xff0c;主要基于以下技术栈&#xff1a; Spring Boot&#xff1a;作为后端的核心框架&#xff…...

C++禁用赋值操作符

1.禁用赋值操作符 在C中&#xff0c;void operator(const ClassName&) delete; 是一种特殊的语法&#xff0c;用于明确地禁止赋值操作符&#xff08;assignment operator&#xff09;的默认实现或自定义实现。 这通常用于防止类的实例被意外赋值。通过明确地删除赋值操作…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法&#xff1a;原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件&#xff0c;如包含恶意代码、敏感数据或欺诈内容的文档&#xff0c;在企业协同办公环境中&#xff08;如Teams、Google Workspace&#xff09;尤为重要。结合大模型技术&…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...