0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

FPGA图像处理基础----实现缓存卷积窗口

FPGA设计论坛 来源:FPGA设计论坛 2025-02-07 10:43 次阅读

像素行与像素窗口

一幅图像是由一个个像素点构成的,对于一幅480*272大小的图片来说,其宽度是480,高度是272。在使用FPGA进行图像处理时,最关键的就是使用FPGA内部的存储资源对像素行进行缓存与变换。由于在图像处理过程中,经常会使用到卷积,因此需要对图像进行开窗,然后将开窗得到的局部图像与卷积核进行卷积,从而完成处理。
  图像数据一般按照一定的格式和时序进行传输,在我进行实验的时候,处理图像时,让其以VGA的时序来进行工作,这样能够为我处理行缓存提供便利。

基于FIFO的行缓存结构

f21ed3e8-e44d-11ef-9310-92fbcf53809c.png


  在FPGA中对图像的一行数据进行缓存时,可以采用FIFO这一结构,如上图所示,新一行图像数据流入到FIFO1中,FIFO1中会对图像数据进行缓存,当FIFO1中缓存有一行图像数据时,在下一行图像数据来临的时候,将FIFO1中缓存的图像数据读出,并传递给下一个FIFO,于此同时,将新一行的图像数据缓存到FIFO1中,这样就能完成多行图像的缓存。
  若要缓存多行图像,下面的菊花链式的结果更能够直观地表现图像数据地流向。

f22c3d62-e44d-11ef-9310-92fbcf53809c.png


  新输入地图像缓存到FIFO1当中,当FIFO中缓存有一行数据的时候,下一个输入像素来临的时候,会将数据从本FIFO中读出,并给到下一个FIFO,来形成类似于一个流水线的结构。
  上面的图中,就是实现一个5X5大小的窗口的一个结构图。

代码设计

实现一个可以生成任意尺寸大小的开窗的模块,需要注意参数的使用,可以通过调节KSZ来调整窗口的大小。最终将窗口中的图像像素,转换成一个一维的数据输出给到下一个模块。
  在设计的时候,对于FIFO要选择精准计数模式,这样才能让流水正常工作起来。
  在代码中通过generate语句来实现多个line_buffer的例化,line buffer的个数可以根据卷积窗口的大小来选择,例如3X3大小的卷积窗口需要缓存两行,5X5大小的卷积窗口需要缓存4行,可以通过设置参数来选择要例化多少个line_buffer。

f23951d2-e44d-11ef-9310-92fbcf53809c.png

时序设计

f2539024-e44d-11ef-9310-92fbcf53809c.png


  在设计FIFO的菊花链结构时,需要根据当前FIFO中存储的数据个数来判断,这时候使用到精准计数模式,可以反应FIFO中的存储的数据。当FIFO中存储有一行数据的时候,使能pop_en信号,表示当前可以将数据从FIFO中读出。
  在将数据写入到FIFO中的时候,需要对数据进行扩充,也即需要对输入的图像的边界补充数据,因为进行卷积之后的图像将会比原始图像数据尺寸减少,因此在形成卷积窗口时,将图像扩充,能够让图像处理完成之后,保持原来的尺寸,只是会在边界出现黑边。
  win_buf这个模块的最终输出,就是一个矩阵内的所有像素,组成一个信号输出到外部,供进行卷积的处理。

/*============================================
#
# Author: Wcc - 1530604142@qq.com
#
# QQ : 1530604142
#
# Last modified: 2020-07-08 20:02
#
# Filename: win_buffer.v
#
# Description: 
#
============================================*/
`timescale 1ns / 1ps
module win_buf #(
//==========================================
//parameter define
//==========================================
parameterKSZ = 3,//卷积核大小
parameterIMG_WIDTH = 128,//图像宽度,每个Line_buffer需要缓存的数据量
parameterIMG_HEIGHT= 128 //图像高度
)(
input wire clk ,
inputwirerst ,
inputwirepi_hs,
inputwirepi_vs,
inputwirepi_de,
inputwirepi_dv,
inputwire[7: 0]pi_data,
//输出图像新有效信号
output wirepo_dv,
inputwirepo_hs,
inputwirepo_vs,
inputwirepo_de,
//输出3X3图像矩阵
outputwire[8*KSZ*KSZ - 1: 0]image_matrix
    );
//==========================================
//internal signals
//==========================================
reg frame_flag;//当前处于一帧图像所在位置

//==========================================
//linebuffer 缓存数据
//==========================================
//KSZ * KSZ 大小的卷积窗口,需要缓存(KSZ - 1行)
wire [7:0]wr_buf_data[KSZ-2: 0];//写入每个buffer的数据
wire wr_buf_en  [KSZ-2: 0];//写每个buffer的使能
wire full [KSZ-2: 0];//每个buffer的空满信号
wire empty [KSZ-2: 0];
wire [11:0]rd_data_cnt [KSZ-2: 0];//每个bufeer的内存数据个数
wirerd_buf_en[KSZ-2: 0];//读出每个buffer的使能信号
wire [7:0]rd_buf_data[KSZ-2: 0];//读出每个buffer的数据
reg pop_en [KSZ-2: 0];//每个buffer可以读出数据信号

reg pi_dv_dd [KSZ-2 : 0];//输入有效数据延时
wireline_vld;//行图像数据有效信号
wire [7:0]in_line_data;
reg [7:0]pi_data_dd [KSZ-2 : 0];
reg [1:0]pi_vs_dd;

reg [KSZ-2 : 0]po_dv_dd;
reg [KSZ-2 : 0]po_de_dd;
reg [KSZ-2 : 0]po_hs_dd;
reg [KSZ-2 : 0]po_vs_dd;

reg [0 : 0]po_dv_r;
reg [0 : 0]po_de_r;
reg [0 : 0]po_hs_r;
reg [0 : 0]po_vs_r;

reg [12:0]cnt_col ;//行列计数器
wireadd_cnt_col;
wireend_cnt_col;
reg [12:0]cnt_row ;
wireadd_cnt_row;
wireend_cnt_row ;

localparamMATRIX_SIZE = KSZ * KSZ;

//输出列数据延时,形成矩阵
reg[7:0]matrix_data[MATRIX_SIZE - 1: 0];
reg [8*KSZ*KSZ - 1: 0]image_matrix_r;


assign  image_matrix = image_matrix_r;
assign po_dv = po_dv_r;
assign po_hs = po_hs_r;
assign po_vs = po_vs_r;
assign po_de = po_de_r;


//----------------pi_vs_dd------------------
always @(posedge clk) begin
if (rst==1'b1) begin
pi_vs_dd <= 'd0;
end
else begin
pi_vs_dd <= {pi_vs_dd[0], pi_vs};
end
end

//----------------frame_flag------------------
always @(posedge clk) begin
if (rst==1'b1) begin
frame_flag <= 1'b0;
end
//检测到上升沿,结束上一帧
else if (pi_vs_dd[0] == 1'b1 && pi_vs_dd[1] == 1'b0) begin
frame_flag <= 1'b0;
end
//检测到下降沿,开始本帧
else if(pi_vs_dd[0] == 1'b0 && pi_vs_dd[1] == 1'b1) begin
frame_flag <= 1'b1;
end
end

//==========================================
//pi_dv_dd
//进行边界的扩充,以3X3的窗口为例,需要在图像的
//外围各边添加一行一列的的空白像素
//若是5X5的窗口,需要在图像的外围各边,添加2行2列
//==========================================
generate
genvar i;
//对于KSZ*KSZ的卷积核,每一行需要延时(KSZ-1)拍
for (i = 0; i < KSZ-1; i = i + 1)
begin:Expand_the_boundary_dv
if (i == 0) begin
always @(posedge clk) begin
if (rst==1'b1) begin
pi_dv_dd[i] <= 1'b0;
pi_data_dd[i] <= 'd0;
end
else begin
pi_dv_dd[i] <= pi_dv;
pi_data_dd[i] <= pi_data;
end
end
end
else begin
always @(posedge clk) begin
if (rst==1'b1) begin
pi_dv_dd[i] <= 1'b0;
pi_data_dd[i] <= 'd0;
end
else begin
pi_dv_dd[i] <= pi_dv_dd[i - 1];
pi_data_dd[i] <= pi_data_dd[i - 1];
end
end
end
end
endgenerate

assign line_vld = pi_dv_dd[KSZ-2] | pi_dv;
assign in_line_data = pi_data_dd[(KSZ>>1) - 1];

//==========================================
//行列计数器
//==========================================
//----------------cnt_col------------------
always @(posedge clk) begin
if (rst == 1'b1) begin
cnt_col <= 'd0;
end
else if (add_cnt_col) begin
if(end_cnt_col)
cnt_col <= 'd0;
else
cnt_col <= cnt_col + 1'b1;
end
else begin
cnt_col <= 'd0;
end
end

assign add_cnt_col = frame_flag == 1'b1 && line_vld == 1'b1;
assign end_cnt_col = add_cnt_col &&cnt_col == (IMG_WIDTH + KSZ-1) - 1;

//----------------cnt_row------------------
always @(posedge clk) begin
if (rst == 1'b1) begin
cnt_row <= 'd0;
end
else if (add_cnt_row) begin
if(end_cnt_row)
cnt_row <= 'd0;
else
cnt_row <= cnt_row + 1'b1;
end
end

assign add_cnt_row = end_cnt_col == 1'b1 ;
assign end_cnt_row = add_cnt_row &&cnt_row == IMG_HEIGHT - 1;

//==========================================
//输入到line_buffer,构成对齐的列
//通过例化FIFO的方式,来完成行缓存
//当FIFO缓存有一行数据时,将数据读出,并填充到下一个FIFO中
//==========================================
generate
genvar j;
for (j = 0; j < KSZ - 1; j = j + 1)
begin:multi_line_buffer
//第一个 line_buffer
if (j == 0) begin : first_buffer
//写入第一个line_buffer的数据是从外部输入的数据
assign wr_buf_data[j] = in_line_data;
assign wr_buf_en[j] = line_vld;
end
//其他line_buffer
else begin : other_buffer
//写入其他line_buffer的数据是上一个line_buffer中输出的数据
assign wr_buf_en[j] = rd_buf_en[j - 1] ;
assign wr_buf_data[j] = rd_buf_data[j - 1] ;
end

//----------------rd_buf_en------------------
//从buffer中读出数据
//pop_en是当前FIFO中已经缓存了一行的图像数据的指示信号
//wr_buf_en是当前新一行写入的有效数据指示信号
assign rd_buf_en[j] = pop_en[j] & wr_buf_en[j];

always @(posedge clk) begin
if (rst==1'b1) begin
pop_en[j] <= 0;
end
//当前不处于图像有效数据区域
else if (frame_flag == 1'b0) begin
pop_en[j] <= 1'b0;
end
//当buffer中缓存有一行图像数据时(扩充的图像行)
else if (rd_data_cnt[j] >= IMG_WIDTH+2) begin
pop_en[j] <= 1'b1;
end
end

line_buf line_buffer (
  .wr_clk(clk),                // input wire wr_clk
  .rd_clk(clk),                // input wire rd_clk
  .din(wr_buf_data[j]),        // input wire [7 : 0] din
  .wr_en(wr_buf_en[j]),        // input wire wr_en
  .rd_en(rd_buf_en[j]),                  // input wire rd_en
  .dout(rd_buf_data[j]),                    // output wire [7 : 0] dout
  .full(full[j]),                    // output wire full
  .empty(empty[j]),                  // output wire empty
  .rd_data_count(rd_data_cnt[j])  // output wire [11 : 0] rd_data_count
);
end
endgenerate

//==========================================
//得到矩阵中的每一行的第一个数据
//==========================================
generate
genvar k;
for (k = 0; k < KSZ; k = k + 1)
begin:matrix_data_first_col
if (k == KSZ -1) begin
//最后一行数据为刚输入的数据
always @(*) begin
matrix_data[KSZ * k] = in_line_data;
end
end
else begin
//从buffer中读取出来的图像书籍
//以3X3为例,buffer是菊花链的结果
//最开始输入的数据,在最后一个buffer中被读出
//最后输入的数据,在第一个buffer中被读出
always @(*) begin
 matrix_data[KSZ * k] = rd_buf_data[(KSZ -2) - k];
end
end
end
endgenerate
//==========================================
//延时得到矩阵数据
//以3X3为例
//matrix[0]~[2] ==>p13,p12,p11
//matrix[3]~[5] ==>p23,p22,p21
//matrix[6]~[8] ==>p33,p32,p31
//KSZ-1 clk
//==========================================
generate
genvar r,c;
for (r = 0; r < KSZ; r = r + 1)
begin:row
for (c = 1; c < KSZ; c = c + 1)
begin:col
always @(posedge clk) begin
if (rst==1'b1) begin
matrix_data[r*KSZ + c] <= 'd0;
end
else begin
matrix_data[r*KSZ + c] <= matrix_data[r*KSZ + (c-1)];
end
end
end
end
endgenerate

//==========================================
//输出图像矩阵
//以3X3为例
//image_matrix [7:0] ==> p13
//image_matrix [15:8] ==> p12
//image_matrix [23:16] ==> p11
//1 clk
//==========================================
generate
genvar idx;
for (idx = 0; idx < KSZ*KSZ; idx = idx + 1)
begin:out_put_data
always @(posedge clk) begin
if (rst==1'b1) begin
po_dv_r <= 1'b0;
po_de_r <= 1'b0;
po_hs_r <= 1'b0;
po_vs_r <= 1'b0;
image_matrix_r[8*(idx+1) -1 : 8*(idx)] <= 'd0;
end
else begin
po_dv_r <= po_dv_dd[KSZ-2] & line_vld;
po_de_r <= po_de_dd[KSZ-2] & line_vld;
po_hs_r <= po_hs_dd[KSZ-2] ;
po_vs_r <= po_vs_dd[KSZ-2] ;
if (po_dv_dd[KSZ-2] & line_vld == 1'b1) begin
image_matrix_r[8*(idx+1) -1 : 8*(idx)] <= matrix_data[idx];
end
else begin
image_matrix_r[8*(idx+1) -1 : 8*(idx)] <= 'd0;
end
end
end 
end
endgenerate
//==========================================
//(KSZ-1) clk
//==========================================

always @(posedge clk) begin
if (rst==1'b1) begin
po_vs_dd <= 'd0;
po_hs_dd <= 'd0;
po_de_dd <= 'd0;
po_dv_dd <= 'd0;
end
else begin
po_vs_dd <= {po_vs_dd[KSZ-3:0], pi_vs};
po_hs_dd <= {po_hs_dd[KSZ-3:0], pi_hs};
po_de_dd <= {po_de_dd[KSZ-3:0], pi_de};
po_dv_dd <= {po_dv_dd[KSZ-3:0], pi_dv};
end
end
endmodule

仿真验证

3X3开窗

f2673ebc-e44d-11ef-9310-92fbcf53809c.png


输入的第三行数据的前三个数据是:0x00,0x78,0x7c
输入的第二行数据的前三个数据是:0x00,0x7d,0x7d
输入的第一行数据的前三个数据是:0x00,0x7e,0x7f
输出的第一个矩阵的值是:0x0078_7c00_7d7d_007e_7f
输入行数据第一个数据是0x00这是因为扩充了边界的原因。
  可以看到,设置KSZ为3,可以得到一个位宽为72bit的输出数据,该数据包含了一个窗口中的9个数据。

5X5开窗

设置开窗大小为5x5之后,也可以看到输出信号的位宽变为了8*25=200bit,也就是一个5X5大小的矩阵中的数据。

f27fe85e-e44d-11ef-9310-92fbcf53809c.png


输入的第5行数据的前5个数据是:0x00,0x00,0x7e,0x7c,0x7f
输入的第4行数据的前5个数据是:0x00,0x00,0x7e,0x7e,0x7e,
输入的第3行数据的前5个数据是:0x00,0x00,0x78,0x7c,0x7c
输入的第2行数据的前5个数据是:0x00,0x00,0x7d,0x7d,0x7a
输入的第1行数据的前5个数据是:0x00,0x00,0x7e,0x7f,0x7d
从输出结果看,输出的矩阵数据,刚好是这5行的前5数据,并且前两个数据是0x00,这是因为在每一行前面补充了两个0的原因。
  经过测试,这种开窗算子是能够完成任意此村的开窗的。

实际应用

在实际应用中,我也将这个模块正确地使用上了,完成了一个3x3的sobel算子和5x5的均值滤波。

原始图像

f2969220-e44d-11ef-9310-92fbcf53809c.png

3x3 Sobel

f2b194f8-e44d-11ef-9310-92fbcf53809c.png

原文链接:

https://tencentcloud.csdn.net/678a0adeedd0904849a65d80.html

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

    关注

    1643

    文章

    21933

    浏览量

    613017
  • 图像处理
    +关注

    关注

    27

    文章

    1321

    浏览量

    57601
  • 缓存
    +关注

    关注

    1

    文章

    245

    浏览量

    27018

原文标题:FPGA图像处理基础----实现缓存卷积窗口

文章出处:【微信号:gh_9d70b445f494,微信公众号:FPGA设计论坛】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐
    热点推荐

    FPGA设计经验之图像处理

    可以说是类似于CPU里面的Cache,但Cache不是你能完全控制的,但Block Ram是完全可控的,可以用它实现各种灵活的运算处理。这样FPGA通过缓存若干行
    发表于 06-12 16:26

    FPGA图像缓存部分电路是怎么写的

    FPGA 将采集到的图像数据保存到缓存中,为后端对图像的进一步处理提供数据。下面提供一种图像
    发表于 12-10 09:54

    图像处理FPGA 设计基本方法和代码

    图像处理FPGA 设计基本方法:1.阵列结构结合流水线处理设计例如RGB图像,包括三组数据,处理
    发表于 01-04 13:59

    基于赛灵思FPGA卷积神经网络实现设计

    FPGA实现卷积神经网络 (CNN)。CNN 是一类深度神经网络,在处理大规模图像识别任务以及与机器学习类似的其他问题方面已大获成功。
    发表于 06-19 07:24

    基于FPGA图像平滑处理

    \sources_1\new下的average_filter.v模块实现了1/16的图像加权均值滤波处理。该模块功能框图如下,使用2个FIFO,分别缓存前后行,即进入
    发表于 07-05 13:51

    【工程源码】基于FPGA图像处理之行缓存(linebuffer)的设计

    背景知识 在FPGA数字图像处理中,行缓存的使用非常频繁,例如我们需要图像矩阵操作的时候就需要进行缓存
    发表于 02-17 19:32

    DIY在FPGA图像处理实现矩阵提取

    处理的元素,接下来在bufWIndow里通过寄存器缓存实现矩阵窗口及数据有效标志位处理即可。针对矩阵
    发表于 07-15 14:51

    卷积神经网络为什么适合图像处理

    卷积神经网络为什么适合图像处理
    发表于 09-08 10:23

    荐读:FPGA设计经验之图像处理

    系列:基于 FPGA图像边缘检测系统设计(sobel算法) FPGA设计中 Verilog HDL实现基本的图像滤波
    发表于 06-08 15:55

    基于FPGA实现固定倍率的图像缩放

    基于FPGA硬件实现固定倍率的图像缩放,将2维卷积运算分解成2次1维卷积运算,对输入原始图像像素
    发表于 05-09 15:52 35次下载
    基于<b class='flag-5'>FPGA</b><b class='flag-5'>实现</b>固定倍率的<b class='flag-5'>图像</b>缩放

    基于FPGA的视频图像处理系统的设计

    通过研究视频图像处理和视频图像帧格式以及FIF0缓存技术,提出了基于FPGA的视频图像
    发表于 11-22 09:13 5018次阅读

    如何使用FPGA实现顺序形态图像处理器的硬件实现

    该文在阐述了灰度图像顺序形态变换的基础上,介绍了顺序形态变换硬件实现图像处理系统.该系统采用DSP+FPGA的框架结构,利用
    发表于 04-01 11:21 8次下载

    基于FPGA图像实时处理系统设计

    ,系统实时性较差的问题。本文将FPGA的IP核内置缓存模块和乒乓读写结构相结合,实现图像数据的缓存与提取,节省了存储芯片所占用的片上空间,
    的头像 发表于 06-15 15:20 1783次阅读
    基于<b class='flag-5'>FPGA</b>的<b class='flag-5'>图像</b>实时<b class='flag-5'>处理</b>系统设计

    卷积神经网络在图像识别中的应用

    卷积神经网络(Convolutional Neural Networks, CNNs)是一种深度学习模型,广泛应用于图像识别、视频分析、自然语言处理等领域。 1. 卷积神经网络的基本原
    的头像 发表于 07-02 14:28 1708次阅读

    图像处理中的卷积运算

    卷积运算是图像处理中一种极其重要的操作,广泛应用于图像滤波、边缘检测、特征提取等多个方面。它基于一个核(或称为卷积核、滤波器)与
    的头像 发表于 07-11 15:15 3447次阅读