基于FPGA的暗通道先验去雾算法
前言
暗通道先验的去雾算法在去雾算法中是比较经典的。但是一般都是用Matlab实现,用veirlog语言实现的较少。在参考何恺明的论文和大磊的教学视频后,用VIP架构写了一个暗通道去雾算法。
原理
暗通道算法解释:
在大多数无雾图像中,对于每一个局部区域,至少有一种颜色通道(通常是 RGB 中的某一个)存在一个像素点,其值非常小,接近于零。这个局部区域中颜色最暗的那个颜色分量的最小值所形成的图像通道就被称为暗通道。
在晴朗天气下,当光线照射到物体表面时,物体会吸收一部分光线,只反射特定波长的光线进入人眼,使得我们能看到物体的真实颜色。然而,在雾天环境中,空气中的水汽和微粒会散射光线,导致物体反射的光线在传播过程中被散射和吸收,使得图像变得模糊且颜色偏移。而暗通道先验正是抓住无雾图像中局部区域存在颜色极暗点这一特性,以此来区分无雾区域和有雾区域。
Veirlog实现流程:
总体分为暗通道模块、大气光强模块、透射率模块和还原模块四部分。
暗通道模块:
暗通道模块中滤波算法对何恺明算法进行了改动,将15x15模块改成了3x3模块,缺点是图像会变得过饱和。
大气光强模块:
对算法做了简化。用有雾图片中最大像素的点作为大气光强。
透射率模块:
t
(
x
)
=
1
−
ω
⋅
(
min
p
∈
Ω
(
x
)
(
min
c
∈
{
R
,
G
,
B
}
I
c
(
p
)
)
)
t(x) = 1 - \omega \cdot \left( \min_{p \in \Omega(x)} \left( \min_{c \in \{R, G, B\}} I^c(p) \right) \right)
t(x)=1−ω⋅(p∈Ω(x)min(c∈{R,G,B}minIc(p)))
上式是透射率的计算公式,其中:
t(x)是折射率
ω 是一个介于 0 和 1 之间的常数,通常取 0.95 或 0.99,用于控制去雾的程度
括号里面的是暗通道的最小像素值
恢复图片模块:
I
(
x
)
=
J
(
x
)
⋅
t
(
x
)
+
A
⋅
(
1
−
t
(
x
)
)
I(x) = J(x) \cdot t(x) + A \cdot (1 - t(x))
I(x)=J(x)⋅t(x)+A⋅(1−t(x))
其中:
I (x) 表示有雾图像
J (x) 是无雾图像
A 是大气光强度
t(x)是透射率
代码结构及重要代码
代码结构:
\\代码结构
//暗通道模块
dark_channel();
//计算大气光强
calculate_A();
//计算透射率
tx_get();
//时序对齐(对齐有雾彩色图像和处理后的暗通道图像)
time_alignment();
//还原图像
haze_removal_cal();
获取三个通道中的暗通道:
\\获取三个通道中最暗的通道
//规定高位为前,低位为后
//(1)当前数大于本数据之前输入的数据时记为1,小于或等于记为0;
//(2)当前数大于等于本数据之后输入的数据记为1,小于记为0;
//(3)与自身比较结果为0;
//(4)累加和即为排序结果。
assign cmp_result_r[0] = 1'b0;
assign cmp_result_r[1] = (pre_img_data[23:16] >= pre_img_data[15:8]) ? 1'b1 : 1'b0;
assign cmp_result_r[2] = (pre_img_data[23:16] >= pre_img_data[7:0]) ? 1'b1 : 1'b0;
assign cmp_result_b[0] = (pre_img_data[15:8] > pre_img_data[23:16]) ? 1'b1 : 1'b0;
assign cmp_result_b[1] = 1'b0;
assign cmp_result_b[2] = (pre_img_data[15:8] >= pre_img_data[7:0]) ? 1'b1 : 1'b0;
assign cmp_result_g[0] = (pre_img_data[7:0] > pre_img_data[23:16]) ? 1'b1 : 1'b0;
assign cmp_result_g[1] = (pre_img_data[7:0] > pre_img_data[15:8]) ? 1'b1 : 1'b0;
assign cmp_result_g[2] = 1'b0;
3*3滤波模块:
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
end
else if(read_frame_href) begin
if(read_frame_clken) begin
{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p12, matrix_p13, row1_data};
{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data};
{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data};
end
else begin
{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};
{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};
{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};
end
end
else begin
{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
end
end
计算光强值:
//最大像素
assign pixel_max_of_rgb_1st = pixel_of_r > pixel_of_g ? pixel_of_r : pixel_of_g;
assign pixel_max_of_rgb_2st = pixel_of_b > pixel_max_of_rgb_1st ? pixel_of_b : pixel_max_of_rgb_1st;
//最大像素值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
A_value <= 0;
end
else if(pre_frame_href & pre_frame_clken) begin
A_value <= A_value > pixel_max_of_rgb_2st ? A_value : pixel_max_of_rgb_2st;
end
end
计算透射率:
parameter modification_value = 8'd230; //modification_value=0.95(w设置为0.95,按论文说1也可以)*2^8,
reg [15 : 0] modify_A ;
reg pre_frame_vsync_d1 ;
reg pre_frame_href_d1 ;
reg pre_frame_clken_d1 ;
reg [7 : 0] A_value_d1 ;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
modify_A <= 0;
end
else begin
modify_A <= (pre_img << 8) - (pre_img<<4) - (pre_img << 3) - (pre_img << 1); //pre_img * modification_value(256-1-4-8)(256-16-8-2)
end
end
图像时序对齐:
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
for(i = 0; i< src_delay; i = i + 1)begin
pre_src_frame_vsync_d [ i ] <= 0;
pre_src_frame_href_d [ i ] <= 0;
pre_src_frame_clken_d [ i ] <= 0;
pre_img_d [ i ] <= 0;
end
end
else begin
pre_src_frame_vsync_d [ 0 ] <= pre_src_frame_vsync ;
pre_src_frame_href_d [ 0 ] <= pre_src_frame_href ;
pre_src_frame_clken_d [ 0 ] <= pre_src_frame_clken ;
pre_img_d [ 0 ] <= pre_img ;
for(i = 1; i< src_delay; i = i + 1)begin
pre_src_frame_vsync_d [ i ] <= pre_src_frame_vsync_d [ i - 1 ] ;
pre_src_frame_href_d [ i ] <= pre_src_frame_href_d [ i - 1 ] ;
pre_src_frame_clken_d [ i ] <= pre_src_frame_clken_d [ i - 1 ] ;
pre_img_d [ i ] <= pre_img_d [ i - 1 ] ;
end
end
end
恢复无雾图像:
//理论最小折射率
parameter tx_min = 8'd26;//min_value of A, 0.1 * 2*8
wire [7 : 0] tx_value ;
assign tx_value = pre_tx_img < tx_min ? tx_min : pre_tx_img;
reg signed [16 : 0] value_tem_r ;
reg signed [16 : 0] value_tem_g ;
reg signed [16 : 0] value_tem_b ;
reg [7 : 0] pre_A_d1 ;
reg [7 : 0] tx_value_d1 ;
//打一拍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
pre_A_d1 <= 0 ;
tx_value_d1 <= 0 ;
end
else begin
pre_A_d1 <= pre_A ;
tx_value_d1 <= tx_value ;
end
end
//后面计算 也是先放大256倍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
value_tem_r <= 0;
value_tem_g <= 0;
value_tem_b <= 0;
end
else begin
value_tem_r <= (( pre_img[23 : 16] - pre_A ) <<< 8 ) + pre_A * tx_value;
value_tem_g <= (( pre_img[15 : 8] - pre_A ) <<< 8 ) + pre_A * tx_value;
value_tem_b <= (( pre_img[ 7 : 0] - pre_A ) <<< 8 ) + pre_A * tx_value;
end
end
//打拍定义
reg signed [10 : 0] post_img_r;
reg signed [10 : 0] post_img_g;
reg signed [10 : 0] post_img_b;
reg pre_tx_frame_vsync_d1 ;
reg pre_tx_frame_href_d1 ;
reg pre_tx_frame_clken_d1 ;
reg pre_tx_frame_vsync_d2 ;
reg pre_tx_frame_href_d2 ;
reg pre_tx_frame_clken_d2 ;
//公式计算最后一步
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
post_img_r <= 0;
post_img_g <= 0;
post_img_b <= 0;
end
else begin
post_img_r <= value_tem_r / tx_value_d1;
post_img_g <= value_tem_g / tx_value_d1;
post_img_b <= value_tem_b / tx_value_d1;
end
end
//两级流水线 两个时间延迟
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
pre_tx_frame_vsync_d1 <= 0 ;
pre_tx_frame_href_d1 <= 0 ;
pre_tx_frame_clken_d1 <= 0 ;
end
else begin
pre_tx_frame_vsync_d1 <= pre_tx_frame_vsync ;
pre_tx_frame_href_d1 <= pre_tx_frame_href ;
pre_tx_frame_clken_d1 <= pre_tx_frame_clken ;
pre_tx_frame_vsync_d2 <= pre_tx_frame_vsync_d1 ;
pre_tx_frame_href_d2 <= pre_tx_frame_href_d1 ;
pre_tx_frame_clken_d2 <= pre_tx_frame_clken_d1 ;
end
end
//逻辑输出
assign post_img = {post_img_r[7 : 0],post_img_g[7 : 0],post_img_b[7 : 0]} ;
assign post_frame_vsync = pre_tx_frame_vsync_d2 ;
assign post_frame_href = pre_tx_frame_href_d2 ;
assign post_frame_clken = pre_tx_frame_clken_d2
门级网表电路
仿真代码
Modelsim仿真图片输出:
1、仿真OV7670摄像头时序:
module sim_cmos#
(
parameter PIC_PATH = "C:\\Users\\saoge\\Desktop\\FPGA\\FPGA3\\defog\\PIC\\ceshi1.bmp"
, parameter IMG_HDISP = 640
, parameter IMG_VDISP = 480
)(
input clk
, input rst_n
, output CMOS_VSYNC
, output CMOS_HREF
, output CMOS_CLKEN
, output [23:0] CMOS_DATA
, output [10:0] X_POS
, output [10:0] Y_POS
);
integer iBmpFileId;
integer oTxtFileId;
integer iIndex = 0;
integer iCode;
integer iBmpWidth; //输入BMP 宽度
integer iBmpHight; //输入BMP 高度
integer iBmpSize; //输入BMP 字节数
integer iDataStartIndex; //输入BMP 像素数据偏移量
localparam BMP_SIZE = 54 + IMG_HDISP * IMG_VDISP * 3 - 1; //输出BMP 字节数
reg [ 7:0] rBmpData [0:BMP_SIZE];
integer i,j;
2、处理后的图片保存
initial begin
for(iIndex = 0; iIndex < 54; iIndex = iIndex + 1) begin
BmpHead[iIndex] = 0;
end
#2
{BmpHead[1],BmpHead[0]} = {8'h4D,8'h42};
{BmpHead[5],BmpHead[4],BmpHead[3],BmpHead[2]} = BMP_SIZE + 1;//File Size (Bytes)
BmpHead[10] = 8'd54;//Bitmap Data Offset
BmpHead[14] = 8'h28;//Bitmap Header Size
{BmpHead[21],BmpHead[20],BmpHead[19],BmpHead[18]} = IMG_HDISP;//Width
{BmpHead[25],BmpHead[24],BmpHead[23],BmpHead[22]} = IMG_VDISP;//Height
BmpHead[26] = 8'd1;//Number of Color Planes
BmpHead[28] = 8'd24;//Bits per Pixel
end
//---------------------------------------------
//write the data to the output bmp file
initial begin
//---------------------------------------------
//waiting for the start frame
wait(frame_cnt == START_FRAME);
iBmpFileId = $fopen(PIC_PATH,"wb+");
for (iIndex = 0; iIndex < BMP_SIZE + 1; iIndex = iIndex + 1) begin
if(iIndex < 54) begin
Vip_BmpData[iIndex] = BmpHead[iIndex];
end
else begin
Vip_BmpData[iIndex] = vip_pixel_data[iIndex-54];
end
end
for (iIndex = 0; iIndex < BMP_SIZE + 1; iIndex = iIndex + 1) begin
$fwrite(iBmpFileId,"%c",Vip_BmpData[iIndex]);
end
$fclose(iBmpFileId);
$display("The picture is saved in %s",PIC_PATH);
$stop;
end
3、整体去雾算法仿真
`timescale 1ns / 1ps
module haze_removal_tb();
`define Modelsim_Sim
// `define Vivado_Sim
//--------------------------------------------------------------------------------
`ifdef Modelsim_Sim
localparam PIC_INPUT_PATH = "C:\\Users\\saoge\\Desktop\\FPGA\\FPGA3\\defog\\PIC\\ceshi1.bmp" ;
localparam PIC_OUTPUT_PATH = "C:\\Users\\saoge\\Desktop\\FPGA\\FPGA3\\defog\\PIC\\outcom2.bmp" ;
`endif
//--------------------------------------------------------------------------------
`ifdef Vivado_Sim
localparam PIC_INPUT_PATH = "../../../../../PIC/duck.bmp" ;
localparam PIC_OUTPUT_PATH = "../../../../../PIC/outcom.bmp" ;
`endif
localparam PIC_WIDTH = 640 ;
localparam PIC_HEIGHT = 480 ;
reg cmos_clk = 0;
reg cmos_rst_n = 0;
wire cmos_vsync ;
wire cmos_href ;
wire cmos_clken ;
wire [23:0] cmos_data ;
wire haze_removal_vsync ;
wire haze_removal_hsync ;
wire haze_removal_de ;
wire [23:0] haze_removal_data ;
parameter cmos0_period = 6;
always#(cmos0_period/2) cmos_clk = ~cmos_clk;
initial #(20*cmos0_period) cmos_rst_n = 1;
//--------------------------------------------------
//Camera Simulation
sim_cmos #(
.PIC_PATH (PIC_INPUT_PATH )
, .IMG_HDISP (PIC_WIDTH )
, .IMG_VDISP (PIC_HEIGHT )
)u_sim_cmos0(
.clk (cmos_clk )
, .rst_n (cmos_rst_n )
, .CMOS_VSYNC (cmos_vsync )
, .CMOS_HREF (cmos_href )
, .CMOS_CLKEN (cmos_clken )
, .CMOS_DATA (cmos_data )
, .X_POS ()
, .Y_POS ()
);
//--------------------------------------------------
//Image Processing
haze_removal_top #(
.Y_ENHANCE_ENABLE (0 )
, .PIC_WIDTH (PIC_WIDTH )
)u_haze_removal_top(
.clk (cmos_clk )
, .rst_n (cmos_rst_n )
//处理前数据
, .pre_frame_vsync (cmos_vsync )
, .pre_frame_href (cmos_href )
, .pre_frame_clken (cmos_clken )
, .pre_img (cmos_data )
//处理后的数据
, .post_frame_vsync (haze_removal_vsync )
, .post_frame_href (haze_removal_hsync )
, .post_frame_clken (haze_removal_de )
, .post_img (haze_removal_data )
);
//--------------------------------------------------
//Video saving
video_to_pic #(
.PIC_PATH (PIC_OUTPUT_PATH )
, .START_FRAME (2 )
, .IMG_HDISP (PIC_WIDTH )
, .IMG_VDISP (PIC_HEIGHT )
)u_video_to_pic0(
.clk (cmos_clk )
, .rst_n (cmos_rst_n )
, .video_vsync (haze_removal_vsync )
, .video_hsync (haze_removal_hsync )
, .video_de (haze_removal_de )
, .video_data (haze_removal_data )
);
结果
可能是设计原因吧,这个算法其实不适用于要求较高的去雾场所,对于全局去雾效果更差: