这里是引用
近年来,青岛附近海域的藻华现象越来越严重,针对这一问题,我们暑期实践选择了对藻华图像的处理,利用FPGA识别出图像中的浒苔。
我们选用的板卡是xilinx中的ZYNQ-z7-20,软件需要使用VIVADO,同时使用vitis HLS将阈值计算部分的C代码转为Verilog代码。VIVADO和HLS的安装步骤网上有很详细的步骤,下面是我安装时参考的博客。
参考博客–最详细的Vivado安装教程
我们选择将图片数据由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
根据图片中浒苔的灰度值更高这一特点,我们选择利用阈值算法对导入的数据进行二值化,将图片中的浒苔识别出来。由于某些图片中存在大气干扰、浒苔数量过多、过少等区别,一个固定的阈值并不能满足所有数据的需要,识别效果很差。因此我们在对图像数据进行预处理后,利用OTSU这一阈值算法进行阈值计算。
OTSU部分我们先用Matlab仿真,无误后利用Matlab中的app将Matlab代码转为C代码,之后利用Vitis HLS将C代码转为Verilog代码,封装成一个IP核导入到工程中进行使用。
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
转化后代码如下:
/*
* 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]
*/
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)
);
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
OTSU阈值算法进行图像二值化