FPGA伪红外图像处理过程演示

描述

介绍

红外摄像机因为对可见光不敏感,所以在一些特殊行业应用越来越广泛。

红外摄像机甚至可以透过太阳镜看到人眼,并且摄像机图像不受白天或夜晚的影响,并且几乎没有环境光。

因为真正的红外sensor价格比较昂贵,所以这次选用一种伪红外sensor,即利用相机自己的光源,即安装在镜头旁边的 LED,反射红外光后进项图像采集,这是一种利用近红外成像,和我们熟知的红外摄像头还是有区别的。 

该项目展示了一些红外图像处理算法,这些算法可以提高图像质量。

所选FPGA是 ZYNQ-020 SoC,摄像头是便宜的 Raspberry PI 摄像头,带有两个红外 LED,最大分辨率为 1080p@60Hz。

该项目中呈现的体系结构是可扩展的,可以轻松添加更多算法。

理论

我选择了五种基于 3x3 内核的图像处理算法:

坏点校正

Raspberry Pi

这是所有这类传感器的普遍问题,是一种常见的预处理算法。

中值滤波器

Raspberry Pi

常见的噪声平滑预处理算法。

低通滤波器(平滑滤波器)

Raspberry Pi

噪声平滑,这个算法使图像平滑,不会像中值滤波器那样使图像模糊。

图像锐化

Raspberry Pi

通过“边缘锐化”提高图像质量,即强调边缘。

边缘检测

Raspberry Pi

应用其中一种算法后,对图像边缘处理后,图像尺寸会减小(可选)。

Raspberry Pi

架构

所有算法都基于 3x3 内核,这就是为什么所有算法内核 (PE) 都必须与 FIFO 通信,每个 PE 都有一行的延迟。只有当第二行数据到达时才开始应用算法内核,考虑到图像处理时候会对边界有影响,但是我们需要输出端输出相同的图像大小。

Raspberry Pi

架构

Raspberry Pi

选择模块是一个可扩展的 MUX 网络,在上图情况下,具有五个图像处理算法,由六个级联的 MUX-es 组成,一个用于滤波器输出,一个用于输入信号。数据流可以配置,在这种情况下,视频流从输入到输出,它通过的图像处理元素的顺序和数量是可配置的。

算法内核的结构如下所示,基本上在这种情况下是一个延迟线,它以视频流作为输入并输出一个 3x3 矩阵,输出是处理后的帧。

Raspberry Pi

设计

在该架构中,我在 VDMA 和 Gamma Correction 模块之间插入了我的模块。

Raspberry PiRaspberry Pi

我为每个行缓冲区添加了一个 FIFO。

接口

所有模块都使用规定好的帧接口 (FI),它与参考设计中使用的 AXI Stream 接口非常相似(https://reference.digilentinc.com/learn/programmable-logic/tutorials/zybo-z7-pcam-5c-demo/start),可以在两者之间进行转换。从 AXI Stream 到 Frame 不需要转换,反之则必须生成一些额外的信号。AXI Stream 接口只有帧开始和行结束控制信号。

Raspberry Pi

 

module axi_stream2frame#(
 parameter DATA_WIDTH = 24
)(
 input                       clk                   , // Syste clock
 input                       rst_n                 , // Asynchronous reset active low
//------------------------- Configuration interface ----------------------------------
 input  [11:0]               cfg_img_w             , // Image width
 input  [11:0]               cfg_img_h             , // Image width
//------------------------- AXI-Stream interface -------------------------------------
 input                       m_axi_stream_tuser    , // Start of frame 
 input                       m_axi_stream_tvalid   , // Slave has valid data 
 input                       m_axi_stream_tlast    , // End of frame
 input     [DATA_WIDTH-1:0]  m_axi_stream_tdata    , // Data transferred 
 output                      m_axi_stream_tready   , // Master is ready to receive
// ------------------------------ Frame Interface -----------------------------------
 output reg                  s_frm_val             , // Master has valid data 
 input                       s_frm_rdy             , // Slave is ready to receive
 output reg [DATA_WIDTH-1:0] s_frm_data            , // Data transferred 
 output reg                  s_frm_sof             , // Start of Frame
 output reg                  s_frm_eof             , // End of Frame
 output reg                  s_frm_sol             , // Start of Line
 output reg                  s_frm_eol               // End of Line
);
reg [11:0] pix_cnt ;
reg [11:0] line_cnt;
wire invalrdy;
wire outvalrdy;
wire set_eof;
assign invalrdy = m_axi_stream_tvalid & m_axi_stream_tready;
assign outvalrdy = s_frm_rdy & s_frm_val;
assign m_axi_stream_tready = s_frm_rdy;
assign set_eof = (line_cnt == (cfg_img_h - 1'd1)) & m_axi_stream_tlast & invalrdy;
  
always@(posedge clk or negedge rst_n)
if(~rst_n                        ) pix_cnt <= 11'd0         ; else
if(m_axi_stream_tuser & invalrdy ) pix_cnt <= 11'd0         ; else // Reset at start of frame
if(m_axi_stream_tlast & invalrdy ) pix_cnt <= 11'd0         ; else // Reset at end of frame
if(invalrdy                      ) pix_cnt <= pix_cnt + 1'd1;      // Increment at each pixel
always@(posedge clk or negedge rst_n)
if(~rst_n                       ) line_cnt <= 11'd0          ; else
if(m_axi_stream_tuser & invalrdy) line_cnt <= 11'd0          ; else // Reset at start of frame
if(m_axi_stream_tlast & invalrdy) line_cnt <= line_cnt + 1'd1;      // Increment at each pixel
always@(posedge clk or negedge rst_n)
if(~rst_n                              ) s_frm_sol <= 1'b0; else
if(outvalrdy & s_frm_sol               ) s_frm_sol <= 1'b0; else // Reset sol is transmitted
if(m_axi_stream_tuser & invalrdy       ) s_frm_sol <= 1'b1; else // Set start of line after last pixel of line is transmitted
if(outvalrdy & s_frm_eol & (~s_frm_eof)) s_frm_sol <= 1'b1;      // Set at start of frame
always@(posedge clk or negedge rst_n)
if(~rst_n               ) s_frm_eof <= 1'b0; else
if(outvalrdy & s_frm_eof) s_frm_eof <= 1'b0; else // Reset after eof is transmitted
if(set_eof              ) s_frm_eof <= 1'b1;      // Set when last pixel is received
always@(posedge clk or negedge rst_n)
if(~rst_n                            ) s_frm_val <= 1'b0; else
if(s_frm_rdy & (~m_axi_stream_tvalid)) s_frm_val <= 1'b0; else // Reset when ready and no valid data at the input
if(invalrdy                          ) s_frm_val <= 1'b1;   // Set if data is received
always@(posedge clk or negedge rst_n)
if(~rst_n                       ) s_frm_eol <= 1'b0; else
if(outvalrdy & s_frm_eol        ) s_frm_eol <= 1'b0; else // Reset after eol is transmitted
if(m_axi_stream_tlast & invalrdy) s_frm_eol <= 1'b1;      // Set when last pixel in a row is received
always@(posedge clk or negedge rst_n)
if(~rst_n                        ) s_frm_sof <= 1'b0; else
if(outvalrdy & s_frm_sof         ) s_frm_sof <= 1'b0; else // Reset after sof is                               transmitted
if(m_axi_stream_tuser  & invalrdy) s_frm_sof <= 1'b1;      // Set when first pixel is received
always@(posedge clk or negedge rst_n)
if(~rst_n  ) s_frm_data <= {(DATA_WIDTH){1'b0}}; else
if(invalrdy) s_frm_data <= m_axi_stream_tdata  ;
endmodule //axi_stream2Frame

 

配置sensor

这个摄像头是搭配树莓派使用的,所有驱动都是闭源的,所以没有配置示例。我在 SCL 和 SDA 引脚上的 I2C 引脚上焊接了两根电线。将相机连接到 Raspeberry Pi 并将逻辑分析仪连接到焊线,我按照相机接口指南

逻辑分析仪解码了I2C,抓取的值将在最后附上excel。

Raspberry Pi

该配置已添加到 C++ 代码中。

摄像头是 RGB 摄像头,只有在房间黑暗时才会启动红外摄像头。为了解决这个问题,我在sensor前面粘上了一块塑料,这是红外 LED 前面的过滤器。这不是一个很好的解决方案,但可以。

配置模块

使用 APB 接口进行配置。

Raspberry Pi

 

void filter_cfg()
{
 Xil_Out32(APB_BASE_ADDR + CFG_IMG_WIDTH_ADDR, IMG_W);
 Xil_Out32(APB_BASE_ADDR + CFG_IMG_HEIGHT_ADDR, IMG_H);
 Xil_Out32(APB_BASE_ADDR + CFG_PIX_CORR_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_SHARP_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_SMOOTH_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_MEDIAN_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_LAPLACE_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_OUTPUT_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_PIX_CORR_THR_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_SHARP_COEF_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_TEST_MODE_EN_ADDR, 0);
}

 

上面给出的配置是每个选择器模块的选择。现在它被配置为输入流不进行任何处理的情况下转到输出。

Raspberry Pi

 

void filter_cfg()
{
 Xil_Out32(APB_BASE_ADDR + CFG_IMG_WIDTH_ADDR, IMG_W);
 Xil_Out32(APB_BASE_ADDR + CFG_IMG_HEIGHT_ADDR, IMG_H);
 Xil_Out32(APB_BASE_ADDR + CFG_PIX_CORR_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_SHARP_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_SMOOTH_SEL_ADDR, SMOOTH_IN_CODE);
 Xil_Out32(APB_BASE_ADDR + CFG_MEDIAN_SEL_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_LAPLACE_SEL_ADDR, SMOOTH_IN_CODE);
 Xil_Out32(APB_BASE_ADDR + CFG_OUTPUT_SEL_ADDR, LAPLACE_IN_CODE);
 Xil_Out32(APB_BASE_ADDR + CFG_PIX_CORR_THR_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_SHARP_COEF_ADDR, 0);
 Xil_Out32(APB_BASE_ADDR + CFG_TEST_MODE_EN_ADDR, 0);
} 

Xil_Out32(APB_BASE_ADDR + CFG_SMOOTH_SEL_ADDR, SMOOTH_IN_CODE);

 

将输入视频流放入算法核心。

Raspberry Pi

演示

我展示了带平滑和不带平滑的拉普拉斯滤波器,我们可以观察到图像有噪声,应用平滑滤波器后图像有所变化。

为了比较原始图像和处理后的两个图像,在 Gamma 校正之后添加了第二个 VDMA,,现在校正后的图像和原始图像都在 DDR 中,因此可以复制裁剪处理后的图像并将裁剪区域替换为原始图像。






审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分