ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

图漾相机——C++之Sample_V1之3.X.X版本官网示例解析(停止维护)

图漾相机——C++之Sample_V1之3.X.X版本官网示例解析(停止维护)

文章目录

  • 前言
  • 示例程序说明
  • 1.SDK支持的平台类型
    • 1.1 Windows 平台
    • 1.2 Linux平台
  • 2.SDK基本知识
    • 2.1 SDK目录结构
    • 2.2 设备组件简介
    • 2.3 设备组件属性
    • 2.4 设备的帧数据管理机制
    • 2.5 SDK中的坐标系变换
  • 3.Sample_V1示例程序
    • 3.1 DeviceStorage
    • 3.2 DumpCalibInfo
    • 3.3 NetStatistic
    • 3.4 SimpleView_SaveLoadConfig
    • 3.5 SimpleView_FetchFrame
    • 3.6 SimpleView_Callback
    • 3.7 SimpleView_SGBM
      • 3.7.1 disparityNum和disparityOffset参数调整技巧
      • 3.7.2 matchWinW和matchWinH参数调整技巧
      • 3.7.3 P1和P2参数调整技巧
      • 3.7.4 uniqueFator和uniqueAbsDiff参数调整技巧
    • 3.8 SimpleView_Point3D
      • 3.8.1 如何获取相机内参
      • 3.8.2 相机内参描述
      • 3.8.3 fetch_config.xml文件描述
      • 3.8.4 D2C对齐方式说明
      • 3.8.5 C2D对齐方式说明
    • 3.9 SimpleView_SGBM_XYZ48
    • 3.10 SimpleView_TriggerMode1
      • 3.10.1 软触模式
      • 3.10.2 硬触发模式
    • 3.11 SimpleView_TriggerMode_M2S1
    • 3.12 SimpleView_TriggerMode_M3S1
  • 4.常见问题FAQ
    • 4.1 编译完成后,无SimpleView_Point3D和SimpleView_Registration例子
    • 4.2 选择Opencv安装路径后仍编译不成功
    • 4.3 在Ubuntu16系统,编译报错
    • 4.4 Linux环境编译官网SDK,无SimpleView_Point3D和SimpleView_Point3D_XYZ48例子
  • 5.参考资料

前言

请参考图漾官网的在线文档:
C++示例程序下载

示例程序说明

1.ListDevices:该示例程序用于枚举连接到上位机上的所有深度相机。

2.DeviceStorage:该示例程序用于读写相机的 Custom Block Storage 组件区域 (64KB) 和 ISP Block Storage 组件区域 (64KB)。

3.DumpAllFeatures:该示例程序用于枚举深度相机支持的各个组件和属性,以及各个属性支持的读写等信息。

4.ForceDeviceIP:该示例程序用于强制设置网络深度相机的 IP 地址。

5.LoopDetect:该示例程序用于处理因环境不稳定等因素造成的数据连接异常。

6.NetStatistic:该示例程序用于统计网络深度相机的丢包率。

7.SimpleView_FetchFrame:该示例程序用于深度相机工作在连续采集模式下连续采集图像并输出图像数据。

8.SimpleView_Callback:该示例程序用于深度相机工作在连续采集模式下连续采集图像,在独立的数据处理线程(防止堵塞图像数据获取)中进行 OpenCV 渲染,并输出图像数据。

9.SimpleView_FetchHisto:该示例程序用于获取图像亮度数据直方图。

10.SimpleView_MultiDevice:该示例程序用于多台深度相机同时连续采集图像并输出图像数据。

11.SimpleView_Point3D:该示例程序用于获取 3D 点云数据。

12.SimpleView_Registration:该示例程序用于获取深度相机的内参、外参、深度图和彩色图,并将深度图和彩色图对齐。

13.SimpleView_TriggerDelay:该示例程序用于设置硬件触发延时时间,深度相机在接收到硬件触发信号并等待特定延时之后采集图像。

14.SimpleView_TriggerMode0:该示例程序用于设置深度相机工作在模式 0,相机连续采集图像并以最高帧率输出图像数据。

15.SimpleView_TriggerMode1:该示例程序用于设置深度相机工作在模式 1,相机收到软触发指令或硬触发信号后采集图像并输出图像数据。

16.SimpleView_TriggerMode_M2S1:该示例程序用于设置主设备(相机)工作在模式 2,多台从设备(相机)工作在模式 1,以实现多台深度相机级联触发,同时采集图像。
主设备收到上位机发送的软件触发信号后,通过硬件 TRIG_OUT 接口输出触发信号,同时触发自身采集并输出深度图;从设备收到主设备的硬件触发信号后,采集并输出深度图。

17.SimpleView_TriggerMode_M3S1:该示例程序用于设置主设备(相机)工作在模式 3,多台从设备(相机)工作在模式 1,以实现多台深度相机按照设置的帧率级联触发,同时采集图像。
主设备按照设置的帧率,通过硬件 TRIG_OUT 接口输出触发信号,同时触发自身采集并输出深度图;从设备收到主设备的硬件触发信号后,采集并输出深度图。

18.SimpleView_SaveLoadConfig:该示例程序用于将本地的 JSON 文件写入 Storage 组件,并从 Storage 组件中加载已写入的 JSON 文件。此外,程序还支持将 JSON 文件导出到本地,以便检查和配置参数。

19.SimpleView_XYZ48:该示例程序用于解析并显示 XYZ 格式的深度图。

20.SimpleView_Point3D_XYZ48:该示例程序用于获取深度为 xyz48 格式下的 3D 点云数据

21.SimpleView_HDR:该示例程序用于展示如何使用HDR功能进行相机采图。

22.SimpleView_OpenWithIP:该示例程序用于使用IP打开相机 。

23.SimpleView_PixelsRegistration:该示例程序展示深度图像中心在彩色图像上的位置。

24.SimpleView_SGBM:该示例程序展示了如何配置SGBM参数,并实现采图的方法。

25.SimpleView_StreamAsync:该示例程序展示软触发下如何配置相机数据流异步并进行采集图像。

26.SimpleView_StreamAsync1:该示例程序用于展示相机在自由采集模式下如何配置相机数据流异步并进行采集图像。

1.SDK支持的平台类型

1.1 Windows 平台

Windows SDK 支持 X86(32位)和 X64(64位)硬件平台,并提供支持 Window7 及以上版本操作系统的驱动文件。

1.2 Linux平台

Linux SDK 支持 AArch64、ARMv7hf、RaspberryPi、i686 和 x86-64 硬件平台,并为开发者编译程序提供相应平台的库文件。

2.SDK基本知识

2.1 SDK目录结构

Include:头文件

TYApi.h:设备操作的常规接口
TYCoordinateMapper.h:坐标系变换相关接口
TYImageProc.h:畸变校正/滤波接口
TyIsp.h:RAW RGB数据的软ISP处理代码

Lib :
各类平台下的动态库文件及windows平台下sample程序
sample:
SDK中示例代码,通过CMake跨平台自动构建项目,详细功能参见在线文档:https://doc.percipio.xyz/cam/latest/getstarted/compile.html

2.2 设备组件简介

TY_DEVICE_COMPONENT_LIST(TYApi.h):

TY_COMPONENT_DEVICE:用于设置相机本身属性,例如触发属性,网口相机的IP参数等
TY_COMPONENT_DEPTH_CAM:用于设置深度相机属性,例如是能深度数据通道,设置深度数据分辨率等
TY_COMPONENT_IR_CAM_LEFT:用于设置左IR相机属性
TY_COMPONENT_IR_CAM_RIGHT:用于设置右IR相机属性
TY_COMPONENT_LASER:用于设置激光器属性,例如激光器强度,设置激光器自动控制使能等
TY_COMPONENT_RGB_CAM:用于设置RGB相机属性,例如使能RGB通道数据,设置RGB分辨率,曝光时间,增益等

2.3 设备组件属性

TY_FEATURE_ID_LIST:

1.相机的所有组件所有属性
2.并不是相机会支持这里面所有的属性,可通过API接口TYHasFeature的返回值判断是否支持。
3.属性的设置/获取可以通过API接口进行操作:TYSetBool/TYSetInt/TYSetFloat/TYSetStruct…
TYGetBool/TYGetInt/TYGetFloat/TYGetStruct…

4.部分属性参数存在一个range范围,可通过API接口TYGetIntRange/ TYGetFloatRange读取。

2.4 设备的帧数据管理机制

1.设备初始化的时候设置好使能的通道/分辨率/格式之后,可通过API接口TYGetFrameBufferSize获取Frame包大小。
2.可根据获取的Frame包尺寸分配buffer。
3.调用TYEnqueueBuffer将分配的buffer加入到SDK的数据缓冲队列中。
4.调用TYFetchFrame将能从SDK缓冲队列中获取一帧数据。
5.处理完数据之后,重新调用TYEnqueueBuffer将buffer重新加入SDK缓冲队列。

设备的帧数据管理机制示意图

2.5 SDK中的坐标系变换

深度图的像素坐标系和空间坐标系(点云)

TYMapDepthToPoint3d/ TYMapDepthImageToPoint3d TYMapPoint3dToDepth/TYMapPoint3dToDepthImage

点云空间坐标系变换

TYMapPoint3dToPoint3d

深度图和RGB图像素坐标系变换

TYMapDepthToColorCoordinate/TYMapDepthImageToColorCoordinate
TYMapRGBImageToDepthCoordinate

3.Sample_V1示例程序

下面运行exe程序,可以通过鼠标右键+shift按键,打开powershell运行

3.1 DeviceStorage

该示例程序用于操作custom_block.bin和isp_block.bin的存储空间。
使用方法:
1.操作custom_block.bin 存储空间

1)写入文件内容:将1.txt文件中的内容写入custom_block.bin。
运行示例程序:

.\DeviceStorage.exe-id207000151696-b custom-i1.txt

2)读出内存中的文件内容并输出到程序运行目录下。
运行示例程序:

.\DeviceStorage.exe-id207000151696-b custom-o2.txt

注释:207000151696是相机序列号,根据实际情况更改

2.操作isp_block.bin存储空间

1)写入文件内容:将1.txt文件中的内容写入isp_block.bin 。

运行示例程序:

.\DeviceStorage.exe-id207000151696-b isp-i1.txt

2)读出内存中的文件内容并输出到程序运行目录下。
运行示例程序:

.\DeviceStorage.exe-id207000151696-b isp-o2.txt

3.2 DumpCalibInfo

SDK3.6.52之后版本新增示例程序,此示例程序可以将相机的标定参数以json文件的形式导出到本地。
使用方法:
1.导出原始的标定参数文件:

.\DumpCalibInfo.exe-id 相机序列号-cs0-ds0-out_json FM855-E1-G.json

-cs 0:表示设置RGB分辨率为列表中的第一个
-ds 0:表示设置depth分辨率为列表中的第一个
-out_json:指定输出文件的名字及路径

2.导出当前的标定参数文件:

.\DumpCalibInfo.exe-id207000151696-cs1-ds2-mode0-out_json FM855-E1-G.json

-mode 0:表示读取当前设置后的标定参数,读取到的内参会随分辨率变化,其他模式均为原始的标定参数。

3.3 NetStatistic

该示例程序用于测试网络相机图像的丢包率。
示例程序如下:

TY_CAMERA_STATISTICS st;ASSERT_OK(TYGetStruct(hDevice,TY_COMPONENT_DEVICE,TY_STRUCT_CAM_STATISTICS,&st,sizeof(st)));LOGI("Statistics:");LOGI(" packetReceived: %"PRIu64" ",st.packetReceived);LOGI(" packetLost : %"PRIu64" ",st.packetLost);LOGI(" imageOutputed : %"PRIu64" ",st.imageOutputed);LOGI(" imageDropped : %"PRIu64" ",st.imageDropped);

packetReceived:上位机共收到的数据包数量
packetLost:上位机共未收到的数据包数量
imageOutputed:上位机总的收到的图像数量(与数据流个数成整倍数关系)
imageDropped:上位机总的未收到的图像数量

3.4 SimpleView_SaveLoadConfig

该示例程序给出了保存/导出相机配置参数的demo。

参数配置文件有自己的格式,格式错误会校验不通过,从而功能失效。
若需正确的参数配置文件,请使用PV软件导出一份。

使用方法:
1.将参数配置文件导入相机存储空间
运行示例程序:

.\SimpleView_SaveLoadConfig.exe-id207000151696-s FM855.json

2.将参数配置文件导出到本地程序运行目录下
运行示例程序:

.\SimpleView_SaveLoadConfig.exe-id207000151696-o fm855.json
  1. write_parameters_to_storage
    向DeviceStorage写入包含featrue配置的json数据
  2. load_parameters_from_storage
    从DeviceStorage读出json并解码出json然后配置到相机

常见错误反馈:

1.Error:The configuration file is too large,the maximum size shouldnotexceed4000bytes:写入存储空间时,文件内容大于存储空间的大小2.Error:The CRC check code is empty:加载参数时,存储空间没有内容3.parameters load fail!:用write_parameters_to_storage()写入的文件内容错误,格式不匹配4.Error:The data in the storage area has a CRC check error.: 存储空间的文件内容,不是以write_parameters_to_storage()接口写入的,在调用load_parameters_from_storage()时将返回此错误

3.5 SimpleView_FetchFrame

该示例程序用于深度相机工作在自由采集模式下连续采集图像并输出图像数据。

3.6 SimpleView_Callback

该示例程序用于深度相机工作在自由采集模式下连续采集图像,在独立的数据处理线程(防止堵塞图像数据获取)中进行 OpenCV 渲染,并输出图像数据。

sample/common/TYThread.cpp提供了简单的线程创建接口,通过开启线程循环fetch图像,以避免主线程的阻塞。

3.7 SimpleView_SGBM

SGM是一种用于计算双目视觉中视差(disparity)的半全局匹配算法,在OpenCV中的实现为semi-global block matching(SGBM)。


而图漾的FM815/PS800/PS801/PS802/FM855等相机,SGBM参数作用如下:

3.7.1 disparityNum和disparityOffset参数调整技巧

disparityNum:视差范围。
disparityOffset:最小视差值。

使用场景:客户想要更改预定义的深度范围(近远端值)。
调整方向:
深度范围减小, disparityNum大概率减小。
深度范围远端往相机端缩进, disparityOffset变大,反之亦反。

而disparityNum和disparityOffset参数设置,计算公式如下:

disparityNum=(baseline*fx)/depth_thres.first-(baseline*fx)/depth_thres.second+15;disparityOffset=((baseline*fx)/(depth_thres.second))-DISPARITY_TRANSLATION;

计算公式里的每个参数的具体含义如下:

3.7.2 matchWinW和matchWinH参数调整技巧

matchWinW:匹配块横向大小
matchWinH:匹配块纵向大小
使用场景:针对被测物要测量的特征(针对的是平滑面,还是物体细节),进行细微的调整。
调整方向:一般的预设置的匹配块大小可认为是一个全局考虑的值;深度图越需要平滑的,匹配块大概率是从预设值向大调整,反之,亦反。
注意事项:请首先按照 matchWinW = matchWinH 的方式进行调整;
一般匹配块大小必须是奇数且大于等于3;

3.7.3 P1和P2参数调整技巧

P1:控制视差平滑度的第一个参数。
P1scale:控制视差平滑度的尺度参数。
P2:控制视差平滑度的第二个参数。
使用场景: 需要调整深度图平滑度的情况。
调整方向P1越大,越倾向于生成连续的视差图。 P1scale作用在平滑度上,P1sacle范围是[0~1],越大就越平滑。 P2越大,越倾向于消除小的视差变化。P2必须大于P1。
注意事项:建议先调整P1sacle,后P1,再P2。

3.7.4 uniqueFator和uniqueAbsDiff参数调整技巧

uniqueFator:唯一性检查阈值,最优与次优的比值约束。
uniqueAbsDiff:唯一性检查阈值,绝对值差阈值。
使用场景:被测物有杂点、飞点、空洞过多等情况。
调整方向:后处理约束参数调整过小会产生杂点或飞点;过大则会产生大片空洞。 一般uniqueAbsDiff比uniqueFator约束性更强。

3.8 SimpleView_Point3D

该例子的主要功能:输出带有color信息的点云

而fetch_thread流程如下提所示:

3.8.1 如何获取相机内参

第一种方式方式:在PercipioViewer软件同级目录下,建一个fetch_config.xml文件,之后打开相机取流,相机内参会写到fetch_config.xml文件中。此文件包含了该相机支持的所有组件和属性,
具体步骤如下:
1).在电脑上新建一个名为fetch_config的.xml文件。
2).将.xml 文件与 Percipio Viewer 软件放置于同一目录层级。
3).打开软件, 选择相机取流,
之后便可在此文件中查看相机支持的组件和属性。

第二种是下载官网C++版本SDK,运行DumpAllFeatures.exe例程,输入.\DumpAllFeatures.exe -d,也可在当前目录下得到fetch_config.xml文件
而程序中获取相机RGB/深度图内参,可通过如下函数接口获取:TY_STRUCT_CAM_CALIB_DATA内参是相机最大分辨率的内参;
TY_STRUCT_CAM_INTRINSIC内参是相机当前分辨率的内参

3.8.2 相机内参描述


fx,fy:图像在x,y方向的有效焦距
cx,cy:镜头中心点的坐标

rxx:描述相机相对于其它坐标轴的方向
tx:描述相机在其它坐标系中的位置

k1,k2:径向畸变参数
p1,p2:切向畸变参数

3.8.3 fetch_config.xml文件描述


关于fetch_config.xml的详细描述,请参考如下链接:
https://doc.percipio.xyz/cam/latest/apiguides/baisc_concepts.html

3.8.4 D2C对齐方式说明

D2C方式:深度图对齐到彩色图,对齐后转换到彩色图坐标系。
D2C对齐流程如下:


3.8.5 C2D对齐方式说明

C2D方式:彩色图对齐到深度图,对齐后转换到彩色图坐标系。
C2D对齐流程如下:


3.9 SimpleView_SGBM_XYZ48

SimpleView_SGBM_XYZ48流程

而SimpleView_Point3D和SimpleView_SGBM_XYZ48例子的主要区别如下:

3.10 SimpleView_TriggerMode1

3.10.1 软触模式

软触发:sdk通过以太网或usb向相机发送软触发指令,相机收到后进行触发动作。

TY_TRIGGER_PARAM trigger;//设置触发模式trigger.mode=TY_TRIGGER_MODE_SLAVE;//软触发和硬触发模式ASSERT_OK(TYSetStruct(hDevice,TY_COMPONENT_DEVICE,TY_STRUCT_TRIGGER_PARAM,&trigger,sizeof(trigger)));

3.10.2 硬触发模式

硬触发:向相机trig引脚发送沿信号,每次触发,相机进行一次触发动作。
硬触发模式防抖:

////硬触发模式防抖boolhasDI0_WORKMODE;ASSERT_OK(TYHasFeature(hDevice,TY_COMPONENT_DEVICE,TY_STRUCT_DI0_WORKMODE,&hasDI0_WORKMODE));if(hasDI0_WORKMODE){TY_DI_WORKMODE di_wm;di_wm.mode=TY_DI_PE_INT;di_wm.int_act=TY_DI_INT_TRIG_CAP;uint32_ttime_hw=10;//单位ms,硬件滤波,小于设定时间的电平信号会被过滤uint32_ttime_sw=200;//单位ms,软件滤波,连续高频触发情形,小于设置周期的后一个触发信号将被过滤di_wm.reserved[0]=time_hw|(time_sw<<16);ASSERT_OK(TYSetStruct(hDevice,TY_COMPONENT_DEVICE,TY_STRUCT_DI0_WORKMODE,&di_wm,sizeof(di_wm)));}

硬触发延迟时间:
trig_delay 仅在slave模式下启用,并且仅适用于硬触发
相机收到触发信号后,延时delay微秒后触发sensor采图

TYSetInt(hDevice,TY_COMPONENT_DEVICE,TY_INT_TRIGGER_DELAY_US,delay)TYGetInt(hDevice,TY_COMPONENT_DEVICE,TY_INT_TRIGGER_DELAY_US,&delay)

3.11 SimpleView_TriggerMode_M2S1

TY_TRIGGER_MODE_M_SIG 配置主设备(相机)工作在模式 2,多台从设备(相机)工作在模式 1,以实现多台深度相机级联触发,同时采集图像。

master 模式,每一次软触发命令,会同步输出一个trig out信号 trig out信号可以连接多台slave模式相机的trig in,实现1台master控制n台slave同步触发

具体实现代码如下:

LOGD("=== Set trigger mode");if(((strcmp(selected[i].id,list[0])==0)&&(list.size()>0))||((count==0)&&(list.size()==0))){LOGD("=== set master device, id: %s",cams[count].sn);cams[count].tag=std::string(cams[count].sn)+"_master";TY_TRIGGER_PARAM param;param.mode=TY_TRIGGER_MODE_M_SIG;ASSERT_OK(TYSetStruct(cams[count].hDev,TY_COMPONENT_DEVICE,TY_STRUCT_TRIGGER_PARAM,(void*)&param,sizeof(param)));}else{cams[count].tag=std::string(cams[count].sn)+"_slave";TY_TRIGGER_PARAM param;param.mode=TY_TRIGGER_MODE_SLAVE;ASSERT_OK(TYSetStruct(cams[count].hDev,TY_COMPONENT_DEVICE,TY_STRUCT_TRIGGER_PARAM,(void*)&param,sizeof(param)));}

而这种模式下,触发过滤时间设置如下:

TYSetInt(hDevice,TY_COMPONENT_DEVICE,TY_INT_TRIGGER_DURATION_US,dura)TYGetInt(hDevice,TY_COMPONENT_DEVICE,TY_INT_TRIGGER_DURATION_US,&dura)trig_duration 仅在master模式下启用,并且仅适用于软触发 相机收到触发信号后,延时dura微秒后输出trig out信号

3.12 SimpleView_TriggerMode_M3S1

多台从设备(相机)工作在模式 1,以实现多台深度相机按照设置的帧率级联触发,同时采集图像。

master 模式,不需要触发信号,相机start后,按周期连续输出trig out信号 trig out信号可以连接多台slave模式相机的trig in,实现1台master控制n台slave同步触发。

具体实现代码如下:

LOGD("=== Set trigger mode");if(((strcmp(selected[i].id,list[0])==0)&&(list.size()>0))||((count==0)&&(list.size()==0))){LOGD("=== set master device");cams[count].tag=std::string(cams[count].sn)+"_master";TY_TRIGGER_PARAM param;param.mode=TY_TRIGGER_MODE_M_PER;param.fps=5;//设置的帧率,相机根据fps计算周期,周期是1000000/fps usASSERT_OK(TYSetStruct(cams[count].hDev,TY_COMPONENT_DEVICE,TY_STRUCT_TRIGGER_PARAM,(void*)&param,sizeof(param)));}else{cams[count].tag=std::string(cams[count].sn)+"_slave";TY_TRIGGER_PARAM param;param.mode=TY_TRIGGER_MODE_SLAVE;ASSERT_OK(TYSetStruct(cams[count].hDev,TY_COMPONENT_DEVICE,TY_STRUCT_TRIGGER_PARAM,(void*)&param,sizeof(param)));}

4.常见问题FAQ

下面主要介绍图漾官网C++版本SDK编译过程中,常遇见的问题及解决方式:

4.1 编译完成后,无SimpleView_Point3D和SimpleView_Registration例子

早期的SDK版本,需要在Cmake编译过程中勾选Opengl_View,如不勾选,编译生成的例子无SimpleView_Point3D和SimpleView_Registration

4.2 选择Opencv安装路径后仍编译不成功

选择Opencv安装路径后编译仍报错

如遇到此情况,建议将选择的Opencv路径更改为

..\opencv\build\x64\vc16\lib

4.3 在Ubuntu16系统,编译报错

在使用ubuntu16系统编译官网C++版本的SDK,出现如下报错

而解决方式需要加入C++版本标准,如下图:

如果需要只编译sample_v2,关闭sample_v1,则cmake增加如下参数:

cmake..-DBUILD_SAMPLES=off

在sample_v2基础上关闭对opencv依赖,则cmake增加如下

cmake..-DBUILD_SAMPLES=off-DBUILD_SAMPLE_V2_WITH_OPENCV=off

4.4 Linux环境编译官网SDK,无SimpleView_Point3D和SimpleView_Point3D_XYZ48例子

需要先安装opengl库

sudo apt-get install freeglut3-dev

之后打开camport4-main/sample/sample_v1路径下的CMakeLists.txt,进行编译

将option(OPENGL_VIEW "Enable GLUT OpenGL point cloud view " ON)设置ON。

5.参考资料

1.双目立体匹配算法SGBM
https://blog.csdn.net/A_L_A_N/article/details/81490043?spm=1001.2014.3001.5506
2.双目立体匹配博客&资料汇总
https://blog.csdn.net/qq_38233873/article/details/135450586?spm=1001.2014.3001.5506

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进