天天看点

FPGA实现边缘检测Sobel算法

一. Sobel算法

首先先在这里,介绍一下Sobel算法的原理,以及实现过程,由于Sobel算法并不复杂,可以说是相对简单的,就不作过多的介绍.

  1. 先求x,y方向的梯度dx,dy
  2. 然后求出近似梯度 G = d x 2 + d y 2 G = dx^{2}+dy^{2} G=dx2+dy2然后开根号,也可以为了分别计算近似为 G = ∣ d x ∣ + ∣ d y ∣ G = |dx|+|dy| G=∣dx∣+∣dy∣
  3. 最后根据G的值,来判断该点是不是边缘点,是的话,就将该点的像素复制为255,否则为0,,当然0或255可以自己随意指定,也可以是其他两个易于区分的像素值。

二. dx,dy的求法

  • dx方向的核值如下,核值与图像上3*3的区域对应相乘然后相加,
    FPGA实现边缘检测Sobel算法
  • dy同dx求法一样
    FPGA实现边缘检测Sobel算法
  • 最后判断对应图像3*3区域的中心点是否为边缘点
  • 当然核值的1,2是可以修改的,例如3,10

上面就简单的介绍了一下Sobel算法的原理以及实现步骤,接下来就在FPGA中实现它吧。

三. 目标图片的准备

我们先将图片写入rom中,然后将数据从rom中读出进行处理。

先借助python和opencv将图片转为灰度图,然后生成mif文件,代码如下

import numpy as np
import matplotlib.pyplot as plt
import os
import shutil
import cv2
headfile = '''
DEPTH = 10000;
WIDTH = 8;
ADDRESS_RADIX = HEX;
DATA_RADIX = HEX;
CONTENT
BEGIN
'''
img = cv2.imread("13.png")     #读取图片
img = cv2.resize(img,(100,100))  #将图片resize到100*1001的大小
img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)		#将图片转为灰度图
mif = open('image.mif', 'w')
mif.writelines(headfile)
i = 0
for m in range(0,100):
    for n in range(0,100):
        mif.writelines(str(hex(i)[2:]))
        mif.writelines(' : ')
        mif.writelines(str(hex(img[m][n]))[2:])# + str(hex(img[n][m][1]))[2:] + str(hex(img[n][m][0]))[2:])
        mif.writelines(';')
        mif.writelines('\n')
        i += 1
mif.write('END;')
           

经过上面的代码,就可以生成mif文件了,然后在软件中调用单端rom的ip,并且选择mif就可以了,位宽8,深度10000。

根据我们的dx,dy的核值,可以看出,每一次操作需要三行图像数据,
以及每一行图像数据的三个连续的值。
所以我们需要两个ram来存储两行数据,另外一行为当前读取的数据,
不需要事先存储,连续的三个值可以用延时
           

四. 两行图像数据的存储

先事先声明一下如下数据

reg[7:0]	data_line_11,data_line_12,data_line_13;		#第一行数据的3个值
reg[7:0]	data_line_21,data_line_22,data_line_23;		#第二行数据的3个值
reg[7:0]	data_line_31,data_line_32,data_line_33;		#第三行数据的3个值
           

采用双端ram,进行存储数据,由于图像的宽度为100,所以深度为128即可。

实例出来代码如下

RAM RAM_V1(
	.clock(clk_9M),				//时钟
	.data(ram1_data_in),		//写入的数据
	.rdaddress(ram1_raddr),	//读出的地址
	.wraddress(ram1_waddr),	//写入的地址
	.wren(1'b1),					//读写使能
	.q(ram1_data_out));	//读出的数据
	
RAM RAM_V2(
	.clock(clk_9M),
	.data(ram2_data_in),
	.rdaddress(ram2_raddr),
	.wraddress(ram2_waddr),
	.wren(1'b1),
	.q(ram2_data_out));
           

为了使最边缘的图像数据建立起3*3的区域,这里将图像的最框添加了0像素值,所以现在处理的图像数据大小为102 * 102了。

  1. 假定一二行数据以及存储进去了,现在读取的是第三行。
    当第三行读取到第三个数据时,是不是可以将第二行的数据的第一个更新为
     第三行读取到第一个数据,将第一行的数据的第一个更新为第二行数据的第一个
     同样当第三行数据读到第四,五,六...个数据时,依次更新第一行第二行的第二,
     三,四...个数据
               
  2. 假定第一行,第二行没有数据,当为显示到图像的第一行时,将第一行的数据写入0,然后按照1的方式更新第二行数据。
//图像显示的区域 cur_x >= 'd101 && cur_x <='d200 && 
//				cur_y >= 'd50 && cur_y < 'd150
//第一行 写
[email protected](posedge clk_9M or negedge rst)
begin
	if(rst == 1'b0)
		ram1_waddr <= 'd0;
	else if(cur_x >= 'd100 && cur_x <= 'd201 && cur_y >= 'd49)
		ram1_waddr <= ram1_waddr + 1'b1;
	else
		ram1_waddr <= 'd0;
end
[email protected](posedge clk_9M or negedge rst)
begin
	if(rst == 1'b0)
		ram1_data_in <= 'd0;
	else if(cur_x >= 'd100 && cur_x <= 'd201 && cur_y >= 'd49)
		if(cur_y == 'd49)
			ram1_data_in <= 'd0;
		else
			ram1_data_in <= data_line_21;
end


//第二行 写
[email protected](posedge clk_9M or negedge rst)
begin
	if(rst == 1'b0)
		ram2_waddr <= 'd0;
	else if(cur_x >= 'd100 && cur_x <= 'd201 && cur_y >= 'd49)
		ram2_waddr <= ram2_waddr + 1'b1;
	else
		ram2_waddr <= 'd0;
end
[email protected](posedge clk_9M or negedge rst)
begin
	if(rst == 1'b0)
		ram2_data_in <= 'd0;
	else if(cur_x >= 'd100 && cur_x <= 'd201 && cur_y >= 'd49)
		ram2_data_in <= data_line_31;
end

           

五. 三行数据的读取

这个较为简单了,直接从对应的rom和ram中读出即可

下面代码是第二行和第三行的,第一行就不拿出来了,都一模一样的。

[email protected](posedge clk_9M or negedge rst)
begin
	if(rst == 1'b0)
		begin
			data_line_21 = 'd0;
			data_line_22 = 'd0;
			data_line_23 = 'd0;
		end
	//这里读取的cur_x的值对比于显示的值需要注意一下
	else if(cur_x >= 'd98 && cur_x <= 'd199 && cur_y >= 'd50) 
		begin
			data_line_21 <= data_line_22;
			data_line_22 <= data_line_23;
			data_line_23 <= ram2_data_out;		
		end
	else
		begin
			data_line_21 <= data_line_22;
			data_line_22 <= data_line_23;
			data_line_23 <= 'd0;	
		end
end
[email protected](posedge clk_9M or negedge rst)
begin
	if(rst == 1'b0)
		begin
			data_line_31 <= 'd0;
			data_line_32 <= 'd0;
			data_line_33 <= 'd0;
		end
	else if(cur_x >= 'd98 && cur_x <= 'd199 && cur_y >= 'd49 && cur_y < 149)
		begin
			data_line_33 <= img;
			data_line_32 <= data_line_33;
			data_line_31 <= data_line_32;
		end
	else
		begin
			data_line_31 <= data_line_32;
			data_line_32 <= data_line_33;
			data_line_33 <= 'd0;
		end
end
           

六. 进行Sobel运算

这里需要注意一下dx和dy的值可能会有1000多,所以说其位宽不在是8了而是10.

分别计算负的和正的,然后判断大小,最近用大的减去小的。最后与阈值进行判断来赋值。 阈值的大小不是0-255了,而是0-1520(肯能不太准确),我下面的是1035.

reg[10:0]	Sobel_px ,Sobel_nx;
reg[10:0]	Sobel_py ,Sobel_ny;

wire[10:0]	Sobel_x;
wire[10:0]	Sobel_y;

wire[7:0]	Sobel_data;


assign Sobel_x = (Sobel_px > Sobel_nx) ? (Sobel_px - Sobel_nx) : (Sobel_nx - Sobel_px);
assign Sobel_y = (Sobel_py > Sobel_ny) ? (Sobel_py - Sobel_ny) : (Sobel_ny - Sobel_py);
assign Sobel_data = (Sobel_x + Sobel_y > 'd1035) ? 'd0 : 'd255;



[email protected](posedge clk_9M or negedge rst)
begin
	if(rst == 1'b0)
		begin
			Sobel_px <= 'd0;
			Sobel_nx <= 'd0;
		end
	else if(cur_x >= 'd100 && cur_x <= 'd199 && cur_y >= 'd50)
		begin
			Sobel_nx <= data_line_11 + data_line_21 + data_line_21 + data_line_31;
			Sobel_px <= data_line_13 + data_line_23 + data_line_23 + data_line_33;
		end
	else
		begin
		Sobel_nx <= 'd0;
		Sobel_px <= 'd0;
		end
end


[email protected](posedge clk_9M or negedge rst)
begin
	if(rst == 1'b0)
		begin
			Sobel_py <= 'd0;
			Sobel_ny <= 'd0;
		end
	else if(cur_x >= 'd100 && cur_x <= 'd199 && cur_y >= 'd50)
		begin
			Sobel_py <= data_line_11 + data_line_12 + data_line_12 + data_line_13;
			Sobel_ny <= data_line_31 + data_line_32 + data_line_32+ data_line_33;
		end
	else
		begin
			Sobel_ny <=	'd0;
			Sobel_py <=	'd0;
		end
end
           

最后给出在FPGA上的原图和效果图,效果不错,完整项目下载链接

FPGA实现边缘检测Sobel算法
FPGA实现边缘检测Sobel算法