基于FPGA的暗通道先验去雾算法

基于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(1t(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      )
);

结果

可能是设计原因吧,这个算法其实不适用于要求较高的去雾场所,对于全局去雾效果更差:
在这里插入图片描述

参考

大磊的FPGA去雾算法视频
何恺明大佬的论文

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyCmt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值