基于FPGA的图像边缘检测(OV5640)

一、简介

1.应用范围

边缘主要存在于图像中目标与目标之间,目标与背景之间,区域与区域之间。

边缘检测的目的就是找到图像中亮度变化剧烈的像素点构成的集合,表现出来往往是轮廓。如果图像中边缘能够精确的测量和定位,那么,就意味着实际的物体能够被定位和测量,包括物体的面积,物体的直径,物体的形状等就能被测量。

基于此,边缘检测技术在许多场景下被应用,如:车牌检测与提取,物体识别等。

2.边缘检测背景介绍

数字图像处理是指将图像信号转换成数字信号并利用计算机对其进行处理的过程。图像处理最早出现于20世纪50年代,当时的电了计算机已经发展到一定水平,人们开始利用计算机来处理图形和图像信息。数字图像处理作为一门学科人约形成于20世纪60年代初期。早期的图像处理的目的是改善图像的质量,它以人为对象,以改善人的视觉效果为日的。图像处理中,输入的是质量低的图像,输出的是改善质量后的图像,常用的图像处理方法有图像增强,复原,编码,压缩等。

边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。

边缘检测的实质是采用某种算法来提取出图像中对象与背景问的交界线。我们将边缘定义为图像中灰度发生急刷变化的区域边界。图像灰度的变化情况可以川图像灰度分布的梯度来反唤,因此我们可以用局部图像微分技术来获得边缘检测算子。经典的边缘检测方法,是通过对原始图像中像素的某小邻域构造边缘检测算子来达到检测边缘这一目的。

3.工程实践

3.1需求分析

3.2 系统模块说明

3.3系统架构

模块名称

模块功能

摄像头驱动

初始化配置ov5640摄像头

摄像头数据采集

采集ov5640摄像头输出的图像数据

图像处理单元

实现图像处理功能--灰度化,二值化,边缘检测等

sdram控制器

视频数据缓存

vga显示驱动

vga显示器驱动时序实现

4、摄像头简介

二、程序设计

1.摄像头配置

1.1 摄像头配置原理

本次设计使用的摄像头是OV5640,摄像头配置的详细原理,请参考:OV5640手册解读

1.2 程序设计
1.2.1 接口模块程序设计

本次设计中,接口模块采用的是IIC协议。因为IIC向下兼容SCCB协议,只是在写时序时,SCCB第九位传输的是Don't care,而IIC传输的是ACK响应,故IIC接口模块直接拿来使用时,要将写时序的ACK响应废除。

module i2c_intf (
    input           clk         ,
    input           rst_n       ,
    input   [3:0]   cmd         ,
    input           req         ,
    input   [7:0]   wr_data     ,
    output  [7:0]   dout        ,
    output          done        ,
    output  reg     m_scl       ,
    inout           m_sda                
);

parameter   CMD_START   = 4'b0001,
            CMD_WITER   = 4'b0010,
            CMD_READ    = 4'b0100,
            CMD_STOP    = 4'b1000;

parameter   SCL_MAX     = 50_000_000 / 100_000 ,// 500分频-->100k的时钟频率
            SCL_LOW     = 125,//低电平的中间时刻,发送 1/4
            SCL_HIGHT   = 375;//高电平的中间时刻,采样 3/4


//wr_data
reg     [7:0]   wr_data_r;
reg     [7:0]   rd_data  ;
//cmd寄存
reg     [3:0]   cmd_r;
//ack响应
reg             rx_ack;   
//scl计数器
reg	    [8:0]   cnt_scl     ;
wire		    add_cnt_scl ;
wire            end_cnt_scl ;
//bit计数器
reg     [3:0]   cnt_bit     ;
wire		    add_cnt_bit ;
wire            end_cnt_bit ;
reg     [3:0]   bit_max     ;//bit最大计数复用

//状态转移条件
reg [3:0]   state_c;
reg [3:0]   state_n;
wire        idle2start  ;
wire        idle2witer  ;
wire        idle2read   ;
wire        start2witer ;
wire        witer2rack  ;
wire        rack2idle   ;
wire        rack2stop   ;
wire        read2sack   ;
wire        sack2idle   ;
wire        sack2stop   ;
wire        stop2idle   ;
//三态门
reg         m_sda_en        ; // 设置SDA模式,1位输出,0为输入
reg         m_sda_out       ; // SDA寄存器
wire        m_sda_in;
/**************************************************************
                         状态机        
**************************************************************/
parameter   IDLE    =   0,
            START   =   1,
            WITER   =   2,
            RACK    =   3,
            READ    =   4,
            SACK    =   5,
            STOP    =   6;
                                
//第一段状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
    state_c <= IDLE;
end
else begin
    state_c <= state_n;
end
end
                
//第二段状态机
always @(*)begin
case(state_c)
    IDLE    :	if(idle2start)
                    state_n = START;
                else if(idle2witer)
                    state_n = WITER;
                else if(idle2read)
                    state_n = READ;
                else 
                    state_n = state_c;
    START    :	if(start2witer)
                    state_n = WITER;
                else 
                    state_n = state_c;
    WITER    :	if(witer2rack)
                    state_n = RACK;
                else 
                    state_n = state_c;                                   
    RACK    :	if(rack2idle)
                    state_n = IDLE;
                else if(rack2stop)
                    state_n = STOP;
                else 
                    state_n = state_c;
    READ    :	if(read2sack)
                    state_n = SACK;
                else 
                    state_n = state_c;
    SACK    :	if(sack2idle)
                    state_n = IDLE;
                else if(sack2stop)
                    state_n = STOP;
                else 
                    state_n = state_c;                                                                         
    STOP    :   if(stop2idle)
                    state_n = IDLE;
                else
                    state_n = state_c;            
    default : state_n = state_c;
    endcase
end	
                               
//状态跳转条件
assign 	   idle2start  = state_c == IDLE  && req && (cmd & CMD_START)	; 
assign 	   idle2witer  = state_c == IDLE  && req && (cmd & CMD_WITER)	;
assign 	   idle2read   = state_c == IDLE  && req && (cmd & CMD_READ )	;
assign 	   start2witer = state_c == START && end_cnt_bit ;
assign 	   witer2rack  = state_c == WITER && end_cnt_bit ;
assign 	   rack2idle   = state_c == RACK  && end_cnt_bit && (!(cmd_r & CMD_STOP));
assign 	   rack2stop   = state_c == RACK  && end_cnt_bit && ((cmd_r & CMD_STOP) /* || rx_ack */);
assign 	   read2sack   = state_c == READ  && end_cnt_bit ;
assign 	   sack2idle   = state_c == SACK  && end_cnt_bit && (!(cmd_r & CMD_STOP));
assign 	   sack2stop   = state_c == SACK  && end_cnt_bit && (cmd_r & CMD_STOP);
assign     stop2idle   = state_c == STOP  && end_cnt_bit ;
/**************************************************************
                    时序约束            
**************************************************************/
//cmd寄存
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        cmd_r <= 4'b0000;
    else if(req)
        cmd_r <= cmd;

//接收从机回应的ack
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            rx_ack <= 1'b1;
        end 
        else if(state_c == RACK && cnt_scl == SCL_HIGHT)begin 
            rx_ack <= m_sda_in;
        end 
    end

//写入的数据 
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            wr_data_r <= 0;
        end 
        else if(req)begin 
            wr_data_r <= wr_data;
        end 
    end
//接收读取的数据
//rd_data       接收读入的数据
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            rd_data <= 0;
        end
        else if(state_c == READ && cnt_scl == SCL_HIGHT)begin
            rd_data[7-cnt_bit] <= m_sda_in;    //将接收到的SDA信号串并转换发送到eeprom_rw模块
        end
    end
/**************************************************************
                    双向端口m_sda的使用方式                   
**************************************************************/
assign	m_sda_in = m_sda;					 //高阻态的话,则把总线上的数据赋给m_sda_in
assign	m_sda =  m_sda_en ? m_sda_out : 1'bz;//使能1则输出,0则高阻态                    

//m_sda_en
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        m_sda_en <= 1'b0;
    else if(state_c == READ | state_c == RACK)   
        m_sda_en <= 1'b0;     
    else 
        m_sda_en <= 1'b1; 
           
//m_sda_out
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            m_sda_out <= 1;
        end 
        else begin 
            case (state_c)
                START : if(cnt_scl == SCL_LOW)
                            m_sda_out <= 1'b1;
                        else if(cnt_scl == SCL_HIGHT)
                            m_sda_out <= 1'b0;
                WITER : if(cnt_scl == SCL_LOW)
                            m_sda_out <= wr_data_r[7-cnt_bit];//MSB
                STOP  : if(cnt_scl == SCL_LOW)
                            m_sda_out <= 1'b0;
                        else if(cnt_scl == SCL_HIGHT)
                            m_sda_out <= 1'b1;
                SACK  : if(cnt_scl == SCL_LOW)
                            m_sda_out <= (cmd & CMD_STOP)?1'b1:1'b0;
                default: m_sda_out <= 1'bz;
            endcase
        end 
    end
/**************************************************************
                    系统时钟降频模块             
**************************************************************/
//cnt_scl
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt_scl <= 0;
        end 
        else if(add_cnt_scl)begin 
            if(end_cnt_scl)begin 
                cnt_scl <= 0;
            end
            else begin 
                cnt_scl <= cnt_scl + 1;
            end 
        end
        else begin
            cnt_scl <= cnt_scl;
        end
    end 

    assign add_cnt_scl = state_c != IDLE ;
    assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_MAX - 1;

//m_scl
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            m_scl <= 1'b1;
        end 
        else if(cnt_scl <= (SCL_MAX>>1))begin 
            m_scl <= 1'b0;
        end 
        else begin 
            m_scl <= 1'b1;
        end 
    end

/**************************************************************
                    bit计数器              
**************************************************************/
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt_bit <= 0;
        end 
        else if(add_cnt_bit)begin 
            if(end_cnt_bit)begin 
                cnt_bit <= 0;
            end
            else begin 
                cnt_bit <= cnt_bit + 1'b1;
            end 
        end
        else begin
            cnt_bit <= cnt_bit;
        end
    end 
    
    assign add_cnt_bit = end_cnt_scl ;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1;

//bit_max
    always@(*)
        if(state_c == WITER || state_c == READ)
            bit_max = 8; 
        else 
            bit_max = 1; 
/**************************************************************
                   输出信号              
**************************************************************/
assign  dout = rd_data;
assign  done = rack2idle | sack2idle | stop2idle; 

endmodule
1.2.2 OV5640配置模块程序设计

配置流程:

主要采用状态机加计数器的方式来设计配置模块;

当上电之后计数20ms,之后就可以进行摄像头的配置,有一个配置完成信号,当配置完254(测试模式254,实际显示模式252)个寄存器后,配置信号有效。

配置模块主要就是通过IIC_master模块向摄像头里面写入数据,完成配置。

发送数据是以任务的方式发请求、命令和数据

注意: OV5640的寄存器地址是 16 位的,加上数据,sccb_data 的值为 24 位 ,写数据传输时要传输4个字节:1字节写命令+2字节地址+1字节数据

`include "param.v"
module cmos_config(
    input               clk         ,
    input               rst_n       ,
    //i2c_master
    output              req         ,
    output      [3:0]   cmd         ,
    output      [7:0]   dout        ,
    input               done        ,
    
    output              config_done 
);

//定义参数

    localparam  WAIT   = 4'b0001,//上电等待20ms
                IDLE   = 4'b0010,
                WREQ   = 4'b0100,//发写请求
                WRITE  = 4'b1000;//等待一个字节写完

    parameter   DELAY  = 1000_000;//上电延时20ms开始配置
//信号定义

    reg     [3:0]       state_c     ;
    reg     [3:0]       state_n     ;
    
    reg     [19:0]      cnt0        ;
    wire                add_cnt0/* synthesis syn_keep*/    ;
    wire                end_cnt0/* synthesis syn_keep*/    ;

    reg     [1:0]       cnt1        ;
    wire                add_cnt1/* synthesis syn_keep*/    ;
    wire                end_cnt1/* synthesis syn_keep*/    ;

    reg                 config_flag ;	//1:表示在配置摄像头 0:表示配置完成
    reg     [23:0]      lut_data    ;

    reg                 tran_req    ; 	//发送请求命令和数据
    reg      [3:0]      tran_cmd    ; 
    reg      [7:0]      tran_dout 
FPGA读写OV5640摄像头显示例程 Verilog逻辑源码Quartus工程文件+文档说明,FPGA型号Cyclone4E系列中的EP4CE6F17C8,Quartus版本17.1。 本实验将采用 500 万像素的 OV5640 摄像头模组(模块型号:AN5640)为大家显示更高分辨率 的视频画面。OV5640 摄像头模组最大支持 QSXGA (2592x1944)的拍照功能,支持 1080P、720P、 VGA、QVGA 视频图像输出。本实验将 OV5640 配置为 RGB565 输出,先将视频数据写入外部存储 器,再从外部存储器读取送到 VGA、LCD 等显示模块。 module top( input clk, input rst_n, output cmos_scl, //cmos i2c clock inout cmos_sda, //cmos i2c data input cmos_vsync, //cmos vsync input cmos_href, //cmos hsync refrence,data valid input cmos_pclk, //cmos pxiel clock output cmos_xclk, //cmos externl clock input [7:0] cmos_db, //cmos data output cmos_rst_n, //cmos reset output cmos_pwdn, //cmos power down output vga_out_hs, //vga horizontal synchronization output vga_out_vs, //vga vertical synchronization output[4:0] vga_out_r, //vga red output[5:0] vga_out_g, //vga green output[4:0] vga_out_b, //vga blue output sdram_clk, //sdram clock output sdram_cke, //sdram clock enable output sdram_cs_n, //sdram chip select output sdram_we_n, //sdram write enable output sdram_cas_n, //sdram column address strobe output sdram_ras_n, //sdram row address strobe output[1:0] sdram_dqm, //sdram data enable output[1:0] sdram_ba, //sdram bank address output[12:0] sdram_addr, //sdram address inout[15:0] sdram_dq //sdram data ); parameter
FPGA图像边缘检测是一种基于FPGA图像处理技术,可以用于计算机视觉、图像分析和图像处理等领域。在FPGA图像边缘检测中,通常会采用一些基本的图像处理算法,如灰度转换、高斯滤波、二值化和Sobel算法等。 在FPGA图像边缘检测的实现过程中,需要进行一系列的步骤和配置。首先,需要对图像进行灰度转换,将彩色图像转换为灰度图像。接下来,可以通过高斯滤波来平滑图像,减少噪声的影响。然后,将滤波后的图像进行二值化处理,将灰度值转换为0或255,以便于后续边缘检测。最后,可以使用Sobel算法进行边缘检测,通过计算图像中像素点的梯度值来识别边缘。 在FPGA图像边缘检测的项目框架中,通常会包括摄像头配置模块、图像处理模块、数据缓存模块和其他模块。摄像头配置模块用于设置摄像头的参数和采集图像数据,图像处理模块用于实现图像处理算法,数据缓存模块用于存储和传输图像数据。此外,还可能涉及到其他模块,如显示模块或控制模块等。 在FPGA图像边缘检测的实现中,可能会使用一些代码来实现各个模块的功能。例如,数据采集模块用于采集摄像头传输的图像数据,读写控制模块用于对数据进行读写操作。具体的代码实现可以参考相关的参考资料和源码。 综上所述,FPGA图像边缘检测是一种基于FPGA图像处理技术,通过一系列的图像处理算法和模块来实现。通过灰度转换、高斯滤波、二值化和Sobel算法等步骤,可以实现对图像边缘检测。在具体实现中,需要进行管脚的配置和硬件的搭建,并可以借助FIFO和其他模块来实现图像处理功能。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值