<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://wiki.lessokaji.com/index.php?action=history&amp;feed=atom&amp;title=3D%E6%BF%80%E5%85%89%E9%9B%B7%E8%BE%BE%E9%80%82%E9%85%8D</id>
	<title>3D激光雷达适配 - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.lessokaji.com/index.php?action=history&amp;feed=atom&amp;title=3D%E6%BF%80%E5%85%89%E9%9B%B7%E8%BE%BE%E9%80%82%E9%85%8D"/>
	<link rel="alternate" type="text/html" href="https://wiki.lessokaji.com/index.php?title=3D%E6%BF%80%E5%85%89%E9%9B%B7%E8%BE%BE%E9%80%82%E9%85%8D&amp;action=history"/>
	<updated>2026-04-15T16:06:18Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.40.0</generator>
	<entry>
		<id>https://wiki.lessokaji.com/index.php?title=3D%E6%BF%80%E5%85%89%E9%9B%B7%E8%BE%BE%E9%80%82%E9%85%8D&amp;diff=884&amp;oldid=prev</id>
		<title>Artheru：​创建页面，内容为“引用LidarContoller，开发一个MainIOObject类继承于LidarDIOObject。参考以下代码获取帧并使用Output()提交点云数据。  == 1.雷达适配参数含义 ==  === 1.1 3D点云数据结构 ===  '''public''' '''struct''' '''LidarPoint3D'''  {      '''public''' '''float''' d;//点云距离      '''public''' '''float''' azimuth;//点云角度      '''public''' '''float''' altitude;//点云俯仰角度      '''public''' '''float''' intensity; //反…”</title>
		<link rel="alternate" type="text/html" href="https://wiki.lessokaji.com/index.php?title=3D%E6%BF%80%E5%85%89%E9%9B%B7%E8%BE%BE%E9%80%82%E9%85%8D&amp;diff=884&amp;oldid=prev"/>
		<updated>2025-03-10T12:26:58Z</updated>

		<summary type="html">&lt;p&gt;创建页面，内容为“引用LidarContoller，开发一个MainIOObject类继承于LidarDIOObject。参考以下代码获取帧并使用Output()提交点云数据。  == 1.雷达适配参数含义 ==  === 1.1 3D点云数据结构 ===  &amp;#039;&amp;#039;&amp;#039;public&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;struct&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;LidarPoint3D&amp;#039;&amp;#039;&amp;#039;  {      &amp;#039;&amp;#039;&amp;#039;public&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;float&amp;#039;&amp;#039;&amp;#039; d;//点云距离      &amp;#039;&amp;#039;&amp;#039;public&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;float&amp;#039;&amp;#039;&amp;#039; azimuth;//点云角度      &amp;#039;&amp;#039;&amp;#039;public&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;float&amp;#039;&amp;#039;&amp;#039; altitude;//点云俯仰角度      &amp;#039;&amp;#039;&amp;#039;public&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;float&amp;#039;&amp;#039;&amp;#039; intensity; //反…”&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;引用LidarContoller，开发一个MainIOObject类继承于LidarDIOObject。参考以下代码获取帧并使用Output()提交点云数据。&lt;br /&gt;
&lt;br /&gt;
== 1.雷达适配参数含义 ==&lt;br /&gt;
&lt;br /&gt;
=== 1.1 3D点云数据结构 ===&lt;br /&gt;
 '''public''' '''struct''' '''LidarPoint3D'''&lt;br /&gt;
 {&lt;br /&gt;
     '''public''' '''float''' d;//点云距离&lt;br /&gt;
     '''public''' '''float''' azimuth;//点云角度&lt;br /&gt;
     '''public''' '''float''' altitude;//点云俯仰角度&lt;br /&gt;
     '''public''' '''float''' intensity; //反光度（实际上本意指reflectivity，历史原因用错了名称，但为了兼容性目前不做修改）&lt;br /&gt;
     '''public''' '''float''' progression;//点云在帧中的时间顺序在哪&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== 1.2 其余参数说明 ===&lt;br /&gt;
&lt;br /&gt;
* ScanAngleSgn: 雷达硬件属性--扫描方向 1为逆时针，-1为顺时针 对应detour雷达配置中的angleSgn&lt;br /&gt;
* ScanStartAngle: 雷达硬件属性--一帧扫描起始角度（-180°~+180°）&lt;br /&gt;
* ScanEndAngle: 雷达硬件属性--一帧扫描结束角度（-180°~+180°）对应detour雷达配置中的endAngle&lt;br /&gt;
* angleStart: 过滤点云角度用 最小角度&lt;br /&gt;
* angleEnd: 过滤点云角度用 最大角度&lt;br /&gt;
* maxDist: 过滤点云距离用 最大距离&lt;br /&gt;
* minDist: 过滤点云距离用 最小距离&lt;br /&gt;
* ReflexRange: 归一化尺度,从雷达解析的反光度需要除以该值&lt;br /&gt;
* cachedLidar :缓存用于output的帧数据&lt;br /&gt;
* frame: output一帧后加一 output 的帧数量&lt;br /&gt;
* tick: output前更新当前tick&lt;br /&gt;
* scanC: 雷达提供的自上电起帧数量&lt;br /&gt;
* maxReflex: 当前一帧数据中最强的反光率&lt;br /&gt;
* pointsN: 一帧数据的点数&lt;br /&gt;
* mirror: 是否倒装 倒装为true&lt;br /&gt;
&lt;br /&gt;
== 2.3D雷达适配流程 ==&lt;br /&gt;
注意：适配时，雷达正前方为0°&lt;br /&gt;
 '''namespace''' '''LeishenC16_v4_0_8'''&lt;br /&gt;
 {&lt;br /&gt;
     '''public''' '''class''' '''MainIOObject''' : Lidar3DIOObject&lt;br /&gt;
     {&lt;br /&gt;
         '''private''' '''string''' _targetIp;//雷达IP&lt;br /&gt;
         '''private''' '''const''' '''int''' MsopPort = '''2368''';//雷达点云数据发送端口&lt;br /&gt;
         '''private''' '''const''' '''int''' DifopPort = '''2369''';//雷达设备数据发送端口&lt;br /&gt;
 &lt;br /&gt;
         '''public''' '''MainIOObject'''()&lt;br /&gt;
         {&lt;br /&gt;
             //雷达硬件参数&lt;br /&gt;
             ScanAngleSgn = -'''1''';&lt;br /&gt;
             ScanStartAngle = '''180''';&lt;br /&gt;
             ScanEndAngle = -'''180''';&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         [IOObjectMonitor] '''private''' '''int''' _motSpd; //rpm&lt;br /&gt;
         '''private''' '''int''' _returnMode; // 55: strongest&lt;br /&gt;
                                  // 56: last&lt;br /&gt;
                                  // 57: dual&lt;br /&gt;
 &lt;br /&gt;
         [IOObjectMonitor] '''public''' '''long''' CachedTick = '''0''';&lt;br /&gt;
         [IOObjectMonitor] '''public''' '''double''' ScanInterval; // ms&lt;br /&gt;
         [IOObjectMonitor] '''public''' '''double''' RealInterval; // ms&lt;br /&gt;
 &lt;br /&gt;
         [IOObjectUtility]&lt;br /&gt;
         '''public''' '''void''' '''Stuck'''()&lt;br /&gt;
         {&lt;br /&gt;
             Task.Factory.'''StartNew'''(() =&amp;gt;&lt;br /&gt;
             {&lt;br /&gt;
                 '''var''' tic = DateTime.Now;&lt;br /&gt;
                 '''while''' (tic.'''AddSeconds'''('''5''') &amp;gt; DateTime.Now)&lt;br /&gt;
                 {&lt;br /&gt;
                     CachedTick += ('''long''')(Math.'''Sin'''(tic.Ticks) * '''100''');&lt;br /&gt;
                 }&lt;br /&gt;
             });&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         '''public''' '''static''' '''float''' '''ThDiff'''('''float''' th1, '''float''' th2)&lt;br /&gt;
         {&lt;br /&gt;
             '''return''' ('''float''')(th1 - th2 -&lt;br /&gt;
                            Math.'''Round'''((th1 - th2) / '''360.0f''') * '''360''');&lt;br /&gt;
         }&lt;br /&gt;
 		//16线雷达每条线俯仰角度&lt;br /&gt;
         '''private''' '''readonly''' '''float'''[] _altitudeTable = '''new''' '''float'''[]&lt;br /&gt;
         {&lt;br /&gt;
             -'''15''', '''1''', -'''13''', '''3''', -'''11''', '''5''', -'''9''', '''7''', -'''7''', '''9''', -'''5''', '''11''', -'''3''', '''13''', -'''1''', '''15'''&lt;br /&gt;
         };&lt;br /&gt;
 &lt;br /&gt;
         '''private''' '''long''' _startHour;&lt;br /&gt;
 &lt;br /&gt;
         '''private''' '''void''' '''Loop'''('''int''' port1, '''int''' port2)&lt;br /&gt;
         {&lt;br /&gt;
             // MSOP&lt;br /&gt;
             Console.'''WriteLine'''(&amp;quot;RSLidar16 MSOP Starting...&amp;quot;);&lt;br /&gt;
             '''var''' msoPclient = '''new''' '''UdpClient'''(port1);//绑定本地接受端口&lt;br /&gt;
             // msoPclient.Client.Bind(new IPEndPoint(IPAddress.Any, port1));//绑定本地端口?&lt;br /&gt;
             '''var''' sendEndPoint = '''new''' '''IPEndPoint'''(IPAddress.Any, '''0''');//用于获取发送端的ip 和 端口 不用指定port&lt;br /&gt;
             Console.'''WriteLine'''($&amp;quot;RSLidar16 starts streaming on port {MsopPort}&amp;quot;);&lt;br /&gt;
 &lt;br /&gt;
             // DIFOP&lt;br /&gt;
             Console.'''WriteLine'''(&amp;quot;LeishenC16 DIFOP Starting...&amp;quot;);&lt;br /&gt;
             '''var''' difoPclient = '''new''' '''UdpClient'''(port2);//绑定本地接受端口&lt;br /&gt;
             // difoPclient.Client.Bind(new IPEndPoint(IPAddress.Any, port2));&lt;br /&gt;
             '''var''' difoPsendEndPoint = '''new''' '''IPEndPoint'''(IPAddress.Any, '''0''');&lt;br /&gt;
 			//获取设备信息&lt;br /&gt;
             '''void''' '''GetDeviceInfo'''()&lt;br /&gt;
             {&lt;br /&gt;
                 '''while''' ('''true''')&lt;br /&gt;
                 {&lt;br /&gt;
                     //读取数据包（difop）&lt;br /&gt;
                     '''var''' pck = difoPclient.'''Receive'''('''ref''' difoPsendEndPoint);&lt;br /&gt;
                     //判断数据包是否来自雷达&lt;br /&gt;
                     '''if''' (difoPsendEndPoint.Address.'''ToString'''() != _targetIp)&lt;br /&gt;
                     {&lt;br /&gt;
                         Console.'''WriteLine'''(&amp;quot;Wrong IP! Trying agian...&amp;quot;);&lt;br /&gt;
                         '''continue''';&lt;br /&gt;
                     }&lt;br /&gt;
                     _motSpd = pck['''8'''] * '''256''' + pck['''9'''];&lt;br /&gt;
                     //计算扫描周期&lt;br /&gt;
                     ScanInterval = '''60f''' / _motSpd * '''1000''';&lt;br /&gt;
                     '''break''';&lt;br /&gt;
                 }&lt;br /&gt;
             }&lt;br /&gt;
             '''var''' typicalLen = '''0''';&lt;br /&gt;
             '''GetDeviceInfo'''();&lt;br /&gt;
             '''long''' ticLastSent = '''0''';&lt;br /&gt;
             '''long''' ticLast = '''0''';&lt;br /&gt;
             '''var''' lastFrame = '''new''' List&amp;lt;LidarPoint3D&amp;gt;();&lt;br /&gt;
             '''long''' ticNew = '''0''';&lt;br /&gt;
             '''var''' newFrame = '''new''' List&amp;lt;LidarPoint3D&amp;gt;();&lt;br /&gt;
             '''long''' lidarTicLast = -'''1''';&lt;br /&gt;
             '''long''' pTime = '''0''';&lt;br /&gt;
             '''while''' ('''true''')&lt;br /&gt;
             {&lt;br /&gt;
                 //读取数据包（msop）&lt;br /&gt;
                 '''var''' pck = msoPclient.'''Receive'''('''ref''' sendEndPoint);&lt;br /&gt;
                 '''if''' (sendEndPoint.Address.'''ToString'''() != _targetIp || pck.Length != '''1212''')//校验发送方IP,和报文长度&lt;br /&gt;
                 {&lt;br /&gt;
                     Console.'''WriteLine'''(&amp;quot;Invalid UDP package received! from ip:{sendEndPoint.Address.ToString()} pckl:{pck.Length}&amp;quot;);&lt;br /&gt;
                     '''continue''';&lt;br /&gt;
                 }&lt;br /&gt;
                 '''if''' (pck['''1210'''] == '''57''')//按照单回波方式解析的报文,只支持雷达端设置单回波模式&lt;br /&gt;
                 {&lt;br /&gt;
                     Console.'''WriteLine'''($&amp;quot;* Return mode not single return!&amp;quot;);&lt;br /&gt;
                     '''throw''' '''new''' '''Exception'''(&amp;quot;Only single return supported&amp;quot;);&lt;br /&gt;
                 }&lt;br /&gt;
 &lt;br /&gt;
                 //pck 1200y,1201m,1202d,1203h,1204m,1205s&lt;br /&gt;
                 '''var''' second = '''new''' '''DateTime'''(pck['''1200'''] + '''1''', pck['''1201'''], pck['''1202'''], pck['''1203'''], pck['''1204'''], pck['''1205''']).Ticks /&lt;br /&gt;
                              TimeSpan.TicksPerSecond;&lt;br /&gt;
                 '''var''' nanosecond = BitConverter.'''ToInt32'''(pck, '''1206''');&lt;br /&gt;
                 pTime = second * '''1000000''' + nanosecond / '''1000''';//计算当前包的时间戳&lt;br /&gt;
                 '''var''' idx = '''0''';&lt;br /&gt;
                 '''float''' delta = '''0''';&lt;br /&gt;
                 '''for''' ('''var''' i = '''0'''; i &amp;lt; '''12'''; ++i)&lt;br /&gt;
                 {&lt;br /&gt;
                     idx += '''2''';&lt;br /&gt;
                     List&amp;lt;LidarPoint3D&amp;gt; target;&lt;br /&gt;
                     '''var''' azimuth = (pck[idx + '''1'''] * '''256''' + pck[idx]) * '''0.01f''';//按照报文解析方位角&lt;br /&gt;
                     '''if''' (idx + '''100''' &amp;lt; '''1200''')&lt;br /&gt;
                     {&lt;br /&gt;
                         '''var''' aNext = (pck[idx + '''100''' + '''1'''] * '''256''' + pck[idx + '''100''']) * '''0.01f''';&lt;br /&gt;
                         delta = '''ThDiff'''(aNext, azimuth);&lt;br /&gt;
                     }&lt;br /&gt;
 &lt;br /&gt;
                     idx += '''2''';&lt;br /&gt;
 					//判断是否有效&lt;br /&gt;
                     '''bool''' '''DetermineFrame'''()&lt;br /&gt;
                     {&lt;br /&gt;
                         target = lastFrame;&lt;br /&gt;
                         '''var''' eFrameSt = pTime - ('''long''')(azimuth / '''360''' * ScanInterval * '''1000''');&lt;br /&gt;
                         '''if''' (ticLast == '''0''')&lt;br /&gt;
                         {&lt;br /&gt;
                             ticLast = eFrameSt;&lt;br /&gt;
                             '''return''' '''true''';&lt;br /&gt;
                         }&lt;br /&gt;
 &lt;br /&gt;
                         '''if''' (ticLast - ScanInterval * '''1000''' * '''0.1''' &amp;lt; eFrameSt &amp;amp;&amp;amp;&lt;br /&gt;
                             eFrameSt &amp;lt; ticLast + ScanInterval * '''1000''' * '''0.1''') '''return''' '''true'''; // previous frame.&lt;br /&gt;
 &lt;br /&gt;
                         '''if''' (eFrameSt &amp;gt; ticLast + ScanInterval * '''1000''' * '''1.3''')&lt;br /&gt;
                         {&lt;br /&gt;
                             ticLast = eFrameSt;&lt;br /&gt;
                             target.'''Clear'''();&lt;br /&gt;
                             newFrame.'''Clear'''();&lt;br /&gt;
                             '''return''' '''false''';&lt;br /&gt;
                         }&lt;br /&gt;
                         target = newFrame;&lt;br /&gt;
                         '''if''' (eFrameSt &amp;lt; ticLast - ScanInterval * '''1000''' * '''0.3''')&lt;br /&gt;
                             '''return''' '''false'''; // even older frame.&lt;br /&gt;
                         '''if''' (eFrameSt &amp;gt; ticLast + ScanInterval * '''1000''' * '''0.3''')&lt;br /&gt;
                         {&lt;br /&gt;
                             // new frame.&lt;br /&gt;
                             ticNew = eFrameSt;&lt;br /&gt;
                         }&lt;br /&gt;
                         '''return''' '''true''';&lt;br /&gt;
                     }&lt;br /&gt;
                     '''void''' '''Read'''()&lt;br /&gt;
                     {&lt;br /&gt;
                         '''for''' ('''var''' j = '''0'''; j &amp;lt; '''16'''; ++j)&lt;br /&gt;
                         {&lt;br /&gt;
                             '''var''' d = pck[idx + '''1'''] * '''256''' + pck[idx];&lt;br /&gt;
                             '''var''' refl = pck[idx + '''2'''];&lt;br /&gt;
                             idx += '''3''';&lt;br /&gt;
                             target.'''Add'''('''new''' '''LidarPoint3D'''()&lt;br /&gt;
                             {&lt;br /&gt;
                                 azimuth = '''ThDiff'''(mirror ? azimuth - '''180''' : -(azimuth - '''180'''),'''0'''),//减去180度旋转0度方向&lt;br /&gt;
                                 altitude = mirror ? -_altitudeTable[j] : _altitudeTable[j],&lt;br /&gt;
                                 d = d * '''4f''',&lt;br /&gt;
                                 intensity = refl / '''256f''',&lt;br /&gt;
                                 progression = ('''float''')((azimuth - Math.'''Floor'''(azimuth / '''360''') * '''360''') / '''360''')&lt;br /&gt;
                             });&lt;br /&gt;
                         }&lt;br /&gt;
                     }&lt;br /&gt;
 &lt;br /&gt;
                     '''if''' (!'''DetermineFrame'''()) '''continue''';&lt;br /&gt;
                     '''Read'''();&lt;br /&gt;
 					//由于两个俯仰角度的点云只给出一个角度信息，所以另一个需要根据相邻两包的数据进行差值计算&lt;br /&gt;
                     azimuth += delta / '''2''';&lt;br /&gt;
                     '''if''' (azimuth &amp;gt; '''360''') azimuth -= '''360''';&lt;br /&gt;
                     '''if''' (!'''DetermineFrame'''()) '''continue''';&lt;br /&gt;
                     '''Read'''();&lt;br /&gt;
                 }&lt;br /&gt;
 				//计算时间，若大于一个扫描周期，则进行output&lt;br /&gt;
                 '''if''' (pTime - ticLast &amp;gt; ScanInterval * '''1.3''' * '''1000''')&lt;br /&gt;
                 {&lt;br /&gt;
                     '''var''' scanCnt = '''1''';&lt;br /&gt;
                     '''if''' (ticLastSent != '''0''' &amp;amp;&amp;amp; ticLast - ticLastSent &amp;gt; ScanInterval * '''1000''' * '''1.3f''')&lt;br /&gt;
                         scanCnt = ('''int''')Math.'''Round'''((ticLast - ticLastSent) / (ScanInterval * '''1000'''));&lt;br /&gt;
 &lt;br /&gt;
                     scanC += scanCnt;&lt;br /&gt;
                     frame += '''1''';&lt;br /&gt;
                     cachedLidar = lastFrame.'''ToArray'''();&lt;br /&gt;
                     '''if''' (frame &amp;lt; '''10''')&lt;br /&gt;
                         typicalLen = Math.'''Max'''(typicalLen, cachedLidar.Length);&lt;br /&gt;
                     '''else''' '''if''' (typicalLen * '''0.8''' &amp;lt; cachedLidar.Length)&lt;br /&gt;
                         '''Output'''();&lt;br /&gt;
                     '''else'''&lt;br /&gt;
                        Console.'''WriteLine'''($&amp;quot;bad length or corrupted packet, only {cachedLidar.Length} points&amp;quot;);&lt;br /&gt;
 &lt;br /&gt;
                     RealInterval = (ticLast - ticLastSent) / '''1000d''';&lt;br /&gt;
                     lastFrame = newFrame;&lt;br /&gt;
                     newFrame = '''new''' List&amp;lt;LidarPoint3D&amp;gt;();&lt;br /&gt;
                     ticLastSent = ticLast;&lt;br /&gt;
                     ticLast = ticNew;&lt;br /&gt;
 					//每10帧读取一次difop&lt;br /&gt;
                     '''if''' (scanC % '''10''' == '''0''') '''new''' '''Thread'''(GetDeviceInfo).'''Start'''();&lt;br /&gt;
                 }&lt;br /&gt;
             }&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         '''public''' '''void''' '''Start'''('''string''' ip, '''int''' port1, '''int''' port2)&lt;br /&gt;
         {&lt;br /&gt;
             Console.'''WriteLine'''($&amp;quot;LeishenC16 on {ip}&amp;quot;);&lt;br /&gt;
             '''this'''._targetIp = ip;&lt;br /&gt;
             '''new''' '''Thread'''(() =&amp;gt;&lt;br /&gt;
             {&lt;br /&gt;
                 Console.'''WriteLine'''($&amp;quot;Try LeishenC16&amp;quot;);&lt;br /&gt;
                 '''while''' ('''true''')&lt;br /&gt;
                 {&lt;br /&gt;
                     '''try'''&lt;br /&gt;
                     {&lt;br /&gt;
                         //设置is3D属性为true，代表是3D雷达&lt;br /&gt;
                         is3D = '''true''';&lt;br /&gt;
                         '''Loop'''(port1, port2);&lt;br /&gt;
                     }&lt;br /&gt;
                     '''catch''' (Exception ex)&lt;br /&gt;
                     {&lt;br /&gt;
                         Console.'''WriteLine'''($&amp;quot;msg:{ex.Message}, stack:{ex.StackTrace}&amp;quot;);&lt;br /&gt;
                         Thread.'''Sleep'''('''1000''');&lt;br /&gt;
                     }&lt;br /&gt;
                 }&lt;br /&gt;
             }).'''Start'''();&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
生成插件后，需在Medulla的startup.iocmd中增加以下代码：&lt;br /&gt;
 //io load xxx.dll时，会寻找dll里面class MainIOObject并实例化相应的对象赋值给等号左侧的frontlidar3d&lt;br /&gt;
 frontlidar3d = io load plugins/LeishenC16_v4_0_8.dll&lt;br /&gt;
 //Start对应Start函数，传的参数放在后面，空格隔开。&lt;br /&gt;
 frontlidar3d Start '''192.168'''.'''0.2''' '''2368''' '''2369'''&lt;/div&gt;</summary>
		<author><name>Artheru</name></author>
	</entry>
</feed>