36

下冰雹 · 7月3日

基于FPGA的单目内窥镜定位系统设计(附代码)

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来基于FPGA的单目内窥镜定位系统设计,由于篇幅较长,分三篇。今天带来第三篇,下篇,话不多说,上货。

这里也超链接前两篇内容。

基于FPGA的单目内窥镜定位系统设计(上)

基于FPGA的单目内窥镜定位系统设计(中)

导读 

随着现科技的发展和社会的进步,信息科技迅速发展,我们可从互联网、电台等媒体获取大量信息。现代信息的存储、处理和传输变得越来越数字化。在人们的日常生活中,常用的计算机、电视、音响系统、视频记录设备、远程通讯电子设备无一不采用电子系统、数字电路系统。因此,数字技术的应用越来越广泛。尤其在通信系统和视频系统中,数字系统尤为突出。而随着FPGA的出世,数字系统更加受到人们青睐,它为数字系统的设计提供更加便捷的通道,使得数字系统设计可以芯片小型化,电路规模大型化,庞大的逻辑资源,可满足各种数字系统设计。

随着社会的发展,科学技术已经应用于各个领域,尤其是医疗领域尤为突出。而在医疗领域中,心脏电信号模拟器手术辅助仪器发展迅速。为了训练经验少的医生熟悉心脏手术的操作过程,而专门开发心脏信号模拟仪器,让医生迅速掌握心脏手术操作过程,成为一个经验丰富心脏手术医生。

因此,本文将于FPGA平台,以图像处理结合信号采集原理,实现医生在做心脏模拟手术操作导管的过程中,不需要观察心脏内部情况,即可获取导管头在心脏内部信息的功能,采用内窥镜摄像头采集视频和并对导管头进行跟踪定位,信号采集技术可将采集到的导管头在心脏内部触碰区域的信号采集出来送到专业医用仪器,进行心脏3D建模。

本设计的实现对医院培养的经验少的医生尽快掌握心脏手术操作流程很有价值,未来将可以培养更多从事心脏手术工作的医学专业毕业的学生或刚刚从事这个行业的社会医生。

第三篇内容摘要:本篇会介绍系统调试与测试以及结论,包括系统资源性能调试与分析、系统功能测试等相关内容,还会有VGA的常用分辨率参数表、整体电路图、主要程序分享等附录。

六、系统调试与测试

本设计对系统的性能和系统的功能分别进行了测试,性能测试是对FPGA的资源利用情况和运行速度情况进行测试,功能测试有腐蚀算法测试,帧差算法测试,定位功能调试等。

6.1 系统资源性能调试与分析

本设计中,FPGA使用资源如图6.1所示,组合逻辑资源使用45%,分布式寄存器资源使用23%,总的逻辑宏单元使用52%,存储器单元使用了15%,一个锁相环。EP4CE6F17C8共有2个锁相环,总逻辑宏单元使用资源小于80%。满足设计任务需求。

缓冲模块稳定运行时钟200M,可用逻辑分析仪SigalTap验证,验证结果如图6.2和图6.3所示。

image.png

结论:设计缓冲长度为512,缓冲节点在256,每次突发256个数据,如图6.2和图6.3,在数据有效信号范围内,传输数据正确。而且工作时钟在200MHz下运行的,基本能满足多端点图像数据缓冲需求。

6.2 系统功能测试

腐蚀功能测试:腐蚀前可以看到有许多孤立噪声点在视频输出上,输出结果如图6.4所示,当进行腐蚀算法处理后孤立噪声点明显减少,输出结果如图6.5所示。

image.png
image.png

帧差法调试:调试过程中遇到了如下一些问题以及对应的解决方法。

出现问题:

a. 移屏,整个屏幕的图像向左方移动一部分,图像未失真,如图6.6所示。

b. 帧差结果出现混乱,如图6.6所示。

分析问题及解决方法:

a. 移屏问题原因分析,由于SDRAM缓冲读写问题,读上一帧时,还未来得及将所有数据读完,下一帧图像已经来临,SDRAM写的优先级高于读优先级,所以下一帧数据会将上一帧图像部分数据覆盖导致每一帧的输出的图像都会有偏差而出现移屏。因此,解决方法为调节复位延时参数,使得写入帧和读出帧保持在先写入,延时一段时间后在读出的关系。

b. 帧差结果混乱原因分析,一开始使用16位RGB565数据做帧差,结果如图6.6所示,出现混乱,所以使用合成图像RGB565做帧差导致出现各个通道颜色对应颜色相减溢出到其他通道的情况,所以最终相减的结果出现混乱。因此解决的方法是对其灰度图像进行帧差,两帧图像相减,再对相减的结果取绝对值(注意进行减法操作和取绝对值操作时,防止数据溢出),即为正确帧差结果,如图6.4和6.5所示。观察这两张图像边缘轮廓明显,且有明显的拖尾现象。

image.png

功能测试结果:定位如图6.7所示,随着目标物体的运动,运动目标物体有很多坐标点输出,根据设计任务要求,我们只输出目标尾端的坐标,下图右上角为要抓取目标的坐标显示,可以看到目标所处的位置和输出的坐标基本符合,图6.7,图6.8和图6.9为运动目标在不同位置的定位结果。

结论

本文通过以下几种手段解决了帧差算法实现,导管头定位问题和系统工作性能问题。

  1. 乒乓操作将摄像头采集到的图像交替存储到两个不同的SDRAM存储空间,通过SDRAM的缓冲作用,在VGA向SDRAM发出请求信号时,同时读出相邻两帧的数据,然后就可做帧差。
  2. 导管头的定位是通过对帧差后的图像作水平方向和垂直方向的投影,确定出运动目标四条边界,可确定四个坐标点输出,但是仅仅有一个坐标点是导管头的端点,所以计算上边界与运动物体的交点坐标与左右两条边界的距离,判断导管头是左上到右下进入摄像头视觉还是左下到右上进入摄像头视觉,确定导管两点坐标输出,然后再判断剩余的两点坐标是否在整张图像边界上,确定导管头坐标输出。
  3. 这个算法设计更多的使用流水算法,使用移位和拼接运算代替乘法器和除法器,提高系统运行速度,减少资源利用率。

附录A VGA的常用分辨率参数表

image.png

附录B 主要代码


`include "../sdram_4port_ip/sdram_para.v"

module fd_target_location(
  ref_clk,
  rst_n,
  
  clk_out,
  
  //cmos interface
  CMOS_SCLK,    //cmos i2c clock
  CMOS_SDAT,    //cmos i2c data
  CMOS_VSYNC,    //cmos vsync
  CMOS_HREF,    //cmos hsync refrence
  CMOS_PCLK,    //cmos pxiel clock
  CMOS_XCLK,    //cmos externl clock
  CMOS_DB,    //cmos data
  
  //VGA port      
  VGA_HSYNC,      //horizontal sync 
  VGA_VSYNC,      //vertical sync
  VGAD,    //VGA data
  
  //SDRAM物理端口
  S_CLK,
  S_CKE,
  S_NCS,
  S_NCAS,
  S_NRAS,
  S_NWE,
  S_BA,
  S_A,
  S_DB,
  S_DQM
);

  input ref_clk;
  input rst_n;
  
  output clk_out;
  
  //cmos interface
  output CMOS_SCLK;    //cmos i2c clock
  inout CMOS_SDAT;    //cmos i2c data
  input CMOS_VSYNC;    //cmos vsync
  input CMOS_HREF;    //cmos hsync refrence
  input CMOS_PCLK;    //cmos pxiel clock
  output CMOS_XCLK;    //cmos externl clock
  input  [7:0]  CMOS_DB;    //cmos data
  
  //VGA port      
  output VGA_HSYNC;      //horizontal sync 
  output VGA_VSYNC;      //vertical sync
  output  [15:0]  VGAD;    //VGA data

  //SDRAM物理端口
  output S_CLK;
  output S_CKE;
  output S_NCS;
  output S_NCAS;
  output S_NRAS;
  output S_NWE;
  output [`BA-1:0] S_BA;
  output [`ROW-1:0] S_A;
  inout [`DQ-1:0] S_DB;
  output [`DQ/8-1:0] S_DQM;


assign clk_out = clk;
assign S_DQM = 0;
  
wire vga_clk, camera_clk, clk, sys_rst_n;
// wire rst_dly1, soft_rst_n;
wire soft_rst_0;
wire soft_rst_1;
wire soft_rst_2;
wire soft_rst_3;

wire Config_Done, sdram_init_done;
wire sys_we;
wire [15:0] sys_data_in;
wire frame_valid;
wire lcd_request;
wire [15:0] lcd_data_1;
wire [15:0] lcd_data_2;
wire [15:0] lcd_data;

wire sdram_wrreq;
wire sdram_wrval;
wire [`TOTAL_ADDR-1:0] sdram_wraddr;
wire [`DQ-1:0] sdram_wdata;
wire sdram_wdone;
wire sdram_rdreq;
wire sdram_rdval;
wire [`TOTAL_ADDR-1:0] sdram_rdaddr;
wire [`DQ-1:0] sdram_rdata;
wire sdram_rdone;
wire data_valid;

wire write_done_1;
wire read_done_1;
wire write_done_2;
wire read_done_2;


wire [23:0] waddr_min_1;
wire [23:0] waddr_max_1;

wire bound_valid;
wire [10:0] lcd_xpos;
wire [10:0] lcd_ypos;
wire [10:0] x_pos;
wire [10:0] y_pos;
wire lcd_val;
wire [15:0] lcd_dat;

wire start;
wire [11:0] bcd_x_pos;
wire [11:0] bcd_y_pos;
wire [10:0] target_x_pos;
wire [10:0] target_y_pos;
wire char_en;
wire [3:0] char_data;

reg dval_r1, dval_r2, dval_r3;



dcm dcm(
  .clk          (ref_clk),
  .rst_n          (rst_n),
  .soft_rst_0        (soft_rst_0),
  .soft_rst_1        (soft_rst_1),
  .soft_rst_2        (soft_rst_2),
  .soft_rst_3        (soft_rst_3),
  .clk_c0          (camera_clk), //camera配置模块时钟输出
  .clk_c1          (vga_clk), //vga模块时钟输出  
  .clk_c2          (clk), //sdram控制器时钟输出  
  .clk_c3          (S_CLK) //sdram端口时钟  
);  


  
I2C_AV_Config I2C_AV_Config
(
  /*Global clock*/
  .iCLK          (vga_clk),    //25MHz
  .iRST_N          (soft_rst_0),    //Global Reset
  
  
  .I2C_SCLK        (CMOS_SCLK),  //I2C CLOCK
  .I2C_SDAT        (CMOS_SDAT),  //I2C DATA
  
  .Config_Done      (Config_Done),//Config Done
  .LUT_INDEX        (),  //LUT Index
  .I2C_RDATA        ()  //I2C Read Data
);

CMOS_Capture
(
  /*Global Clock*/
  .iCLK          (camera_clk),      //13MHz
  .iRST_N          (soft_rst_2),

  /*I2C Initilize Done*/
  .Init_Done        (Config_Done & sdram_init_done),    //Init Done
  
  /*Sensor Interface*/
  .CMOS_RST_N        (),    //cmos work state(5ms delay for sccb config)
  .CMOS_PWDN        (),      //cmos power on  
  .CMOS_XCLK        (CMOS_XCLK),    //
  .CMOS_PCLK        (CMOS_PCLK),    //25MHz
  .CMOS_iDATA        (CMOS_DB),    //CMOS Data
  .CMOS_VSYNC        (CMOS_VSYNC),    //L: Vaild
  .CMOS_HREF        (CMOS_HREF),    //H: Vaild
  
  /*Ouput Sensor Data*/
  .x_pos          (),
  .y_pos          (),
  .CMOS_HREF_pos      (),
  .CMOS_oCLK        (sys_we),    //1/2 PCLK
  .CMOS_oDATA        (sys_data_in),    //16Bits RGB    
  .CMOS_VALID        (frame_valid),    //Data Enable
  .CMOS_FPS_DATA      ()  //cmos fps
);

wire corrode_dval_w1;
wire corrode_data_w1;
wire dilation_dval_w2;
wire dilation_data_w2;
wire dval_w2;
wire data_w2;
wire dval_w3;
wire [15:0] data_w3;

// image_smooth image_smooth(
  // .clk          (CMOS_PCLK),
  // .rst_n          (soft_rst_2),
  // .dval_i          (sys_we),
  // .data_i          (sys_data_in[7:0]),
  // .dval_o          (dval_1),
  // .data_o          (data_1)
// );

// median_filter median_filter(
  // .clk          (CMOS_PCLK),
  // .rst_n          (soft_rst_2),
  // .dval_i          (sys_we),
  // .data_i          (sys_data_in[7:0]),
  // .dval_o          (dval_1),
  // .data_o          (data_1)
// );

image_corrode image_corrode(
  .clk          (vga_clk),
  .rst_n          (soft_rst_3),
  .dval_i          (lcd_val),
  .data_i          (lcd_dat[0]),
  .dval_o          (corrode_dval_w1),
  .data_o          (corrode_data_w1)
);

image_dilation image_dilation(
  .clk          (vga_clk),
  .rst_n          (soft_rst_3),
  .dval_i          (corrode_dval_w1),
  .data_i          (corrode_data_w1),
  .dval_o          (dilation_dval_w2),
  .data_o          (dilation_data_w2)
);

//二值图像投影
projection projection(
  .clk          (vga_clk),
  .rst_n          (soft_rst_3),
  .dval_i          (lcd_val),
  .data_i          (lcd_dat[0]),
  .x_pos          (x_pos),
  .y_pos          (y_pos),
  .target_x_pos      (target_x_pos),
  .target_y_pos      (target_y_pos),  
  .dval_o          (dval_w3),
  .data_o          (data_w3) //
);


bin_to_bcd u0_bin_to_bcd(.bin(target_x_pos), .bcd(bcd_x_pos));
bin_to_bcd u1_bin_to_bcd(.bin(target_y_pos), .bcd(bcd_y_pos));

char_mac u_char_mac(
  .clk        (vga_clk),
  .rst_n        (soft_rst_3),
  .start        (start),
  .bcd_x_pos      (bcd_x_pos),
  .bcd_y_pos      (bcd_y_pos),
  .char_en      (char_en),
  .char_data      (char_data)
);

char_disp u_char_disp
(
  .clk        (vga_clk),
  .rst_n        (soft_rst_3),
  .char_en      (char_en),
  .char_data      (char_data),
  .move_x_pos      (12'd560),
  .move_y_pos      (12'd30),
  .data_valid_pos    (start),
  .data_valid      (dval_w2), //dval_w2 lcd_val dval_w1 dilation_dval_w2
  .x_pos        (x_pos),
  .y_pos        (y_pos),
  .data_i        ({R[7:3], G[7:2], B[7:3]}), //{R[7:3], G[7:2], B[7:3]} lcd_dat {16{dilation_data_w2}} dilation_data_w2
  .data_valid_o    (),
  .data_o        (lcd_data)
);

//------------------------------------------------------
//将yuv转换成rgb输出
wire [7:0] Y, Cb, Cr;
wire [7:0] R, G, B;
reg [15:0] data_r1;

yuv422_yuv444 yuv422_yuv444(
  .clk        (vga_clk),
  .rst_n        (soft_rst_3),
  .yuv_capture_en    (lcd_request), //ahead 2 clock
  .image_data      (lcd_data_1), //lcd_data_1
  .Y          (Y), 
  .Cb          (Cb), 
  .Cr          (Cr)
);

yuv2rgb yuv2rgb(
  .clk        (vga_clk),      //时钟输入
  .rst        (soft_rst_3),      //复位输入
  .y_in        (Y),                   //变换前Y分量输出
  .cb_in        (Cb),                   //变换前Cb分量输出
  .cr_in        (Cr),      //变换前Cr分量输出
  .ena_in        (dval_r1),      //待变换数据使能,当它为高时,输入数据有效
  .R_out        (R),      //变换后R分量输出
  .G_out        (G),      //变换后G分量输出
  .B_out        (B),      //变换后B分量输出
  .ena_out      (dval_w2)      //变换后数据使能输出
);


always @ (posedge vga_clk or negedge soft_rst_3)
  if(soft_rst_3 == 1'b0)
    dval_r1 <= 0;
  else dval_r1 <= lcd_request;
  
always @ (posedge vga_clk or negedge soft_rst_3)
  if(soft_rst_3 == 1'b0)
    dval_r2 <= 0;
  else dval_r2 <= dval_w2;
  
assign start = ~dval_r2 & dval_w2;


// Virtual_Camera Virtual_Camera(
  // .clk            (vga_clk),
  // .rst_n            (soft_rst_2),
  // .sdram_init_done      (sdram_init_done),
  // .dval            (sys_we),
  // .data            (sys_data_in)
// );

switch_in switch_in(
  .clk          (clk),
  .rst_n          (soft_rst_1),
  .frame_done        (write_done_1),
  .waddr_min_1      (waddr_min_1),
  .waddr_max_1      (waddr_max_1)
);

switch_out switch_out(
  .clk          (vga_clk),
  .rst_n          (soft_rst_3),
  .request        (lcd_request),
  .x_pos_i        (lcd_xpos),
  .y_pos_i        (lcd_ypos),
  .x_pos          (x_pos),
  .y_pos          (y_pos),
  .start_o        (),
  .lcd_data_1        (lcd_data_1),
  .lcd_data_2        (lcd_data_2),
  .lcd_val        (lcd_val),
  .lcd_data        (lcd_dat) //lcd_data
);


async_4fifo async_4fifo(
  .clk          (clk),
  .rst_n          (soft_rst_1),
  .sdram_init_done    (sdram_init_done), //sdram初始化完成信号
  
  /*用户接口*/
  /*写用户1接口*/
  .clk_write_1      (CMOS_PCLK), //vga_clk, CMOS_PCLK
  .wrreq_1        (sys_we), //sys_we dval_1
  .wdata_1        (sys_data_in), //sys_data_in {8'd0, data_1}
  .write_done_1      (write_done_1),
  /*参数设置*/
  .waddr_min_1      (waddr_min_1), //24'd0
  .waddr_max_1      (waddr_max_1), //24'd307200
  .wr_length_1      (9'd256), //,这个是缓冲节点
  
  /*读用户1接口*/
  .clk_read_1        (vga_clk), //vga_clk
  .rdreq_1        (lcd_request), //lcd_request
  .rdata_1        (lcd_data_1), //lcd_data lcd_data_1
  .read_done_1      (),
  .data_valid_1      (data_valid), //sdram读端口同步信号
  /*参数设置*/
  .raddr_min_1      (24'd0),
  .raddr_max_1      (24'd307200),
  .rd_length_1      (9'd256), //,这个是缓冲节点
  
  
  /*写用户2接口*/
  .clk_write_2      (CMOS_PCLK), //vga_clk, CMOS_PCLK
  .wrreq_2        (0), //sys_we
  .wdata_2        (0), //sys_data_in
  .write_done_2      (),
  /*参数设置*/
  .waddr_min_2      (24'd0), //24'd307200
  .waddr_max_2      (24'd0), //24'd614400
  .wr_length_2      (9'd256), //必须添加很重要,这个是缓冲节点
  
  
  /*读用户2接口*/
  .clk_read_2        (vga_clk),
  .rdreq_2        (lcd_request), //lcd_request
  .rdata_2        (lcd_data_2), //lcd_data lcd_data_2
  .read_done_2      (),
  .data_valid_2      (data_valid), //sdram读端口同步信号 data_valid
  /*参数设置*/
  .raddr_min_2      (24'd307200), //24'd307200
  .raddr_max_2      (24'd614400), //24'd614400
  .rd_length_2      (9'd256), //必须添加很重要,这个是缓冲节点
  
  
  /*写SDRAM端口*/
  .sdram_wrreq      (sdram_wrreq),
  .sdram_wrval      (sdram_wrval),
  .sdram_wraddr      (sdram_wraddr),
  .sdram_wdata      (sdram_wdata),
  .sdram_wdone      (sdram_wdone),
  
  /*读SDRAM端口*/
  .sdram_rdreq      (sdram_rdreq),
  .sdram_rdval      (sdram_rdval),
  .sdram_rdaddr      (sdram_rdaddr),
  .sdram_rdata      (sdram_rdata),
  .sdram_rdone      (sdram_rdone)
);


lsm_sdram lsm_sdram(
  /*全局变量*/
  .clk          (clk),
  .rst_n          (soft_rst_1),
  .sdram_init_done    (sdram_init_done),
  
  /*写SDRAM端口*/
  .sdram_wrreq      (sdram_wrreq),
  .sdram_wrval      (sdram_wrval),
  .sdram_wraddr      (sdram_wraddr),
  .sdram_wdata      (sdram_wdata),
  .sdram_wdone      (sdram_wdone),
  
  /*读SDRAM端口*/
  .sdram_rdreq      (sdram_rdreq),
  .sdram_rdval      (sdram_rdval),
  .sdram_rdaddr      (sdram_rdaddr),
  .sdram_rdata      (sdram_rdata),
  .sdram_rdone      (sdram_rdone),
  
  /*SDRAM物理端口*/
  .sdram_clk        (),
  .sdram_cke        (S_CKE),
  .sdram_cs_n        (S_NCS),
  .sdram_cas_n      (S_NCAS),
  .sdram_ras_n      (S_NRAS),
  .sdram_we_n        (S_NWE),
  .sdram_ba        (S_BA),
  .sdram_a        (S_A),
  .sdram_dq        (S_DB),
  .sdram_dqm        ()
);


lcd_top lcd_top
(    
  //global clock
  .clk          (vga_clk),      //system clock
  .rst_n          (soft_rst_3),         //sync reset
  
  //lcd interface
  .lcd_dclk        (),     //lcd pixel clock
  .lcd_blank        (),    //lcd blank
  .lcd_sync        (),    //lcd sync
  .lcd_hs          (VGA_HSYNC),        //lcd horizontal sync
  .lcd_vs          (VGA_VSYNC),        //lcd vertical sync
  .lcd_en          (),      //lcd display enable
  .lcd_rgb        (VGAD),    //lcd display data

  //user interface
  .lcd_request      (lcd_request),  //lcd data request
  .lcd_framesync      (data_valid),  //lcd frame sync
  .lcd_pos        (),
  .lcd_xpos        (lcd_xpos),    //lcd horizontal coordinate
  .lcd_ypos        (lcd_ypos),    //lcd vertical coordinate
  .lcd_data        (lcd_data)    //lcd data
);  


endmodule
作者:The last one
原文:FPGA技术江湖

相关文章推荐

更多FPGA干货请关注FPGA的逻辑技术专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
10546
内容数
533
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息