利用OTSU阈值算法在ZYNQ上实现浒苔识别

文章目录

  • 前言
  • 一、软件安装
  • 二、算法实现
    • 1.图片数据读入
    • 2.数据处理
      • 1.Matlab仿真
      • 2.利用Matlab Coder将Matlab代码转为C代码
      • 3.利用Vitis HLS将C代码转为Verilog代码
    • 3.图像显示
  • 3.完整FPGA工程代码:


这里是引用

前言

近年来,青岛附近海域的藻华现象越来越严重,针对这一问题,我们暑期实践选择了对藻华图像的处理,利用FPGA识别出图像中的浒苔。

一、软件安装

我们选用的板卡是xilinx中的ZYNQ-z7-20,软件需要使用VIVADO,同时使用vitis HLS将阈值计算部分的C代码转为Verilog代码。VIVADO和HLS的安装步骤网上有很详细的步骤,下面是我安装时参考的博客。
参考博客–最详细的Vivado安装教程

二、算法实现

1.图片数据读入

我们选择将图片数据由Matlab转化为coe文件后导入FPGA的rom中,在后续的数据处理中读取rom中的图片数据。

Matlab将PNG文件转为coe文件代码如下:

%读入的图片是灰度图,如果是RGB格式需要先转换为灰度图。
src = imread('19.png');
[m,n] = size( src );%
N = m*n;
word_len = 8;
data = reshape(src, 1, N);

fid=fopen('19.coe', 'wt');%打开文件
fprintf(fid, 'MEMORY_INITIALIZATION_RADIX=16;\n');
fprintf(fid, 'MEMORY_INITIALIZATION_VECTOR=\n');
for i = 1 : N-1
    fprintf(fid, '%x,\n', data(i));%使用%x表示十六进制数
end
fprintf(fid, '%x;\n', data(N));
fclose(fid);

FPGA中ROM的使用可以借助IP核,ROM核的使用教程可以参考以下博客:
片内ROM读写测试实验

FPGA中ROM的使用部分代码如下:

wire [7:0] rom_data;	  
reg	 [17:0] rom_addr;    

rom_ip rom_ip_inst
(
    .clka   (pixclk),      //inoput clka
    .addra  (rom_addr),      //input [4:0] addra
    .douta  (rom_data)       //output [7:0] douta
);
always@(posedge pixclk)
begin
     if((x_cnt >= 740) && (x_cnt < 1240) && (y_cnt >= 164) && (y_cnt < 828 - 164)) 
        rom_addr     <= rom_addr +1'b1;
    else if(rom_addr == 18'd250000)//图片为500x500格式,若图片尺寸不同,这一行和上一行需要根据图片的尺寸进行修改。
        rom_addr        <=18'b0; 
    else
        rom_addr        <=rom_addr; 
end

2.数据处理

根据图片中浒苔的灰度值更高这一特点,我们选择利用阈值算法对导入的数据进行二值化,将图片中的浒苔识别出来。由于某些图片中存在大气干扰、浒苔数量过多、过少等区别,一个固定的阈值并不能满足所有数据的需要,识别效果很差。因此我们在对图像数据进行预处理后,利用OTSU这一阈值算法进行阈值计算。
OTSU部分我们先用Matlab仿真,无误后利用Matlab中的app将Matlab代码转为C代码,之后利用Vitis HLS将C代码转为Verilog代码,封装成一个IP核导入到工程中进行使用。

1.Matlab仿真

Matlab中OTSU的代码如下:

im=imread("19.png");
m=500;n=500;
num=zeros(1,256);
sum=0;
start=1;
End=256;
for i = 1:m
    for j = 1:n
        k=im(i,j);
        k=k+1;
        num(k)=num(k)+1;
        sum=sum+1;
    end
end
p=zeros(1,256);
for i = 1:256
    p(i)=num(i)/(m*n);
end
M=zeros(1,256);
pa=zeros(1,256);
pb=zeros(1,256);
ma=zeros(1,256);
mb=zeros(1,256);
for k = 1:256
    for i = 1:k
        pa(k)=pa(k)+p(i);
        M(k)=M(k)+(i-1)*p(i);
    end
end
for k = 1:256
    ma(k)=M(k)/pa(k);
    mb(k)=(M(256)-M(k))/(pa(256)-pa(k));
end
max_variance=0;
for k = 1:256
    variance=pa(k)*(pa(256)-pa(k))*(ma(k)-mb(k))^2;
    %variance=pa(k)*(ma(k)-M(255))^2+pb(k)*(mb(k)-M(255))^2;
    if(max_variance<variance)
        max_variance=variance;
        threshold=k-1;
    end
end
threshold

2.利用Matlab Coder将Matlab代码转为C代码

转化后代码如下:

/*
 * File: otsu.c
 *
 * MATLAB Coder version            : 5.2
 * C/C++ source code generated on  : 29-Jun-2022 15:57:35
 */

 /* Include Files */
#include 
#include 
#include 
#include "otsu.h"
/* Function Definitions */
/*
 * Arguments    : const unsigned char im[250000]
 * Return Type  : double
 */


unsigned int finish;
unsigned int pix_num;
double num[255];


int otsu(unsigned int im,int threshold)
{
#pragma HLS INTERFACE mode=ap_hs port=threshold
#pragma HLS INTERFACE mode=ap_hs port=im
#pragma HLS INTERFACE mode=ap_ctrl_hs port=return
	pix_num=pix_num+1;
	if(pix_num!=250000)
	{
		if(im<120) im = 120U;
		num[im-1]=num[im-1]+1;
		return 0;
	}
    double M[255];
    double pa[255];
    double pb[255];
    double resum[255];
    double max_variance;
    double variance;
    int b_i;
    int b_k;
    int i;
    int j;
    int start;
    unsigned char k;
    memset(&resum[0], 0, 255U * sizeof(double));
    start = 0;
    for (i = 0; i < 254; i++) {
        resum[253 - i] = resum[254 - i] + num[253 - i];
    }
    if (resum[120] - resum[169] > 10000.0) {
        start = 169;
    }
    for (i = 0; i < 255; i++) {
        resum[i] = num[i] / 250000.0;
        M[i] = 0.0;
        pa[i] = 0.0;
        pb[i] = 0.0;
    }
    for (b_k = 0; b_k < 255; b_k++) {
        for (i = 0; i <= b_k; i++) {
            variance = resum[i];
            pa[b_k] += variance;
            M[b_k] += ((double)i + 1.0) * variance;
        }
        b_i = 253 - b_k;
        for (i = 0; i <= b_i; i++) {
            pb[b_k] += resum[(b_k + i) + 1];
        }
    }
    for (b_k = 0; b_k < 255; b_k++) {
        resum[b_k] = M[b_k] / pa[b_k];
        num[b_k] = (M[254] - M[b_k]) / pb[b_k];
    }
    max_variance = 0.0;
    threshold = 200.0;
    b_i = 254 - start;
    for (b_k = 0; b_k <= b_i; b_k++) {
        j = start + b_k;
        variance = resum[j] - num[j];
        variance = pa[j] * pb[j] * (variance * variance);
        /* variance=pa(k)*(ma(k)-M(255))^2+pb(k)*(mb(k)-M(255))^2; */
        if (max_variance < variance) {
            max_variance = variance;
            threshold = j + 1.0;
        }
    }
    return threshold;
}


/*
 * File trailer for otsu.c
 *
 * [EOF]
 */

3.利用Vitis HLS将C代码转为Verilog代码

Vitis具体的使用步骤可以参考以下博客:
Vitis HLS 构建项目并生成IP核

FPGA中生成的OTSU例化部分代码如下:

otsu_0 otsu_cal
(
    .im_ap_vld            (1'b1),
    .im_ap_ack            (im_ap_ack),
    .threshold_ap_vld     (1'b1),
    .threshold_ap_ack     (threshold_ap_ack),
    .ap_clk               (pixclk),
    .ap_rst               (1'b0),
    .ap_start             (1'b1),
    .ap_done              (done),
    .ap_idle              (idle),
    .ap_ready             (ready),
    .ap_return            (threshold_otsu),
    .im                   (rom_data),
    .threshold            (threshold_start)
); 

3.图像显示

zynq板卡上有HDMI输出接口,使用一根HDMI连接线,将板卡连接到显示器上,板卡发送数据即可显示。显示部分使用HDMI的IP核。

FPGA中HDMI例化代码如下:

HDMI_FPGA_ML_0 u_HDMI
(
    .PXLCLK_I           (pixclk),
    .PXLCLK_5X_I        (serclk),
    .LOCKED_I           (lock),
    .RST_N              (1'b1),
    .VGA_HS             (HS),
    .VGA_VS             (VS),
    .VGA_DE             (DE),
    .VGA_RGB            (RGB),
    .HDMI_CLK_P         (HDMI_CLK_P),
    .HDMI_CLK_N         (HDMI_CLK_N),
    .HDMI_D2_P          (HDMI_D2_P),
    .HDMI_D2_N          (HDMI_D2_N),
    .HDMI_D1_P          (HDMI_D1_P),
    .HDMI_D1_N          (HDMI_D1_N),
    .HDMI_D0_P          (HDMI_D0_P),
    .HDMI_D0_N          (HDMI_D0_N)
); 

FPGA中HDMI显示代码如下:

`timescale 1ns / 1ps
module hdmi_data_gen
	(
	input				pix_clk,
	input	   		    turn_mode,
	input  wire[7:0]     data,
	input  wire[11:0]    x_cnt,
	input  wire[11:0]    y_cnt,
	input  wire[7:0]    threshold,
	output      reg[3:0]led,
	output [7:0]	    VGA_R,
	output [7:0]		VGA_G,
	output [7:0]		VGA_B,
	output				VGA_HS,
	output				VGA_VS,
	output				VGA_DE
	);

parameter H_Total		=	1680; //e
parameter H_Sync		=	136;  //a
parameter H_Back		=	200; //b
parameter H_Active		=	1280;          //c
parameter H_Front		=	64;      //d
parameter H_Start		=	336;
parameter H_End	    =	1616;

parameter V_Total		=	828;
parameter V_Sync		=	3;
parameter V_Back		=	24;
parameter V_Active		=	800;
parameter V_Front		=	1;
parameter V_Start		=	27;
parameter V_End			=	827;

reg[11:0]	x_cnt;
reg[7:0]    pix_data;
reg[7:0]    pix_data_two;
reg[23:0]	color_bar;
always @(posedge pix_clk )
begin
    if(threshold>8'd160)
        led[0]<=1'b1;
end

always @(posedge pix_clk)
begin
    if(threshold<9'd180)
        led[1]<=1'b1;
end

reg [7:0]threshold_cal;
always@(posedge pix_clk)
begin
    if(threshold>threshold_cal)
        threshold_cal<=threshold;
 end
always@(posedge pix_clk)
begin
    if((x_cnt >= 740) && (x_cnt < 1240) && (y_cnt >= 164) && (y_cnt < 828 - 164)) begin
        pix_data <= data;
    end
    else 
        pix_data    <= 24'hffffff;
        
 end
always@(posedge pix_clk)
begin
    if((x_cnt >= 740) && (x_cnt < 1240) && (y_cnt >= 164) && (y_cnt < 828 - 164)) begin
        if(data>threshold_cal) begin
            pix_data_two<= 24'hffffff;
        end
        else begin
            pix_data_two<=2'b0;
        end
     end
    else
        pix_data_two <= 24'hffffff;
 end
reg[3:0]	dis_mode=0;
always @(posedge turn_mode)
    begin
      if(dis_mode<2'b11)
        dis_mode<= dis_mode + 1'b1;
      else
        dis_mode<=1'b0;
    end

reg[7:0]	VGA_R_reg;
reg[7:0]	VGA_G_reg;
reg[7:0]	VGA_B_reg;
always @(posedge pix_clk)
begin  
	if(1'b0) 
    begin 
		VGA_R_reg<=0; 
	    VGA_G_reg<=0;
	    VGA_B_reg<=0;		 
	end
    else
     case(dis_mode)
         4'd0:begin
			     VGA_R_reg<=0;            //LCD显示全黑
                 VGA_G_reg<=0;
                 VGA_B_reg<=0;
		       end
		 4'd1:begin
			     VGA_R_reg<=pix_data;                //LCD显示原图
                 VGA_G_reg<=pix_data;
                 VGA_B_reg<=pix_data;
		       end
		 4'd2:begin
			     VGA_R_reg<=pix_data_two;                //LCD显示结果图
                 VGA_G_reg<=pix_data_two;
                 VGA_B_reg<=pix_data_two;  
               end			  
		   default:begin
			        VGA_R_reg<=8'hff;                //LCD显示全白
                    VGA_G_reg<=8'hff;
                    VGA_B_reg<=8'hff;
			     end					  
         endcase
end





wire  hs_de = (x_cnt<H_Start)? 0:(x_cnt<=H_End)?1:0;
wire  vs_de = (y_cnt<V_Start)? 0:(y_cnt<=V_End)?1:0;
reg [7:0] grid_data;

assign VGA_HS = (x_cnt==1'b0)? 1:(x_cnt<H_Sync)?0:1;
assign VGA_VS = (y_cnt==1'b0)? 1:(y_cnt<V_Sync)?0:1;

assign VGA_DE	=	hs_de & vs_de;
assign VGA_R	=	(hs_de & vs_de)?VGA_R_reg:8'h0;
assign VGA_G	=	(hs_de & vs_de)?VGA_G_reg:8'h0;
assign VGA_B	=	(hs_de & vs_de)?VGA_B_reg:8'h0;
endmodule


3.完整FPGA工程代码:

OTSU阈值算法进行图像二值化

你可能感兴趣的:(fpga开发,算法,图像处理)