.NET ABP.Zero 项目疑似内存排查历程

        当前项目是 .NET 5 EentityFrameworkCore,疑似内存泄漏,之所以说是疑似是因为到目前位置还没有能准确的定位到问题。当前这个框架从 .NET Core 2.1 就开始用,期间有升级到 3.1、5.0、6.0,在排查过程中还把 5.0 分支升级到了 7.0 。不幸的是这些分支都存在疑似泄漏的现象。

        项目部署在 Linux 中的 Docker,Linux 有 RedHat、CentOS、Deban,Docker 版本也是多个版本。这个项目很多分支版本,现在所在公司的主要业务存在很多相似的需求,当出现客户定制化需求的时候就会做差异化代码甚至单独切一个分支出来。根据目前的情况来看问题应该是在 .NET Core 2.1 的时候就已经存在。

        发现这个问题是在大约是在2023年的6月份,这篇过程记录写于10月份,问题的现象是部署的 Docker API 服务内存占用从刚刚启动时的 3xx MB然后一直涨,只要有人访问就涨。不同的项目存在些许差异。这里主要分析的是两个 .NET Core 3.1 和 .NET 5.0 版本的两个分支项目。

        这里虽然提到 .NET 版本并不是说版本存在问题,这里可以排除,首先作为大厂 Release 版本的分支理论上出现这种情况的概率小之又小,另外当前公司存在另外一个 .NET 6 项目使用不同的框架而并没有出现内存泄漏的现象。所以目前怀疑的主要是业务代码。版本只是区分不同项目。

        这两个项目的存在使用上的明显的区别:

        .NET Core 3.1:

        背景:这个项目使用人少,但是接入物联网设备数百个,一直处于持续运行被动接收数据的状态几乎没有喘息的机会。

        现象:内存上涨缓慢(同比 .NET 5 项目),一天 200+ MB 的涨幅。

       

        .NET 5:

        背景:这个项目使用的时段主要是工作日的白天,其中有几个接口的数据单次拉取大的时候有几十上百兆。晚上几乎没人用。

        现象:无人访问时内存稳定,在调用大数据接口后暴增至 1.xx ~ 2.xx GB,在月末月初使用高峰时期,内存占用持续增加至物理内存上限,然后 Docker 自动重启。(服务器有升级见正文)

1. 首先想到的是存在没有释放的内存。

排查方案:使用 using 释放内存

于是排查整个项目中的代码,到处尝试加 using 手动释放资源。这个过程连续搞了大约两周,此后断断续续都在反复看代码,应该是把整个项目的代码都看完了。这段时间看代码都会自带特效,几乎一眼就能看出哪些需要加 using。这个滤镜有一个后遗症,就看大部分代码都可以加。感觉不能加的都要手动去加一个试一下,万一呢 ~~~

结论:没有效果 ~~~ 可能是因为当前项目主要是原生代码,这些 GC 都有安排就算没用手动释放,只要没有一直处于被应用状态,都会被安排回收。期间写了下面这段代码,每隔一分钟输出当前 API 情况,就是通过这个观察到 Docker 是在内存耗尽时重启的。

        #region 系统资源监测[DllImport("kernel32.dll")]public static extern void GlobalMemoryStatus(ref MemoryInfo32 meminfo);[DllImport("kernel32.dll")]public static extern void GlobalMemoryStatus(ref MemoryInfo64 meminfo);public void GetSystemInfo(){var stopwatch = Stopwatch.StartNew();try{if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)){var meminfos = System.IO.File.ReadAllText(@"/proc/meminfo").Split(' ').Where(o => o != string.Empty).ToList();var remainPercent = decimal.Parse(meminfos[5]) / decimal.Parse(meminfos[1]) * 100;// if (remainPercent > 79) Collect();Logger.Info($"============================> 物理内存:{GetUnit(meminfos[1])}");Logger.Info($"============================> 已用内存:{GetUnit(meminfos[3])}");Logger.Info($"============================> 可用内存:{GetUnit(meminfos[5])}");Logger.Info($"======================> 剩余内存百分比:{remainPercent:N2}%");/*Logger.Info(meminfos.ToJsonString());["MemTotal:","8008064","kB\nMemFree:","1924628","kB\nMemAvailable:","4107656","kB\nBuffers:","15132","kB\nCached:","2466712","kB\nSwapCached:","0",\"kB\nActive:","3761208","kB\nInactive:","2060276","kB\nActive(anon):","3373112","kB\nInactive(anon):","88024","kB\nActive(file):","388096","kB\nInactive(file):","1972252","kB\nUnevictable:","0","kB\nMlocked:","0","kB\nSwapTotal:","0","kB\nSwapFree:","0","kB\nDirty:","280","kB\nWriteback:","0","kB\nAnonPages:","3337920","kB\nMapped:","212864","kB\nShmem:","121496","kB\nSlab:","118600","kB\nSReclaimable:","88208","kB\nSUnreclaim:","30392","kB\nKernelStack:","4656","kB\nPageTables:","15832","kB\nNFS_Unstable:","0","kB\nBounce:","0","kB\nWritebackTmp:","0","kB\nCommitLimit:","4004032","kB\nCommitted_AS:","4199784","kB\nVmallocTotal:","34359738367","kB\nVmallocUsed:","20180","kB\nVmallocChunk:","34359684828","kB\nPercpu:","880","kB\nHardwareCorrupted:","0","kB\nAnonHugePages:","1921024","kB\nCmaTotal:","0","kB\nCmaFree:","0","kB\nHugePages_Total:","0\nHugePages_Free:","0\nHugePages_Rsvd:","0\nHugePages_Surp:","0\nHugepagesize:","2048","kB\nDirectMap4k:","81408","kB\nDirectMap2M:","4112384","kB\nDirectMap1G:","6291456","kB\n"]*/}else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){if (RuntimeInformation.OSArchitecture == Architecture.X64){var memInfo = new MemoryInfo64();GlobalMemoryStatus(ref memInfo);var remainPercent = memInfo.AvailablePhysical / (decimal)memInfo.TotalPhysical * 100;// if (remainPercent > 79) Collect();Logger.Info($"============================> 物理内存:{GetUnit((long)memInfo.TotalPhysical)}");Logger.Info($"============================> 已用内存:{GetUnit((long)(memInfo.TotalPhysical - memInfo.AvailablePhysical))}");Logger.Info($"============================> 可用内存:{GetUnit((long)memInfo.AvailablePhysical)}");Logger.Info($"======================> 剩余内存百分比:{remainPercent:N2}%");}else if (RuntimeInformation.OSArchitecture == Architecture.X86){var memInfo = new MemoryInfo32();GlobalMemoryStatus(ref memInfo);var remainPercent = (decimal)memInfo.AvailablePhysical / (decimal)memInfo.TotalPhysical * 100;// if (remainPercent > 79) Collect();Logger.Info($"============================> 物理内存:{GetUnit((long)memInfo.TotalPhysical)}");Logger.Info($"============================> 已用内存:{GetUnit((long)(memInfo.TotalPhysical - memInfo.AvailablePhysical))}");Logger.Info($"============================> 可用内存:{GetUnit((long)memInfo.AvailablePhysical)}");Logger.Info($"======================> 剩余内存百分比:{remainPercent:N2}%");}}}catch (Exception ex){Logger.Error($"系统信息获取异常:{ex.Message}");}try{using var proc = Process.GetCurrentProcess();Logger.Info($"============================> 启动时间:{proc.StartTime}");Logger.Info($"============================> 运行时间:{Clock.Now - proc.StartTime}");// Logger.Info($"====================> 2小时登录/总人数:{login}/{total}");Logger.Info($"==============================> 线程数:{proc.Threads.Count}");Logger.Info($"============================> 虚拟内存:{GetUnit(proc.VirtualMemorySize64)}");Logger.Info($"========================> 应用峰值内存:{GetUnit(proc.PeakWorkingSet64)}");Logger.Info($"========================> 应用当前内存:{GetUnit(proc.WorkingSet64)}");Logger.Info($"======================> 专用工作集内存:{GetUnit(proc.PrivateMemorySize64)}");}catch (Exception ex){Logger.Error($"进程信息获取异常:{ex.Message}");}Logger.Info($"========================> 监测日志耗时:{stopwatch.ElapsedMilliseconds}ms");}/// <summary>一次性回收三代内存</summary>private void Collect(){GC.Collect(0);GC.Collect(1);GC.Collect(2);}#endregion

2.怀疑是服务器性能不够导致排队处理不过来

排查方案:认为服务器不行,换服务器

当前服务器是 ARM 4核8GB,在迁移到这个服务器之前使用的是 x86 服务器也是 4核8GB,并且当时在迁移过来后有明显的“反应慢”的体验。所以怀疑服务器不行,其实更怀疑是代码问题,但是换服务器比来得快,同时当前对于代码其实是没有头绪的。所以选择升级服务器至 x86 8核16GB 这种更快的方式。

结论:升级后整个站点的访问性能有明显提升,但是内存占用高的问题并没有解决,并且占用彷佛就是物理内存的上限(当前没有再继续升级内存不能验证)。从 ARM 升级至 x86 后整个站点有明显“快” 的感觉。

3.怀疑静态 ConcurrentDictionary 一直在增长

排查方案:1.确认 ConcurrentDictionary 是否一直增长。2.验证 Remove 后内存是否释放。

        1.将所有代码中的 static ConcurrentDictionary 集中输出 .Count 来观察是不是一直在增加,这个动作搞了半天,把全是总数都输出了,发现只有部分静态变量出现双倍数据,没有一直增长。解决了个小问题,去掉了一半。期间还把部分 static 转移到 Redis 中去,给 API 服务腾空间(效果不怎么明显,因为内存还是在涨,可以说是没用效果)

        2.通过测试代码验证到底 Remove 后有没有释放内存。观看下面代码,左边主要是两个按钮的点击事件第一个是往 ConcurrentDictionary 中插入 10W 个 Guid。可以从右边的内存占用中看到三个峰值就是在这个动作下产生的。

        然后执行第二个方法,将 ConcurrentDictionary 中的数据 Remove 和 Clear。两种操作看起来并没有完全削峰,手动多次后会释放。期间还测试了联系多次继续 TryAdd 内存会一直涨,并且在 Remove 和 Clear 后并不会立刻释放。等 GC 大佬安排,等多久?什么时候?完全不清楚 ~~~

        在 67 ~ 70 行新增了手动回收 0、1、2 三代内存多次后会释放内存。

结论:static ConcurrentDictionary 变量没有一直增长。在 Remove 和 Clear 后不会释立刻放内存,除非手动回收。但是问题并没有解决,因为这两个场景的数据量都不是特别的多。

4. 升级至 .NET 7 版本,怀疑是 .NET 5 有问题(已经开始不要脸了)

排查过程:当前分支中有 .NET 6,.NET 7 说是对于性能和内存管理都有大幅的提升,所以乘此机会升级试试(万一呢),说实话,其实内心对于 .NET 出问题的概率还是比较低,不至于被我碰到,毕竟都是很普通的业务场景没有多少高科技的逻辑。并且 Visusl Studio 2022 的升级频率也相当高,但是现在对于解决问题的渴望已经达到了顶点,这种感觉就像是前面的 using ,明知道没用但是哪怕有一丝丝希望还是愿意尝试。

结论:从 .NET 5 升级到 .NET 7 后应用的启动性能有了明显提升,但是内存持续增长的问题并没用消失。最后继续不要脸(这两个版本都不是长期支持版本 ~~~ 等下一版 .NET 8 发布后继续升级)

5. 使用 Visual Studio 诊断工具分析内存占用

排查过程:在不断的搜索中,接触到 VS 诊断工具,不确定这个工具是从哪个版本开始的,但是指导2023年才知道可以分析内存占用。这个过程有几天,不是全情投入,因为“摸不到头脑”,又被难住了。通过工具得知 .NET 5 项目之所有在调用接口后内存暴增的原因是 EntityFrameworkCore 的缓存导致的,但是对于这个缓存机制的理解仅限于“缓存”这两个字,其他的不清楚 ~~~

        又尝试使用 builder.EnableServiceProviderCaching(false); 禁用 EFCore 的缓存,确实有效果整个站点立马变得比之前 ARM 平台还要慢几倍,吓得我立刻注释掉。毕竟现在是疑似内存泄漏,就这个动作解决了问题,但是这种体验是不能被接受的。

        在搜索 Visual Studio 诊断工具如何使用的过程中,又接触到 WinDbg。也搜索了一些 WinDbg 的使用方法,成功在服务器中导出了 dmp 文件(特别大 ~~~)但是这个的使用方法还没入门就进行不下去了,大约两天时间经历了一次“从入门到放弃”。

        这里顺带提一下,这个问题虽然几个月都没能解决但是在人肉运维的加持下还没有出大问题。例如 .NET 5 这个版本虽然内存增长明显,但是晚上没人用,所以我们就加了凌晨3点的重启自动任务,使得 API 每天充满活力,我们一边开发一边如痴如醉的排查问题,好几次都是把开发任务拖到 deadline 才从死胡同中抽身去完成。内心来说,因为这个问题不解决,自己面子上有点挂不住(几个月过去了,其实已经开始习惯了 ~~~)

结论:知道了 EntityFrameworkCore 默认启用缓存,看起来缓存的时间非常的长,感觉不重启彷佛一直都在(不知道缓存的内部实现规则),没有验证禁用缓存是否任然会有内存溢出的,因为不能接受没有缓存时的丝滑。

        虽然没有真正定位到为题,但是根据目前查询的信息来 Visual Studio 诊断工具和 WinDbg 是最有可能定位到问题的方式,但是精力不够没能继续探索下去。

6. 将实例引用改为接口引用(不知道有没有用,因为理解不够透彻)

排查方案:将项目中的实例应用改为接口引用。

这个项目已经迭代好多年了,从 2018 年开始一直由本人亲自开发维护,对于这些代码已经熟悉到忘记了。时不时的还会吐槽几年前的自己为什么这么不严谨,自己定的规范自己都不遵守。

结论:不清楚有没用用,但是感觉整个项目的代码正在蜕变并趋于“完美”

7. 排查 new 关键字

排查方案:鉴定 new 关键字的业务是否会存在风险

减少多余的 new,可以不赋初值的就不赋。而且在 new 数组的时候,尽可能的不要填长度,因为如果长度太大喀得就把内存给吃了,不熟悉的从表面上还看不出来,就比如现在这之前的我就没看出来。

        在搜索的过程中发现有很多人出现内存泄漏的原因是用到了 HttpClient,可惜目前项目中没有“直接”使用 HttpClient,而且使用开源项目 WebApiClientCore,老实说我怀疑过这个组件(我就没有脸,不要脸 ~~~ ),但是现在项目中应用比较多,还不能通过去掉的方式来验证。但是一边又想这个开源项目也是从 .NET Core 2.1 开始用的当时是 WebApiClient.JIT 目前 .NET Core 3.1 项目使用的最新版 1.1.4,WebApiClientCore 是在 .NET 5 项目中使用,并且也一直在升级 目前使用的版本是 2.0.2 最新版本是 2.0.4 准备有机会就把它点了。

        在排查期间整出个幺蛾子,一个线程里 while(true) 监测链接状态的业务,因为前面排查 static 变量时没注意把 static 去掉了,导致一直在 new Thread,在本地开发调试没发现问题,发布到测试环境因为用的人少,也没发现问题。直到上线的时候发现线程数一直在新增直到大约13,000 的时候 Docker 会自动重启。这个问题排查代码找了三天,全工时投入期间还不要脸的怀疑有第三方组件出问题又挨个升级了一遍。这个情况比内存泄漏的问题要急迫,因为只要用户数一起来一多个小时甚至不到一小时就会重启一次,这几天搞得心惊胆战心力憔悴生怕数据出问题。

结论:也许有效果,如前面的 “3.怀疑静态 ConcurrentDictionary 一直在增长” 相似,只要不是一直被引用的部分,最终 GC 都会给安排。这次到是减少了部分可以不许 new 赋初值的逻辑,感觉代码又变得更“完美”了,彷佛仅限于感觉。

8. 怀疑数据库资源没有释放

排查过程:当前使用的 PostgreSQL 数据,通过下面的语句发现连接数很少一共只有5个其中还要两个是 Navicat 也就是说 API 只有三个连接数,backend_start 与 query_start 的时间都是今天的。按理说这种情况就不存在资源没有释放了。

SELECT * FROM pg_stat_activity

结论:不确定是不是方法不对,虽然认为连接数没问题,但是始终认为 EntityFreamworkCore 存在没有释放的资源,肯定有什么设置可以解决(取消缓存除外)。


后续有进展再更新,目前问题仍然存在没有解决 2023年10月9日

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/101663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HashMap -- 调研

HashMap 调研 前言JDK1.8之前拉链法: JDK1.8之后JDK1.7 VS JDK1.8 比较优化了一下问题: HashMap的put方法的具体流程?HashMap的扩容resize操作怎么实现的? 前言 在Java中&#xff0c;保存数据有两种比较简单的数据结构:数组和链表。 数组的特点是:寻址容易&#xff0c;插入…

【RabbitMQ 实战】11 队列的结构和惰性队列

一、 队列的结构 队列的组成&#xff1a; 队列由 rabbit_amgqueue_process 和 backing_queue两部分组成。rabbit_amqqueue_process负责协议相关的消息处理&#xff0c;即接收生产者发布的消息、向消费者交付消息、处理消息的确认 (包括生产端的 confirm 和消费端的 ack) 等。…

Qt/C++原创推流工具/支持多种流媒体服务/ZLMediaKit/srs/mediamtx等

一、前言 1.1 功能特点 支持各种本地视频文件和网络视频文件。支持各种网络视频流&#xff0c;网络摄像头&#xff0c;协议包括rtsp、rtmp、http。支持将本地摄像头设备推流&#xff0c;可指定分辨率和帧率等。支持将本地桌面推流&#xff0c;可指定屏幕区域和帧率等。自动启…

【Vuex+ElementUI】Vuex中取值存值以及异步加载的使用

一、导言 1、引言 Vuex是一个用于Vue.js应用程序的状态管理模式和库。它建立在Vue.js的响应式系统之上&#xff0c;提供了一种集中管理应用程序状态的方式。使用Vuex&#xff0c;您可以将应用程序的状态存储在一个单一的位置&#xff08;即“存储”&#xff09;中&#xff0c;…

iPhone15手机拓展坞方案,支持手机快充+传输数据功能

手机拓展坞的组合有何意义&#xff1f;首先是数据存储场景&#xff0c;借助拓展坞扩展出的接口&#xff0c;可以连接U盘、移动硬盘等采用USB接口的设备&#xff0c;实现大文件的快速存储或者流转&#xff1b;其次是图片、视频的读取场景&#xff0c;想要读取相机、无人机SD/TF存…

【angular】实现简单的angular国际化(i18n)

文章目录 目标过程运行参考 目标 实现简单的angular国际化。本博客实现中文版和法语版。 将Hello i18n!变为中文版&#xff1a;你好 i18n!或法语版:Bonjour l’i18n !。 过程 创建一个项目&#xff1a; ng new i18nDemo在集成终端中打开。 添加本地化包&#xff1a; ng a…

042:mapboxGL点击某feature点,使其为中心点

第042个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中通过鼠标点击某feature点,让其成为中心点。这里用到了click事件和flyTo的方法。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共113行)相关API参…

spring boot+ vue位置信息大数据综合管理平台源码

spring boot vue位置信息大数据综合管理平台源码 UWB技术的人员定位系统源码 智慧工厂是产业升级的外在表现形式&#xff0c;利用物联网技术加强信息管理的新模式&#xff0c;人员定位管理通过物联网技术、位置信息大数据的综合处理应用&#xff0c;在智慧工厂人员管理方面具有…

大模型之Prompt研究和技巧

大模型之Prompt研究和技巧 大模型之Prompt编写简介组成技术Zero-ShotFew-shotCOTCOT-SCTOTGoTReAct 大模型之Prompt编写 简介 Prompt是是给 AI **模型的指令&#xff0c;**一个简短的文本输入&#xff0c;用于引导AI模型生成特定的回答或执行特定任务。 Prompt是你与语言模型沟…

【方法】PDF不能转换成其它格式如何解决?

想把PDF文件转换成其他格式&#xff0c;比如Word、PPT&#xff0c;却发现无法操作&#xff0c;这是什么情况呢&#xff1f;又该如何解决&#xff1f;下面我们一起来看看吧。 原因1&#xff1a;没有使用PDF编辑器 如果是在线打开PDF&#xff0c;或者使用PDF阅读器打开PDF&…

将 mysql 数据迁移到 clickhouse (最新版)

一、前驱知识 已经在mysql中插入了海量的数据了&#xff0c;这个时候mysql 承载不了这么大的数据&#xff0c;并且数据只需要查询&#xff0c;修改和删除非常少&#xff0c;并且不需要支持事务&#xff0c;这个时候需要换一个底层存储&#xff0c;这里选用的是 clickhouse 来进…

【论文阅读】面向抽取和理解基于Transformer的自动作文评分模型的隐式评价标准(实验结果部分)

方法 结果 在这一部分&#xff0c;我们展示对于每个模型比较的聚合的统计分析当涉及到计算特征和独立的特征组&#xff08;表格1&#xff09;&#xff0c;抽取功能组和对齐重要功能组&#xff08;表格2&#xff09;&#xff0c;并且最后&#xff0c;我们提供从模型比较&#x…

解读非托管流动性协议Hover: 差异化、层次化的全新借贷体系

“Hover 是 DeFi 借贷赛道的另辟蹊径者&#xff0c;除了在自身机制&#xff08;借贷模型、治理体系&#xff09;上进行创新获得内生动力外&#xff0c;背靠日渐繁荣的 Kava、Cosmos 生态进一步获得外生动力&#xff0c;发展潜力俱佳” 与 DEX 类似&#xff0c;借贷也是 DeFi 世…

深度学习DAY3:FFNNLM前馈神经网络语言模型

1 神经网络语言模型NNLM的提出 文章&#xff1a;自然语言处理中的语言模型预训练方法&#xff08;ELMo、GPT和BERT&#xff09; https://www.cnblogs.com/robert-dlut/p/9824346.html 语言模型不需要人工标注语料&#xff08;属于自监督模型&#xff09;&#xff0c;所以语言…

React js原生 详解 HTML 拖放 API(鼠标拖放功能)

最近碰到了个需求&#xff0c;大概就是要通过可视化拖拽的方式配置一个冰柜&#xff0c;需要把预设好的冰柜内部架子模板一个个拖到冰箱内。一开始的想法是用鼠标事件&#xff08;mousedown、mouseup等&#xff09;那一套去实现&#xff0c;能实现但是过程过于复杂&#xff0c;…

qt判断当前日期的当月的最后一天是几号

1、拖个dateTimeEdit在界面上&#xff0c;同时来判断输入的时间的最后一天的日期是什么&#xff1f; int year,month;int monthArr[12]{31,28,31,30,31,30,31,31,30,31,30,31};QDateTime time ui->dateTimeEdit->dateTime();year time.toString("yyyy").toIn…

uniapp 显示icon异常

按照文档创建的uni-ui项目&#xff0c;仿照示例程序写的代码中icon显示异常 &#xe470; 异常情况&#xff1a; 正常情况&#xff1a; 通过比对代码发现&#xff0c;示例程序的App.vue中 有一个引用是问题的关键 正是因为多了这一个引用文件&#xff0c;图表的显示才能正常 …

3d tiles规范boundingVolume属性学习

3d tiles的瓦片&#xff08;Tiles&#xff09;包含一些属性&#xff0c;其中第一项是boundingVolume&#xff1b;下面学习boundingVolume&#xff1b; boundingVolume&#xff0c;这个翻译为边界范围框&#xff0c;如果直译为边界体积可能有问题&#xff0c;其实就是包围盒的意…

[Unity][VR]Passthrough2-创建一个基本的Passthrough应用

上一期我们对PassthroughXR项目做好了基本的项目设置,今天我们就开始构建一个基本的Passthrough应用。 我们还是从基本场景开始。先把默认的main camera删除。因为后续我们会引入OVR Rig对象,这个对象自带Camera用来实现VR视角。 在Project面板我们搜索OVR camera rig。看见…

[Mono Depth/3DOD]单目3D检测基础

1.数据增强 图像放缩和裁剪后&#xff0c;相机内参要做相应变化 import random def random_scale(image, calib, scale_range(0.8, 1.2)):scale random.uniform(*scale_range)width, height image.sizeimage image.resize((int(width * scale), int(height * scale)))cali…