<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>机器视觉 &#8211; Daoker小站</title>
	<atom:link href="https://daoker.cc/category/%E6%9C%BA%E5%99%A8%E8%A7%86%E8%A7%89/feed" rel="self" type="application/rss+xml" />
	<link>https://daoker.cc</link>
	<description>个人随笔小记</description>
	<lastBuildDate>Sun, 19 Mar 2023 12:40:29 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.2</generator>

<image>
	<url>https://daoker.cc/wp-content/uploads/2022/08/cropped-daoker_blog_s-32x32.png</url>
	<title>机器视觉 &#8211; Daoker小站</title>
	<link>https://daoker.cc</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Gazebo室外仿真之地形植物建模</title>
		<link>https://daoker.cc/daokerto41.html</link>
					<comments>https://daoker.cc/daokerto41.html#comments</comments>
		
		<dc:creator><![CDATA[博主]]></dc:creator>
		<pubDate>Mon, 25 Apr 2022 12:51:00 +0000</pubDate>
				<category><![CDATA[机器视觉]]></category>
		<guid isPermaLink="false">https://daoker.eu.org/?p=41</guid>

					<description><![CDATA[效果展示： 地形 法一：terrain_generator GAZEBO构建室外环境地图之创建高度图 GAZE [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>效果展示：</p>



<iframe src="//player.bilibili.com/player.html?aid=638660120&amp;bvid=BV1iY4y1k7P6&amp;cid=710927933&amp;page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 100%; height: 500px; max-width: 100%；align:center; padding:20px 0;"> </iframe>



<h2 class="wp-block-heading">地形</h2>



<h3 class="wp-block-heading">法一：terrain_generator</h3>



<p><a href="https://blog.csdn.net/baidu_36211769/article/details/104795887?spm=1001.2014.3001.5502" target="_blank" rel="noreferrer noopener">GAZEBO构建室外环境地图之创建高度图</a></p>



<p><a href="https://blog.csdn.net/baidu_36211769/article/details/104799100" target="_blank" rel="noreferrer noopener">GAZEBO构建室外环境地图之加载山地地形</a></p>



<p>由于我的项目需求是需要甘蔗地的地形模型，对于从卫星地图中获取高程图的办法明显不能达到要求，所以这里我选择采用World Creator 建模，然后导出成高程图：</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="1024" src="http://daoker.cc/wp-content/uploads/2022/08/image-5-1024x1024.png" alt="" class="wp-image-43" srcset="https://daoker.cc/wp-content/uploads/2022/08/image-5-1024x1024.png 1024w, https://daoker.cc/wp-content/uploads/2022/08/image-5-300x300.png 300w, https://daoker.cc/wp-content/uploads/2022/08/image-5-150x150.png 150w, https://daoker.cc/wp-content/uploads/2022/08/image-5-768x768.png 768w, https://daoker.cc/wp-content/uploads/2022/08/image-5-1536x1536.png 1536w, https://daoker.cc/wp-content/uploads/2022/08/image-5-2048x2048.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><br>然后使用opencv调整大小，这里转换了多张不同大小的图像：</p>



<pre class="wp-block-code"><code>#include &lt;opencv2/highgui.hpp&gt;
#include &lt;opencv2/opencv.hpp&gt;
using namespace cv;
using namespace std;
int main(int argc, char **argv)
{
    // read image
    Mat hmap = imread("heightmap.png", IMREAD_GRAYSCALE);
    //Mat hmap = imread("mtsthelens_1025.tif", IMREAD_ANYDEPTH);
    if (hmap.empty()) //如果没有读到图片，则为true
    {
        std::cout &lt;&lt; "图片读取错误" &lt;&lt; std::endl;
    }
    Mat rmap;
    //把图片改成2^n-1大小的8位图片
    for (int i = 7; i &lt;= 12; i++)
    {
        int map_size = pow(2, i) + 1;
        string addr = "hetmap_png2png" + to_string(map_size) + "_l.png";
        resize(hmap, rmap, Size(map_size, map_size));
        imwrite(addr, rmap);
    }
    /*
    编译：
    g++ test.cpp -o test `pkg-config --cflags --libs opencv4`
    return 0;
}
</code></pre>



<p>然后采用资料2中的脚本https://github.com/Sarath18/terrain_generator，生成了.world文件，然后使用gazeo打开，得</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="514" src="http://daoker.cc/wp-content/uploads/2022/08/image-6-1024x514.png" alt="" class="wp-image-44" srcset="https://daoker.cc/wp-content/uploads/2022/08/image-6-1024x514.png 1024w, https://daoker.cc/wp-content/uploads/2022/08/image-6-300x151.png 300w, https://daoker.cc/wp-content/uploads/2022/08/image-6-768x385.png 768w, https://daoker.cc/wp-content/uploads/2022/08/image-6.png 1116w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>查看文件，也找不到材质文件在哪，所以之后另寻他法。</p>



<h3 class="wp-block-heading">法二：gdal</h3>



<p><a href="https://zhuanlan.zhihu.com/p/368711095" target="_blank" rel="noreferrer noopener">Gazebo 11分类教程——构建仿真世界（三）</a></p>



<p>然而，在最后一步出错，并不能复现上面的文章里的效果</p>



<pre class="wp-block-code"><code>GAZEBO_RESOURCE_PATH="$GAZEBO_RESOURCE_PATH:/tmp" gazebo /tmp/volcano.world
</code></pre>



<p>也找不到原因：<br>更多参考文献:<br><a href="https://blog.csdn.net/ZhangRelay/article/details/52789542?utm_medium=distribute.wap_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0.wap_blog_relevant_default&amp;spm=1001.2101.3001.4242.1&amp;utm_relevant_index=3">在Gazebo中使用DEM構建起伏地形環境</a><br><a href="https://blog.csdn.net/qq_34521859/article/details/106404708?spm=1001.2101.3001.6650.3&amp;utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-3.pc_relevant_paycolumn_v3&amp;depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-3.pc_relevant_paycolumn_v3">gazebo仿真记事（二）：gazebo里加载自己的DEM</a><br><a href="http://gazebosim.org/tutorials/?tut=dem">Digital Elevation Models</a><br><a href="https://blog.csdn.net/qq_28368377/article/details/119933236">gdal将16位的tif转为8位tif</a><br><a href="https://gdal.org/programs/gdalwarp.html#synopsis">gdalwarp</a></p>



<h3 class="wp-block-heading">法三：blender（成功）</h3>



<p>参考视频<a rel="noreferrer noopener" href="https://www.youtube.com/watch?v=GNbH8Pf7nGk" target="_blank">How to create terrain for Gazebo simulation with Blender 2.9</a>了解到使用blender也可以建模（国内我已经转载到了B站）。</p>



<iframe src="//player.bilibili.com/player.html?aid=987245661&amp;bvid=BV1Tt4y1K7Lq&amp;cid=877628130&amp;page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 100%; height: 500px; max-width: 100%；align:center; padding:20px 0;"> </iframe>



<p>视频中的项目地址：<a rel="noreferrer noopener" href="https://github.com/SyllogismRXS/gazebo_terrain_tutorial" target="_blank">Gazebo Terrain Tutorial</a>，按照视频指导，成功建立地形模型并导入gazebo。</p>



<div class="wp-block-argon-github github-info-card card shadow-sm github-info-card-full" data-author="SyllogismRXS" data-project="gazebo_terrain_tutorial"><div class="github-info-card-header"><a href="https://github.com/" target="_blank" title="Github" rel="noopener"><span><i class="fa fa-github"></i> GitHub</span></a></div><div class="github-info-card-body"><div class="github-info-card-name-a"><a href="https://github.com/SyllogismRXS/gazebo_terrain_tutorial" target="_blank" rel="noopener"><span class="github-info-card-name">SyllogismRXS/gazebo_terrain_tutorial</span></a></div><div class="github-info-card-description"></div></div><div class="github-info-card-bottom"><span class="github-info-card-meta github-info-card-meta-stars"><i class="fa fa-star"></i> <span class="github-info-card-stars"></span></span><span class="github-info-card-meta github-info-card-meta-forks"><i class="fa fa-code-fork"></i> <span class="github-info-card-forks"></span></span></div></div>



<p><br>一些细节设置的资料：<br><a rel="noreferrer noopener" href="http://www.xitongzhijia.net/xtjc/20210830/223708.html" target="_blank">Blender怎么快速建模一片绿树叶？Blender快速建模一片绿树叶教程</a><br><a rel="noreferrer noopener" href="https://www.html.cn/tools/photoshop/112794196409225.html" target="_blank">blender2.9怎么快速建模一颗树苗? blender树的建模技巧</a><br>最终效果图：</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="581" src="http://daoker.cc/wp-content/uploads/2022/08/image-4-1024x581.png" alt="" class="wp-image-42" srcset="https://daoker.cc/wp-content/uploads/2022/08/image-4-1024x581.png 1024w, https://daoker.cc/wp-content/uploads/2022/08/image-4-300x170.png 300w, https://daoker.cc/wp-content/uploads/2022/08/image-4-768x436.png 768w, https://daoker.cc/wp-content/uploads/2022/08/image-4-1536x872.png 1536w, https://daoker.cc/wp-content/uploads/2022/08/image-4.png 1558w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>补充：</p>



<p>1.看到有网友说不知道blender如何导入gazebo以及导入后贴图丢失的问题，我做了各视频来演示解决这个问题。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1022" height="264" src="https://daoker.cc/wp-content/uploads/2023/03/image.png" alt="" class="wp-image-1014" srcset="https://daoker.cc/wp-content/uploads/2023/03/image.png 1022w, https://daoker.cc/wp-content/uploads/2023/03/image-300x77.png 300w, https://daoker.cc/wp-content/uploads/2023/03/image-768x198.png 768w" sizes="auto, (max-width: 1022px) 100vw, 1022px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="186" src="https://daoker.cc/wp-content/uploads/2023/03/image-1-1024x186.png" alt="" class="wp-image-1015" srcset="https://daoker.cc/wp-content/uploads/2023/03/image-1-1024x186.png 1024w, https://daoker.cc/wp-content/uploads/2023/03/image-1-300x54.png 300w, https://daoker.cc/wp-content/uploads/2023/03/image-1-768x139.png 768w, https://daoker.cc/wp-content/uploads/2023/03/image-1.png 1147w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="436" src="https://daoker.cc/wp-content/uploads/2023/03/image-2-1024x436.png" alt="" class="wp-image-1017" srcset="https://daoker.cc/wp-content/uploads/2023/03/image-2-1024x436.png 1024w, https://daoker.cc/wp-content/uploads/2023/03/image-2-300x128.png 300w, https://daoker.cc/wp-content/uploads/2023/03/image-2-768x327.png 768w, https://daoker.cc/wp-content/uploads/2023/03/image-2.png 1198w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>简单点说就是删除model.sdf文件中的下图的内容即可</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="591" src="https://daoker.cc/wp-content/uploads/2023/03/image-3-1024x591.png" alt="" class="wp-image-1019" srcset="https://daoker.cc/wp-content/uploads/2023/03/image-3-1024x591.png 1024w, https://daoker.cc/wp-content/uploads/2023/03/image-3-300x173.png 300w, https://daoker.cc/wp-content/uploads/2023/03/image-3-768x444.png 768w, https://daoker.cc/wp-content/uploads/2023/03/image-3.png 1404w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>视频演示：</p>



<iframe src="//player.bilibili.com/player.html?aid=908668931&#038;bvid=BV1cM4y167vM&#038;cid=1060504080&#038;page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 100%; height: 500px; max-width: 100%；align:center; padding:20px 0;"> </iframe>
]]></content:encoded>
					
					<wfw:commentRss>https://daoker.cc/daokerto41.html/feed</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>基于二维激光雷达的蔗田垄高检测机器人——现场试验</title>
		<link>https://daoker.cc/daokerto695.html</link>
					<comments>https://daoker.cc/daokerto695.html#respond</comments>
		
		<dc:creator><![CDATA[博主]]></dc:creator>
		<pubDate>Wed, 23 Nov 2022 01:56:09 +0000</pubDate>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[ROS]]></category>
		<category><![CDATA[单片机]]></category>
		<category><![CDATA[机器人]]></category>
		<category><![CDATA[机器视觉]]></category>
		<category><![CDATA[树莓派]]></category>
		<guid isPermaLink="false">https://daoker.cc/?p=695</guid>

					<description><![CDATA[遗忘越来越快 之前做仿真时学看ros，然后休息了几个月，现在要做试验了，感觉已经忘得差不多了，所以体会到了做笔 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>遗忘越来越快</p>
<cite>之前做仿真时学看ros，然后休息了几个月，现在要做试验了，感觉已经忘得差不多了，所以体会到了做笔记的重要性，因此这里做个试验笔记。</cite></blockquote>



<p>参考资料:<a rel="noreferrer noopener" href="http://www.autolabor.com.cn/book/ROSTutorials/" target="_blank">http://www.autolabor.com.cn/book/ROSTutorials/</a></p>



<p>对于试验，本质就是搭建一个机器人，从控制的角度看，机器人系统可以分为：传感系统、控制系统、驱动系统、执行机构。</p>



<p><strong>执行机构:</strong>&nbsp;执行机构只要满足能在蔗田环境下工作，采用四个直流编码电机带动四个主动轮进行行走，由于执行机构比较简单，不再做单独介绍。</p>



<p><strong>驱动系统:&nbsp;</strong>电池、arduino 以及电机驱动模块；</p>



<p><strong>控制系统:</strong>&nbsp;树莓派；</p>



<p><strong>传感系统:</strong>&nbsp;编码器、单线激光雷达、IMU；</p>



<p>其中，执行机构与驱动系统构成了机器人底盘。</p>



<h2 class="wp-block-heading">执行机构和驱动系统</h2>



<h3 class="wp-block-heading">执行结构</h3>



<h4 class="wp-block-heading">硬件介绍</h4>



<p><s>底盘是淘宝买的四驱mini小车，由于上位机是树莓派下的ROS，因此为了省事，把重点放在算法的研究上，驱动控制板选择了另一家和ros深度配合的驱动板，该驱动板基于stm32，并且集成了imu惯性传感器，所以把原来买的IMU退了。</s></p>



<p>经过仔细阅读说明书，发现小车底盘和深度定制的ros驱动板不太兼容，目前有两种方案：</p>



<p>1.驱动板和树莓派ros深度定制，例程丰富，开发手册详细，但是其配的小车不太适合我的的试验环境</p>



<p>2.例程很粗糙，开发手册没有，对应的小车比较适合我们的试验环境</p>



<p>最终我选择了第二种，因为首先试验要保证的是硬件条件能满足试验环境，对于软件环境，我觉得可以多看多学，慢慢研究，并且我也不是完全没基础，对于1中的教程有的显得太过于多余。对于方案2，要考虑的是使用arduino进行四驱小车的pid控制，对于imu信息的接收，可以由arduino承担，也可以直接由树莓派承担，最终的目的就是树莓派中的ros里有imu数据的话题，有编码器的话题，同时考虑将他们如何做成odom信息。</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-24.png" alt="" class="wp-image-708" width="296" height="459" srcset="https://daoker.cc/wp-content/uploads/2022/11/image-24.png 357w, https://daoker.cc/wp-content/uploads/2022/11/image-24-193x300.png 193w" sizes="auto, (max-width: 296px) 100vw, 296px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="380" height="702" src="http://daoker.cc/wp-content/uploads/2022/11/image-25.png" alt="" class="wp-image-709" srcset="https://daoker.cc/wp-content/uploads/2022/11/image-25.png 380w, https://daoker.cc/wp-content/uploads/2022/11/image-25-162x300.png 162w" sizes="auto, (max-width: 380px) 100vw, 380px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-26.png" alt="" class="wp-image-710" width="407" height="453" srcset="https://daoker.cc/wp-content/uploads/2022/11/image-26.png 706w, https://daoker.cc/wp-content/uploads/2022/11/image-26-269x300.png 269w" sizes="auto, (max-width: 407px) 100vw, 407px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="690" height="672" src="http://daoker.cc/wp-content/uploads/2022/11/image-33.png" alt="" class="wp-image-741" srcset="https://daoker.cc/wp-content/uploads/2022/11/image-33.png 690w, https://daoker.cc/wp-content/uploads/2022/11/image-33-300x292.png 300w" sizes="auto, (max-width: 690px) 100vw, 690px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/701b4f72a6d79aa2cfb7ec947c6b6c2.png" alt="" class="wp-image-742" width="308" height="248" srcset="https://daoker.cc/wp-content/uploads/2022/11/701b4f72a6d79aa2cfb7ec947c6b6c2.png 920w, https://daoker.cc/wp-content/uploads/2022/11/701b4f72a6d79aa2cfb7ec947c6b6c2-300x242.png 300w, https://daoker.cc/wp-content/uploads/2022/11/701b4f72a6d79aa2cfb7ec947c6b6c2-768x619.png 768w" sizes="auto, (max-width: 308px) 100vw, 308px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"></div>
</div>



<div class="wp-block-argon-collapse collapse-block shadow-sm collapsed hide-border-left" style="border-left-color:#ffffff00"><div class="collapse-block-title" style="background-color:#ffffff00"><span class="collapse-block-title-inner">弃用的方案</span><i class="collapse-icon fa fa-angle-down"></i></div><div class="collapse-block-body" style="display: none">这里要编写STM32的底层源码<br>ΜVISION5安装包和安装破解：HTTPS://WWW.MR-WU.CN/KEIL-MDK-UVISION-5-CRACK<img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-30.png" alt="" style="width: 500px;">STM32控制器程序执行图<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-31-1024x463.png" alt="" style="width: 500px;">初始化和任务对应的代码</div></div>



<p></p>



<h4 class="wp-block-heading">接线</h4>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="458" src="http://daoker.cc/wp-content/uploads/2022/11/image-34-1024x458.png" alt="" class="wp-image-743" srcset="https://daoker.cc/wp-content/uploads/2022/11/image-34-1024x458.png 1024w, https://daoker.cc/wp-content/uploads/2022/11/image-34-300x134.png 300w, https://daoker.cc/wp-content/uploads/2022/11/image-34-768x343.png 768w, https://daoker.cc/wp-content/uploads/2022/11/image-34-1536x687.png 1536w, https://daoker.cc/wp-content/uploads/2022/11/image-34.png 1539w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">驱动系统</h3>



<div class="wp-block-argon-collapse collapse-block shadow-sm collapsed hide-border-left" style="border-left-color:#ffffff00"><div class="collapse-block-title" style="background-color:#ffffff00"><span class="collapse-block-title-inner"><a href="https://www.cnblogs.com/1996jiwei/p/6561681.html">Arduino Wire.h（IIC）库函数详解</a></span><i class="collapse-icon fa fa-angle-down"></i></div><div class="collapse-block-body" style="display: none">此库中包含<br>1 Wire.begin()<br>2 Wire.requestFrom()<br>3 Wire.beginTransmission()<br>4 Wire.endTransmission()<br>5 Wire.write()<br>6 Wire.available()<br>7 Wire.read()<br>8 Wire.onReceive()<br>9 Wire.onRequest()<br>Wire.begin() 和 Wire.begin(address)<br>描述<br>初始化wire库 ，并且加入到I2C网络，前者作为Master 或 Slaver，并且只能调用一次<br>参数<br>address ：7位的器件地址（可选），如果没有输入，则以Master的形式加入到I2C网络<br>Wire.requestFrom()<br>描述<br>主设备请求从设备一个字节，这个字节可以被主设备用 read()或available()接受<br>Wire.requrstFrom(addtess,quantity)<br>Wire.requrstFrom(addtess,quantity,stop)<br>参数<br>addtess ： 7位的器件地址<br>quantity ： 请求得到的数量<br>stop ：布尔形，‘1’ 则在请求结束后发送一个停止命令，并释放总线。‘0’则继续发送请求保持连接<br>返回<br>字节形 ，从从机接受到的字节数目<br>Wire.beginTransmission(address)<br>描述<br>开始一次传输数据，发送一个I2C开始字符<br>参数<br>address ： 器件的7位地址<br>返回<br>none<br>Wire.endTransmission()和Wire.endTransmission(stop)<br>描述<br>结束一个由beginTransmission（）开始的并且由write（）排列的从机的传输。在Arduino中 endTransmission（）接受到<br>一个布尔形变量，如果为1 则endTransmission（）发送一个停止信息；如果为0 则发送开始信息<br>返回<br>0 成功 1 数据溢出 2 发送addtess时从机接受到NACK 3 发送数据时接受到 NACK 4 其他错误<br>Wire.write()<br>描述<br>向从机发送数据<br>形式<br>Wire.write(value)<br>Wire.write(string)<br>Wire.write(data, length)<br>参数<br>value ：要发送的数值<br>string ：字符组的指针<br>data ：一个字节数组<br>length ：传输的数量<br>Wire.available()<br>描述<br>Wire.requestFrom()请求从机数据后，可以使用available接收<br>Wire.read()<br>描述<br>Wire.requestFrom()请求从机数据后，可以使用read接收<br>Wire.onReceive()<br>描述<br>从机接收主机发来的数据<br>Wire.onRequest()<br>描述<br>从机请求主机发送数据</div></div>



<p>对于I2C,其实还有个更新的库，但是这里驱动板的示例程序是基于上面的，为了节约时间，新库以后有时间再研究，新库：</p>



<div class="wp-block-argon-github github-info-card card shadow-sm github-info-card-full" data-author="rambo" data-project="I2C"><div class="github-info-card-header"><a href="https://github.com/" target="_blank" title="Github" rel="noopener"><span><i class="fa fa-github"></i> GitHub</span></a></div><div class="github-info-card-body"><div class="github-info-card-name-a"><a href="https://github.com/rambo/I2C" target="_blank" rel="noopener"><span class="github-info-card-name">rambo/I2C</span></a></div><div class="github-info-card-description"></div></div><div class="github-info-card-bottom"><span class="github-info-card-meta github-info-card-meta-stars"><i class="fa fa-star"></i> <span class="github-info-card-stars"></span></span><span class="github-info-card-meta github-info-card-meta-forks"><i class="fa fa-code-fork"></i> <span class="github-info-card-forks"></span></span></div></div>



<h4 class="wp-block-heading">驱动板使用代码学习：</h4>



<div  class='collapse-block shadow-sm collapse-block-transparent collapsed hide-border-left'><div class='collapse-block-title'><i class='fa fa-flag'></i> <span class='collapse-block-title-inner'>内容过长，点击展开</span><i class='collapse-icon fa fa-angle-down'></i></div><div class='collapse-block-body' style='display:none;'></p>



<pre class="wp-block-code"><code>
#include &lt;Wire.h&gt;

#define I2C_ADDR        0x34            //12C地址


#define ADC_BAT_ADDR                  0   //电压地址
#define MOTOR_TYPE_ADDR               20 //编码电机类型设置
#define MOTOR_ENCODER_POLARITY_ADDR   21 //设置编码方向极性，
//如果发现电机转速根本不受控制，要么最快速度转动，要么停止。可以将此地址的值重新设置一下
//范围0或1，默认0
#define MOTOR_FIXED_PWM_ADDR      31 //固定PWM控制，属于开环控制,范围（-100~100）
//#define SERVOS_ADDR_CMD 40        
#define MOTOR_FIXED_SPEED_ADDR    51 //固定转速控制，属于闭环控制，
//单位：脉冲数每10毫秒，范围（根据具体的编码电机来，受编码线数，电压大小，负载大小等影响，一般在±50左右）

#define MOTOR_ENCODER_TOTAL_ADDR  60 //4个编码电机各自的总脉冲值
//如果已知电机每转一圈的脉冲数为U，又已知轮子的直径D，那么就可以通过脉冲计数的方式得知每个轮子行进的距离
//比如读到电机1的脉冲总数为P，那么行进的距离为(P/U) * (3.14159*D)
//对于不同的电机可以自行测试每圈的脉冲数U，可以手动旋转10圈读出脉冲数，然后取平均值得出


//电机类型具体值
#define MOTOR_TYPE_WITHOUT_ENCODER        0  //无编码器的电机
#define MOTOR_TYPE_TT                     1  //TT编码电机
#define MOTOR_TYPE_N20                    2  //n20编码电机
#define MOTOR_TYPE_JGB37_520_12V_110RPM   3 //磁环每转是44个脉冲   减速比:90  默认

u8 data&#091;20];
bool WireWriteByte(uint8_t val)
{
    Wire.beginTransmission(I2C_ADDR);//开始一次数据传输，发送一个I2C开始字符0x34
    Wire.write(val);//像从机发送数据
    if( Wire.endTransmission() != 0 ) { //返回值为0才成功，否则失败
        return false;
    }
    return true;
}
//向地址中写入数据 （reg：地址 val：数据内容 len：数据长度）
bool WireWriteDataArray(  uint8_t reg,uint8_t *val,unsigned int len)
{
    unsigned int i;

    Wire.beginTransmission(I2C_ADDR);
    Wire.write(reg);//发送地址数据
    for(i = 0; i &lt; len; i++) {
        Wire.write(val&#091;i]);//发送内容
    }
    if( Wire.endTransmission() != 0 ) {//结果判断
        return false;
    }

    return true;
}
//读取地址中的数据（reg：地址 val：数据内容）
bool WireReadDataByte(uint8_t reg, uint8_t &amp;val)
{
    if (!WireWriteByte(reg)) {//如果WireWriteByte（）非真
        return false;
    }
    
    Wire.requestFrom(I2C_ADDR, 1);//主设备请求从设备I2C_ADDR地址处的一个字节，这个字节可以被主设备用 read()或available()接受
    while (Wire.available()) //接收获取的字节
    {
        val = Wire.read();//Wire.requestFrom()请求从机数据后，可以使用read接收
    }
    
    return true;
}
//读取地址中指定长度的数据（reg：地址 val：数据内容 len：数据长度）
int WireReadDataArray(   uint8_t reg, uint8_t *val, unsigned int len)
{
    unsigned char i = 0;
    
    /* Indicate which register we want to read from */
    if (!WireWriteByte(reg)) {
        return -1;
    }
    
    /* Read block data */
    Wire.requestFrom(I2C_ADDR, len);
    while (Wire.available()) {
        if (i &gt;= len) {
            return -1;
        }
        val&#091;i] = Wire.read();
        i++;
    }
    
    return i;
}


int serial_putc( char c, struct __file * )
{
  Serial.write( c );//Serial.print() 发送的是字符，Serial.write() 发送的字节.
  return c;
}
void printf_begin(void)
{
  fdevopen( &amp;serial_putc, 0 );//将函数serial_putc指向串口
}




uint8_t MotorType = MOTOR_TYPE_TT;  //设定电机类型
uint8_t MotorEncoderPolarity = 1; 
void setup()
{
  Wire.begin();//初始化 I2C，以 Ardunio UNO 为例 I2C 口为：A4(SCL)、A5(CLK)
  Serial.begin(9600);
  printf_begin();
  delay(200);
  WireWriteDataArray(MOTOR_TYPE_ADDR,&amp;MotorType,1);//在电机类型地址中写入电机类型编号
 //向地址中写入数据 （reg：地址 val：数据内容 len：数据长度）
 
  delay(5);
  WireWriteDataArray(MOTOR_ENCODER_POLARITY_ADDR,&amp;MotorEncoderPolarity,1);//向编码方向极性地址中写入MotorEncoderPolarity地址，长度为1

}
/*用数组传递电机速度，正数为设置前进速度，负数为设置后退速度
以 p1、p2 为例：p1=4 个电机以 50 的速度前进 p2=4 个电机以 20 的速度后退*/
int8_t p1&#091;4]={50,50,50,50};
int8_t p2&#091;4]={-50,-50,-50,-50};
int8_t s1&#091;4]={2,2,2,2};
int8_t s2&#091;4]={-2,-2,-2,-2};
//int8_t s1&#091;4]={2,20,10,30};
//int8_t s2&#091;4]={-20,-20,-30,-9};
int32_t EncodeTotal&#091;4]; //用于暂存电机累积转动量的值，正转递增，反转递减
int8_t result&#091;4]={0,0,0,0};
void loop()
{
  u16 v;  //用于暂存电压值
  WireReadDataArray(ADC_BAT_ADDR,data,2);   //读取电压地址中存放的电压
  v = data&#091;0]+ (data&#091;1]&lt;&lt;8); //将电压转换为 mV
  Serial.print("V = ");Serial.print(v);Serial.println("mV     "); //打印电压的值

  WireReadDataArray(MOTOR_ENCODER_TOTAL_ADDR,(uint8_t*)EncodeTotal,16);  //读取电机累积转动量
  printf("Encode1 = %ld  Encode2 = %ld  Encode3 = %ld  Encode4 = %ld  \r\n", EncodeTotal&#091;0], EncodeTotal&#091;1], EncodeTotal&#091;2], EncodeTotal&#091;3]);
//打印四个电机的累积转动量
  
/*在电机转速地址中写入电机的转动方向和速度：WireWriteDataArray（转速控制地址，电
机转速数组，电机个数）*/
//  WireWriteDataArray(MOTOR_FIXED_PWM_ADDR,p1,4);//固定pwm控制，开环，属于开环控制,范围（-100~100）
  WireWriteDataArray(MOTOR_FIXED_SPEED_ADDR,s1,4);//固定转速控制，闭环，单位：脉冲数每10毫秒，范围（根据具体的编码电机来，受编码线数，电压大小，负载大小等影响，一般在±50左右）
  delay(700);

//  WireWriteDataArray(MOTOR_FIXED_PWM_ADDR,p2,4);
  WireWriteDataArray(MOTOR_FIXED_SPEED_ADDR,s2,4);
  delay(700);

  //读取4个电机10ms内的脉冲数据
  for(int i=0;i&lt;10;i++)
  {
  WireReadDataArray(MOTOR_FIXED_SPEED_ADDR,result,4);
  printf("10ms内的脉冲数据为： %ld ， %ld  ， %ld ， %ld  \r\n", result&#091;0], result&#091;1], result&#091;2], result&#091;3]); 
  delay(2000);
  }
}</code></pre>



<p></div></div>



<h4 class="wp-block-heading">ros_arduino_bridge</h4>



<p>上述代码只有驱动板与arduino交互的驱动，对于arduino与ros的交互，则需要自己编写才行，好在有相关的开源项目，因此在上面进行二次开发即可。我只使用其中的和ros进行交互的部分，对于其中的底盘控制，舵机控制则不使用。</p>



<div class="wp-block-argon-github github-info-card card shadow-sm github-info-card-full" data-author="hbrobotics" data-project="ros_arduino_bridge"><div class="github-info-card-header"><a href="https://github.com/" target="_blank" title="Github" rel="noopener"><span><i class="fa fa-github"></i> GitHub</span></a></div><div class="github-info-card-body"><div class="github-info-card-name-a"><a href="https://github.com/hbrobotics/ros_arduino_bridge" target="_blank" rel="noopener"><span class="github-info-card-name">hbrobotics/ros_arduino_bridge</span></a></div><div class="github-info-card-description"></div></div><div class="github-info-card-bottom"><span class="github-info-card-meta github-info-card-meta-stars"><i class="fa fa-star"></i> <span class="github-info-card-stars"></span></span><span class="github-info-card-meta github-info-card-meta-forks"><i class="fa fa-code-fork"></i> <span class="github-info-card-forks"></span></span></div></div>



<h4 class="wp-block-heading"><strong>rosserial arduino</strong></h4>



<p>ros_arduino-bridge问题较多，换成研究下<strong>rosserial arduino</strong>试试</p>



<p><strong>1.安装Arduino</strong>（树莓派没安装桌面环境，这步可以忽略，编译上传在安装桌面环境的电脑上进行即可）</p>



<p><strong><code>sudo apt-get install arduino</code></strong></p>



<p><strong>2.启动arduino并进行相关设置</strong>（树莓派没安装桌面环境，这步可以忽略，编译上传在安装桌面环境的电脑上进行即可）</p>



<p><strong><code>arduino</code></strong></p>



<p><strong>3.安装软件包rosserial arduino</strong></p>



<p><strong><code>sudo apt-get install ros-melodic-rosserial-arduino</code><br><code>sudo apt-get install ros-melodic-rosserial</code></strong></p>



<p><strong>4.编译ros_lib文件，并复制到arduino libraries里</strong>（这里是负责编译和上传的电脑的arduino的库里，我的是Windows上的，压缩成zip,然后导入库）</p>



<p>编译：</p>



<pre class="wp-block-code"><code>rosrun rosserial_arduino make_libraries.py &#091;这里写输出目录，中括号要去掉]</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="907" height="213" src="http://daoker.cc/wp-content/uploads/2022/12/image-10.png" alt="" class="wp-image-835" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-10.png 907w, https://daoker.cc/wp-content/uploads/2022/12/image-10-300x70.png 300w, https://daoker.cc/wp-content/uploads/2022/12/image-10-768x180.png 768w" sizes="auto, (max-width: 907px) 100vw, 907px" /></figure>



<p>在windows上下载下来</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" src="http://daoker.cc/wp-content/uploads/2022/12/image-11.png" alt="" class="wp-image-836" width="690" height="300" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-11.png 885w, https://daoker.cc/wp-content/uploads/2022/12/image-11-300x131.png 300w, https://daoker.cc/wp-content/uploads/2022/12/image-11-768x334.png 768w" sizes="auto, (max-width: 690px) 100vw, 690px" /></figure>



<p>然后压缩成zip,在arduino里导入</p>



<p><strong>5.测试，创建helloworld话题</strong></p>



<pre class="wp-block-code"><code>#include &lt;ros.h&gt;
#include &lt;std_msgs/String.h&gt;
ros::NodeHandle nh;
std_msgs::String str_msg;
ros::Publisher chatter("chatter", &amp;str_msg);
char hello&#091;13] = "hello world!";
void setup()
{
  nh.initNode();
  nh.advertise(chatter);
}
void loop()
{
  str_msg.data = hello;
  chatter.publish( &amp;str_msg );
  nh.spinOnce();
  delay(1000);
}</code></pre>



<div class="wp-block-media-text alignwide is-stacked-on-mobile"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="500" height="777" src="http://daoker.cc/wp-content/uploads/2022/12/image-12.png" alt="" class="wp-image-837 size-full" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-12.png 500w, https://daoker.cc/wp-content/uploads/2022/12/image-12-193x300.png 193w" sizes="auto, (max-width: 500px) 100vw, 500px" /></figure><div class="wp-block-media-text__content">
<p>然后插入arduino编译上传后把arduino插到树莓派上</p>
</div></div>



<p>接着在树莓派上分别开终端运行</p>



<p>终端1：</p>



<pre class="wp-block-code"><code>roscore</code></pre>



<p>终端2：（这里硬件端口根据实际情况修改）</p>



<pre class="wp-block-code"><code>rosrun rosserial_python serial_node.py /dev/ttyACM0</code></pre>



<p>终端3：</p>



<pre class="wp-block-code"><code>rostopic echo /chatter</code></pre>



<p>如下图</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="529" src="http://daoker.cc/wp-content/uploads/2022/12/image-9-1024x529.png" alt="" class="wp-image-834" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-9-1024x529.png 1024w, https://daoker.cc/wp-content/uploads/2022/12/image-9-300x155.png 300w, https://daoker.cc/wp-content/uploads/2022/12/image-9-768x397.png 768w, https://daoker.cc/wp-content/uploads/2022/12/image-9-1536x794.png 1536w, https://daoker.cc/wp-content/uploads/2022/12/image-9.png 1856w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>参考文章</p>
<cite>https://www.guyuehome.com/38503</cite></blockquote>



<h4 class="wp-block-heading">实测电机参数</h4>



<p>经过实际测试，电机数据和淘宝上以及卖家给的说明书都不太一致（淘宝的坑？），但是应该可以用。</p>



<figure class="wp-block-table"><table><tbody><tr><td>平均每10圈的脉冲数</td><td>5850</td></tr><tr><td>每一圈脉冲数</td><td>585</td></tr><tr><td>轮子直径</td><td>67.5mm</td></tr><tr><td>轮子周长</td><td>212.06mm</td></tr><tr><td>两个脉冲之间，小车前进距离</td><td>0.36mm</td></tr></tbody></table></figure>



<p>知道以上参数，可以编写函数来实现测速，即单位位移除以单位时间。</p>



<h4 class="wp-block-heading">测速函数如下：</h4>



<pre class="wp-block-code"><code>long start_time = millis();//一个计算周期的开始时刻，初始值为 millis();
long interval_time = 50;//一个计算周期 50ms
double current_vel&#091;4];
int32_t start_EncodeTotal&#091;4];//构造用于储存开始时的脉冲数的数组
int32_t current_EncodeTotal&#091;4];//构造用于储存当前脉冲数的数组
void get_current_vel()
{
  long right_now = millis();  
  long past_time = right_now - start_time;//计算逝去的时间
   
  if(past_time &gt;= interval_time){//如果逝去时间大于等于一个计算周期
    //1.禁止中断
   // noInterrupts();
    //2.计算转速 转速单位可以是秒，也可以是分钟... 自定义即可
    WireReadDataArray(MOTOR_ENCODER_TOTAL_ADDR,(uint8_t*)current_EncodeTotal,16);//获取当前的转动量
    for(int i=0;i&lt;4;i++)
    {
      current_vel&#091;i]=(current_EncodeTotal&#091;i]-start_EncodeTotal&#091;i])*0.3625/past_time;//求当前各个轮子的速度，单位m/s
     
    }
    //3.重置计数器
        for(int i=0;i&lt;4;i++)
    {
      start_EncodeTotal&#091;i]=current_EncodeTotal&#091;i];//求当前各个轮子的速度，单位m/s
    }
    //4.重置开始时间
    start_time = right_now;
    //5.重启中断
   // interrupts();

     Serial.println("四个轮子的速度分别为：");
     Serial.print(current_vel&#091;0]);Serial.print("\t");Serial.print(current_vel&#091;1]);Serial.print("\t");Serial.print(current_vel&#091;2]);Serial.print("\t");Serial.print(current_vel&#091;3]);Serial.println("\t");

  }
}</code></pre>



<p>同时在setup中加入以下代码，获取开始时的转动脉冲数</p>



<pre class="wp-block-code"><code> WireReadDataArray(MOTOR_ENCODER_TOTAL_ADDR,(uint8_t*)start_EncodeTotal,16);//获取开始时的转动量</code></pre>



<p>编码器重置函数和各个电机速度控制函数如下</p>



<pre class="wp-block-code"><code>void rest_encode()//编码器计数清零
{
   EncodeTotal&#091;0]=0,EncodeTotal&#091;1]=0,EncodeTotal&#091;2]=0,EncodeTotal&#091;3]=0;//数组赋值0
  WireWriteDataArray(MOTOR_ENCODER_TOTAL_ADDR,(uint8_t*)EncodeTotal,16);//把驱动板上的编码器计数器归0
  start_EncodeTotal&#091;0]=0;start_EncodeTotal&#091;1]=0;start_EncodeTotal&#091;2]=0;start_EncodeTotal&#091;3]=0;//避免清0后第一次测速数据异常
}
void contrl_vel(int16_t *val)                                             //指定各轮的速度，单位：m/s
{//要把速度转化成脉冲数每10ms,result=val*10/0.3625
  int8_t vel2pulse&#091;4]={0,0,0,0};
  for(int i=0;i&lt;4;i++){
    //vel2pulse&#091;i]=val&#091;i]/100/0.3625;//这里会先把val&#091;i]/100取整型再除以0.3625
    vel2pulse&#091;i]=val&#091;i]/36.25;
  }
    WireWriteDataArray(MOTOR_FIXED_SPEED_ADDR,vel2pulse,4);//由于输入的数据只能为整型，控制精度低，后期考虑自己写pid,用pwm
      Serial.println("四个轮子的目标速度（单位mm/s）为：");
     Serial.print(val&#091;0]);Serial.print("\t");Serial.print(val&#091;1]);Serial.print("\t");Serial.print(val&#091;2]);Serial.print("\t");Serial.print(val&#091;3]);Serial.println("\t");
      Serial.println("四个轮子的目标脉冲(单位脉冲/10ms)为：");
     Serial.print(vel2pulse&#091;0]);Serial.print("\t");Serial.print(vel2pulse&#091;1]);Serial.print("\t");Serial.print(vel2pulse&#091;2]);Serial.print("\t");Serial.print(vel2pulse&#091;3]);Serial.println("\t");
     for(int i=0;i&lt;4;i++)
     {
     delay(2000);
     get_current_vel();
     }
}
</code></pre>



<p>上面我们把各个轮子单位时间内的脉冲数转化成了小车各个轮子单位时间内能行进的距离，但是要想让机器人运动，我们还是要进行机器人的运动学分析。</p>



<h4 class="wp-block-heading">机器人运动学分析</h4>



<p>参考文献1：https://mp.weixin.qq.com/s/Fzrpn5_3TB6apqG2Ds1v5Q</p>



<p>参考文献2：王雪松. 四驱轮式移动机器人运动控制系统研究与设计[D].上海电机学院,2016.</p>



<p>把机器人的目标速度转换成每个电机的目标速度，这个叫做机器人的运动学分析。</p>



<p>通过机器人各轮的速度求解出机器人沿X、Y、Z轴方向的速度，叫做运动学<strong>正解</strong>；</p>



<p>通过机器人沿X、Y、Z轴方向的速度求解出机器人各轮的速度，叫做运动学<strong>逆解</strong>。</p>



<div class="wp-block-argon-collapse collapse-block shadow-sm collapsed hide-border-left" style="border-left-color:#ffffff00"><div class="collapse-block-title" style="background-color:#ffffff00"><span class="collapse-block-title-inner">卖家资料</span><i class="collapse-icon fa fa-angle-down"></i></div><div class="collapse-block-body" style="display: none"><img loading="lazy" decoding="async" width="599" height="849" src="http://daoker.cc/wp-content/uploads/2022/11/image-35.png" alt=""><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-36.png" alt=""></div></div>



<p>细看卖家的最后的四个式子感觉推导的有点尴尬，好在通过阅读资料，我做了简化，控制时保证左侧轮子速度一致，右侧轮子速度也一致，就可以简化为两轮差速控制，如下：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-37.png" alt=""><br>&nbsp;<strong>简化正运动学模型</strong>是基于虚拟左右驱动轮的速度来计算几何质心COM的速度，可表示为<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-38.png" alt=""><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>简化逆运动学模型</strong>是基于几何质心COM的速度分解出左右驱动轮的速度，可表示为<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-39.png" alt=""><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;若采用公式（9-10）来描述四轮驱动移动机器人(SSMR)，这里有两个点需要进一步讨论：<br><strong>问题：在正运动学模型（9）中，左右虚拟轮的线速度vl和vr如何得到？</strong><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;根据公式（7）可知，两左（或右）侧轮的纵向线速度大小与左（或右）虚拟轮的线速度相同。但两轮的实际转速可能不同。&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;从简化实际运动控制的角度出发，两左（或右）侧轮应该尽可能保持一致。这里做一个假象实验：若机器人的两前轮的速度相同，两后轮的速度相同，但前后轮的速度不同，机器人肯定是以介于前轮速度和后轮速度之间的某一速度直线运动，可想象前轮拖着后轮加快运动，后轮扯着前轮减缓运动。&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;所以无论前后轮的速度如何变化，机器人只会以一个速度运动，由于前后轮速度不相等会引起轮胎与地面在纵向上同时存在滚动和滑动摩擦，这会加速磨损轮胎，且不利于准确运动控制。所以，<strong>结论是尽可能保持两左（或右）侧轮速度相同</strong>，用数学语言描述就是：、<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-40.png" alt=""><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;式中，[<em>w</em>M1&nbsp;<em>w</em>M2&nbsp;<em>w</em>M3&nbsp;<em>w</em>M4]表示对应轮子的转动角速度，[<em>R</em>M1&nbsp;<em>R</em>M2&nbsp;<em>R</em>M3&nbsp;<em>R</em>M4]表示对应的轮子的半径。<br><strong>4</strong><br><strong>问题：虚拟轮间距dLR具体是多少？</strong><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这也是四轮驱动移动机器人(SSMR)简化模型最难的一个参数，是一个随着工况变化的参数，且无法求得解析解。在参考文献[2]中给出的方法，是引入了无量纲参数<em>γ</em>：<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-41.png" alt=""><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;式中，<em>dwb</em>表示机器人的轮间距。&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;问题则转化为如何求<em>γ</em>，该参数与机器人的总负载、轮胎与地面的相对摩擦系数、转弯半径及质心位置都是有关系，是一个非常复杂的参数，所以常用的方法就是做实验，控制不再改动的机器人在特定地面上做差速转向运动，采集到多组实验数据后，拟合估算<em>γ</em>。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;再回过头来看四轮驱动移动机器人(SSMR)运动学模型，基于公式（9-12）可知<br><strong>正运动学模型</strong>为<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-42.png" alt=""><br><strong>逆运动学模型</strong>为<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/11/image-43.png" alt=""></cite></blockquote>



<h4 class="wp-block-heading">踩坑</h4>



<p>1.断电时一般先断开arduino和上位机（电脑或者树莓派的连线），再断开驱动板和电池的连线，特别是电机失控，要断电急停的时候，否则可能导致驱动板损坏。</p>



<p>2.arduino可以给驱动板供电，驱动板也可以给反向给arduino供电，不知道我是不是在某次操作中，进行了1中的操作，后来发觉驱动板不能给arduino供电了，检查原来是r如下图，重新在后面5v标注的焊点处焊上排针，耶，也可以用。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="768" height="1024" src="http://daoker.cc/wp-content/uploads/2022/12/image-3-768x1024.png" alt="" class="wp-image-805" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-3-768x1024.png 768w, https://daoker.cc/wp-content/uploads/2022/12/image-3-225x300.png 225w, https://daoker.cc/wp-content/uploads/2022/12/image-3-1152x1536.png 1152w, https://daoker.cc/wp-content/uploads/2022/12/image-3.png 1440w" sizes="auto, (max-width: 768px) 100vw, 768px" /></figure>



<p>3.不知道是不是1的操作还把SCL 和SDA的上拉电阻给烧了（新板在路上，保险起见，又买了块），在调试程序中，发现<a href="https://arduino.stackexchange.com/questions/5339/wire-endtransmission-hangs">Wire.endTransmission()&nbsp;</a>函数无响应，并且是间歇性出现，有时重新断开所有电源好了，但是过一会又死在那，然后查了资料（https://arduino.stackexchange.com/questions/5339/wire-endtransmission-hangs），把SCL 和SDA外接一个4.7K的5V上拉电阻，貌似再也没有卡住了</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="897" height="499" src="http://daoker.cc/wp-content/uploads/2022/12/image-5.png" alt="" class="wp-image-807" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-5.png 897w, https://daoker.cc/wp-content/uploads/2022/12/image-5-300x167.png 300w, https://daoker.cc/wp-content/uploads/2022/12/image-5-768x427.png 768w" sizes="auto, (max-width: 897px) 100vw, 897px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" src="http://daoker.cc/wp-content/uploads/2022/12/image-6.png" alt="" class="wp-image-808" width="477" height="192" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-6.png 693w, https://daoker.cc/wp-content/uploads/2022/12/image-6-300x121.png 300w" sizes="auto, (max-width: 477px) 100vw, 477px" /></figure>
</div>
</div>
</div>
</div>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="461" src="http://daoker.cc/wp-content/uploads/2022/12/7048C8GQXD26Z9JI2KV4-1024x461.jpg" alt="" class="wp-image-809" srcset="https://daoker.cc/wp-content/uploads/2022/12/7048C8GQXD26Z9JI2KV4-1024x461.jpg 1024w, https://daoker.cc/wp-content/uploads/2022/12/7048C8GQXD26Z9JI2KV4-300x135.jpg 300w, https://daoker.cc/wp-content/uploads/2022/12/7048C8GQXD26Z9JI2KV4-768x346.jpg 768w, https://daoker.cc/wp-content/uploads/2022/12/7048C8GQXD26Z9JI2KV4-1536x691.jpg 1536w, https://daoker.cc/wp-content/uploads/2022/12/7048C8GQXD26Z9JI2KV4-2048x922.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>4.自带的示例程序里pwm控制根本没用，轮子只是抖一下，我以为是这板子太垃圾，不支持，于是就用了他的闭环控制，然后我自己又套了层PID，相当于套了2层PID,反应慢。突然我想起了，或许他示例的pwm只是给个脉冲，用循环持续的给脉冲，不就可以了吗，改了下，果真可以。唉，基础不牢，地动山摇。</p>



<p>5.但是使用4中的pwm控制发觉不能低速，最低速度也五秒3米左右，远高于试验要求，然后使用自带的闭环控制，速度可以慢一点，但是还是偏大（整体来说，买的电机减速比偏低），无奈，只能更改ros-arduino-bridge里的pid函数了</p>



<p>6.发觉就算加了上拉电阻，也会卡死，嗐，等新驱动板到了再试</p>



<p>7.为了验证是否是因为基于wire.h库的I2c挂起导致ros_arduino_bridge无法连接，编译上传不含有i2c的ros_arduino_bridge项目到arduino试试。经验证，果真是，嗐，卖家配的示例真坑</p>



<p>8.使用github另外一位大佬改编的IC2库（https://github.com/rambo/I2C），成功移植之前的代码，然后成功连接，现在就是改ros_arduino_python里的关于pid的代码，把他屏蔽掉就行</p>



<p>9.实测在连接书梅派串口后，i2c总线会卡死，导致连接不上，就算使用了新的I2C.h库，超时可以跳出，可以连接上，但三仍然功能不正常，暂时找不到解决方案，时间关系，以后再研究。</p>



<p>10.问题解决，arduino的供电来自树莓派，驱动板的供电来自电源，两个之前的vcc不用连接，连接gnd即可，在arduino程序启动的时候可以先不建立I2c通信，后面再初始话硬件然后通信</p>



<h2 class="wp-block-heading">控制系统</h2>



<p>使用ros控制电机转动从而控制小车运动，之前尝试有太多bug,并且也不是我试验的重点，花大量时间不值得，所以就修改为使用蓝牙遥控器控制，和arduino上的驱动系统相集成，因此arduino上一共集成了以下代码：I2C通信代码    红外控制电机代码   ros_arduino_bridge代码</p>



<p>首先，检测遥控器的按键编码：（灯没必要接，接好红外传感器就行）</p>



<div  class='collapse-block shadow-sm collapse-block-transparent collapsed hide-border-left'><div class='collapse-block-title'><i class='fa fa-flag'></i> <span class='collapse-block-title-inner'>遥控器按键编码代码内容过长，点击展开</span><i class='collapse-icon fa fa-angle-down'></i></div><div class='collapse-block-body' style='display:none;'></p>



<pre class="wp-block-code"><code>#include &lt;IRremote.h&gt;
int RECV_PIN = 11;
int LED1 = 2;
int LED2 = 3;
int LED3 = 4;
int LED4 = 5;
int LED5 = 6;
int LED6 = 7;
long on1  = 0x00FFA25D;//1
long off1 = 0x00FFE01F;
long on2 = 0x00FF629D;//2
long off2 = 0x00FFA857;
long on3 = 0x00FFE21D;//3
long off3 = 0x00FF906F;
long on4 = 0x00FF22DD;
long off4 = 0x00FF6897;
long on5 = 0x00FF02FD;//5
long off5 = 0x00FF9867;
long on6 = 0x00FFC23D;
long off6 = 0x00FFB04F;
IRrecv irrecv(RECV_PIN);
decode_results results;
// Dumps out the decode_results structure.
// Call this after IRrecv::decode()
// void * to work around compiler issue
//void dump(void *v) {
//  decode_results *results = (decode_results *)v
void dump(decode_results *results) {
  int count = results-&gt;rawlen;
  if (results-&gt;decode_type == UNKNOWN)
  {
    Serial.println("Could not decode message");
  }
  else
  {
    if (results-&gt;decode_type == NEC)
    {
      Serial.print("Decoded NEC: ");
    }
    else if (results-&gt;decode_type == SONY)
    {
      Serial.print("Decoded SONY: ");
    }
    else if (results-&gt;decode_type == RC5)
    {
      Serial.print("Decoded RC5: ");
    }
    else if (results-&gt;decode_type == RC6)
    {
      Serial.print("Decoded RC6: ");
    }
    Serial.print(results-&gt;value, HEX);
    Serial.print(" (");
    Serial.print(results-&gt;bits, DEC);
    Serial.println(" bits)");
  }
  Serial.print("Raw (");
  Serial.print(count, DEC);
  Serial.print("): ");

  for (int i = 0; i &lt; count; i++)
  {
    if ((i % 2) == 1) {
      Serial.print(results-&gt;rawbuf&#091;i]*USECPERTICK, DEC);
    }
    else
    {
      Serial.print(-(int)results-&gt;rawbuf&#091;i]*USECPERTICK, DEC);
    }
    Serial.print(" ");
  }
  Serial.println("");
}

void setup()
{
  pinMode(RECV_PIN, INPUT);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
  pinMode(LED4, OUTPUT);
  pinMode(LED5, OUTPUT);
  pinMode(LED6, OUTPUT);
  pinMode(13, OUTPUT);
  Serial.begin(9600);

  irrecv.enableIRIn(); // Start the receiver
}

int on = 0;
unsigned long last = millis();

void loop()
{
  if (irrecv.decode(&amp;results))
  {
    // If it's been at least 1/4 second since the last
    // IR received, toggle the relay
    if (millis() - last &gt; 250)
    {
      on = !on;
      //       digitalWrite(8, on ? HIGH : LOW);
      digitalWrite(13, on ? HIGH : LOW);
      dump(&amp;results);
    }
    if (results.value == on1 )
    {
      Serial.println("1号键");
      digitalWrite(LED1, HIGH);
    }
    if (results.value == off1 )
    {
      digitalWrite(LED1, LOW); Serial.println("2号键");
    }
    if (results.value == on2 )
    {
      digitalWrite(LED2, HIGH); Serial.println("3号键");
    }
    if (results.value == off2 )
    {
      digitalWrite(LED2, LOW); Serial.println("4号键");
    }
    if (results.value == on3 )
    {
      digitalWrite(LED3, HIGH); Serial.println("5号键");
    }
    if (results.value == off3 )
      digitalWrite(LED3, LOW);
    if (results.value == on4 )
      digitalWrite(LED4, HIGH);
    if (results.value == off4 )
      digitalWrite(LED4, LOW);
    if (results.value == on5 )
      digitalWrite(LED5, HIGH);
    if (results.value == off5 )
      digitalWrite(LED5, LOW);
    if (results.value == on6 )
      digitalWrite(LED6, HIGH);
    if (results.value == off6 )
      digitalWrite(LED6, LOW);
    last = millis();
    irrecv.resume(); // Receive the next value
  }
}</code></pre>



<p></div></div>



<p>然后编写控制逻辑代码，最后集成的时候在相关地方加入控制函数即可</p>



<pre class="wp-block-code"><code>#include &lt;IRremote.h&gt;
int RECV_PIN = 11;//11号引脚为数据引脚
long accelerate = 0x00FF629D;//2号数字键,前进/加速
long decelerate = 0x00FF02FD;//5号键，后退/减速
long  left=  0x00FFA25D;//1号数字键，左转
long right=  0x00FFE21D;//3号数字键，右转
long carstop =  0x00FF38C7;//停止，OK键
IRrecv irrecv(RECV_PIN);
decode_results results;
void setup() {
pinMode(RECV_PIN, INPUT);
Serial.begin(9600);
irrecv.enableIRIn(); // Start the receiver
}
int on = 0;
unsigned long last = millis();
void loop() {
   if (irrecv.decode(&amp;results))
  {
    // If it's been at least 1/4 second since the last
    // IR received, toggle the relay
    if (millis() - last &gt; 250)
    {
      on = !on;
      digitalWrite(13, on ? HIGH : LOW);
      //dump(&amp;results);
    }
    if (results.value == accelerate )
    {
      Serial.println("前进");
     
    }
    if (results.value == decelerate )
    {
      Serial.println("后退");
    }
    if (results.value == left )
    {
     Serial.println("左转");
    }
    if (results.value == right )
    {
      Serial.println("右转");
    }
    if (results.value == carstop )
    {
      Serial.println("停止");
    }
    last = millis();
    irrecv.resume(); // Receive the next value
  }
}</code></pre>



<h2 class="wp-block-heading">传感系统</h2>



<div class="wp-block-argon-collapse collapse-block shadow-sm collapsed hide-border-left" style="border-left-color:#ffffff00"><div class="collapse-block-title" style="background-color:#ffffff00"><span class="collapse-block-title-inner">查看局域网其他计算机ip</span><i class="collapse-icon fa fa-angle-down"></i></div><div class="collapse-block-body" style="display: none">windows:<br>以管理员身份运行cmd输入net view后回车，即可查看到自己的计算机所在的局域网内的其他计算机的计算机名<br>ping 计算机名 ，可以查到其ip<br>或者使用局域网查看工具：<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/12/image-1024x674.png" alt=""><br>ubuntu:<br>使用sudo apt-get install <a href="https://so.csdn.net/so/search?q=arp&amp;spm=1001.2101.3001.7020">arp</a>-scan 安装(确保网络链接正常)<br><br>利用ifconfig 或ip addr 查看本机的ip地址,一般有线在interface en0/eth0, 无线在wlan0上.<br>ifconfig<br>然后使用arp-scan -I wlo1 &#8211;localnet 或 sudo arp-scan -I wlo1 &#8211;localnet 就可以查看同一局域网的其他设备<br>sudo arp-scan -I wlp3s0 &#8211;localnet<br>wlp3s0换成自己的网口名<br><img decoding="async" src="http://daoker.cc/wp-content/uploads/2022/12/截图.png" alt=""><br></div></div>



<pre class="wp-block-code"><code>sudo apt install arp-scan -y</code></pre>



<pre class="wp-block-code"><code>sudo arp-scan -I wlp3s0 --localnet</code></pre>



<p>ubuntu镜像下载：http://cdimage.ubuntu.com/releases/</p>



<p>由于我用到的ros arduino bridge 不支持我的ros-noetic,所以树莓派上必须安装ros-melodic,笔记本上可以用原来的不变。而ros-melodic是在ubuntu18.04环境下，故树莓派的安装版本为：ubuntu18.04+rosmelodic,但是官方的软件刷ubuntu18.04设置得wifi不生效，于是https://blog.csdn.net/qq_30613365/article/details/120739069</p>



<h3 class="wp-block-heading">树莓派上部署ROS：</h3>



<div class="wp-block-argon-github github-info-card card shadow-sm github-info-card-full" data-author="fishros" data-project="install"><div class="github-info-card-header"><a href="https://github.com/" target="_blank" title="Github" rel="noopener"><span><i class="fa fa-github"></i> GitHub</span></a></div><div class="github-info-card-body"><div class="github-info-card-name-a"><a href="https://github.com/fishros/install" target="_blank" rel="noopener"><span class="github-info-card-name">fishros/install</span></a></div><div class="github-info-card-description"></div></div><div class="github-info-card-bottom"><span class="github-info-card-meta github-info-card-meta-stars"><i class="fa fa-star"></i> <span class="github-info-card-stars"></span></span><span class="github-info-card-meta github-info-card-meta-forks"><i class="fa fa-code-fork"></i> <span class="github-info-card-forks"></span></span></div></div>



<p>一行代码搭建机器人开发环境(ROS/ROS2/ROSDEP)</p>



<pre class="wp-block-code"><code>wget http://fishros.com/install -O fishros &amp;&amp; . fishros</code></pre>



<p>使用树莓派官方的工具刷入ubuntu20.04，刷入之前设置好用户名、密码、wifi等信息（这里我设置成收手机的，因为以后试验也是用手机共享热点当成无线路由器），同时记得打开ssh，为了方便远程连接(这里主要是不想电脑也连接手机wifi)，我们进入系统后首先安装zerotier，然后再使用上面小鱼的一键安装命令安装无桌面版ros，安装结束后添加到环境变量：</p>



<pre class="wp-block-code"><code>echo "source /opt/ros/melodic/setup.bash" &gt;&gt; ~/.bashrc
source ~/.bashrc</code></pre>



<p>然后用小鱼工具箱安装rosdepc</p>



<pre class="wp-block-code"><code>rosdepc init
rosdepc update</code></pre>



<p>最后roscore启动，没错误，ros环境部署成功。</p>



<p><a href="https://blog.csdn.net/weixin_47932709/article/details/108456635" target="_blank" rel="noreferrer noopener">树莓派配置网络出现问题之解决办法</a></p>



<h4 class="wp-block-heading">踩坑</h4>



<p>创建工作空间并初始化</p>



<p>mkdir -p field_trial/src</p>



<p>cd field_trial/</p>



<pre class="wp-block-code"><code>catkin_make</code></pre>



<p>如果提示没安装catkin，安装又发现和ros的依赖冲突，这里可以先卸载ros然后先安装catkin，再安装ros</p>



<p>1.catkin_make报错：<strong>IOError: [Errno 13] Permission denied:</strong></p>



<p><a href="https://answers.ros.org/question/246820/ros_arduino_bridge-reconnection-error/">https://answers.ros.org/question/246820/ros_arduino_bridge-reconnection-error/</a></p>



<p><a href="https://blog.csdn.net/gongdiwudu/article/details/123983828">https://blog.csdn.net/gongdiwudu/article/details/123983828</a></p>



<p>原因是创建工作空间时用了sudo或者在root下，解决方案：</p>



<pre class="wp-block-code"><code>sudo chown $USER: -R /home/pi/ros_catkin_ws</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="707" height="281" src="http://daoker.cc/wp-content/uploads/2022/12/image-7.png" alt="" class="wp-image-812" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-7.png 707w, https://daoker.cc/wp-content/uploads/2022/12/image-7-300x119.png 300w" sizes="auto, (max-width: 707px) 100vw, 707px" /></figure>



<p>2.diagnostic_updater“,CMake did not find diagnostic_updater.</p>



<p>缺少相应的依赖包，可以：</p>



<pre class="wp-block-code"><code>sudo apt-cache search diagnostics updater #在apt源里寻找同样名字的包</code></pre>



<p>根据返回安装合适的依赖包</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" src="http://daoker.cc/wp-content/uploads/2022/12/image-8.png" alt="" class="wp-image-815" width="740" height="182" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-8.png 740w, https://daoker.cc/wp-content/uploads/2022/12/image-8-300x74.png 300w" sizes="auto, (max-width: 740px) 100vw, 740px" /></figure>



<pre class="wp-block-code"><code>sudo apt install ros-humble-diagnostic-updater</code></pre>



<h3 class="wp-block-heading">激光雷达相关</h3>



<p>连接上激光雷达，并认当前的 USB 转串口终端并修改权限，</p>



<h4 class="wp-block-heading">USB查看命令:</h4>



<pre class="wp-block-code"><code>ll /dev/ttyUSB*</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="621" height="58" src="http://daoker.cc/wp-content/uploads/2022/11/image-21.png" alt="" class="wp-image-701" srcset="https://daoker.cc/wp-content/uploads/2022/11/image-21.png 621w, https://daoker.cc/wp-content/uploads/2022/11/image-21-300x28.png 300w" sizes="auto, (max-width: 621px) 100vw, 621px" /></figure>



<p>如果找不到，可能是数据线问题，换一根数据线即可。</p>



<p>使用</p>



<pre class="wp-block-code"><code>ls -l /dev/ttyUSB0</code></pre>



<p>发现，当前用户没有使用它的权限，因此要添加权限</p>



<pre class="wp-block-code"><code>sudo usermod -a -G dialout 你的用户名</code></pre>



<p>之后要重启才能生效</p>



<p>由于本次试验有多个外接设备，为了不造成串口混乱，可以绑定串口设备</p>



<div data-wp-interactive="core/file" class="wp-block-file"><object data-wp-bind--hidden="!state.hasPdfPreview" hidden class="wp-block-file__embed" data="http://daoker.cc/wp-content/uploads/2022/12/ubuntu绑定串口设备.pdf" type="application/pdf" style="width:100%;height:690px" aria-label="嵌入 ubuntu绑定串口设备"></object><a id="wp-block-file--media-2d22ab73-5a0d-4fd0-a0a2-daa0d7c29151" href="http://daoker.cc/wp-content/uploads/2022/12/ubuntu绑定串口设备.pdf">ubuntu绑定串口设备</a><a href="http://daoker.cc/wp-content/uploads/2022/12/ubuntu绑定串口设备.pdf" class="wp-block-file__button wp-element-button" download aria-describedby="wp-block-file--media-2d22ab73-5a0d-4fd0-a0a2-daa0d7c29151">下载</a></div>



<h4 class="wp-block-heading">雷达驱动包安装：</h4>



<p>进入src工作空间，然后下载驱动包</p>



<pre class="wp-block-code"><code>git clone https://github.com/slamtec/rplidar_ros</code></pre>



<p>返回工作空间，并catkin_make编译，并<code>source ./devel/setup.bash</code>，把功能包里的launch文件的设备端口改成之前映射好的端口。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="710" height="174" src="http://daoker.cc/wp-content/uploads/2022/12/image-1.png" alt="" class="wp-image-795" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-1.png 710w, https://daoker.cc/wp-content/uploads/2022/12/image-1-300x74.png 300w" sizes="auto, (max-width: 710px) 100vw, 710px" /></figure>



<h4 class="wp-block-heading">测试</h4>



<p>启动驱动包里的例程，如果不报错且能正确获取到当前雷达的信息，则没问题，我这里暂时没有做分布式，树莓派上安装的是无桌面版本，所以就不能通过rviz查看。</p>



<pre class="wp-block-code"><code>roslaunch rplidar_ros rplidar.launch</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="720" height="243" src="http://daoker.cc/wp-content/uploads/2022/11/image-22.png" alt="" class="wp-image-702" srcset="https://daoker.cc/wp-content/uploads/2022/11/image-22.png 720w, https://daoker.cc/wp-content/uploads/2022/11/image-22-300x101.png 300w" sizes="auto, (max-width: 720px) 100vw, 720px" /></figure>



<p>通过 rostopic list和rostopic echo /scan也能看到相关信息输出</p>



<h3 class="wp-block-heading">IMU相关</h3>



<p>参考资料：https://blog.csdn.net/ganjb1/article/details/118153554</p>



<p>参考项目：https://github.com/COONEO/Arduino_Jetson_nano_ROS_Car/tree/melodic/Jetson_nano_ROS_code/catkin_ws/src/imu_901</p>



<p><a href="https://www.cirmall.com/articles/35838/" target="_blank" rel="noreferrer noopener">如何将 IMU （惯性测量单元） 传感器与 Arduino 对接</a></p>



<p><a rel="noreferrer noopener" href="https://blog.csdn.net/COONEO/article/details/126271853" target="_blank">开源！手把手教你搭建Arduino+英伟达Jetson的ROS小车（中）</a></p>



<p><a href="https://blog.csdn.net/zhuoyueljl/article/details/75453808">https://blog.csdn.net/zhuoyueljl/article/details/75453808</a></p>



<p>1.在windows电脑上使用商家提供的上位机软件设置IMU参数</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="717" height="612" src="http://daoker.cc/wp-content/uploads/2022/12/QQ截图20221210121627.jpg" alt="" class="wp-image-852" srcset="https://daoker.cc/wp-content/uploads/2022/12/QQ截图20221210121627.jpg 717w, https://daoker.cc/wp-content/uploads/2022/12/QQ截图20221210121627-300x256.jpg 300w" sizes="auto, (max-width: 717px) 100vw, 717px" /></figure>



<p>2.然后把设备插到ubuntu设备的usb口上</p>



<p>安装串口功能包：</p>



<pre class="wp-block-code"><code>sudo apt-get install ros-melodic-serial</code></pre>



<p>然后创建imu信息读取解析的相关功能包（参考教程：https://blog.csdn.net/qqliuzhitong/article/details/114384297）</p>



<pre class="wp-block-code"><code>catkin_create_pkg serial_demo roscpp serial</code></pre>



<p>能再屏幕上打印后根据芯片的开发手册对串口数据进行解码，最后编写数据发布代码发布/imu</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="872" height="911" src="http://daoker.cc/wp-content/uploads/2022/12/image-14.png" alt="" class="wp-image-854" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-14.png 872w, https://daoker.cc/wp-content/uploads/2022/12/image-14-287x300.png 287w, https://daoker.cc/wp-content/uploads/2022/12/image-14-768x802.png 768w" sizes="auto, (max-width: 872px) 100vw, 872px" /></figure>



<p>相关代码如下：</p>



<div  class='collapse-block shadow-sm collapse-block-transparent collapsed hide-border-left'><div class='collapse-block-title'><i class='fa fa-flag'></i> <span class='collapse-block-title-inner'>内容过长，点击展开</span><i class='collapse-icon fa fa-angle-down'></i></div><div class='collapse-block-body' style='display:none;'></p>



<p><p class="reply-to-read">温馨提示: 此处内容需要在文末<a href="#respond" title="评论本文">评论本文</a>并刷新页面后才能查看.</p></p>



<p></div></div>



<h3 class="wp-block-heading">编码器相关</h3>



<p>我的是arduino接收编码器的信息，然后arduino再通过ros_arduino_bridge和编码器交互，在ros端，我主要使用的是ros_arduino_python 下的arduino_drive.py.</p>



<p>查看权限</p>



<pre class="wp-block-code"><code>ls -l /dev/ttyACM0</code></pre>



<p>授权（前面雷达部分已经弄过就可以不弄了）</p>



<pre class="wp-block-code"><code>sudo usermod -a -G dialout your_user_name</code></pre>



<p>ros_arduino_bridge是依赖于python-serial功能包的</p>



<pre class="wp-block-code"><code>sudo apt-get install python-serial</code></pre>



<p>在ros_arduino_msg中添加自定义编码器消息</p>



<pre class="wp-block-code"><code>Header header
int16 voltage
int32&#091;4] enc</code></pre>



<p>然后按照ros自定义消息的步骤进行配置和编译，最后在arduino_drive.py中进行解析和发布话题</p>



<div  class='collapse-block shadow-sm collapse-block-transparent collapsed hide-border-left'><div class='collapse-block-title'><i class='fa fa-flag'></i> <span class='collapse-block-title-inner'>内容过长，点击展开</span><i class='collapse-icon fa fa-angle-down'></i></div><div class='collapse-block-body' style='display:none;'></p>



<p><p class="reply-to-read">温馨提示: 此处内容需要在文末<a href="#respond" title="评论本文">评论本文</a>并刷新页面后才能查看.</p></p>



<p></div></div>



<h3 class="wp-block-heading">odom里程计</h3>



<p>odom里程计数据由编码器和imu数据融合而成，https://blog.csdn.net/baimei4833953/article/details/80768762</p>



<p>imu tool:<br>http://wiki.ros.org/imu_complementary_filter?distro=noetic</p>



<p>论文：https://www.mdpi.com/1424-8220/15/8/19302</p>



<p>两种融合的方法<br>（1） 一种简单的方法<br>从imu得到的数据为一个相对角度(主要使用yaw，roll和pitch 后面不会使用到)，使用该角度来替代由编码器计算得到的角度。<br>这个方法较为简单，出现打滑时候因yaw不会受到影响，即使你抬起机器人转动一定的角度，得到的里程也能正确反映出来</p>



<p>（2）扩展的卡尔曼滤波<br>官方提供了个扩展的卡尔曼滤波的包robot_pose_ekf，robot_pose_ekf开启扩展卡尔曼滤波器生成机器人姿态，支持</p>



<p>odom（编码器）<br>imu_data（IMU）<br>vo（视觉里程计）<br>还可以支持GPS<br>引用官方图片<br></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="777" height="922" src="http://daoker.cc/wp-content/uploads/2022/12/image-15.png" alt="" class="wp-image-871" srcset="https://daoker.cc/wp-content/uploads/2022/12/image-15.png 777w, https://daoker.cc/wp-content/uploads/2022/12/image-15-253x300.png 253w, https://daoker.cc/wp-content/uploads/2022/12/image-15-768x911.png 768w" sizes="auto, (max-width: 777px) 100vw, 777px" /></figure>



<p>chatGPT真是个神器：</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="266" height="1024" src="http://daoker.cc/wp-content/uploads/2022/12/imu和编码器数据融合-266x1024.png" alt="" class="wp-image-876" srcset="https://daoker.cc/wp-content/uploads/2022/12/imu和编码器数据融合-266x1024.png 266w, https://daoker.cc/wp-content/uploads/2022/12/imu和编码器数据融合-78x300.png 78w, https://daoker.cc/wp-content/uploads/2022/12/imu和编码器数据融合-768x2958.png 768w, https://daoker.cc/wp-content/uploads/2022/12/imu和编码器数据融合-399x1536.png 399w, https://daoker.cc/wp-content/uploads/2022/12/imu和编码器数据融合-532x2048.png 532w, https://daoker.cc/wp-content/uploads/2022/12/imu和编码器数据融合.png 1920w" sizes="auto, (max-width: 266px) 100vw, 266px" /></figure>



<h2 class="wp-block-heading">集成</h2>



<h3 class="wp-block-heading">TF坐标变换</h3>



<p>预备知识：<a href="https://www.jianshu.com/p/cb99188fec49">https://www.jianshu.com/p/cb99188fec49</a></p>



<p>在launch文件中增加以下节点的代码：</p>



<pre class="wp-block-code"><code>&lt;node pkg="tf2_ros" type="static_transform_publisher" name="imu_transform_publisher" args="0 0 0 0 0 0 base_link imu_link 100" /&gt;
</code></pre>



<pre class="wp-block-code"><code>&lt;node pkg="tf2_ros" type="static_transform_publisher" name="imu_static_transform_publisher" args="x y z yaw pitch roll base_link imu_link 100" /></code></pre>



<h3 class="wp-block-heading">ROS std_msgs/Header 数据含义</h3>



<p><a href="https://blog.csdn.net/qq_18676517/article/details/109270525">https://blog.csdn.net/qq_18676517/article/details/109270525</a></p>



<p>创建功能包</p>



<pre class="wp-block-code"><code>catkin_create_pkg integration roscpp rospy std_msgs ros_arduino_python usb_cam rplidar_ros</code></pre>



<p>功能包下创建launch文件夹，launch文件夹中新建run.launch文件,内容如下：</p>



<pre class="wp-block-code"><code>&lt;!-- 机器人启动文件：
        1.启动底盘
        2.启动激光雷达
        3.IMU
        4.启动编码器
        5.启动odom
        6.录制数据
 --&gt;
&lt;launch&gt;
        &lt;!-- &lt;include file="$(find ros_arduino_python)/launch/arduino.launch" /&gt; --&gt;
        &lt;!-- &lt;include file="$(find usb_cam)/launch/usb_cam-test.launch" /&gt; --&gt;
         &lt;!-- 2.启动激光雷达 --&gt;
        &lt;include file="$(find rplidar_ros)/launch/rplidar.launch" /&gt;
        &lt;!-- 6.录制数据 --&gt;
        &lt;!-- &lt;node pkg="rosbag" type="record" name="record" args="/imu /odom /scan /tf /tf_static  -o /home/daoker/vscode_ws/src/sugarcane_car/bag/onece.bag"/&gt; --&gt;
        &lt;node pkg="rosbag" type="record" name="record" args="/scan   -o /home/daoker/field_trial/src/integration/bag/onece.bag"/&gt;
         &lt;node pkg="tf2_ros" type="static_transform_publisher" name="static_transform_publisher" args="0 0 0 -1.57 0 -1.57 /front_bumper_link /front_bumper_link_depth" /&gt;
    &lt;node pkg="tf" type="static_transform_publisher" name="odom_to_world" args="0.0 0.0 0.0 0 0 0.0 /odom /world 1000"/&gt;
    &lt;node pkg="rosbag" type="record" name="record" args="/imu /odom /scan /tf /tf_static  -o /home/daoker/vscode_ws/src/sugarcane_car/bag/onece.bag"/&gt;

&lt;/launch&gt;
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://daoker.cc/daokerto695.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>基于二维激光雷达的蔗田垄高检测研究——仿真和算法研究</title>
		<link>https://daoker.cc/daokerto738.html</link>
					<comments>https://daoker.cc/daokerto738.html#comments</comments>
		
		<dc:creator><![CDATA[博主]]></dc:creator>
		<pubDate>Fri, 25 Nov 2022 04:28:17 +0000</pubDate>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[ROS]]></category>
		<category><![CDATA[单片机]]></category>
		<category><![CDATA[机器人]]></category>
		<category><![CDATA[机器视觉]]></category>
		<category><![CDATA[树莓派]]></category>
		<guid isPermaLink="false">https://daoker.cc/?p=738</guid>

					<description><![CDATA[很久之前就做了，等有空再整理，先挖坑。]]></description>
										<content:encoded><![CDATA[
<p>很久之前就做了，等有空再整理，先挖坑。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://daoker.cc/daokerto738.html/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>数值分析Jacobi迭代法c/c++实现</title>
		<link>https://daoker.cc/daokerto30.html</link>
					<comments>https://daoker.cc/daokerto30.html#respond</comments>
		
		<dc:creator><![CDATA[博主]]></dc:creator>
		<pubDate>Fri, 25 Sep 2020 12:37:00 +0000</pubDate>
				<category><![CDATA[机器视觉]]></category>
		<guid isPermaLink="false">https://daoker.eu.org/?p=30</guid>

					<description><![CDATA[matlab还没学精通，正好好久没写代码了，借此温习温习。]]></description>
										<content:encoded><![CDATA[
<p><a rel="noreferrer noopener" href="https://so.csdn.net/so/search?q=matlab&amp;spm=1001.2101.3001.7020" target="_blank">matlab</a>还没学精通，正好好久没写代码了，借此温习温习。</p>



<pre class="wp-block-code"><code>//Jacobi.cpp
#include "Jacobi.h"
#include &lt;iostream>
#include &lt;vector>
#include &lt;stdlib.h>
using namespace std;
using std::vector;
vector&lt;double> matrix_A;
vector&lt;double> vector_b;

Jacobi::Jacobi()
{
	cout &lt;&lt; "输入方阵维数：";
	cin >> order;
	std::cout &lt;&lt; order &lt;&lt; "阶矩阵构造成功" &lt;&lt; endl; 
}



void Jacobi::input_matrix_A()
{	//由于数组在编译阶段就已经分配内存，维度必须是常量，故此处用向量来存储矩阵
	//初始化为order*order个int元素，没个元素都被初始化为0
	
	double elemt = 0;
	for (int i = 0; i &lt;= order * order - 1; i++)
	{
		cout &lt;&lt; "输入第" &lt;&lt; i + 1 &lt;&lt; "个元素" &lt;&lt; endl;
		cin >> elemt;
		matrix_A.push_back(elemt);
	}
	pointer_A = &amp;matrix_A&#91;0];
	cout &lt;&lt; "输入的矩阵为" &lt;&lt; endl;
	output_matrix(pointer_A);
	
}

void Jacobi::input_vector_b()
{
	double element;
	for (int i = 0; i &lt; order; i++)
	{
		cout &lt;&lt; "请输入第" &lt;&lt; i + 1 &lt;&lt; "个b元素" &lt;&lt; endl;
		cin >> element;
		vector_b.push_back(element);//构造成Ax-b=0
	}
	pointer_b = &amp;vector_b&#91;0];
	cout &lt;&lt; "输入的列向量为" &lt;&lt; endl;
	output_vector(pointer_b);
	
}
void Jacobi::structure_iterator()
{
	
	
	
	for (int i = 0; i &lt;= order - 1; i++)
	{
		int denominator = pointer_A&#91;i * order + i];
		pointer_b&#91;i] = (pointer_b&#91;i]) / denominator;
		for (int j = 0; j &lt;= order - 1; j++)
		{
			pointer_A&#91;i * order + j] = -(pointer_A&#91;i * order + j]) / denominator;
		}
		pointer_A&#91;i * order + i] = 0;
		
	}
	cout &lt;&lt; "绝对误差限";
	double e;
	cin >> e;
	vector&lt;double> vector_xk(order, 0);
	int n = 0,k=0;//n为满足绝对误差的解的个数,k为迭代次数
	do
	{
		n = 0;
		vector&lt;double> vector_xk1(order, 0);//每次都初始化k+1
		
		for (int i = 0; i &lt; order; i++)
		{
			for (int j = 0; j &lt; order; j++)
			{
				vector_xk1&#91;i] += (vector_xk&#91;j] * pointer_A&#91;i * order + j]);
			}
			vector_xk1&#91;i] += pointer_b&#91;i];
		}
		
		for (int i = 0; i &lt; order; i++)
		{
			
			if (abs(vector_xk&#91;i]-vector_xk1&#91;i]) &lt; e) 
			{
				n++;
			}

		}
		vector_xk = vector_xk1;
		k++;
		cout &lt;&lt; "k=" &lt;&lt; k &lt;&lt; endl;
		output_vector(&amp;vector_xk&#91;0]);
	} while (n&lt;order);

}
void Jacobi::output_matrix(double* p)
{
	double* pointer = p;
	for (int i = 0; i &lt;= order - 1; i++)
	{
		for (int j = 0; j &lt;= order - 1; j++)
		{
			cout &lt;&lt; pointer&#91;i * order + j] &lt;&lt; "\t";
		}
		cout &lt;&lt; endl;
	}

}

void Jacobi::output_vector(double* q)
{
	double* pointer = q;
	for (int i = 0; i &lt; order; i++)
	{
		cout &lt;&lt; pointer&#91;i] &lt;&lt; "\t";
	}
	cout &lt;&lt; endl;
}```

```cpp
//Jacobi.h
#ifndef _JACOBI_H_
#define _JACOBI_H_

class Jacobi
{
public:
	void input_matrix_A(); //输入系数矩阵A
	void input_vector_b();//输入AX=b中的向量b
	void structure_iterator();//构造迭代公式
	void output_matrix(double* p);//输出矩阵
	void output_vector(double* q);//输出向量
	

	Jacobi();
private:
	
	double*  pointer_A = 0;//指向系数矩阵的指针
	double* pointer_b = 0;//指向b的指针
	int order = 0; //矩阵阶数
};

#endif
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://daoker.cc/daokerto30.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>powell法c/c++程序</title>
		<link>https://daoker.cc/daokerto32.html</link>
					<comments>https://daoker.cc/daokerto32.html#comments</comments>
		
		<dc:creator><![CDATA[博主]]></dc:creator>
		<pubDate>Thu, 10 Dec 2020 12:39:00 +0000</pubDate>
				<category><![CDATA[机器视觉]]></category>
		<guid isPermaLink="false">https://daoker.eu.org/?p=32</guid>

					<description><![CDATA[以下代码的vs2019工程文件打包点此下载去掉运行部分的注释即可]]></description>
										<content:encoded><![CDATA[
<p><a href="https://download.csdn.net/download/u011863319/15400941">以下代码的vs2019工程文件打包点此下载</a>去掉运行部分的注释即可</p>



<pre class="wp-block-code"><code>/*powell.cpp*/
#include "stdafx.h"
#include &lt;stdio.h>
#include &lt;math.h>
#include "Powell.h"
#include &lt;iostream>
#define m 10              /*数组长度m  >=  维数n    */


float powell::powell_f(float x&#91;])
{
	float result;
	//result = (x&#91;0] - 2)*(x&#91;0] - 2) + (x&#91;1] - 3)*(x&#91;1] - 3) + (x&#91;2] - 4)*(x&#91;2] - 4);
	result = x&#91;0] * x&#91;0] + 2 * x&#91;1] * x&#91;1] - 2 * x&#91;0] * x&#91;1] - 4 * x&#91;0];
	return result;
}

/*多维进退法子程序*/
void powell::mjtf(int n, float x0&#91;], float h, float s&#91;], float a&#91;], float b&#91;])
{
	int i;
	float x1&#91;m], x2&#91;m], x3&#91;m], powell_f1, powell_f2, powell_f3;
	for (i = 0; i&lt;n; i++)         /*计算初始两试点*/
	{
		x1&#91;i] = x0&#91;i];
		x2&#91;i] = x0&#91;i] + h*s&#91;i];
	}
	powell_f1 = powell_f(x1);
	powell_f2 = powell_f(x2);
	if (powell_f2 >= powell_f1)               /*判断搜索方向*/
	{                       /*搜索方向为反向，转身*/
		h = (-1)*h;
		for (i = 0; i&lt;n; i++)
			x3&#91;i] = x1&#91;i];
		powell_f3 = powell_f1;
		for (i = 0; i&lt;n; i++)
			x1&#91;i] = x2&#91;i];
		powell_f1 = powell_f2;
		for (i = 0; i&lt;n; i++)
			x2&#91;i] = x3&#91;i];
		powell_f2 = powell_f3;
	}                       /*搜索方向为正向*/

	for (i = 0; i&lt;n; i++)         /*计算第三试点*/
		x3&#91;i] = x2&#91;i] + h*s&#91;i];
	powell_f3 = powell_f(x3);
	while (powell_f3&lt;powell_f2)             /*判断是否未完成搜索*/
	{                       /*未完成，继续搜索*/
		h = 2 * h;
		for (i = 0; i&lt;n; i++)
			x1&#91;i] = x2&#91;i];
		powell_f1 = powell_f2;
		for (i = 0; i&lt;n; i++)
			x2&#91;i] = x3&#91;i];
		powell_f2 = powell_f3;
		for (i = 0; i&lt;n; i++)
			x3&#91;i] = x2&#91;i] + h*s&#91;i];
		powell_f3 = powell_f(x3);
	}                       /*已完成*/
	for (i = 0; i&lt;n; i++)         /*输出初始搜索区间*/
	{
		if (x1&#91;i]&lt;x3&#91;i])
		{
			a&#91;i] = x1&#91;i];
			b&#91;i] = x3&#91;i];
		}
		else
		{
			a&#91;i] = x3&#91;i];
			b&#91;i] = x1&#91;i];
		}
	}
}

/*多维黄金分割法子程序*/
void powell::mhjfgf(int n, float a&#91;], float b&#91;], float flag, float x&#91;])
{
	int i;
	float x1&#91;m], x2&#91;m], powell_f1, powell_f2, sum;
	for (i = 0; i&lt;n; i++)              /*计算初始两试点*/
		x1&#91;i] = b&#91;i] - (float)0.618*(b&#91;i] - a&#91;i]);
	powell_f1 = powell_f(x1);
	for (i = 0; i&lt;n; i++)
		x2&#91;i] = a&#91;i] + (float)0.618*(b&#91;i] - a&#91;i]);
	powell_f2 = powell_f(x2);
	do
	{
		if (powell_f1 &lt;= powell_f2)                  /*判断消去区间*/
		{                          /*消去右*/
			for (i = 0; i&lt;n; i++)
				b&#91;i] = x2&#91;i];
			for (i = 0; i&lt;n; i++)
				x2&#91;i] = x1&#91;i];
			powell_f2 = powell_f1;
			for (i = 0; i&lt;n; i++)
				x1&#91;i] = b&#91;i] - (float)0.618*(b&#91;i] - a&#91;i]);
			powell_f1 = powell_f(x1);
		}
		else
		{                          /*消去左*/
			for (i = 0; i&lt;n; i++)
				a&#91;i] = x1&#91;i];
			for (i = 0; i&lt;n; i++)
				x1&#91;i] = x2&#91;i];
			powell_f1 = powell_f2;
			for (i = 0; i&lt;n; i++)
				x2&#91;i] = a&#91;i] + (float)0.618*(b&#91;i] - a&#91;i]);
			powell_f2 = powell_f(x2);
		}
		sum = 0;
		for (i = 0; i&lt;n; i++)
			sum += (b&#91;i] - a&#91;i])*(b&#91;i] - a&#91;i]);
	} while (sqrt(sum)>flag*0.1);
	for (i = 0; i&lt;n; i++)
		x&#91;i] = (float)0.5*(b&#91;i] + a&#91;i]);
}

/*鲍威尔法子程序*/
void powell::mbwef(int n, float x0&#91;], float h, float flag, float a&#91;], float b&#91;], float x&#91;])
{
	int i, j, k, r;
	float x1&#91;m], x11&#91;m], x2&#91;m], powell_f0, powell_f1, powell_f2, fn&#91;m], s&#91;m]&#91;m], sum;
	for (i = 0; i&lt;n; i++)//构造一个单位对角矩阵s
		for (k = 0; k&lt;n; k++)
			if (i == k)
				s&#91;i]&#91;k] = 1;
			else
				s&#91;i]&#91;k] = 0;
	k = 0;
	while (1)
	{
		for (i = 0; i&lt;n; i++)
		{
			x1&#91;i] = x0&#91;i];
			x11&#91;i] = x1&#91;i];
		}
		for (i = 0; i&lt;n; i++)
		{
			mjtf(n, x1, h, s&#91;i], a, b);
			mhjfgf(n, a, b, flag, x1);
			fn&#91;i] = powell_f(x11) - powell_f(x1);
		}
		for (i = 0; i&lt;n; i++)
			x2&#91;i] = 2 * x1&#91;i] - x0&#91;i];
		for (i = 1; i&lt;n; i++)
			if (fn&#91;0]&lt;fn&#91;i])
			{
				fn&#91;0] = fn&#91;i];
				r = i;
			}
			else
				r = 0;
		powell_f0 = powell_f(x0);
		powell_f1 = powell_f(x1);
		powell_f2 = powell_f(x2);
		if (powell_f2 >= powell_f0 || (powell_f0 - 2 * powell_f1 + powell_f2)*(powell_f0 - powell_f1 - fn&#91;0])*(powell_f0 - powell_f1 - fn&#91;0]) >= 0.5*fn&#91;0] * (powell_f0 - powell_f2)*(powell_f0 - powell_f2))
		{
			sum = 0;
			for (i = 0; i&lt;n; i++)
				sum += (x1&#91;i] - x0&#91;i])*(x1&#91;i] - x0&#91;i]);
			if (powell_f1 &lt;= powell_f2)
				for (i = 0; i&lt;n; i++)
					x0&#91;i] = x1&#91;i];
			else
				for (i = 0; i&lt;n; i++)
					x0&#91;i] = x2&#91;i];
		}
		else
		{
			for (i = r; i&lt;n - 1; i++)
				for (j = 0; j&lt;n; j++)
					s&#91;i]&#91;j] = s&#91;i + 1]&#91;j];
			for (i = 0; i&lt;n; i++)
				s&#91;n - 1]&#91;i] = x1&#91;i] - x0&#91;i];
			mjtf(n, x1, h, s&#91;n - 1], a, b);
			mhjfgf(n, a, b, flag, x1);
			sum = 0;
			for (i = 0; i&lt;n; i++)
				sum += (x1&#91;i] - x0&#91;i])*(x1&#91;i] - x0&#91;i]);
			for (i = 0; i&lt;n; i++)
				x0&#91;i] = x1&#91;i];
		}
		if (sqrt(sum) &lt;= flag)
			break;
		else
			k += 1;
	}
	for (i = 0; i&lt;n; i++)
		x&#91;i] = x1&#91;i];
}

/*鲍威尔法主程序*/

int main(void)
{
	int i, n = 2;
	float h, flag, x0&#91;m], a&#91;m], b&#91;m], x&#91;m];
	printf("\n&lt;鲍威尔法>\n");
	/*printf("请输入维数:\n");
	scanf_s("%d", &amp;n);*/
	printf("请输入初始点:");
	for (i = 0; i&lt;n; i++)
	{
		printf("\nx0&#91;%d]=", i);
		scanf_s("%f", &amp;x0&#91;i]);
	}
	printf("\n请输入初始步长:\n");
	scanf_s("%f", &amp;h);
	printf("\n请输入精度:\n");
	scanf_s("%f", &amp;flag);
	powell one;
	one.mbwef(n, x0, h, flag, a, b, x);
	printf("\n极小点坐标为:\n");
	for (i = 0; i&lt;n; i++)
		printf("x&#91;%d]=%f\n", i, x&#91;i]);
	printf("\n极小值为:\n%f\n", one.powell_f(x));
	while (1)
	{
		;
	}

}


</code></pre>



<pre class="wp-block-code"><code>#ifndef _powell_h_
#define _powell_h_

class powell
{
public:
	float powell_f(float x&#91;]);
	void mjtf(int n, float x0&#91;], float h, float s&#91;], float a&#91;], float b&#91;]);
	void mhjfgf(int n, float a&#91;], float b&#91;], float flag, float x&#91;]);
	void mbwef(int n, float x0&#91;], float h, float flag, float a&#91;], float b&#91;], float x&#91;]);
private:

};

#endif // !
#pragma once
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://daoker.cc/daokerto32.html/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>区间消去法+二次插值法求极小值，并调用matlab绘制图像（c/c++程序）</title>
		<link>https://daoker.cc/daokerto34.html</link>
					<comments>https://daoker.cc/daokerto34.html#respond</comments>
		
		<dc:creator><![CDATA[博主]]></dc:creator>
		<pubDate>Sun, 21 Feb 2021 12:41:00 +0000</pubDate>
				<category><![CDATA[机器视觉]]></category>
		<guid isPermaLink="false">https://daoker.eu.org/?p=34</guid>

					<description><![CDATA[vs2019解决方案源文件打包：区间消去法+二次插值法求极小值，并调用matlab绘制图像（c/c++程序）]]></description>
										<content:encoded><![CDATA[
<p>vs2019解决方案源文件打包：<a href="https://download.csdn.net/download/u011863319/15401068">区间消去法+二次插值法求极小值，并调用matlab绘制图像（c/c++程序）</a></p>



<pre class="wp-block-code"><code>#ifndef _Quadratic_interpolation_H
#define _Quadratic_interpolation_H

#include &lt;iostream> 
#include &lt;vector>
#include &lt;math.h>
#define f1 function_value&#91;0]
#define f2 function_value&#91;1]
#define f3 function_value&#91;2]
#define X1 X_value&#91;0]
#define X2 X_value&#91;1]
#define X3 X_value&#91;2]
#define f(x) this->solve_f_value(x-1)
//#define dataNum 100
class solve_problem
{
public:
	  solve_problem(void);
	  void quad_inter();
	  void Interval_elimination();
	  double solve_f_value(int Serial_number);
	  void span_left();
	  void span_right();
	  int draw_picture();
	  ~solve_problem(void);
	  
private:
	double Coefficient_matrix&#91;4];
	double H=0;
	double function_value&#91;3] = {0,0,0};
	double X_value&#91;3] = { 0,0,0 };
	int cnt=0;
	double left_doit=0, right_doit=0;
	double Precision_e = 0;
	double xp = 0;
	double f4 = 0;
};

#endif

</code></pre>



<pre class="wp-block-code"><code>/*Quadratic_interpolation.cpp*/
#include &lt;iostream>
#include "Quadratic_interpolation.h"
#include "stdafx.h"
#include "draw_picture.h"
#define dataNum 100
using namespace std;
int main()
{
	int select = 0;
	while (!select)
	{
		solve_problem* one = new solve_problem();
		one->Interval_elimination();
		one->quad_inter();
		one->draw_picture();
		cout &lt;&lt; "输入1开始新的计算,输入其他则退出程序" &lt;&lt; endl;
		cin >> select;
		if (1 == select)
		{
			delete one;
			select = 0;
		}
		else
		{
			return 0;
		}
	}

}

solve_problem::solve_problem(void)//构造函数
{
	cout &lt;&lt; "请输入方程的系数\t" &lt;&lt; endl;
	for (int i = 0; i&lt;4; ++i)
	{
		cin >> Coefficient_matrix&#91;i];
	}
	cout &lt;&lt; "输入的方程为：f(x)=" &lt;&lt; showpos &lt;&lt; Coefficient_matrix&#91;0] &lt;&lt; "x^3" &lt;&lt; Coefficient_matrix&#91;1] &lt;&lt; "x^2" &lt;&lt; Coefficient_matrix&#91;2] &lt;&lt; "x" &lt;&lt; Coefficient_matrix&#91;3] &lt;&lt; endl;
}//
solve_problem::~solve_problem(void)
{
	cout &lt;&lt; "计算结束" &lt;&lt; endl;
}
void solve_problem::Interval_elimination()//区间消去函数
{
	cout &lt;&lt; "******************************求解单封区间***************************" &lt;&lt; endl;
	cout &lt;&lt; "输入初始点X1和步长H" &lt;&lt; endl;
	cin >> X1 >> H;
	cout &lt;&lt; "初始点X1=" &lt;&lt; noshowpos &lt;&lt; X1 &lt;&lt; "\t" &lt;&lt; "步长H=" &lt;&lt; H &lt;&lt; endl;
	cnt = sizeof(Coefficient_matrix) / sizeof(Coefficient_matrix&#91;0]);//获取数组元素个数
	f1 = f(1);	X2 = X1 + H;	f2 = f(2);
	if (f1 &lt;= f2)
	{
		H = -H;	X3 = X1;f3 = f1;X1 = X2;f1 = f2;X2 = X3;f2 = f3;
	}
	H *= 2;	X3 = X2 + H;	f3 = f(3);

	while (f3 &lt;= f2)
	{
		X1 = X2;f1 = f2;X2 = X3;f2 = f3;H *= 2;X3 = X2 + H;	f3 = f(3);
	}
	if (H > 0)
	{
		left_doit = X1;		right_doit = X3;
	}
	else
	{
		left_doit = X3;		right_doit = X1;
	}
	cout &lt;&lt; "单封区间为&#91;" &lt;&lt; this->left_doit &lt;&lt; "," &lt;&lt; this->right_doit &lt;&lt; "]" &lt;&lt; endl;

}//区间消去函数

void solve_problem::quad_inter()//二次插值函数
{
	cout &lt;&lt; "*****************使用二次插值法求极值点************************" &lt;&lt; endl;
	cout &lt;&lt; "输入收敛精度e" &lt;&lt; endl;
	cin >> Precision_e;
	cout &lt;&lt; "方程f(x)=" &lt;&lt; showpos &lt;&lt; Coefficient_matrix&#91;0]
		&lt;&lt; "x^3" &lt;&lt; Coefficient_matrix&#91;1]&lt;&lt; "x^2" &lt;&lt; Coefficient_matrix&#91;2] 
		&lt;&lt; "x" &lt;&lt; Coefficient_matrix&#91;3] &lt;&lt; endl;
	cout &lt;&lt; "的单封区间为&#91;" &lt;&lt; this->left_doit &lt;&lt; "," &lt;&lt; this->right_doit &lt;&lt; "]" &lt;&lt; endl;
	cout &lt;&lt; "收敛精度为e=" &lt;&lt; this->Precision_e &lt;&lt; endl;
	cout &lt;&lt; "*****************************求解开始************************" &lt;&lt; endl;
	X1 = left_doit;
	X3 = right_doit;
	X2 = 0.5*(X1 + X3);
	f1 = f(1);
	f2 = f(2);
	f3 = f(3);
	span_left();
}

double solve_problem::solve_f_value(int Serial_number)
{
	double value = 0;

	for (int i = 0; i &lt; 4; i++)
	{
		value += this->Coefficient_matrix&#91;i] * (pow(X_value&#91;Serial_number], cnt - i - 1));
	}
	return(value);
}

void solve_problem::span_left()
{
	double c1 = (f3 - f1) / (X3 - X1);
	double c2 = ((f2 - f1) / (X2 - X1) - c1) / (X2 - X3);
	if (c2 != 0)
	{
		xp = 0.5 * (X1 + X3 - c1 / c2);		if ((xp - X1) * (X3 - xp) > 0)
		{
			f4 = 0;
			for (int i = 0; i &lt; 4; i++)
			{
				f4 += this->Coefficient_matrix&#91;i] * (pow(xp, cnt - i - 1));
			}
			if (abs(xp - X2) &lt; Precision_e)
			{
				if (f4 &lt; f2)
				{
					cout &lt;&lt; "极限值在x=" &lt;&lt; xp &lt;&lt; "处取得" &lt;&lt; endl;
					cout &lt;&lt; "f(" &lt;&lt; xp &lt;&lt; ")=" &lt;&lt; f4 &lt;&lt; endl;
				}
				else
				{
					cout &lt;&lt; "极限值在x=" &lt;&lt; X2 &lt;&lt; "处取得" &lt;&lt; endl;
					cout &lt;&lt; "f(" &lt;&lt; X2 &lt;&lt; ")=" &lt;&lt; f2 &lt;&lt; endl;
				}
			}
			else
			{
				this->span_right(); //右边函数块
			}
		}
		else
		{
			cout &lt;&lt; "极限值在x=" &lt;&lt; X2 &lt;&lt; "处取得" &lt;&lt; endl;
			cout &lt;&lt; "f(" &lt;&lt; X2 &lt;&lt; ")=" &lt;&lt; f2 &lt;&lt; endl;
		}
	}
	else if (c2 == 0)
	{
		cout &lt;&lt; "极限值在x=" &lt;&lt; X2 &lt;&lt; "处取得" &lt;&lt; endl;
		cout &lt;&lt; "f(" &lt;&lt; X2 &lt;&lt; ")=" &lt;&lt; f2 &lt;&lt; endl;
	}
}

void solve_problem::span_right()
{
	if (xp > X2)
	{
		if (f2 > f4)
		{
			X1 = X2;
			f1 = f2;
			X2 = xp;
			f2 = f4;
		}
		else
		{
			X3 = xp;
			f3 = f4;
		}
	}
	else if (f2>f4)
	{
		X3 = X2;
		f3 = f2;
		X2 = xp;
		f2 = f4;
	}
	else
	{
		X1 = xp;
		f1 = f4;
	}
	this->span_left();
}
int solve_problem::draw_picture()
{
	
	Engine* eg = NULL;
	if (!(eg = engOpen(NULL)))
	{
		printf("Open matlab enging fail!");
		return 1;
	}

	int ret = 0;

	double xtemp&#91;dataNum] = { 0 };
	double ytemp&#91;dataNum] = { 0 };
	double distance = this->right_doit - this->left_doit;
	distance = 2*distance;
	double value;
	for (int i = 0; i &lt; dataNum; i++)
	{
		xtemp&#91;i] =(X1-0.5*distance)+ i * distance / dataNum;
	    value = 0;

		for (int j = 0; j &lt; 4; j++)
		{ 
			
			value += this->Coefficient_matrix&#91;j] * pow(xtemp&#91;i], cnt - j - 1);
			
		}
		ytemp&#91;i] = value;

	}
	mat_plot(eg, xtemp, ytemp, dataNum, "-r", 1, 5);
	for (int i = 0; i &lt; dataNum; i++)
	{
		xtemp&#91;i] = (X1 - 0.5*distance) + i * distance / dataNum;
		value = 0;

		for (int j = 0; j &lt; 4; j++)
		{

			value += this->Coefficient_matrix&#91;j] * pow(xtemp&#91;i], cnt - j );

		}
		ytemp&#91;i] = value;

	}
	mat_plot(eg, xtemp, ytemp, dataNum, "-r", 1, 5);

	getchar();
	if (eg)
		engClose(eg);
	return 0;
}

</code></pre>



<pre class="wp-block-code"><code>#ifndef _draw_picture_h_
#define _draw_picture_h_

#include "stdafx.h"
#include "draw_picture.h"
#include&lt;stdio.h>
#include&lt;string.h>
#include&lt;math.h>
#include&lt;engine.h>

extern int mat_plot(Engine *eg, double *x, double *y, int N, char *LineStyle, double LineWidth, double MarkerSize);



#endif // !_draw_picture_h_
#pragma once
</code></pre>



<pre class="wp-block-code"><code>// draw_picture.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "draw_picture.h"
#include&lt;stdio.h>
#include&lt;string.h>
#include&lt;math.h>
#include&lt;engine.h>

extern int mat_plot(Engine *eg, double *x, double *y, int N, char *LineStyle, double LineWidth, double MarkerSize);

//忽略4096错误
#pragma warning(disable:4996)

int mat_plot(Engine *eg, double *x, double *y, int N, char *LineStyle, double LineWidth, double MarkerSize)
{
	int ret = 0;
	mxArray *X = mxCreateDoubleMatrix(1, N, mxREAL);
	mxArray *Y = mxCreateDoubleMatrix(1, N, mxREAL);
	mxArray *MS = mxCreateDoubleScalar(MarkerSize);
	memcpy(mxGetPr(X), x, N * sizeof(double));
	memcpy(mxGetPr(Y), y, N * sizeof(double));

	if ((ret = engPutVariable(eg, "X", X)) != 0)
		printf("engPutVariable error：%d\n", ret);
	if ((ret = engPutVariable(eg, "Y", Y)) != 0)
		printf("engPutVariable error：%d\n", ret);

	//gennerate the plot command
	char plotCommand&#91;256] = "fig=plot(X,Y,'";
	//set line style and marker
	if (strlen(LineStyle) > 0)
		strncat(plotCommand, LineStyle, strlen(LineStyle));
	else
	{
		strncat(plotCommand, "-", strlen("-"));
	}
	strncat(plotCommand, "',", strlen(LineStyle));

	char temp&#91;20] = "";
	//set line width
	if (LineWidth &lt; 1.0)
		LineWidth = 1.0;
	strncat(plotCommand, "'LineWidth',", strlen("'LineWidth',"));
	memset(temp, 0, sizeof(temp));
	sprintf(temp, "%f,", LineWidth);
	strncat(plotCommand, temp, strlen(temp));

	//set marker size
	strncat(plotCommand, "'MarkerSize',", strlen("'MarkerSize',"));
	sprintf(temp, "%f", MarkerSize);
	strncat(plotCommand, temp, strlen(temp));
	strncat(plotCommand, ");", strlen(temp));

	//plot
	if ((ret = engEvalString(eg, plotCommand)) != 0)
	{
		printf("\nplot Command error：%s\n", plotCommand);
		return ret;
	}
	engEvalString(eg, "set(gcf,'color','w');");
	printf("plot Command ok：%s\n", plotCommand);
	//destroy mxArray,but they are still in matlab workspace
	mxDestroyArray(X);
	mxDestroyArray(Y);
	return 0;
}


</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://daoker.cc/daokerto34.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>opencv学习记录</title>
		<link>https://daoker.cc/daokerto37.html</link>
					<comments>https://daoker.cc/daokerto37.html#comments</comments>
		
		<dc:creator><![CDATA[博主]]></dc:creator>
		<pubDate>Sun, 21 Feb 2021 12:44:00 +0000</pubDate>
				<category><![CDATA[机器视觉]]></category>
		<guid isPermaLink="false">https://daoker.eu.org/?p=37</guid>

					<description><![CDATA[gitee地址：https://gitee.com/jhjyes/opencv_learing]]></description>
										<content:encoded><![CDATA[
<p>gitee地址：<a href="https://gitee.com/jhjyes/opencv_learing">https://gitee.com/jhjyes/opencv_learing</a></p>



<pre class="wp-block-code"><code>#include&lt;opencv2\opencv.hpp>
#include "ep_17.h"
#include &lt;tbb/tbb.h>
#include &lt;iostream>
using namespace cv;
/*
ep_1:图像显示
ep_2:图像腐蚀，模糊，边缘检测
ep_3:视频播放，摄像头采集
ep_4:彩色目标追踪
ep_5:光流optical flow
ep_6:点追踪ikdemo
ep_7:人脸识别
ep_8:svm分类
ep_9:无监督分类 
ep_10:生成图片并写入目录
ep_11:图像混合,并设置相对透明度
ep_12:鼠标基本操作
ep_13:格式化输出
ep_14:基本图像绘制
ep_15;访问像素的三种方法及时间消耗
ep_16:利用感兴趣区域进行线性图像混合
ep_17:利用机器学习+神经网络识别车牌数字
ep_18:手写数字识别
*/
int main(int argc, char** argv)
{



	
		
	ep_17();
	return 0;
 }
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://daoker.cc/daokerto37.html/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>opencv线性滤波：方框滤波，均值滤波，高斯滤波</title>
		<link>https://daoker.cc/daokerto39.html</link>
					<comments>https://daoker.cc/daokerto39.html#comments</comments>
		
		<dc:creator><![CDATA[博主]]></dc:creator>
		<pubDate>Mon, 29 Mar 2021 12:45:00 +0000</pubDate>
				<category><![CDATA[机器视觉]]></category>
		<guid isPermaLink="false">https://daoker.eu.org/?p=39</guid>

					<description><![CDATA[学习浅墨大佬：教程 函数调用： 源码解析：文件路径：D:\opencv4.5.1\sources\module [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>学习浅墨大佬：<a href="https://blog.csdn.net/poem_qianmo/article/details/22745559?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161682013116780261922435%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&amp;request_id=161682013116780261922435&amp;biz_id=0&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_v29-1-22745559.nonecase&amp;utm_term=%E5%9D%87%E5%80%BC%E6%BB%A4%E6%B3%A2">教程</a></p>



<p>函数调用：</p>



<pre class="wp-block-code"><code>#include "ep_19.h"
using namespace std;
using namespace cv;

void ep_19()
{
	/*方框滤波
	*第一个参数，InputArray类型的src，输入图像，即源图像，填Mat类的对象即可。该函数对通道是独立处理的，且可以处理任意通道数的图片，但需要注意，待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数，OutputArray类型的dst，即目标图像，需要和源图片有一样的尺寸和类型。
第三个参数，int类型的ddepth，输出图像的深度，-1代表使用原图深度，即src.depth()。
第四个参数，Size类型（对Size类型稍后有讲解）的ksize，内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中，w 为像素宽度， h为像素高度)。Size（3,3）就表示3x3的核大小，Size（5,5）就表示5x5的核大小
第五个参数，Point类型的anchor，表示锚点（即被平滑的那个点），注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话，就表示取核的中心为锚点，所以默认值Point(-1,-1)表示这个锚点在核的中心。
第六个参数，bool类型的normalize，默认值为true，一个标识符，表示内核是否被其区域归一化（normalized）了。
第七个参数，int类型的borderType，用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT，我们一般不去管它。

	void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor = Point(-1, -1), boolnormalize = true, int borderType = BORDER_DEFAULT)
	*/
	Mat srcimg = imread("girl.jpg");
	Mat boximg;
	boxFilter(srcimg, boximg, -1, Size(3, 3), Point(-1, -1),0);
	namedWindow("源图像", WINDOW_AUTOSIZE);
	imshow("源图像", srcimg);
	namedWindow("方框滤波",WINDOW_AUTOSIZE);
	imshow("方框滤波",boximg);

	/*均值滤波
第一个参数，InputArray类型的src，输入图像，即源图像，填Mat类的对象即可。该函数对通道是独立处理的，且可以处理任意通道数的图片，但需要注意，待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数，OutputArray类型的dst，即目标图像，需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone，以源图片为模板，来初始化得到如假包换的目标图。
第三个参数，Size类型（对Size类型稍后有讲解）的ksize，内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中，w 为像素宽度， h为像素高度)。Size（3,3）就表示3x3的核大小，Size（5,5）就表示5x5的核大小
第四个参数，Point类型的anchor，表示锚点（即被平滑的那个点），注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话，就表示取核的中心为锚点，所以默认值Point(-1,-1)表示这个锚点在核的中心。
第五个参数，int类型的borderType，用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT，我们一般不去管它。
	void blur(InputArray src, OutputArraydst, Size ksize, Point anchor = Point(-1, -1), int borderType = BORDER_DEFAULT)

	*/
	Mat blurimg;
	blur(srcimg, blurimg, Size(3, 3));
	namedWindow("均值滤波", WINDOW_AUTOSIZE);
	imshow("均值滤波", blurimg);

	/*高斯滤波*/
	/*
	第一个参数，InputArray类型的src，输入图像，即源图像，填Mat类的对象即可。它可以是单独的任意通道数的图片，但需要注意，图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数，OutputArray类型的dst，即目标图像，需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone，以源图片为模板，来初始化得到如假包换的目标图。
第三个参数，Size类型的ksize高斯内核的大小。其中ksize.width和ksize.height可以不同，但他们都必须为正数和奇数。或者，它们可以是零的，它们都是由sigma计算而来。
第四个参数，double类型的sigmaX，表示高斯核函数在X方向的的标准偏差。
第五个参数，double类型的sigmaY，表示高斯核函数在Y方向的的标准偏差。若sigmaY为零，就将它设为sigmaX，如果sigmaX和sigmaY都是0，那么就由ksize.width和ksize.height计算出来。
为了结果的正确性着想，最好是把第三个参数Size，第四个参数sigmaX和第五个参数sigmaY全部指定到。
第六个参数，int类型的borderType，用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT，我们一般不去管它。
 C++: void GaussianBlur(InputArray src,OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, intborderType=BORDER_DEFAULT )
	*/
	Mat gaussimg;
	GaussianBlur(srcimg, gaussimg, Size(3, 3), 0, 0);
	namedWindow("高斯滤波", WINDOW_AUTOSIZE);
	imshow("高斯滤波", gaussimg);
	waitKey(0);                                      
}
</code></pre>



<p>源码解析：<br>文件路径：D:\opencv4.5.1\sources\modules\imgproc\src\box_filter.dispatch.cpp</p>



<pre class="wp-block-code"><code>void boxFilter(InputArray _src, OutputArray _dst, int ddepth,
               Size ksize, Point anchor,
               bool normalize, int borderType)
{
    CV_INSTRUMENT_REGION();

    CV_Assert(!_src.empty());

    CV_OCL_RUN(_dst.isUMat() &amp;&amp;
               (borderType == BORDER_REPLICATE || borderType == BORDER_CONSTANT ||
                borderType == BORDER_REFLECT || borderType == BORDER_REFLECT_101),
               ocl_boxFilter3x3_8UC1(_src, _dst, ddepth, ksize, anchor, borderType, normalize))

    CV_OCL_RUN(_dst.isUMat(), ocl_boxFilter(_src, _dst, ddepth, ksize, anchor, borderType, normalize))

    Mat src = _src.getMat();//读入源图像到临时变量
    int stype = src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype);
	//定义int型临时变量，代表源图深度的sdepth，源图通道的引用cn
    if( ddepth &lt; 0 )//如果用户输入的深度小于0，则采用源图深度
        ddepth = sdepth;
    _dst.create( src.size(), CV_MAKETYPE(ddepth, cn) );//使用上方获取的参数初始化目标图
    Mat dst = _dst.getMat();//拷贝目标图的形参Mat数据到临时变量，用于稍后的操作
//处理 borderType不为 BORDER_CONSTANT 且normalize为真的情况
    if( borderType != BORDER_CONSTANT &amp;&amp; normalize &amp;&amp; (borderType &amp; BORDER_ISOLATED) != 0 )
    {
        if( src.rows == 1 )
            ksize.height = 1;
        if( src.cols == 1 )
            ksize.width = 1;
    }

    Point ofs;
    Size wsz(src.cols, src.rows);
    if(!(borderType&amp;BORDER_ISOLATED))
        src.locateROI( wsz, ofs );

    CALL_HAL(boxFilter, cv_hal_boxFilter, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, ddepth, cn,
             ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, ksize.width, ksize.height,
             anchor.x, anchor.y, normalize, borderType&amp;~BORDER_ISOLATED);

    CV_OVX_RUN(true,
               openvx_boxfilter(src, dst, ddepth, ksize, anchor, normalize, borderType))

    //CV_IPP_RUN_FAST(ipp_boxfilter(src, dst, ksize, anchor, normalize, borderType));

    borderType = (borderType&amp;~BORDER_ISOLATED);
 //调用FilterEngine滤波引擎，正式开始滤波操作
    Ptr&lt;FilterEngine> f = createBoxFilter( src.type(), dst.type(),
                        ksize, anchor, normalize, borderType );

    f->apply( src, dst, wsz, ofs );
}


</code></pre>



<p>其中，FilterEngine类是<a href="https://so.csdn.net/so/search?q=OpenCV&amp;spm=1001.2101.3001.7020" target="_blank" rel="noreferrer noopener">OpenCV</a>关于图像滤波的主力军类，OpenCV图像滤波功能的核心引擎。各种滤波函数比如blur， GaussianBlur，到头来其实是就是在函数末尾处定义了一个Ptr类型的f，然后f-&gt;apply( src, dst )了一下而已。</p>



<p>这个类可以把几乎是所有的滤波操作施加到图像上。它包含了所有必要的中间缓存器。有很多和滤波相关的create系函数的返回值直接就是Ptr。比如cv::createSeparableLinearFilter(),</p>



<p>cv::createLinearFilter(),cv::createGaussianFilter(), cv::createDerivFilter(),</p>



<p>cv::createBoxFilter() 和cv::createMorphologyFilter().，这里给出其中一个函数的原型吧：<br>————————————————</p>



<pre class="wp-block-preformatted"></pre>



<pre class="wp-block-code"><code>
Ptr&lt;FilterEngine>createLinearFilter(int srcType, int dstType, InputArray kernel, Point_anchor=Point(-1,-1), double delta=0, int rowBorderType=BORDER_DEFAULT, intcolumnBorderType=-1, const Scalar&amp; borderValue=Scalar() )
</code></pre>



<pre class="wp-block-code"><code>/**********************FilterEngine类注释********************************/
//文件路径：D:\opencv4.5.1\sources\modules\imgproc\src\filterengine.hpp
class FilterEngine
{
public:
    //! the default constructor默认构造函数
    FilterEngine();
    //! the full constructor. Either _filter2D or both _rowFilter and _columnFilter must be non-empty.
    //完整构造函数，Either _filter2D or both _rowFilter and _columnFilter必须为非空
    FilterEngine(const Ptr&lt;BaseFilter>&amp; _filter2D,
                 const Ptr&lt;BaseRowFilter>&amp; _rowFilter,
                 const Ptr&lt;BaseColumnFilter>&amp; _columnFilter,
                 int srcType, int dstType, int bufType,
                 int _rowBorderType = BORDER_REPLICATE,
                 int _columnBorderType = -1,
                 const Scalar&amp; _borderValue = Scalar());
    //! the destructor默认析构函数
    virtual ~FilterEngine();
    //! reinitializes the engine. The previously assigned filters are released.重新初始化引擎，清空之前的滤波器申请的内存
    void init(const Ptr&lt;BaseFilter>&amp; _filter2D,
              const Ptr&lt;BaseRowFilter>&amp; _rowFilter,
              const Ptr&lt;BaseColumnFilter>&amp; _columnFilter,
              int srcType, int dstType, int bufType,
              int _rowBorderType = BORDER_REPLICATE,
              int _columnBorderType = -1,
              const Scalar&amp; _borderValue = Scalar());

    //! starts filtering of the specified ROI of an image of size wholeSize.开始对指定了完整尺寸的图片的roi区域进行滤波操作
    virtual int start(const cv::Size &amp;wholeSize, const cv::Size &amp;sz, const cv::Point &amp;ofs);
    //! starts filtering of the specified ROI of the specified image.对指定图片的指定roi区域进行滤波操作
    virtual int start(const Mat&amp; src, const cv::Size &amp;wsz, const cv::Point &amp;ofs);
    //! processes the next srcCount rows of the image.处理图像的下一行
    virtual int proceed(const uchar* src, int srcStep, int srcCount,
                        uchar* dst, int dstStep);
    //! applies filter to the specified ROI of the image. if srcRoi=(0,0,-1,-1), the whole image is filtered.//对图像指定的ROI区域进行滤波操作，若srcRoi=(0,0,-1,-1)，则对整个图像进行滤波操作
    virtual void apply(const Mat&amp; src, Mat&amp; dst, const cv::Size &amp;wsz, const cv::Point &amp;ofs);

    //! returns true if the filter is separable//如果滤波器可分离，则返回true
    bool isSeparable() const { return !filter2D; }
    //! returns the number//返回输入和输出行数
    int remainingInputRows() const;
    int remainingOutputRows() const;
//成员参数定义
    int srcType;
    int dstType;
    int bufType;
    Size ksize;
    Point anchor;
    int maxWidth;
    Size wholeSize;
    Rect roi;
    int dx1;
    int dx2;
    int rowBorderType;
    int columnBorderType;
    std::vector&lt;int> borderTab;
    int borderElemSize;
    std::vector&lt;uchar> ringBuf;
    std::vector&lt;uchar> srcRow;
    std::vector&lt;uchar> constBorderValue;
    std::vector&lt;uchar> constBorderRow;
    int bufStep;
    int startY;
    int startY0;
    int endY;
    int rowCount;
    int dstY;
    std::vector&lt;uchar*> rows;

    Ptr&lt;BaseFilter> filter2D;
    Ptr&lt;BaseRowFilter> rowFilter;
    Ptr&lt;BaseColumnFilter> columnFilter;
};
</code></pre>



<p>Szie类：</p>



<pre class="wp-block-code"><code>typedef Size_&lt;int> Size2i;
typedef Size2i Size;
</code></pre>



<p>Size_、Size2i、Size这三个类型名等价</p>



<pre class="wp-block-code"><code>//-----------------------------------【Size_类中文注释版源代码】----------------------------
//     代码作用：作为尺寸相关数据结构的Size_ 模板类
//     说明：以下代码为来自于计算机开源视觉库OpenCV的官方源代码
//     OpenCV源代码版本：2.4.8
//     源码路径：…\opencv\sources\modules\core\include\opencv2\core\core.hpp
//     源文件中如下代码的起始行数：816行
//     中文注释by浅墨
//--------------------------------------------------------------------------------------------------------
template&lt;typename _Tp> class Size_
{
public:
   typedef _Tp value_type;
 
   //不同的构造函数定义
   Size_();
   Size_(_Tp _width, _Tp _height);
   Size_(const Size_&amp; sz);
   Size_(const CvSize&amp; sz);
   Size_(const CvSize2D32f&amp; sz);
   Size_(const Point_&lt;_Tp>&amp; pt);
 //例如，Size(5,5),构造出的类型的宽和高都为5
   Size_&amp; operator = (const Size_&amp; sz);
   //区域(width*height)
   _Tp area() const;
 
   //转化另一种数据类型。
    template&lt;typename_Tp2> operator Size_&lt;_Tp2>() const;
 
   //转换为旧式的OpenCV类型.
   operator CvSize() const;
   operator CvSize2D32f() const;
 
   _Tp width, height; //宽度和高度，常用属性
};
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://daoker.cc/daokerto39.html/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
