【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。

这篇具有很好参考价值的文章主要介绍了【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:做FPGA大赛期间遇到的问题,自己coding过程。
包含:hdmi、摄像头等多输入源的拼接;了解DDR以及多种DMA传输方式,修改底层突发长度以及存储位宽;单输入源任意角度旋转(无需降低帧率)。


前言

写这篇文章的原因呢,是因为之前参加FPGA大赛的时候遇到很多问题找不到系统的解决办法,本文主要提供一个大概的关于图像的大概处理流程;当然根本原因是没晋级决赛了哇,但是不得不承认我在这个比赛的过程中学习到了非常多的东西,记录下来帮助更多希望入门FPGA和图像处理的朋友们。接下来的内容会以提出问题->分析问题->解决问题的形式展示。

免责声明

本人做FPGA很多年,但是系统的做FPGA图像处理这是第一次,本文仅记录一月以来的调试内容,如果内容有问题请通过邮箱联系我,评论偶尔会看。代码(紫光同创版)免费提供(仅仅提供我写的部分)


一、hdmi、摄像头等多输入源的拼接

问题:DDR在某个时刻只能进行读或者写操作的条件下如何进行数据处理?怎么才能最大程度的减缓帧丢失?

在使用xilinx系列FPGA的时候里面有一个很常用的IP:AXI-interconnect;AXI-interconnect是一个一主多从的模块,对多通道从机进行数据缓存通过自动仲裁,输出。他的优点简单,emm,只有简单。

最初,解决这个问题的时候进行了降帧的处理,即DDR的某片区域存够了摄像头或者hdmi输入的一帧数据进行暂停处理,当DDR的另一片区域也存够了hdmi或者摄像头输入的一帧再进行输出。
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能
如图所示,每个区域会在存满一帧以后等待另一个输入也存满的时候进行输出,然后在此进行下一次存储。这样造成的结果会使得两个输入源的帧率成倍的降低。

那么我们在没有AXI-interconnect IP的条件下,如何进行仲裁呢?怎么才能减缓帧率的丢失呢?

为了解决这个问题,我使用的数据仲裁方法是——固定优先级算法进行仲裁。

   if(wfifo_rcount_1 >= wr_bust_len  )begin  
       state_cnt <= WRITE_ADDR_1;                    
   end         
   else if(wfifo_rcount_2 >= wr_bust_len  )begin 
       state_cnt <= WRITE_ADDR_2;                    
   end                
   else if(raddr_rst_h)begin                       
           state_cnt <= DDR3_DONE;                                                    
   end                                
   else if(rfifo_wcount_1 < rd_bust_len  )begin 
       state_cnt <= READ_ADDR_1;                                                 
   end                             
   else if(rfifo_wcount_2 < rd_bust_len  )begin  
       state_cnt <= READ_ADDR_2;                                                                                        
   end 

这个算法的缺点很明显,就是可能会造成总线主设备的“饥饿”或“撑”的现象,但是由于DDR速率远远超过fifo的存储速率在从机不多的条件下远远满足我所需要的要求。

有了仲裁算法,剩下的就是对AXI总线的coding了,有兴趣的人可以参考博客AXI-lite以及debug过程,这里要注意的是紫光同创今年这个版本的DDR的特点:使用的是Simplified AXI4 接口,配置较为简单。即:读写地址需要进行握手,数据则不需要。

在进行完数据仲裁之后,就可以各输入源进行写入和读出的操作了,经测试,帧率完美吻合,使用两帧缓存乒乓操作不会出现画面撕裂现象。

最后,记录一下DDR里面的数据计算方法,因为这个也曾困扰我一个晚上。
首先PGL50H中,DDR支持32bit,含有4Gbit(一片512MB,两片1024MB)内存。生成ip后的地址位宽为28bit;
当所有地址写满的时候我们计算一下内存是否吻合:

2^28*32/8=1,073,741,824Byte = 1024MB

最初版代码使用的是:
burst_len=16; input_data_width=16; AXI_data_width=256;DDR_data_width=32;
那么每次地址变化应该是:addr=addr+burst_len*AXI_data_width/DDR_data_width;
地址变化这里一定要仔细理解,不然后续旋转无法清楚

DDR中每个地址存放的数据位宽为32bit,每一次burst传输256个bit,也就意味着每一次burst的时候DDR对应的地址变化是8
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能
如果进行16次burst,那自然就是burst_len*8;

二、WDMA传输

摘要:WDMA(Waste Direct Memory Access),即一种极其占用DDR内存和时间的传输方式,由于它极其消耗资源,因此我给它命名为WDMA。

在正式介绍WDMA之前,介绍一下目前我目前接触到的几种DMA传输方式:VDMA,CDMA,XDMA,FDMA;前三种DMA传输方式是xilinx厂家提供的ip,最后一个FDMA是米联客提供的ip。简单介绍一下前三种。

VDMA:简单来说就是为了缓存帧图像专门开发出来的一个DMA方式,可以满足改变帧速率,缓存帧的需求。
CDMA:用过MicroBlaze的应该都很了解,就是PL和PS之间的沟通渠道,ZYNQ则是用到DDR的时候会用到这个DMA传输方式。
XDMA:我使用XDMA都是用来进行PCIe数据沟通的,可见xilinx series 7 PCIE xdma测试记录

这里重点说一下FDMA(米联客开发,有详细的文档说明,目前已经更新到第六版,如有需求请通过邮箱联系我),是我在比赛最开始准备使用的一款ip,我这有详细的文档介绍,这里只说一下我前期准备过程中学到的一些东西。
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能

首先是名字FDMA,为什么叫FDMA? 解答:FDMA(Fast Direct Memory Access)是一种快速DMA缓存方式。
FDMA的优点:如果使用过xilinx的DMA ip那么大家应该了解,配置起来稍显麻烦,不够灵活。而FDMA突出特点是简单直接。FDMA主要是定义了一个AXI4 FULL的ip,其余需要的DDR等外设只需要挂载在AXI4总线上就可以进行外设读取存储的操作了,跟着米联客的教程,一步一步做,不要光看一定要有自己debug的过程。
FDMA使用的三帧缓存架构数据框图如下:
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能
使用xilinx的话我们就是为了简单,那么如果此时有多通道的输入,我们只需要调用axi-interconnect ip就行,如果通道数过多的话我建议使用自己写的仲裁方式,因为这个ip只能满足低时序,数据量不是那么大的情况下使用。
三帧缓存架构,这里定义的FBUF_SIZE就是来控制帧缓存的一个parameter。

//write
 if(W0_bcnt == BURST_TIMES) begin
     if(W0_Fbuf == FBUF_SIZE) 
        W0_Fbuf <= 7'd0;
     else 
        W0_Fbuf <= W0_Fbuf + 1'b1; 
     W_MS <= S_IDLE;
 end
//read
 if(R0_bcnt == BURST_TIMES ) begin
 	R_MS <= S_IDLE;
 	if(W0_Fbuf == 7'd0) 
 		R0_Fbuf <= FBUF_SIZE;
 	else 
 		R0_Fbuf <= W0_Fbuf - 1'b1; 
 end

//frame 地址切换
assign O_bufn = I_bufn < BUF_DELAY?  (BUF_LENTH - BUF_DELAY + I_bufn) : (I_bufn - BUF_DELAY) ;

三帧缓存,里面的操作就是对帧的缓存和读出有一个乒乓操作,当然我们的代码中没有修改使用的是两帧缓存方案的乒乓操作,这里介绍一下三帧缓存方案:
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能
如图所示,我读出的帧和写入的帧两个地址永远不会重叠,我们做的地址切换就有这样的操作。

assign O_bufn = I_bufn < BUF_DELAY?  (BUF_LENTH - BUF_DELAY + I_bufn) : (I_bufn - BUF_DELAY) ;
// 3帧缓存 延迟2帧推导 BUF_DELAY     = 2 ,BUF_LENTH     = 3
//0<BUF_DELAY(2)     BUF_LENTH(3) - BUF_DELAY(2) + I_bufn(0)  = 1
//1<BUF_DELAY(2)     BUF_LENTH(3) - BUF_DELAY(2) + I_bufn(1)  = 2
//2=BUF_DELAY(2)     I_bufn(2)    - BUF_DELAY(2)   = 0

FDMA配置问题等等可以看文档说明,说的绝对比我明白的多。

那开始进入正题,WDMA的介绍。
为什么叫WDMA呢,上面已经有过介绍,这里用图片来简单介绍一下,由于输入的图像位宽是16,而一个DDR地址的位宽为32bit,如果我们想让每个地址对应一个点,显示还要做一个像素点的“挑选”。按照简单输入地址和输出地址做对应来说,输出地址从0-1的时候对应的DDR里面都是0,那这么定义的话addr_o>>1=addr_DDR。那我们的挑选规则就是

	addr_o[0] -> data[15:0];
	addr_o[1] -> data[31:16];

由于我们想做的是任意旋转,每次我只要一个点,而点的对应最好就是一个DDR地址存储的数据就是一个像素点,这样会大大简化算法复杂度,于时就有了WDMA的产生,这是它的第一层含义。
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能那么它的第二层含义在哪呢?

使用的紫光同创的DDR最低一次burst最少传输256bit数据,如果对应旋转的话,我一次只要一个点,那就对应不上了,第二层含义就是修改读出地址,重复读出已经读过的数据,这是第二个含义。
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能
为了到达想要的状态,不得不进行这样的资源消耗,这样带来的效果是非常好的,可以在任意旋转的状态下保持和平常一样的帧率。

三、单输入源的任意旋转

有了上述一、二的解释,最后一节就很好说了,我们只需要在满足所需要的缓存内存和地址映射就可以完成任意旋转了。

难点: 突发长度的修改,任意地址的读取,满足时序的要求等。
这里先看一下最开始的效果,一行显示1024个像素点的时候,16个像素点表示1个点的结果如下。
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能
这是最开始,不浪费任何资源时候的效果。
接下来演示使用WDMA的效果
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能

任意突发长度修改

在代码中修改rd_butst_len=5‘d1,这时候我们进行一次burst会传递256个数据,此时出现的结果是上述模糊图像。那么还要修改的地方就是我的输入数据

  wr_fifo wr_fifo_init (
  .wr_clk        (wr_clk),                    // input
  .wr_rst        (!rst_n|wfifo_rst_h),                    // input
  .wr_en         (datain_valid),                      // input
  .wr_data       ({datain,16'd0}),                  // input [31:0]
  .wr_full       (),                  // output
  .wr_water_level(),    // output [12:0]
  .almost_full   (),          // output
  .rd_clk        (ddr_clk),                    // input
  .rd_rst        (!rst_n|wfifo_rst_h),                    // input
  .rd_en         (wfifo_rden),                      // input
  .rd_data       (wfifo_dout),                  // output [255:0]
  .rd_empty      (),                // output
  .rd_water_level(wfifo_rcount),    // output [9:0]
  .almost_empty  ()         // output
);

这里修改之后相当于我每个地址存放的是一个像素点,此时又要进行一个计算,就是一帧图像需要占据的DDR的大小是多少;

1280 * 720 * 32 / 32 = 921600,这是对应的我们DDR的最大地址,很明显资源浪费多了一倍
输入是16bit时 : 1280 * 720 * 16 / 32 = 460800

接下来是对读地址进行重复读操作的代码

      if(axi_arready && axi_arvalid) begin
          axi_arvalid <= 1'b0;
          axi_araddr_n_1 <= axi_araddr_n_1 + 5'd4;
      end

如果正常完成一次burst应该是地址加8(见一所述),因为每次读出的数据不希望是256bit所以对地址进行这样的操作。
如果仅仅修改完这次加载代码会发现,什么东西都没了,为什么呢?
在代码里面仲裁是通过固定优先级来实现的,读操作的许可证,是通过rd_fifo的数据位来进行控制的,当一次burst吐出256个数据而我只要128个数据的时候会造成DDR->fifo时序和数据位的错乱,因此,要做一个强制拉回的操作,在代码里是这样控制的。

		always@(posedge clk or negedge rst_n)
    		if(!rst_n)
        		rd_data_cnt <= 6'd0;
    		else if(rd_data_cnt== 6'd31 && (state_cnt == READ_DATA_1 ||state_cnt == READ_ADDR_1))
        		rd_data_cnt <= 0;
   	 		else if(axi_rvalid && (state_cnt == READ_DATA_1 ||state_cnt == READ_ADDR_1))
      	  		rd_data_cnt <= rd_data_cnt + 1;
    		else if(state_cnt == READ_DATA_1 ||state_cnt == READ_ADDR_1 )
        		rd_data_cnt <= rd_data_cnt ;
    		else 
        		rd_data_cnt <= 0 ;
//------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------
            READ_ADDR_1:begin
                if(rd_data_cnt==6'd31 )
                    state_cnt <= DDR3_DONE;
                else if(axi_arvalid && axi_arready)
                    state_cnt <= READ_DATA_1;
                else
                    state_cnt <= state_cnt;
            end
            READ_DATA_1:begin                   //
                if( rd_data_cnt < 6'd31)
                    state_cnt <= READ_ADDR_1;   //
                else if(rd_data_cnt==6'd31 )
                    state_cnt <= DDR3_DONE;
                else                          //
                    state_cnt   <= state_cnt; //
            end

这就是全部内容了,当完成强制拉回操作之后,就可以对任意地址,任意突发长度,任意数据的读取操作了。

写完文章一下午最多提问的是关于拉回操作为什么这么做,体现在哪里
不管是使用官方例程还是黑金的axi,最初的burst_len=16。
使用代码的时候一开始突发长度为16的时候所有东西都正常这是肯定的,那么当现在burst=1的时候会有问题,那该怎么改呢,才能正确呢?好,强制进行16次burst为1的操作 ,此时的地址跳变为8,结果你会发现,哎,正常了。
那么我们进行下一步,开始浪费资源,重复去读,此时地址跳变为4,那么该怎么做呢?很明显,强制进行16x2次burst为1的操作即可。这是对强制拉回操作的一个解释。

旋转部分

旋转模块采用的是逆向思维,用目标图像的坐标去与原图的坐标进行坐标匹配,若在原图像中能找到匹配的图像,就显示该点旋转后的点坐标,若在原图中找不到该点,则不显示该点。具体算法参考19年FPGA大赛大佬,下面是他的链接https://blog.csdn.net/weixin_42905573/article/details/105941991
在任意旋转模块中,首先要保证的是得到一帧完整存在的图像,对于显示器输出坐标做一个回环,经过旋转算法模块对应到DDR中的数据,取出显示,本模块数据流程框图如图。
【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。,fpga开发,图像处理,人工智能
本方案配置的摄像头分辨率为1280 * 720p,HDMI显示也是1280 * 720p,因此如果对应到坐标位置,每一行用x表示,x从0开始计数到1280,用y来表示此时已经计数到哪一行,对应到DDR一帧图像中选取的坐标点的addr信息为ddr_addr=x_r+1280*y_r。
使用之前介绍的WDMA方式,可以对DDR中的任意数据进行读出,且满足时序要求,使用二级缓存乒乓操作的方式可以保证画面不撕裂。

if(axi_arready && axi_arvalid)begin                                                                                  
    axi_arvalid <= 1'b0;                                                                                             
    axi_araddr_n_1 <= axi_araddr_n_1 + rd_bust_len*5'd4;                                                             
    axi_araddr_n_11 <= ( x_rotate >= 1280 || y_rotate > 720 ) ? 28'd0 :  x_rotate[12:0]	+ y_rotate[12:0] *12'd1280;  
    if(x_cnt> 12'd1280-12'd8)begin                                                                                   
        x_cnt <= 32'd0;                                                                                              
        y_cnt <= y_cnt + 1;                                                                                          
    end                                                                                                              
    else begin                                                                                                       
        x_cnt <= x_cnt + 5'd4;                                                                                       
        y_cnt <= y_cnt;                                                                                              
    end                                                                                                              
end           
//-------------------------------------------------------------------------------------------
coor_trans coor_trans_inst
(
    .clk		(	clk			),
    .rst_n		(	rst_n			),
    
    
    .angle		(	10'd180			),
    .x_in		(	x_cnt			),
    .y_in		(	y_cnt			),
   

	.x_out		(	x_rotate		),
    .y_out		(	y_rotate		)
);                                                                                                       

效果展示

A4EA9C00A50A042A13BD13535A3E5A90

以上是对这次FPGA大赛的一次总结,如果按照工程走可完成的任务是图像拼接,旋转,以太网收发(本文主要讲摄像头,不涉及以太网的东西),上述所用的所有算法代码等如有需求可以通过邮箱联系我:784927721@qq.com

其他事项记录

在这次比赛中,学到了很多东西,最后记录一点小东西,希望能在不经意之间帮到大家,做完之后我们的视频中在旋转的过程中一些该是黑色的地方有很多毛刺,分析原因,是在外层r_data的时候用了组合逻辑进行了输出,不影响输出,但是某些时序可能没有对上。
在这过比赛中收获最大的可能是在资源消耗过大的时候,使用大量组合逻辑会影响布局布线规则,而我使用组合还是时序的条件都是看是否当前输入要和时钟产生关系,没想到最后使用一点组合逻辑就会导致了很多时序问题。
篇幅有限,上述还有很多仿真工作以及debug过程没有展示。

本文全部原创,如果转载请联系作者,侵权必究。


总结

这次比赛,感谢全国大学生FPGA创新设计竞赛组委会所提供的宝贵的参赛机会,感谢FPGA紫光同创为我们提供的一切支持,感谢米联客ip作者在晚上十一点被我打扰还一点一点与我讲解ip的问题所提供的帮助。
因为淋过雨,所以想给别人撑伞。文章来源地址https://www.toymoban.com/news/detail-751748.html

到了这里,关于【FPGA图像处理】——DDR仲裁、多输入源拼接、旋转任意角度、突发长度修改、任意地址读取。的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FPGA 多路视频处理:图像缩放+视频拼接显示,OV5640采集,提供2套工程源码和技术支持

    FPGA高端项目:Xilinx Kintex7系列FPGA 多路视频缩放拼接 工程解决方案 ov5640版本 提供4套工程源码+技术支持 没玩过图像缩放和视频拼接都不好意思说自己玩儿过FPGA,这是CSDN某大佬说过的一句话,鄙人深信不疑。。。本文使用Xilinx的Kintex7系列FPGA实现多路视频缩放拼接方案,视频

    2024年02月08日
    浏览(42)
  • 紫光同创FPGA 多路视频处理:图像缩放+视频拼接显示,OV7725采集,提供PDS工程源码和技术支持

    紫光同创FPGA 多路视频处理:图像缩放+视频拼接显示,OV7725采集,提供PDS工程源码和技术支持 “苟利国家生死以,岂因祸福避趋之!”大洋彼岸的我优秀地下档员,敏锐地洞察到祖国的短板在于高精尖半导体的制造领域,于是本着为中华民族伟大复兴的中国梦贡献绵薄之力的

    2024年02月08日
    浏览(74)
  • 【图像处理】图像拼接原理介绍

    图像拼接是图像处理的基础之一,虽然自己并没有直接做图像拼接方面的研究,但在面试中却多次被问到这方面的内容,可见这个知识点还是很重要的。事实上,很多场景都会用到图像拼接的知识,例如运动检测与跟踪、游戏画面的重建等。 本文参考自公众号“计算机视觉

    2024年02月05日
    浏览(41)
  • 自学SLAM(6)相机与图像实践:OpenCV处理图像与图像拼接(点云)

    如果写过SLAM14讲第一次的作业,或者看过我之前的运行ORB_SLAM2教程应该都安装过OpenCV了,如果没有安装,没关系,可以看我之前的博客,里面有如何安装OpenCV。 链接: 运行ORB-SLAM2(含OpenCV的安装) 让我们先来看一段代码,学习一下OpenCV的函数调用。 改代码中,演示了如下几

    2024年02月06日
    浏览(39)
  • 浅谈无人机遥感图像拼接与处理方法

    遥感(RS-Remote Sensing)——不接触物体本身,用传感器收集目标物的电磁波信息,经处理、分析后,识别目标物,揭示其几何、物理性质和相互关系及其变化规律的现代科学技术。 换言之,即是“遥远的感知”,按传感器搭载平台划分,包括航天遥感、航空遥感、地面遥感。

    2024年02月16日
    浏览(52)
  • python图像处理(旋转)

    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         除了图像镜像之外,另外一个经常遇到的、差不多的功能就是图像旋转。旋转分成顺时针旋转和逆时针旋转两种情况。但是对于isp来说,一般旋转的角度都是提前设置好的,比如只支

    2024年02月12日
    浏览(51)
  • Python图像处理丨图像缩放、旋转、翻转与图像平移

    摘要: 本篇文章主要讲解Python调用OpenCV实现图像位移操作、旋转和翻转效果,包括四部分知识:图像缩放、图像旋转、图像翻转、图像平移。 本文分享自华为云社区《[Python图像处理] 六.图像缩放、图像旋转、图像翻转与图像平移》,作者:eastmount 。 本篇文章主要讲解Pyth

    2024年02月06日
    浏览(53)
  • 《数字图像处理-OpenCV/Python》连载(41)图像的旋转

    本书京东优惠购书链接:https://item.jd.com/14098452.html 本书CSDN独家连载专栏:https://blog.csdn.net/youcans/category_12418787.html 几何变换分为等距变换、相似变换、仿射变换和投影变换,是指对图像的位置、大小、形状和投影进行变换,将图像从原始平面投影到新的视平面。OpenCV图像的几

    2024年02月05日
    浏览(54)
  • MATLAB【数字图像处理】 实验一:图像处理基本操作(平移、放大、缩小、旋转、插值)

    1、熟悉并掌握MATLAB工具的使用;  2、实现图像的读取、显示、存储、平移、镜像、放大、缩小及旋转操作; 3、掌握常用的插值方法,并了解其优缺点。 Matlab 2020B 1、读入一幅RGB图像,变换为灰度图像和二值图像,并在同一个窗口内分别显示RGB图像和灰度图像,注上文字标

    2024年02月06日
    浏览(46)
  • MATLAB图像处理之几何变换——平移与旋转

    可以发现,原图在原坐标基础上向X、Y方向分别平移了50和100个单位。但相应平移的部分也被遮挡了,显然这不符合一些场景的应用需求。 为此,MATLAB还提供了参数设置。在imtranslate函数中设置’OutputView’参数为’full’,即可防止遮挡平移的图像,如下图所示。 MATLAB在进行图

    2024年02月16日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包