.NET coding patterns(davidfowl)

Posted 听雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.NET coding patterns(davidfowl)相关的知识,希望对你有一定的参考价值。

Table of contents

Generics types as a factory

This pattern is used in Microsoft.Extensions.* and in Microsoft.AspNetCore.*. The idea is that you can use a generic type as a factory instead of a function. The type argument is the type you want to instantiate. Consider the example below where we have an IServiceFactory<TService> that can resolve the TService from the DI container or creates an instance if it\'s not in the container.

public interface IServiceFactory<TService>

    TService Service  get; 


public class ServiceFactory<TService> : IServiceFactory<TService>

    public ServiceFactory(IServiceProvider service)
    
        Service = (TService)service.GetService(typeof(TService)) ?? ActivatorUtilities.CreateInstance<TService>(service);
    

    public TService Service  get; 

The constructor has a access to any service and the generic type being requested. These open generic services are registered like this:

public void ConfigureServices(IServiceCollection services)

    services.AddTransient(typeof(IServiceFactory<>), typeof(ServiceFactory<>));

Lazy initialization of services

Sometimes it\'s necessary to create services later than a constructor. The usually work around for this is to inject and IServiceProvider into the constructor of the service that needs to lazily create the instance.

public class Service

    private readonly IServiceProvider _serviceProvider;
    public Service(IServiceProvider serviceProvider)
    
        _serviceProvider = serviceProvider;
    
    
    public IFoo CreateFoo() => _serviceProvider.GetRequiredService<IFoo>();
    public IBar CreateBar() => _serviceProvider.GetRequiredService<IBar>();

If the types are known ahead of time, we can build a custom service provider lazy type to encapsulate the service locator pattern:

public interface ILazy<T>

    T Value  get; 


public class LazyFactory<T> : ILazy<T>

    private readonly Lazy<T> _lazy;

    public LazyFactory(IServiceProvider service)
    
        _lazy = new Lazy<T>(() => service.GetRequiredService<T>());
    

    public T Value => _lazy.Value;


public class Service

    private readonly ILazy<IFoo> _foo;
    private readonly ILazy<IBar> _bar;
    public Service(ILazy<IFoo> foo, ILazy<IBar> bar)
    
        _foo = foo;
        _bar = bar;
    
    
    public IFoo CreateFoo() => _foo.Value;
    public IBar CreateBar() => _bar.Value;

Registered like this:

public void ConfigureServices(IServiceCollection services)

    // We register this as transient so it handles both singleton and scoped services
    // as the IServiceProvider injected will have the relevant lifetime.
    services.AddTransient(typeof(ILazy<>), typeof(LazyFactory<>));

Lazy<T> could also be used as-is if added with a concrete type at service registration time:

public void ConfigureServices(IServiceCollection services)

    services.AddTransient<Lazy<IFoo>>(sp => new Lazy<IFoo>(() => sp.GetRequiredService<IFoo>());
    services.AddTransient<Lazy<IBar>>(sp => new Lazy<IBar>(() => sp.GetRequiredService<IBar>());

Single implementation multiple interfaces

Let\'s say you had a type that implemented multiple interfaces and you wanted to expose it using the DI container. The built in IServiceCollection type doesn\'t natively support this but it\'s easy to emulate using the following pattern.

public class FooAndBar : IFoo, IBar

   // Imagine a useful implementation
public void ConfigureServices(IServiceCollection services)

    services.AddSingleton<FooAndBar>();
    services.AddSingleton<IFoo>(sp => sp.GetRequiredService<FooAndBar>());
    services.AddSingleton<IBar>(sp => sp.GetRequiredService<FooAndBar>());

This will let me resolve FooAndBarIFoo and IBar and it will give me the same instance.

Creating instances of types from an IServiceProvider

Usually you need to register a type in order to instantiate instances of a type from the DI container, somebody needs to call IServiceProvider.GetService. This means that the service needs to be registered in the container. This may not be desirable if these types are discovered dynamically (like controllers in MVC). There\'s a useful utility called ActivatorUtilities that can be used as a factory for types that haven\'t been registered, but have dependencies that are registered in the DI container.

public class MyDependency

    public MyDependency(ILogger logger)
    
    
public class MyDependencyFactory

    private readonly IServiceProvider _serviceProvider;
    public MyDependencyFactory(IServiceProvider serviceProvider)
    
        _serviceProvider = serviceProvider;
    
    public MyDependency GetInstance()
    
        return ActivatorUtilities.CreateInstance<MyDependency>(_serviceProvider);
    

You can build a more optimized version of this uses the CreateFactory. This will pre-calculate the constructor based on the types passed and build a factory for it.

public class MyDependencyFactory

    private readonly IServiceProvider _serviceProvider;
    private readonly ObjectFactory _factory;
    public MyDependencyFactory(IServiceProvider serviceProvider)
    
        _serviceProvider = serviceProvider;
        _factory = ActivatorUtilities.CreateFactory(typeof(MyDependency), Type.EmptyTypes);
    
    public MyDependency GetInstance()
    
        return (MyDependency) _factory(_serviceProvider, null);
    

NOTE: Disposable instances created with this API will not be disposed by the DI container.

Caching singletons in generic types

If you need to cache an instance of something based on type, then you can store it on a static field of a generic type.

public class Factory

    public T Create<T>()
        where T : new()
    
        return Cache<T>.Instance;
    
    
    private static class Cache<T>
        where T : new()
    
        public static readonly T Instance = new();
    

You can use the JIT to cache instances on your behalf instead of a slower ConcurrentDictionary<Type, T>. It can also be used to cache the expensive object creation process:

public class Factory

    public T Create<T>()
        where T : new()
    
        return Cache<T>.Instance;
    
    
    private static class Cache<T>
        where T : new()
    
        public static readonly T Instance = CallExpensiveMethodToBuildANewInstance();
        
        private static T CallExpensiveMethodToBuildANewInstance()
        
            // Imagine a really complex process to construct T
            return instance.
        
    

Resolving services when using IOptions<T>

The options pattern is used in Microsoft.Extensions.* and in Microsoft.AspNetCore.*. It allows anyone to register a callback to mutate a POCO used to configure a library. Sometimes you\'d like access to services when configuring these options. There are multiple ways to do this:

public class LibraryOptions

    public int Setting  get; set; 


public class MyConfigureOptions : IConfigureOptions<LibraryOptions>

    private readonly ISomeService _service;
    public MyConfigureOptions(ISomeService service)
    
        _service = service;
    
    
    public void Configure(LibraryOptions options)
    
        options.Setting = _service.ComputeSetting();
    

Followed by the registration.

public void ConfigureServices(IServiceCollection services)

    services.AddSingleton<IConfigureOptions<LibraryOptions>, MyConfigureOptions>();

That\'s a bit verbose, so we added some helpers to make it easier.

public void ConfigureServices(IServiceCollection services)

    services.AddOptions<LibraryOptions>()
            .Configure<ISomeService>((options, service) =>
            
                options.Setting = service.ComputeSetting();
            );

 

 

Asynchronous Socket Server

Below is a modern asynchronous server using modern .NET APIs:

  • Task based APIs for asynchronous accept/read/write
  • Range syntax for slicing the buffer
using var listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 8080));

Console.WriteLine($"Listening on listenSocket.LocalEndPoint");

listenSocket.Listen();

while (true)

    // Wait for a new connection to arrive
    var connection = await listenSocket.AcceptAsync();

    // We got a new connection spawn a task to so that we can echo the contents of the connection
    _ = Task.Run(async () =>
    
        var buffer = new byte[4096];
        try
        
            while (true)
            
                int read = await connection.ReceiveAsync(buffer, SocketFlags.None);
                if (read == 0)
                
                    break;
                
                await connection.SendAsync(buffer[..read], SocketFlags.None);
            
        
        finally
        
            connection.Dispose();
        
    );

Asynchronous Socket Client

Below is a modern asynchronous client using modern .NET APIs:

  • Uses Task based APIs to establish the connection.
  • Creates a NetworkStream over the Socket in order to use CopyToAsync, a helper that makes it easy to copy content from one Stream to another.
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 8080));

Console.WriteLine("Type into the console to echo the contents");

var ns = new NetworkStream(socket);
var readTask = Console.OpenStandardInput().CopyToAsync(ns);
var writeTask = ns.CopyToAsync(Console.OpenStandardOutput());

// Quit if any of the tasks complete
await Task.WhenAny(readTask, writeTask);

 

https://github.com/davidfowl/DotNetCodingPatterns

ROS实验笔记之——基于kalibr来标定DAVIS346

之前博客《ROS学习笔记之——DAVIS346 calibration》已经实现了用dv-gui(Calibration [Tutorial] · DV)来标定event camera了。但是缺少了跟IMU的外参标定等等。本博文利用Kalibr库来对其进行标定。

Kalibr安装

先创建一个工作空间

mkdir -p ~/kalibr_workspace/src
cd ~/kalibr_workspace
source /opt/ros/melodic/setup.bash
catkin init
catkin config --extend /opt/ros/melodic
catkin config --merge-devel
catkin config --cmake-args -DCMAKE_BUILD_TYPE=Release

cd ~/kalibr_workspace/src
git clone https://github.com/ethz-asl/Kalibr.git

cd ~/kalibr_workspace
catkin build -DCMAKE_BUILD_TYPE=Release -j4

可能会提示缺少libv4l,安装依赖包即可:

sudo apt-get install libv4l-dev

建立完之后需要source一下

source ~/kalibr_workspace/devel/setup.bash

还是没有办法。。。。还要把操蛋的opencv4安装了。。。。。

改为参考:melodic 安装 kalibr_matthewsyc的博客-CSDN博客

sudo apt-get install python-setuptools python-rosinstall ipython 
sudo apt-get install libeigen3-dev libboost-all-dev doxygen
sudo apt-get install ros-melodic-cmake-modules python-software-properties 
sudo apt-get install software-properties-common libpoco-dev python-matplotlib 
sudo apt-get install python-scipy python-git python-pip ipython libtbb-dev 
sudo apt-get install libblas-dev liblapack-dev python-catkin-tools libv4l-dev
sudo apt-get install python-igraph

安装完依赖后,编译好像就比较正常了~

每次运行前,记得加入

source ~/kalibr_workspace/devel/setup.bash

IMU标定

产生数据写入imu.yaml中,imu.yaml文件要用于联合标定。

imu_utils依赖code_utils,要先安装code_utils再安装imu_utils。

mkdir -p ~/imu_catkin_ws/src
cd ~/imu_catkin_ws/src
catkin_init_workspace
cd ..
catkin_make
source ~/imu_catkin_ws/devel/setup.bash

cd ~/imu_catkin_ws/src
git clone https://github.com/gaowenliang/code_utils.git
cd ..
catkin_make

可能出现报错,试试运行

sudo apt-get install libdw-dev

catkin_make时出现backward.hpp没有找到

解决方法:将sumpixel_test.cpp中# include "backward.hpp"改为:#include “code_utils/backward.hpp”

即可安装成功!

然后下载imu_utils

cd ~/imu_catkin_ws/src/
git clone https://github.com/gaowenliang/imu_utils.git
cd ..
catkin_make

然后运行

roslaunch vins davis_testing.launch

rostopic list

rostopic echo /dvs/imu

记录imu信息,这个过程要保持imu静止不动至少2个小时(这个太夸张了吧。。。)

17.26开始

在imu_utils文件下的launch文件目录下添加一个launch文件,我这里添加的是 imu.launch,并把以下代码复制进文件,代码中的/IMU_data改成自己imu的topic。

<launch>
    <node pkg="imu_utils" type="imu_an" name="imu_an" output="screen">
        <!--TOPIC名称-->
        <param name="imu_topic" type="string" value= "/dvs/imu"/>
        <!--imu_name 无所谓-->
        <param name="imu_name" type="string" value= "imu_davis346"/>
         <!--标定结果存放路径-->s
        <param name="data_save_path" type="string" value= "$(find imu_utils)/data/"/>
        <!--数据录制时间-min-->
        <param name="max_time_min" type="int" value= "120"/>
         <!--采样频率,即是IMU频率,采样频率可以使用rostopic hz /dvs/imu查看,设置为400,为后面的rosbag play播放频率-->
        <param name="max_cluster" type="int" value= "400"/>
    </node>
</launch>

运行之前要先source一下

source ~/imu_catkin_ws/devel/setup.bash
roslaunch imu_utils davis_imu.launch
rosbag play -r 400 /home/kwanwaipang/dataset/gwphku/hku_davis346_imu_2021-10-25-17-26-06.bag
rosbag play /home/kwanwaipang/dataset/gwphku/hku_davis346_imu_2021-10-25-17-26-06.bag

   标定结束后在imu_catkin_ws/src/imu_utils/data中生成许多文件,其中(d435i_imu_param.yaml)imu_davis346_imu_param.yaml就是我们想要的结果,展示如下:

%YAML:1.0
---
type: IMU
name: imu_davis346
Gyr:
   unit: " rad/s"
   avg-axis:
      gyr_n: 3.4207443164969696e-03
      gyr_w: 5.0328372766535944e-05
   x-axis:
      gyr_n: 2.8679837408646372e-03
      gyr_w: 5.2377301938209580e-05
   y-axis:
      gyr_n: 3.4204365908127145e-03
      gyr_w: 5.9820318994959333e-05
   z-axis:
      gyr_n: 3.9738126178135566e-03
      gyr_w: 3.8787497366438921e-05
Acc:
   unit: " m/s^2"
   avg-axis:
      acc_n: 4.0577448874376872e-02
      acc_w: 8.7349218314222493e-04
   x-axis:
      acc_n: 3.6370001351966601e-02
      acc_w: 9.6556492723349487e-04
   y-axis:
      acc_n: 3.4926548351679493e-02
      acc_w: 8.3124335009033201e-04
   z-axis:
      acc_n: 5.0435796919484514e-02
      acc_w: 8.2366827210284801e-04

编写imu.yaml,格式参考https://github.com/ethz-asl/kalibr/wiki/yaml-formats中的imu.yaml,具体参数使用之前imu标定得到的参数,示例如下:

#Accelerometers
accelerometer_noise_density: 4.0577448874376872e-02   #Noise density (continuous-time)
accelerometer_random_walk:   8.7349218314222493e-04   #Bias random walk

#Gyroscopes
gyroscope_noise_density:     3.4207443164969696e-03   #Noise density (continuous-time)
gyroscope_random_walk:       5.0328372766535944e-05   #Bias random walk

rostopic:                    /dvs/imu      #the IMU ROS topic
update_rate:                 1000.0      #Hz (for discretization of the values above)

若不加速播放rosbag得到的结果如下:

%YAML:1.0
---
type: IMU
name: imu_davis346
Gyr:
   unit: " rad/s"
   avg-axis:
      gyr_n: 3.4869979619824697e-03
      gyr_w: 4.4740603706140853e-05
   x-axis:
      gyr_n: 2.9539477648808021e-03
      gyr_w: 5.0580216740422281e-05
   y-axis:
      gyr_n: 3.5214141180930189e-03
      gyr_w: 5.4806050604838655e-05
   z-axis:
      gyr_n: 3.9856320029735878e-03
      gyr_w: 2.8835543773161611e-05
Acc:
   unit: " m/s^2"
   avg-axis:
      acc_n: 4.2153479580575477e-02
      acc_w: 8.5654389798364998e-04
   x-axis:
      acc_n: 3.7662088244236419e-02
      acc_w: 8.9242080034822346e-04
   y-axis:
      acc_n: 3.8469169601091632e-02
      acc_w: 8.9970652031743314e-04
   z-axis:
      acc_n: 5.0329180896398394e-02
      acc_w: 7.7750437328529356e-04

还是有点差别的~按这个来把~~~

camera标定

产生数据文件也是用于联合标定

1.标定板,可在kalibr的wiki中下载,地址:https://github.com/ethz-asl/kalibr/wiki/downloads

选择

下载(然后发现根本下载不了。。。eth这群货真的是。。。。event camera的驱动弄得崩溃,连标定的扳子也搞???)链接: 百度网盘 请输入提取码 提取码: r4ts

然后缩放到40%,用A4纸就可以打印出来
原始pdf的格子参数是:
6*6的格子
大格子边长:5.5cm
小格子边长:1.65cm
小格子与大格子边长比例:0.3
调整后的格子参数是:
大格子边长:2.4cm
小格子边长:0.75cm
小格子与大格子边长比例:0.3125

但这只是理想情况,实际情况还得实际测量。
新建april_6x6_A4.yaml文件,格式参考上图的yaml,内容展示如下:

target_type: 'aprilgrid' #gridtype
tagCols: 6               #number of apriltags
tagRows: 6               #number of apriltags
tagSize: 0.024           #size of apriltag, edge to edge [m]
tagSpacing: 0.3125          #ratio of space between tags to tagSize

录制bag,录制bag 的同时,相机对准标定板,或是固定相机或是固定标定板,晃动另一个,动作不要太大,不要让相机看不清标定板(就争取把标定板晃动到过相机像素平面的每个地方)

注意:需要修改相机帧数(官方推荐是4Hz,尽管实际频率不完全准确,但是不影响结果)
kalibr在处理标定数据的时候要求频率不能太高,一般为4Hz,我们可以使用如下命令来更改topic的频率,实际上是将原来的topic以新的频率转成新的topic,实际测试infra1才是对应左目相机

roslaunch vins davis_testing.launch

rostopic list

rostopic hz /dvs/image_raw

rosrun topic_tools throttle messages /dvs/image_raw 4.0 /color


rostopic hz /color


roslaunch vins davis_raw_image_recording.launch 

录制的topic就是转换频率后的topic

rosbag play /home/kwanwaipang/dataset/gwphku/hku_davis346_image_raw_imu_2021-10-26-14-31-10.bag

然后就使用上面安装好的Kalibr进行标定

source ~/kalibr_workspace/devel/setup.bash
kalibr_calibrate_cameras --target /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/april_6x6_A4.yaml --bag  /home/kwanwaipang/dataset/gwphku/hku_davis346_image_raw_imu_2021-10-26-14-31-10.bag --models pinhole-equi --topics /color --show-extraction

其中–target …/yaml/april_6x6_A4.yaml是标定板的配置文件,注意如果选择棋格盘,注意targetCols和targetRows表示的是内侧角点的数量,不是格子数量。–bag multicameras_calibration.bag是录制的数据包,models pinhole-equi pinhole-equi pinhole-equi表示三个摄像头的相机模型和畸变模型(解释参考https://github.com/ethz-asl/kalibr/wiki/supported-models,根据需要选取), --topics /infra_left /infra_right /color表示三个摄像头对应的拍摄的数据话题,–bag-from-to 10 100表示处理bag中10-100秒的数据。–show-extraction表示显示检测特征点的过程,这些参数可以相应的调整。

kalibr_calibrate_cameras --target ../yaml/april_6x6_A4.yaml --bag  multicameras_calibration.bag --models pinhole-equi pinhole-equi pinhole-equi --topics /infra_left /infra_right /color --bag-from-to 10 100 --show-extraction

出来的标定结果如下图所示

得到的结果如下所示

cam0:
  cam_overlaps: []
  camera_model: pinhole
  distortion_coeffs: [0.023761891762741624, -0.6457616668645434, 2.070114304159524,
    -2.4332476089446473]
  distortion_model: equidistant
  intrinsics: [333.6626192244177, 333.5395724565995, 162.74154656440015, 133.0594987830887]
  resolution: [346, 260]
  rostopic: /color

若改为降低速率前的topic效果如下:

source ~/kalibr_workspace/devel/setup.bash
kalibr_calibrate_cameras --target /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/april_6x6_A4.yaml --bag  /home/kwanwaipang/dataset/gwphku/hku_davis346_image_raw_imu_2021-10-26-14-31-10.bag --models pinhole-equi --topics /dvs/image_raw --show-extraction
cam0:
  cam_overlaps: []
  camera_model: pinhole
  distortion_coeffs: [0.007145831923439896, -0.4460587583609059, 1.2828141357453273,
    -1.3797467604219862]
  distortion_model: equidistant
  intrinsics: [333.649413082246, 333.5165283699802, 162.51929394284377, 132.6341713511583]
  resolution: [346, 260]
  rostopic: /dvs/image_raw

编写camchain.yaml,格式参考Kalibr官方教程https://github.com/ethz-asl/kalibr/wiki/yaml-formats中的chain.yaml,具体的参数参考上面得到的yaml文件,没有的参数可以删除,最终结果示例如下:

cam0:
  camera_model: pinhole
  intrinsics: [333.649413082246, 333.5165283699802, 162.51929394284377, 132.6341713511583]
  distortion_model: equidistant
  distortion_coeffs: [0.007145831923439896, -0.4460587583609059, 1.2828141357453273, -1.3797467604219862]
  T_cam_imu:
  - [0.01779318, 0.99967549,-0.01822936, 0.07008565]
  - [-0.9998017, 0.01795239, 0.00860714,-0.01771023]
  - [0.00893160, 0.01807260, 0.99979678, 0.00399246]
  - [0.0, 0.0, 0.0, 1.0]
  timeshift_cam_imu: -8.121e-05
  rostopic:  /dvs/image_raw
  resolution: [346, 260]

IMU与camera的联合标定

将之前矫正好的文件放好

source ~/kalibr_workspace/devel/setup.bash
kalibr_calibrate_imu_camera --bag [filename.bag] --cam [camchain.yaml] --imu [imu.yaml] --target [target.yaml] --bag-from-to 15 75 --show-extraction

kalibr_calibrate_imu_camera --bag /home/kwanwaipang/dataset/gwphku/hku_davis346_image_raw_imu_2021-10-26-14-31-10.bag --cam /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/camchain.yaml --imu /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/imu.yaml --target /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/april_6x6_A4.yaml --show-extraction

也有一种说法需要调整参数频率

注意选择的camera失真模型(https://github.com/ethz-asl/kalibr/wiki/supported-models

roslaunch vins davis_testing.launch
rosrun topic_tools throttle messages /dvs/image_raw 20.0 /image_raw 
rosrun topic_tools throttle messages /dvs/imu 200.0 /imu
roslaunch vins davis_raw_image_imu_recording.launch 

source ~/kalibr_workspace/devel/setup.bash
kalibr_calibrate_cameras --target /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/april_6x6_A4.yaml --bag /home/kwanwaipang/dataset/gwphku/hku_davis346_image_imu_2021-10-26-16-14-23.bag --models pinhole-radtan --topics /image_raw --show-extraction

kalibr_calibrate_imu_camera --bag /home/kwanwaipang/dataset/gwphku/hku_davis346_image_imu_2021-10-26-16-14-23.bag --cam /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/camchain_second.yaml --imu /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/imu_second.yaml --target /home/kwanwaipang/catkin_ws_dvs/src/EVIO/config/kalibr_davis/april_6x6_A4.yaml --show-extraction

至此,矫正完成!测试一下这些参数。

出来的pdf report如下

camera校正的report

 

camera-imu校正的report

 

 

参考资料

GitHub - ethz-asl/kalibr: The Kalibr visual-inertial calibration toolbox

GitHub - gaowenliang/imu_utils: A ROS package tool to analyze the IMU performance.

GitHub - rpng/kalibr_allan: IMU Allan standard deviation charts for use with Kalibr and inertial kalman filters.

联合标定双目相机和imu,使用工具Kalibr_甜甜圈吃不完的博客-CSDN博客

联合标定单目相机和imu,使用工具Kalibr_甜甜圈吃不完的博客-CSDN博客

realsenseD435i imu+双目标定_crazyfox的博客-CSDN博客_d435i imu标定

Installation · ethz-asl/kalibr Wiki · GitHubhttps://blog.csdn.net/Hanghang_/article/details/103546033Installation · ethz-asl/kalibr Wiki · GitHub

Realsense D435i RGB+IMU标定_追-CSDN博客

以上是关于.NET coding patterns(davidfowl)的主要内容,如果未能解决你的问题,请参考以下文章

Davies-boudin 指数和最大比率

anti-pattern - Hard coding

我对 Davies-Bouldin Index 的 python 实现是不是正确?

ROS实验笔记之——DAVIS346测试

DAVY的神龙帕夫——读者的心灵故事|十二橄榄枝的传说

ROS实验笔记之——基于kalibr来标定DAVIS346