一、简介
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