本原创文章由深圳市小眼睛科技有限公司创作,版权归本公司所有,如需转载,需授权并注明出处(www.meyesemi.com)
1. 实验简介
实验目的:
了解如何将二值化图像进行腐蚀和膨胀。
实验环境:
Window11
PDS2022.SP6.4
Modelsim10.6c
MatlabR2023b(可以用别的版本)
硬件环境:
MES2L676-100HP
2. 实验原理
腐蚀膨胀是针对二值化图像来处理的。腐蚀的作用是可以消除一些边界点,消除一些细小物体,让图像在某 些边界可以平滑。膨胀的作用是填充某些空洞,是某些部分向外扩张以达到连接边界等作用。

当 3x3 窗口内有一个像素为 0 那就认为当前像素的输出为 0。本质就是输出等于 9 个数据相与。

当 3x3 窗口内有一个像素为 1 那就认为当前像素的输出为 1。本质就是输出等于 9 个数据相或。
了解其原理后,可以开始编写代码。只要进行相与或者相或即可。
还有个要注意的点就是之前的 3x3 矩阵生成的是 8bit,然而二值化数据只有 1bit,因此需要把 3x3 矩阵改 成 1bit 的。
只需要把端口,以及变量改为 1bit,FIFO IP 的数据位宽也改为 1bit 即可.如下所示:
module matrix_3x3_1bit
#(
parameter IMG_WIDTH = 11'd1920,
parameter IMG_HEIGHT = 11'd1080
)
(
input wire video_clk,
input wire rst_n,
input wire video_vs,
input wire video_de,
input wire video_data, // 3x3 矩阵输出
output wire matrix_de,
output reg matrix11,
output reg matrix12,
output reg matrix13,
output reg matrix21,
output reg matrix22,
output reg matrix23,
output reg matrix31,
output reg matrix32,
output reg matrix33
);
// 矩阵数据生成
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n) begin
{matrix11, matrix12, matrix13} <= 3'd0;
{matrix21, matrix22, matrix23} <= 3'd0;
{matrix31, matrix32, matrix33} <= 3'd0;
end
else if (video_de) begin
{matrix11, matrix12, matrix13} <= {matrix12, matrix13, line1_data};
{matrix21, matrix22, matrix23} <= {matrix22, matrix23, line2_data_d0};
{matrix31, matrix32, matrix33} <= {matrix32, matrix33, line3_data_d1};
end
else begin
{matrix11, matrix12, matrix13} <= 3'd0;
{matrix21, matrix22, matrix23} <= 3'd0;
{matrix31, matrix32, matrix33} <= 3'd0;
end
end

FIFO IP 的设置修改如上。
2.1 接口列表
erosion.v 腐蚀模块接口,延迟 2clk.

dilatw.v 膨胀模块接口,延迟 2clk.

2.2 工程说明

上图为本次实验的工程框架,在二值化的基础上添加腐蚀膨胀后再缓存到 DDR 中,再将数据读出到 HDMI 显示器上进行显示。
2.3. 代码模块说明
// erosion 腐蚀
module erosion
(
input wire video_clk, // 像素时钟
input wire rst_n, // 复位信号
// 输入二值化数据
input wire bin_vs,
input wire bin_de,
input wire bin_data_11,
input wire bin_data_12,
input wire bin_data_13,
input wire bin_data_21,
input wire bin_data_22,
input wire bin_data_23,
input wire bin_data_31,
input wire bin_data_32,
input wire bin_data_33,
output wire erosion_vs,
output wire erosion_de,
output wire erosion_data
);
/**********************************************************
reg define
**********************************************************/
reg erosion_vs_d;
reg erosion_vs_d1;
reg erosion_de_d;
reg erosion_de_d1;
reg erosion_data_d;
reg erosion_line0;
reg erosion_line1;
reg erosion_line2;
// 1clk 行腐蚀 相与
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n) begin
erosion_line0 <= 1'd0;
erosion_line1 <= 1'd0;
erosion_line2 <= 1'd0;
end
else if (bin_de) begin
erosion_line0 <= bin_data_11 && bin_data_12 && bin_data_13;
erosion_line1 <= bin_data_21 && bin_data_22 && bin_data_23;
erosion_line2 <= bin_data_31 && bin_data_32 && bin_data_33;
end
end
// 1clk 腐蚀 相与
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n)
erosion_data_d <= 1'd0;
else
erosion_data_d <= erosion_line0 && erosion_line1 && erosion_line2;
end
// 延迟 2clk
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n) begin
erosion_vs_d <= 1'd0;
erosion_vs_d1 <= 1'd0;
erosion_de_d <= 1'd0;
erosion_de_d1 <= 1'd0;
end
else begin
erosion_vs_d <= bin_vs;
erosion_vs_d1 <= erosion_vs_d;
erosion_de_d <= bin_de;
erosion_de_d1 <= erosion_de_d;
end
end
assign erosion_data = erosion_data_d;
assign erosion_vs = erosion_vs_d1;
assign erosion_de = erosion_de_d1;
endmodule
同样的,笔者没有通过组合逻辑把 9 个数据直接相与,而是通过二级流水线来完成腐蚀操作。
代码的 43-56 行完成每行数据相与,当 bin_de 有效时将三行数据分别相与.
代码的 63-68 行将每行相与的结果再次相与,至此完成了 9 个数据相与.
因为是两级流水线,所以延迟了 2 个 clk.故在代码的 73-89 行对有效信号和场信号打两拍,延迟两个时钟周期,让数据保持同步.
最后通过组合逻辑将数据和有效信号以及场信号输出.
dilate 模块介绍.
// dilate 膨胀
module dilate
(
input wire video_clk, // 像素时钟
input wire rst_n, // 复位信号
// 输入二值化数据
input wire bin_vs,
input wire bin_de,
input wire bin_data_11,
input wire bin_data_12,
input wire bin_data_13,
input wire bin_data_21,
input wire bin_data_22,
input wire bin_data_23,
input wire bin_data_31,
input wire bin_data_32,
input wire bin_data_33,
output wire dilate_vs,
output wire dilate_de,
output wire dilate_data
);
/**********************************************************
reg define
**********************************************************/
reg dilate_vs_d;
reg dilate_vs_d1;
reg dilate_de_d;
reg dilate_de_d1;
reg dilate_data_d;
reg dilate_line0;
reg dilate_line1;
reg dilate_line2;
// 1clk 行膨胀 相或
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n) begin
dilate_line0 <= 1'd0;
dilate_line1 <= 1'd0;
dilate_line2 <= 1'd0;
end
else if (bin_de) begin
dilate_line0 <= bin_data_11 || bin_data_12 || bin_data_13;
dilate_line1 <= bin_data_21 || bin_data_22 || bin_data_23;
dilate_line2 <= bin_data_31 || bin_data_32 || bin_data_33;
end
end
// 1clk 膨胀 相或
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n)
dilate_data_d <= 1'd0;
else
dilate_data_d <= dilate_line0 || dilate_line1 || dilate_line2;
end
// 延迟 2clk
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n) begin
dilate_vs_d <= 1'd0;
dilate_vs_d1 <= 1'd0;
dilate_de_d <= 1'd0;
dilate_de_d1 <= 1'd0;
end
else begin
dilate_vs_d <= bin_vs;
dilate_vs_d1 <= dilate_vs_d;
dilate_de_d <= bin_de;
dilate_de_d1 <= dilate_de_d;
end
end
assign dilate_data = dilate_data_d;
assign dilate_vs = dilate_vs_d1;
assign dilate_de = dilate_de_d1;
endmodule
细心的读者其实可以发现 erosion 模块和 dilate模块的代码基本是一致的,只是把相与操作换成相或操作. 整个代码架构是基本不变的,还是使用二级流水线.
代码的 41-62 行,把 erosion 的相与操作改成了相或操作,两个 clk 后得到 9 个数据相或的结果.
因为是两级流水线,所以延迟了 2 个 clk.故在代码的 67-83 行对有效信号和场信号打两拍,延迟两个时钟周期,让数据保持同步.
最后通过组合逻辑将数据和有效信号以及场信号输出.
2.4. 代码仿真
2.4.1. Matlab 仿真介绍
基本的开窗操作,以及读取图像在前文以及介绍过,因此,后文涉及到相关操作将不在详细介绍,只介绍关键 算法部分.本节介绍其中的腐蚀膨胀部分.
for i = 1:img_height
for j = 1:img_width
% 窗口边界
r1 = max(i - halfWindowSize, 1); % 窗口上边界
r2 = min(i + halfWindowSize, img_height); % 窗口下边界
c1 = max(j - halfWindowSize, 1); % 窗口左边界
c2 = min(j + halfWindowSize, img_width); % 窗口右边界
% 提取窗口
window = binaryImage(r1:r2, c1:c2);
% 计算窗口内的最小值
localMin = min(window(:));
% 进行腐蚀操作
erodedImage(i, j) = localMin;
end
end
腐蚀其实就是 3x3 窗口内有一个是 0,那结果就为 0,而二值化的数要不就是 0 要不就是 1,所以其实 0 就是 窗口内的最小值,所以代码的第 13 行通过 min()函数,直接找到该窗口里的最小值,如果 9 个数据里有一个是 0, 那 16 行的结果肯定就是 0,如果 9 个数据全为 1 的话,那最小值就是 1,那 16 行的结果就是 1。
同样的,如果是膨胀操作的话,就把 min(window(:))改为 max(window(:))即可。

上图为 Matlab 仿真后的图。
2.4.2. Modelsim 仿真介绍
具体工程架构还是和之前一致,在灰度化,二值化后的基础上添加了腐蚀膨胀模块。
// 图像处理仿真模块
`timescale 1ns/1ns
module img_process_tb();
// 图片高度宽度
// 仿真需要改小视频大小 避免太大
// 时序参考模板 仿真中不需要严格按照 vesa 时序标准
parameter IMG_WIDTH = 16'd1280; // 有效区域
parameter H_FP = 16'd110; // 前沿
parameter H_SYNC = 16'd40; // 同步
parameter H_BP = 16'd220; // 后沿
parameter TOTAL_WIDTH = IMG_WIDTH + H_FP + H_SYNC + H_BP;
parameter IMG_HEIGHT = 16'd720; // 有效区域
parameter V_FP = 16'd5; // 前沿
parameter V_SYNC = 16'd5; // 同步
parameter V_BP = 16'd20; // 后沿
parameter TOTAL_HEIGHT = IMG_HEIGHT + V_FP + V_SYNC + V_BP;
localparam HREF_DELAY = 5;
localparam VSYNC_DELAY = 5;
reg video_clk;
reg rst_n;
wire video_vs;
wire video_de;
wire [23:0] video_data;
wire y_vs;
wire y_hs;
wire y_de;
wire [7:0] y_data;
// 全局二值化数据
wire bin_vs;
wire bin_hs;
wire bin_de;
wire bin_data;
wire matrix_de;
wire [7:0] matrix11;
wire [7:0] matrix12;
wire [7:0] matrix13;
wire [7:0] matrix21;
wire [7:0] matrix22;
wire [7:0] matrix23;
wire [7:0] matrix31;
wire [7:0] matrix32;
wire [7:0] matrix33;
// 局部二值化数据
wire area_bin_vs;
wire area_bin_hs;
wire area_bin_de;
wire area_bin_data;
// 腐蚀数据
wire erosion_vs;
wire erosion_de;
wire erosion_data;
// 膨胀数据
wire dilate_vs;
wire dilate_de;
wire dilate_data;
// 定义处理后的文件
integer output_file;
initial begin
video_clk = 1'd0;
rst_n = 1'd0;
#20;
rst_n = 1'd1;
output_file = $fopen("D:/pango_isp/img_test_pg/img_test_pg/01_led_test/sim/img_process.txt", "w");
end
// 生成 100MHZ
always #5 video_clk = ~video_clk;
// 读取图片数据
video_data_gen #(
.TOTAL_WIDTH (12'd1650),
.IMG_WIDTH (12'd1280),
.H_SYNC (12'd40),
.H_BP (12'd220),
.H_FP (12'd110),
.TOTAL_HEIGHT(12'd750),
.IMG_HEIGHT (12'd720),
.V_SYNC (12'd5),
.V_BP (12'd20),
.V_FP (12'd5)
) u_video_data_gen (
.video_clk (video_clk),
.rst_n (rst_n),
.video_vs (video_vs),
.video_de (video_de),
.video_data (video_data)
);
// 灰度化
RGB2YCbCr u_RGB2YCbCr (
.clk (video_clk),
.rst_n (rst_n),
.vsync_in (video_vs),
.hsync_in (video_de),
.de_in (video_de),
.red (video_data[23:19]),
.green (video_data[15:10]),
.blue (video_data[7:3]),
.vsync_out(y_vs),
.hsync_out(y_hs),
.de_out (y_de),
.y (y_data),
.cb (),
.cr ()
);
// 全局二值化
binarization u_binarization (
.clk (video_clk),
.rst_n (rst_n),
.vsync_in (y_vs),
.hsync_in (y_hs),
.de_in (y_de),
.y_in (y_data),
.vsync_out(bin_vs),
.hsync_out(bin_hs),
.de_out (bin_de),
.pix (bin_data)
);
// 二值化矩阵
matrix_3x3_1bit #(
.IMG_WIDTH (IMG_WIDTH),
.IMG_HEIGHT(IMG_HEIGHT)
) u_matrix_3x3_1bit (
.video_clk (video_clk),
.rst_n (rst_n),
.video_vs (bin_vs),
.video_de (bin_de),
.video_data(bin_data),
.matrix_de (matrix_de_1bit),
.matrix11 (matrix11_1bit),
.matrix12 (matrix12_1bit),
.matrix13 (matrix13_1bit),
.matrix21 (matrix21_1bit),
.matrix22 (matrix22_1bit),
.matrix23 (matrix23_1bit),
.matrix31 (matrix31_1bit),
.matrix32 (matrix32_1bit),
.matrix33 (matrix33_1bit)
);
// 腐蚀模块
erosion u_erosion (
.video_clk (video_clk),
.rst_n (rst_n),
.bin_vs (area_bin_vs),
.bin_de (matrix_de_1bit),
.bin_data_11 (matrix11_1bit),
.bin_data_12 (matrix12_1bit),
.bin_data_13 (matrix13_1bit),
.bin_data_21 (matrix21_1bit),
.bin_data_22 (matrix22_1bit),
.bin_data_23 (matrix23_1bit),
.bin_data_31 (matrix31_1bit),
.bin_data_32 (matrix32_1bit),
.bin_data_33 (matrix33_1bit),
.erosion_vs (erosion_vs),
.erosion_de (erosion_de),
.erosion_data(erosion_data)
);
// 腐蚀后的 3x3 矩阵
matrix_3x3_1bit #(
.IMG_WIDTH (IMG_WIDTH),
.IMG_HEIGHT(IMG_HEIGHT)
) u_matrix_3x3_erosion (
.video_clk (video_clk),
.rst_n (rst_n),
.video_vs (erosion_vs),
.video_de (erosion_de),
.video_data(erosion_data),
.matrix_de (matrix_de_erosion),
.matrix11 (matrix11_erosion),
.matrix12 (matrix12_erosion),
.matrix13 (matrix13_erosion),
.matrix21 (matrix21_erosion),
.matrix22 (matrix22_erosion),
.matrix23 (matrix23_erosion),
.matrix31 (matrix31_erosion),
.matrix32 (matrix32_erosion),
.matrix33 (matrix33_erosion)
);
// 膨胀模块
dilate u_dilate (
.video_clk (video_clk),
.rst_n (rst_n),
.bin_vs (erosion_vs),
.bin_de (matrix_de_erosion),
.bin_data_11(matrix11_erosion),
.bin_data_12(matrix12_erosion),
.bin_data_13(matrix13_erosion),
.bin_data_21(matrix21_erosion),
.bin_data_22(matrix22_erosion),
.bin_data_23(matrix23_erosion),
.bin_data_31(matrix31_erosion),
.bin_data_32(matrix32_erosion),
.bin_data_33(matrix33_erosion),
.dilate_vs (dilate_vs),
.dilate_de (dilate_de),
.dilate_data(dilate_data)
);
GTP_GRS GRS_INST(
.GRS_N(1'b1)
);
// 写数据
reg video_vs_d; // 打拍寄存
reg img_done;
wire frame_flag;
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n)
video_vs_d <= 1'd0;
else
video_vs_d <= dilate_vs;
end
assign frame_flag = ~dilate_vs & video_vs_d; // 下降沿
always @(posedge video_clk or negedge rst_n) begin
if (!rst_n)
img_done <= 1'b0;
else if (frame_flag) // 下降沿 判断一帧结束
img_done <= 1'b1;
else
img_done <= img_done;
end
always @(posedge video_clk or negedge rst_n) begin
if (img_done) begin
$stop; // 停止仿真
end
else if (dilate_de) begin // 写入数据
$fdisplay(output_file, "%h\t%h\t%h", 8{dilate_data}, 8{dilate_data}, 8{dilate_data}); // 16进制写入
end
end
endmodule
代码的基本框架没有改变,在代码的145-223行可以看到在二值化的基础上添加了腐蚀膨胀以及3x3_1bit 矩阵生成的模块。
注意把 236-264 行的代码里的数据有效信号和长信号均替换为膨胀处理后的数据有效信号和场信号。
最后代码的 262 行由于是 8bit,16 进制写入到 txt,而二值化数据只有 1bit,因此使用 8{dilate_data}将其 1bit 复制 8 份扩充为 8bit,然后写入到 txt。

3. 实验现象
连接好下载器,电源、HDMI_IN 口连接电脑、HDMD_OUT 口连接显示器,然后下载程序。


上图为实验原图像。

上图为二值化腐蚀膨胀后的图像。