基于EBAZ4205矿板的图像处理:12图像二值化(阈值可调)
我的项目是基于EBAZ4205矿板的阈值可调的图像阈值二值化处理,可以通过按键调整二值化的阈值,key1为阈值加1,key4为阈值减1,key2为阈值加10,key5为阈值减10,key54为阈值重置为128。
先看效果
拿我的pynq当模特
可以清晰的看到xilinx的商标被划分了出来
阈值过大和过小就不行了,这也是全局阈值二值化的缺点,接下来我会完成基于卷积的局部阈值二值化,它能得到更好的效果,尽请期待。
项目解读
我的blockdesign,你不按照我的接,按照正点原子的开源代码接也是可以的,只是我有强迫症,能接的我都接了。
就是在标准的ov5640->VDMA->DDR->VDMA->DVI_Driver->HDMI的流程(可以点击这个超链接看我说的标准流程)的第一个箭头哪里加了三个模块,一个负责将图像从RGB格式转化为灰度图像,一个负责对灰度图像进行二值化处理,最后一个为PS提供了访问PL端reg的AXILite端口,以便实时调整阈值。
下面的代码里我都添加了(* X_INTERFACE_IGNORE = “true” *) ,是禁用vivado的interface自动推断,可以不加
rgb2gray模块
该模块负责将图像从RGB格式转化为灰度图像
公式:
Y = 0.299R +0.587G + 0.114B
Y = (77 R + 150G + 29 *B)>>8
`timescale 1ns / 1ps
//作者:抢公主的大魔王
//功能:将来自ov5640视频流从RGB格式转化为灰度图像
//日期:24.5.5
//版本:1v0
//联系方式:2376635586@qq.com
module rgb2gray(
(* X_INTERFACE_IGNORE = "true" *) input cmos_frame_vsync,
(* X_INTERFACE_IGNORE = "true" *) input [23:0] cmos_frame_data,
(* X_INTERFACE_IGNORE = "true" *) input cmos_frame_href,
(* X_INTERFACE_IGNORE = "true" *) input cmos_frame_clk,
(* X_INTERFACE_IGNORE = "true" *) input cmos_rstn,//同步复位
(* X_INTERFACE_IGNORE = "true" *) input cmos_frame_ce,
(* X_INTERFACE_IGNORE = "true" *) output dataout_frame_vsync,
(* X_INTERFACE_IGNORE = "true" *) output [23:0] dataout_frame_data,
(* X_INTERFACE_IGNORE = "true" *) output dataout_frame_href,
(* X_INTERFACE_IGNORE = "true" *) output dataout_frame_ce
);
// Y = 0.299R +0.587G + 0.114B
// Y = (77 *R + 150*G + 29 *B)>>8
reg [15:0] r_gray1;
reg [15:0] g_gray1;
reg [15:0] b_gray1;
reg [15:0] y1;
reg [7:0] y2;
reg [2:0] dataout_frame_vsync_r;
reg [2:0] dataout_frame_href_r;
reg [2:0] dataout_frame_ce_r;
always@(posedge cmos_frame_clk)begin
if(!cmos_rstn)begin
r_gray1 <= 8'h00;
g_gray1 <= 8'h00;
b_gray1 <= 8'h00;
end
else begin
r_gray1 <= cmos_frame_data[23:16] * 8'd77 ;
g_gray1 <= cmos_frame_data[15:8] * 8'd150;
b_gray1 <= cmos_frame_data[7:0] * 8'd29 ;
end
end
always@(posedge cmos_frame_clk)begin
if(!cmos_rstn)begin
y1 <= 16'h0000;
end
else begin
y1 <= r_gray1 + g_gray1 + b_gray1;
end
end
always@(posedge cmos_frame_clk)begin
if(!cmos_rstn)begin
y2 <= 8'h0000;
end
else begin
y2 <= y1[15:8];
end
end
always@(posedge cmos_frame_clk)begin
if(!cmos_rstn)begin
dataout_frame_ce_r <= 3'b000;
dataout_frame_vsync_r <= 3'b000;
dataout_frame_href_r <= 3'b000;
end
else begin
dataout_frame_ce_r <= {dataout_frame_ce_r[1:0] ,cmos_frame_ce};
dataout_frame_vsync_r <= {dataout_frame_vsync_r[1:0] ,cmos_frame_vsync};
dataout_frame_href_r <= {dataout_frame_href_r[1:0] ,cmos_frame_href};
end
end
assign dataout_frame_data = {y2,y2,y2};
assign dataout_frame_ce = dataout_frame_ce_r[2];
assign dataout_frame_vsync = dataout_frame_vsync_r[2];
assign dataout_frame_href = dataout_frame_href_r[2];
endmodule
global_binary模块
负责根据阈值对灰度图像进行二值化处理
`timescale 1ns / 1ps
//作者:抢公主的大魔王
//功能:根据阈值对灰度图像进行二值化处理
//日期:24.5.5
//版本:1v0
//联系方式:2376635586@qq.com
module global_binary(
(* X_INTERFACE_IGNORE = "true" *) input datain_vsync,
(* X_INTERFACE_IGNORE = "true" *) input [23:0] datain,
(* X_INTERFACE_IGNORE = "true" *) input datain_href,
(* X_INTERFACE_IGNORE = "true" *) input datain_clk,
(* X_INTERFACE_IGNORE = "true" *) input datain_rstn,//同步复位
(* X_INTERFACE_IGNORE = "true" *) input datain_frame_ce,
(* X_INTERFACE_IGNORE = "true" *) input [7:0]threshold,
(* X_INTERFACE_IGNORE = "true" *) output dataout_vsync,
(* X_INTERFACE_IGNORE = "true" *) output reg [23:0] dataout,
(* X_INTERFACE_IGNORE = "true" *) output dataout_vaild,
(* X_INTERFACE_IGNORE = "true" *) output dataout_frame_ce
);
reg [1:0] dataout_vsync_r;
reg [1:0] dataout_valid_r;
reg [1:0] dataout_frame_ce_r;
always@(posedge datain_clk)begin
if(!datain_rstn)
dataout <= 24'hff_ff_ff;
else if(datain[7:0]>=threshold)
dataout <= 24'hff_ff_ff;
else
dataout <= 24'h00_00_00;
end
always@(posedge datain_clk)begin
if(!datain_rstn) begin
dataout_vsync_r <= 3'b000;
dataout_valid_r <= 3'b000;
dataout_frame_ce_r <= 3'b000;
end
else begin
dataout_vsync_r <= {dataout_vsync_r[0] , datain_vsync};
dataout_valid_r <= {dataout_valid_r[0] , datain_href};
dataout_frame_ce_r <= {dataout_frame_ce_r[0] , datain_frame_ce};
end
end
assign dataout_vsync = dataout_vsync_r[1];
assign dataout_vaild = dataout_valid_r[1];
assign dataout_frame_ce = dataout_frame_ce_r[1];
endmodule
AXICtrlThreshold模块
这个就是自己打包的标准的AXILite IP核,然后加了两句代码,一句是将threshold的0到7为连接到这个IP内部的第一个reg的低八位,另一句就是让这个reg复位是被复位为128,而不是0,因为我的图像阈值二值化算法的缺省阈值为128。
打包过程如下
下面要改名字和描述哦。
然后到了这个AXILite IP内部添加下面的代码
assign threshold = slv_reg0[7:0];
然后更改复位值
if ( S_AXI_ARESETN == 1'b0 )
begin
slv_reg0 <= 32'd128;
slv_reg1 <= 0;
slv_reg2 <= 0;
slv_reg3 <= 0;
end
最后把它output出去
output wire [7:0] threshold
IP代码本身,和她的top层都要output
然后在这一栏,所有不是对号的要挨个点一遍,挨个更新一下,最后全是对号之后,就可以打包了。
vitis端代码
IntrHandler是中断回调函数,触发中断后会调用这个函数。
SetupInterruptSystem负责初始化中断,配置触发中断方式,使能中断。
Gpio_Init初始化GPIO,包括key1-5,led1-3和sccb总线(配置OV5640的)
binary_threshold就是我的图像阈值二值化的阈值啦,可以通过按键进行调整。
//作者:抢公主的大魔王
//功能:阈值可调的图像二值化
//日期:24.5.5
//版本:1v0
//联系方式:2376635586@qq.com
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xil_types.h"
#include "xil_cache.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "xplatform_info.h"
#include "xaxivdma.h"
#include "xaxivdma_i.h"
#include "display_ctrl_hdmi/display_ctrl.h"
#include "vdma_api/vdma_api.h"
#include "emio_sccb_cfg/emio_sccb_cfg.h"
#include "ov5640/ov5640_init.h"
#include "sleep.h"
//宏定义
#define DYNCLK_BASEADDR XPAR_AXI_DYNCLK_0_BASEADDR //动态时钟基地址
#define VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID //VDMA器件ID
#define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID //VTC器件ID
#define THRESHOLD_BASEADDR XPAR_AXICTRLTHRESHOLD_0_S00_AXI_BASEADDR
#define EMIO_SCL_NUM 54
#define EMIO_SDA_NUM 55
#define KEY1 56 //T19
#define KEY2 57 //P19
#define KEY3 58 //U20
#define KEY4 59 //U19
#define KEY5 60 //V20
#define LED1 61 //H18
#define LED2 62 //K17
#define LED3 63 //E19
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
XGpioPs Gpio;
#define GPIO_BANK XGPIOPS_BANK0 /* Bank 0 of the GPIO Device */
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define GPIO_INTERRUPT_ID XPAR_XGPIOPS_0_INTR
//全局变量
//frame buffer的起始地址
unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR
+ 0x1000000);
u8 binary_threshold = 128;
XAxiVdma vdma;
DisplayCtrl dispCtrl;
VideoMode vd_mode;
static XScuGic Intc; /* The Instance of the Interrupt Controller Driver */
static void IntrHandler(void *CallBackRef, u32 Bank, u32 Status)
{
XGpioPs *Gpio_cb = (XGpioPs *)CallBackRef;
if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY1)){
binary_threshold++;
Xil_Out32(THRESHOLD_BASEADDR, binary_threshold);
xil_printf("The threshold has been changed\n\rThe threshold now is %d\n\r",binary_threshold);
XGpioPs_IntrClearPin(Gpio_cb, KEY1);
}
else if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY4)){
binary_threshold--;
Xil_Out32(THRESHOLD_BASEADDR, binary_threshold);
xil_printf("The threshold has been changed\n\rThe threshold now is %d\n\r",binary_threshold);
XGpioPs_IntrClearPin(Gpio_cb, KEY4);
}
else if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY2)){
binary_threshold = binary_threshold+10;
Xil_Out32(THRESHOLD_BASEADDR, binary_threshold);
xil_printf("The threshold has been changed\n\rThe threshold now is %d\n\r",binary_threshold);
XGpioPs_IntrClearPin(Gpio_cb, KEY2);
}
else if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY5)){
binary_threshold = binary_threshold-10;
Xil_Out32(THRESHOLD_BASEADDR, binary_threshold);
xil_printf("The threshold has been changed\n\rThe threshold now is %d\n\r",binary_threshold);
XGpioPs_IntrClearPin(Gpio_cb, KEY5);
}
else if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY3)){
binary_threshold = 128;
Xil_Out32(THRESHOLD_BASEADDR, binary_threshold);
xil_printf("The threshold has been reset\n\rThe threshold now is %d\n\r",binary_threshold);
XGpioPs_IntrClearPin(Gpio_cb, KEY3);
}
XGpioPs_WritePin(&Gpio, LED1, !XGpioPs_ReadPin(&Gpio, LED1));
}
void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio,
u16 GpioIntrId){
XScuGic_Config *IntcConfig;
Xil_ExceptionInit();
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
GicInstancePtr);
XScuGic_Connect(GicInstancePtr, GpioIntrId,
(Xil_ExceptionHandler)IntrHandler,
(void *)Gpio);
XScuGic_Enable(GicInstancePtr, GpioIntrId);
XGpioPs_SetIntrTypePin(Gpio, KEY1, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
XGpioPs_SetIntrTypePin(Gpio, KEY2, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
XGpioPs_SetIntrTypePin(Gpio, KEY3, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
XGpioPs_SetIntrTypePin(Gpio, KEY4, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
XGpioPs_SetIntrTypePin(Gpio, KEY5, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
XGpioPs_IntrEnablePin(Gpio, KEY1);
XGpioPs_IntrEnablePin(Gpio, KEY2);
XGpioPs_IntrEnablePin(Gpio, KEY3);
XGpioPs_IntrEnablePin(Gpio, KEY4);
XGpioPs_IntrEnablePin(Gpio, KEY5);
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
}
void Gpio_Init(void){
XGpioPs_Config *ConfigPtr;
ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr);
XGpioPs_SetDirectionPin(&Gpio, LED1, 1);
XGpioPs_SetOutputEnablePin(&Gpio, LED1, 1);
XGpioPs_WritePin(&Gpio, LED1, 0);
XGpioPs_SetDirectionPin(&Gpio, LED2, 1);
XGpioPs_SetOutputEnablePin(&Gpio, LED2, 1);
XGpioPs_WritePin(&Gpio, LED2, 0);
XGpioPs_SetDirectionPin(&Gpio, LED3, 1);
XGpioPs_SetOutputEnablePin(&Gpio, LED3, 1);
XGpioPs_WritePin(&Gpio, LED3, 0);
XGpioPs_SetDirectionPin(&Gpio, KEY1, 0);
XGpioPs_SetDirectionPin(&Gpio, KEY2, 0);
XGpioPs_SetDirectionPin(&Gpio, KEY3, 0);
XGpioPs_SetDirectionPin(&Gpio, KEY4, 0);
XGpioPs_SetDirectionPin(&Gpio, KEY5, 0);
SetupInterruptSystem(&Intc, &Gpio, GPIO_INTERRUPT_ID);
}
int main(void)
{
u32 status;
u16 cmos_h_pixel; //ov5640 DVP 输出水平像素点数
u16 cmos_v_pixel; //ov5640 DVP 输出垂直像素点数
u16 total_h_pixel; //ov5640 水平总像素大小
u16 total_v_pixel; //ov5640 垂直总像素大小
cmos_h_pixel = 1280;
cmos_v_pixel = 720;
total_h_pixel = 2570;
total_v_pixel = 980;
emio_init();
status = ov5640_init( cmos_h_pixel, //初始化ov5640
cmos_v_pixel,
total_h_pixel,
total_v_pixel);//设置OV5640输出分辨率为1280*720 PCLK = 72Mhz
if(status == 0)
xil_printf("OV5640 detected successful!\r\n");
else
xil_printf("OV5640 detected failed!\r\n");
vd_mode = VMODE_1280x720;
//配置VDMA
run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
frame_buffer_addr,0,0,BOTH);
//初始化Display controller
DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
//设置VideoMode
DisplaySetMode(&dispCtrl, &vd_mode);
DisplayStart(&dispCtrl);
Gpio_Init();
while(1){
XGpioPs_WritePin(&Gpio, LED3, !XGpioPs_ReadPin(&Gpio, LED3));
sleep(1);
}
return 0;
}