一、什么是图像边缘检测?
边缘检测是计算机视觉和图像处理中的一个重要任务,常用于提取图像中的结构信息。边缘检测的目的是识别图像中强度变化显著的区域,这些区域通常对应于物体的边界。在前面《FPGA图像处理之局部阈值二值化》中我们知道,在机器识别中,计算机并不关心图像色彩和饱和度,它们大多数都只关心图像的轮廓以及结构特征。因此,图像的边缘检测也是非常常见的一种处理,常用的边缘检测算法包括:Sobel算子,Canny算子,拉普拉斯算子等等。
二、Sobel算子理论
图像的边缘一般是灰度变化比较明显的点;Sobel是离散的微分算子,用来对图像水平和垂直方向进行求导,图像灰度变化越大,一阶导数就越大。Sobel算子就是利用这个原理。
- 水平方向:这是通过用奇数大小的核 G x Gx Gx卷积图像数据:
- 垂直方向:这是通过用奇数大小的核 G y Gy Gy卷积图像数据:
- 计算梯度:
得到梯度后再与设定的阈值做比较,判断当前是轮廓还是边缘。以下是matlab实现sobel边缘检测的代码:
clc;
close all;
% 读取图像
input_image_path = '...........jpg'; % 输入图像的路径
image = imread(input_image_path);
% 将图像转换为灰度图像(如果是彩色图像)
if size(image, 3) == 3
gray_image = rgb2gray(image);
else
gray_image = image;
end
% 使用 Sobel 算子进行边缘检测
edges = edge(gray_image, 'Sobel',0.05);
% 显示原始图像和边缘检测结果
figure;
subplot(1, 2, 1);
imshow(gray_image);
title('Gray Image');
subplot(1, 2, 2);
imshow(edges);
title('Sobel Edge Detection');
可以看到,sobel边缘检测能很好的识别出图像边缘,设定的阈值不一样,图像显示的边缘也不一样,下面修改阈值范围:
clc;
close all;
% 读取图像
input_image_path = 'bulit.jpg'; % 输入图像的路径
image = imread(input_image_path);
% 将图像转换为灰度图像(如果是彩色图像)
if size(image, 3) == 3
gray_image = rgb2gray(image);
else
gray_image = image;
end
% 使用 Sobel 算子进行边缘检测
edges = edge(gray_image, 'Sobel',0.2);
% 显示原始图像和边缘检测结果
figure;
subplot(1, 2, 1);
imshow(gray_image);
title('Gray Image');
subplot(1, 2, 2);
imshow(edges);
title('Sobel Edge Detection');
可以看到显示出的轮廓明显程度也发生了变化。
三、Verilog实现Sobel算子
首先还是需要生成3*3的滑动窗口,直接调用我们前面几篇文章的模板就行,我们主要来看Sobel算法模块:
[p11,p12,p13] [-1,0,1]
Gx_data = [p21,p22,p23] * [-2,0,2] = (p13+2*p23+p33) - (p11+2*p21+p31)
[p31,p32,p33] [-1,0,1]
[p11,p12,p13] [-1,-2,-1]
Gy_data = [p21,p22,p23] * [ 0, 0, 0] = (p31+2*p32+p33) - (p11+2*p12+p13)
[p31,p32,p33] [ 1, 2, 1]
G_data = sqrt(Gx_data^2 + Gy_data^2)
算法核心也非常简单,就是按照Sobel算子窗口求和运算后,再计算其平方根。平方根可以使用cordic IP核,具体配置如下:
其它的配置默认即可,调用如下:
square_data u_square_data (
.aclk(sys_clk), // input wire aclk
.aresetn(!sys_rst), // input wire aresetn
.s_axis_cartesian_tvalid(1'b1), // input wire s_axis_cartesian_tvalid
.s_axis_cartesian_tdata({3'd0,G_square_data}), // input wire [23 : 0] s_axis_cartesian_tdata
.m_axis_dout_tvalid(), // output wire m_axis_dout_tvalid
.m_axis_dout_tdata(m_axis_dout_tdata) // output wire [15 : 0] m_axis_dout_tdata
);
我们打开仿真,输入一张灰度图像:
我们先设置阈值为200(因为开方后的数据位宽为11位,所以阈值范围为0-2048),开始仿真:
img_sobel_edge#(
.IMG_WIDTH ( `IMG_WIDTH ),
.IMG_HEIGHT ( `IMG_HEIGHT ),
.THRESH (200)
)u_img_sobel_edge(
.sys_clk ( clk ),
.sys_rst ( reset ),
.i_img_data ( img_data_i ),
.i_img_data_valid ( valid_i ),
.o_img_data ( img_data_o ),
.o_img_data_valid ( valid_o )
);
仿真初始化完成,我们接着看生成后的图像:
可以看到我们生成的图像边缘和最开始的matlab生成的一致,我们修改阈值为400观察一下图像:
可以看到阈值大小不一样,显示出来的画面效果也不一致,因此找到一个合适的阈值大小是整个需求的关键。