【ZYNQ入门】第十篇、基于FPGA的图像白平衡算法实现

目录

第一部分、关于白平衡的知识    

1、MATLAB 自动白平衡算法的实现

1.1、matlab代码

1.2、测试效果

1.3 测试源图

2、为什么摄像头采集的图像要做白平衡

3、自动白平衡算法总结

4、FPGA设计思路

4.1、实时白平衡的实现

4.2、计算流程优化思路  

第二部分、硬件实现

1、除法IP核的调用方法

2、乘法IP核的调用方法

3、verilog代码

第三部分、实现结果

1、白平衡前后对比

2、总结


第一部分、关于白平衡的知识    

1、MATLAB 自动白平衡算法的实现

        大家先测试下面这段自动白平衡MATLAB代码,代码来源于以下这篇博客我只不过加上了注释,更多细节请大家参考这篇博客图像白平衡原理及实现-CSDN博客    

1.1、matlab代码

%%白平衡与色温紧密相关,不同色温光源下图像会呈现不同程度的偏色
%%由于人眼独特的适应性,在不同光照条件下观看物体时不会出现偏色,而就没这么先进了
%%蓝色光色温高,红色光色温低
%CSDN:https://blog.csdn.net/helimin12345/article/details/78255669
 
clc;
clear all;
close all;

tic;%用来记录程序的使用时间,tic 程序 toc

% imgSrc = imread('green1.jpg');
imgSrc = imread('test2.png');
%定义一个与图像大小一样的三位变量
imgDst = imgSrc;
%将RGB三个通道的数据进行分离
imgR = imgSrc(:,:,1);%单个通道的数据默认 uint8(0~255)
imgG = imgSrc(:,:,2);
imgB = imgSrc(:,:,3);
%对RGB三个通道的像素求均值,相当于mean( mean( A ) ) 即对整一个矩阵求像素平均值
RAve = mean2(imgR);
GAve = mean2(imgG);
BAve = mean2(imgB);
aveGray = (RAve + GAve + BAve) / 3;%比较系数
%计算三个通道的增益系数
RCoef = aveGray / RAve;
GCoef = aveGray / GAve;
BCoef = aveGray / BAve;
%使用增益系数来调整原始图
%注意:其实这里应该是存在一个比较的过程(小于255,那就是当前值;大于255,那就等于255),只不过uint8超出了255自动溢出了
%巧妙的运用溢出,会减少很多计算量
RCorrection = RCoef * imgR;
GCorrection = GCoef * imgG;
BCorrection = BCoef * imgB;
%白平衡后的图像
imgDst(:,:,1) = RCorrection;
imgDst(:,:,2) = GCorrection;
imgDst(:,:,3) = BCorrection;
%显示两张图片
figure(1),imshow(imgSrc),title('original image');
figure(2),imshow(imgDst),title('white balanced image');

1.2、测试效果

        关于这两张测试照片,我也会放在文末。先看效果

1.3 测试源图

        我也是网上从别人博客保存的,找不到原博客了😂

2、为什么摄像头采集的图像要做白平衡

        人眼对白色很敏感, 在不同色温下,都能准确判断出白色。例如下面那张图,色温比较高的时候会偏蓝,色温比较低的的时候会偏红

        当摄像头在不同色温的光源下,采集的图像会出现不同程度的偏色,与人眼看到的颜色不一致,因此需要进行白平衡处理。(还有一点,就是我感觉CMOS摄像头就算在正常光源下,采集的图像也是偏绿的,不知道是不是因为选用的Bayer转RGB的算法太垃圾了,还是什么别的原因。关于Bayer转RGB的算法大家可以看这篇:基于FPGA的Bayer转RGB算法实现

        但是目前稍微好一点的摄像头模组,都包含了自动白平衡算法,都不需要做处理。

3、自动白平衡算法总结

        把上面的matlab代码仔细读几遍,读明白之后,总结出白平衡算法的步骤:

step1、分别对图像的R、G、B三通道的数据进行求和得到Rsum、Gsum、Bsum;

step2、获取图像的R、G、B三通道的平均值Rv,Gv,Bv;imag_width:当前图像的宽,

imag_high:当前图像的高度。

                        Rv  = Rsum /(imag_width*imag_high)

                        Gv  = Gsum /(imag_width*imag_high)

                        Bv  =  Bsum /(imag_width*imag_high)

step3、将求得的Rv、Gv、Bv 进行加和取平均值,得到Kv = (Rv + Gv + Bv) / 3;

step4、分别将R、G、B三通道的数据带入公式进行计算,得到新的值

                        R通道: Rnew = R * Kv / Rv; if(Rnew >255) Rnew = 255;

                        G通道:Gnew  = G * Kv / Gv;if(Gnew >255) Gnew = 255;

                        B通道: Bnew  = B * Kv / Bv; if(Bnew >255) Bnew  = 255;

step5、最后将计算后的图片显示出来,便是白平衡后的图像。

4、FPGA设计思路

4.1、实时白平衡的实现

        FPGA的摄像头采集的是实时的图像数据,每秒采集30帧,那么如何将FPGA获取的图像进行白平衡处理呢?

        解决办法:就是计算当前帧图像的Rv,Gv,Bv,Kv将计算的结果留给下一帧图像使用,下一帧的Rv,Gv,Bv,Kv计算结果又给下下一帧使用...一直循环,就实现了实时的图像白平衡处理

4.2、计算流程优化思路  

        正常情况下,进行除法可以调用除法IP核来解决。但是当除数很大且接近于2^N时,这时就可以用截位的方式来代替除法核。

        例如对于分辨率为1920*1080图片,Rv  = Rsum /(1920*1080),而1920*1080 = 2,073,600非常接近于2^21 = 2,097,152。

        如果我用2^21次幂来代替1920*1080,那么只需要去除Rsum的低21位即可,直接截位,都不需要移位。这这,太牛逼了!!!

计算误差方法:由于误差很小,而且对于图像处理也不需要那么高的精度,所以该方法可行。

(2^21 - (1920*1080)) / (1920*1080)

= (2,097,153 - 2,073,600) / 2,073,600

≈ 0.0113585 ≈ 1.14%

第二部分、硬件实现

1、除法IP核的调用方法

 第一步、搜索,输入divider generator

 第二步、配置第二个界面

 第三步、配置第三个界面

注意:这里的Latency配置为自动模式

第四步、整数有效位宽和余数有效位宽

注意:这里的位宽需要记住,方便后面截位数据

2、乘法IP核的调用方法

第一步、搜索multiplier

第二步、配置第二个界面

第三步、配置Output and control界面

注意:这里是几级流水,那么输出就有几个时钟周期的latency。这里选用系统推荐的流水级数,当然也可以自定手动修改流水级数,级数越多时序越好,延迟越多,因此实际开发要是情况而定。

3、verilog代码

// -----------------------------------------------------------------------------
// Copyright (c) 2014-2024 All rights reserved
// -----------------------------------------------------------------------------
// Author : BigFartPeach
// CSDN   : 大屁桃 
// E-mail : 2624507313@qq.com
// File   : white_balance_my.v
// Create : 2024-01-06 13:58:39
// -----------------------------------------------------------------------------  
module white_balance_my(
	input wire		clk,
	input wire		rst,
	input wire 		vsync,
	input wire 		hsync,
	input wire [7:0]red,
	input wire [7:0]green,
	input wire [7:0]blue,
	
	output wire		fra_vsy,
	output reg		fra_hsy,

	output wire[23:0]rgb_new
	);

//变量定义
/*累计这一帧,并计算上一帧的累加结果*/
reg [1:0]vsync_dly;//对vsync延迟两拍
reg 	 vsync_fall;//检测vsync下降沿

reg [28:0]Rsum,Gsum,Bsum;//求和
reg	[7:0]Ravg,Gavg,Bavg;//求均值,直接舍去低21位

wire[30:0]Ksum;//Rsum+Gsum+Bsum的和

wire[55:0]Temp_Kavg;//临时用来存储Kavg除法IP输出的结果
wire 	  Kavg_valid;//除法IP核输出的结果有效标志
reg [30:0]Kavg;//除法IP核输出的结果
///
/*计算当前帧*/
wire [18:0]temp_red,temp_green,temp_blue;//用来存储red*Kavg、green*Kavg、blue*Kavg的值
wire [31:0]temp_red_new,temp_green_new,temp_blue_new;//用来存储除法IP输出的值,temp_red/Ravg、temp_green/Gavg、temp_blue/Bavg
wire 	  temp_fra_hsy;//用来存储输出的red_new的valid,用作输出的fra_hsy

//凑成24bit输出
reg [7:0]red_new;
reg [7:0]green_new;
reg [7:0]blue_new;

assign rgb_new = {red_new,green_new,blue_new};//凑成24bit输出

//vsync_dly打拍
always @(posedge clk) begin
	vsync_dly <= {vsync_dly[0],vsync};
end
//检测vsync下降沿
always @(posedge clk or posedge rst) begin
	if (rst == 1'b1) begin
		vsync_fall <= 1'b0;
	end
	else if (vsync_dly == 2'b10) begin
		vsync_fall <= 1'b1;
	end
	else begin
		vsync_fall <= 1'b0;//just one cycle clock
	end
end

//Rsum,Gsum,Bsum求和 以及 清零
always @(posedge clk or posedge rst) begin
	if (rst == 1'b1) begin
		Rsum <= 'd0;
		Gsum <= 'd0;
		Bsum <= 'd0;
	end
	else if (vsync_fall == 1'b1) begin
		Rsum <= 'd0;
		Gsum <= 'd0;
		Bsum <= 'd0;		
	end
	else if(hsync == 1'b1)begin
		Rsum <= Rsum + red;
		Gsum <= Gsum + green;
		Bsum <= Bsum + blue;
	end
end
//Ksum求和
assign Ksum = Rsum + Gsum + Bsum;//如果用alway,相较于Rsum,Gsum,Bsum会晚一个时钟周期

//Ravg,Gavg,Bavg求均值,直接舍去Rsum,Gsum,Bsum的低21位
always @(posedge clk or posedge rst) begin
	if (rst == 1'b1) begin
		Ravg <= 'd0;
		Gavg <= 'd0;
		Bavg <= 'd0;
	end
	else if (vsync_fall == 1'b1) begin
		Ravg <= Rsum[28:21];
		Gavg <= Gsum[28:21];
		Bavg <= Bsum[28:21];
	end
end

//Kavg的除法IP,33个latency
div_gen_Ksum_div_1920x1080x3 div_gen_Kavg (
  .aclk(clk),                                      // input wire aclk
  .s_axis_divisor_tvalid(vsync_fall),    // input wire s_axis_divisor_tvalid
  .s_axis_divisor_tdata(23'd6220800),      // input wire [23 : 0] s_axis_divisor_tdata
  .s_axis_dividend_tvalid(vsync_fall),  // input wire s_axis_dividend_tvalid
  .s_axis_dividend_tdata(Ksum),    // input wire [31 : 0] s_axis_dividend_tdata
  .m_axis_dout_tvalid(Kavg_valid),          // output wire m_axis_dout_tvalid
  .m_axis_dout_tdata(Temp_Kavg)            // output wire [55 : 0] m_axis_dout_tdata
);

//锁存Kavg
always @(posedge clk or posedge rst) begin
	if (rst == 1'b1) begin
		Kavg <= 'd0;
	end
	else if (Kavg_valid == 1'b1) begin//可以用这个信号来锁存Kavg
		Kavg <= Temp_Kavg[54:24];//截取输出的整数位
	end
end



///
//进行结果计算
//red_new的计算,先算乘法,再算除法 red_new = (red*Kavg)/Ravg
//3级流水线,3个latency!!!
mult_gen_8xKavg redxKavg (
  .CLK(clk),  // input wire CLK
  .A(red),      // input wire [7 : 0] A
  .B(Kavg[10:0]),      // input wire [10 : 0] B(这里取11位,主要是根据老师推荐的,按道理是要去大一点,但是大多少位并没有限制)
  .P(temp_red)      // output wire [18 : 0] P
);

/*************************************************/
/******************解决白线问题的方法**************/
/*************************************************/
//给hsync进行打拍操作。delay 3个 latency,和上面乘法核的输出对齐
reg [2:0]hsync_dly;
always @(posedge clk) begin
	hsync_dly <= {hsync_dly[1:0],hsync};
end
/*************************************************/
/*************************************************/

div_gen_0 temp_red_div_Ravg (
  .aclk(clk),                                      // input wire aclk
  .s_axis_divisor_tvalid(hsync_dly[2]),    // input wire s_axis_divisor_tvalid
  .s_axis_divisor_tdata(Ravg),      // input wire [7 : 0] s_axis_divisor_tdata
  .s_axis_dividend_tvalid(hsync_dly[2]),  // input wire s_axis_dividend_tvalid
  .s_axis_dividend_tdata(temp_red),    // input wire [23 : 0] s_axis_dividend_tdata
  .m_axis_dout_tvalid(temp_fra_hsy),          // output wire m_axis_dout_tvalid
  .m_axis_dout_tdata(temp_red_new)            // output wire [31 : 0] m_axis_dout_tdata   [26:8]
);
//red进行判断
always @(posedge clk or posedge rst) begin
	if (rst == 1'b1) begin
		red_new <= 'd0;
	end
	else if (temp_red_new[26:8] > 'd255) begin
		red_new <= 'd255;
	end
	else begin
		red_new <= temp_red_new[15:8];//有效值肯定在这个八位区间
	end
end

//green_new
mult_gen_8xKavg greenxKavg (
  .CLK(clk),  // input wire CLK
  .A(green),      // input wire [7 : 0] A
  .B(Kavg[10:0]),      // input wire [10 : 0] B
  .P(temp_green)      // output wire [18 : 0] P
);

div_gen_0 temp_green_div_Gavg (
  .aclk(clk),                                      // input wire aclk
  .s_axis_divisor_tvalid(hsync_dly[2]),    // input wire s_axis_divisor_tvalid
  .s_axis_divisor_tdata(Gavg),      // input wire [7 : 0] s_axis_divisor_tdata
  .s_axis_dividend_tvalid(hsync_dly[2]),  // input wire s_axis_dividend_tvalid
  .s_axis_dividend_tdata(temp_green),    // input wire [23 : 0] s_axis_dividend_tdata
  .m_axis_dout_tvalid(),          // output wire m_axis_dout_tvalid
  .m_axis_dout_tdata(temp_green_new)            // output wire [31 : 0] m_axis_dout_tdata
);

//进行判断
always @(posedge clk or posedge rst) begin
	if (rst == 1'b1) begin
		green_new <= 'd0;
	end
	else if (temp_green_new[26:8] > 'd255) begin
		green_new <= 'd255;
	end
	else begin
		green_new <= temp_green_new[15:8];
	end
end

//blue_new
mult_gen_8xKavg bluexKavg (
  .CLK(clk),  // input wire CLK
  .A(blue),      // input wire [7 : 0] A
  .B(Kavg[10:0]),      // input wire [10 : 0] B
  .P(temp_blue)      // output wire [18 : 0] P
);

div_gen_0 temp_blue_div_Bavg (
  .aclk(clk),                                      // input wire aclk
  .s_axis_divisor_tvalid(hsync_dly[2]),    // input wire s_axis_divisor_tvalid
  .s_axis_divisor_tdata(Bavg),      // input wire [7 : 0] s_axis_divisor_tdata
  .s_axis_dividend_tvalid(hsync_dly[2]),  // input wire s_axis_dividend_tvalid
  .s_axis_dividend_tdata(temp_blue),    // input wire [23 : 0] s_axis_dividend_tdata
  .m_axis_dout_tvalid(),          // output wire m_axis_dout_tvalid
  .m_axis_dout_tdata(temp_blue_new)            // output wire [31 : 0] m_axis_dout_tdata
);

always @(posedge clk or posedge rst) begin
	if (rst == 1'b1) begin
		blue_new <= 'd0;
	end
	else if (temp_blue_new[26:8] > 'd255) begin
		blue_new <= 'd255;
	end
	else begin
		blue_new <= temp_blue_new[15:8];
	end
end

//不是数据没有对齐的问题
always @(posedge clk) begin
	fra_hsy <= temp_fra_hsy;
end

//fra_vsy,(可以随便一个)
assign fra_vsy = vsync_dly[1];


endmodule

第三部分、实现结果

1、白平衡前后对比

        没有白平衡之前,CMOS采集到的图像偏绿,白平衡之后效果就好多了👍👍👍

CMOS摄像头图像白平衡之前和白平衡之后的效果

2、总结

        这篇主要是总结了一下白平衡算法的原理的实现方法,我上面的Verilog 代码,大家只需要看明白就可以移植了。

        QQ交流群聊号码1020775171,有疑问的小伙伴可以加入哦🤗🤗🤗

        本专栏有很多我个人总结的比较好的文章,希望对你开发有帮助:FPGA的学习之旅_大屁桃的博客-CSDN博客

  • 29
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
另一种方法,就是根据大量的数提炼出个 另一种方法,就是根据大量的数提炼出个 另一种方法,就是根据大量的数提炼出个 均值,并把它定义 均值,并把它定义 均值,并把它定义 均值,并把它定义 为灰色。这种方法提炼的值可能因数据库使用不 同而有所为灰色。这种方法提炼的值可能因数据库使用不 同而有所为灰色。这种方法提炼的值可能因数据库使用不 同而有所为灰色。这种方法提炼的值可能因数据库使用不 同而有所为灰色。这种方法提炼的值可能因数据库使用不 同而有所为灰色。这种方法提炼的值可能因数据库使用不 同而有所为灰色。这种方法提炼的值可能因数据库使用不 同而有所为灰色。这种方法提炼的值可能因数据库使用不 同而有所为灰色。这种方法提炼的值可能因数据库使用不 同而有所同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 同。最终提炼的灰色也能仅适用于原始数据库,而对未包 括的图片适用 度就会比较差一些。确定下来灰色表达形式可以括的图片适用 度就会比较差一些。确定下来灰色表达形式可以括的图片适用 度就会比较差一些。确定下来灰色表达形式可以
《基于zynqFPGA基础入门.pdf》是一本介绍使用Zynq FPGA进行基础入门的教材或指南。Zynq FPGA是一种将ARM处理器和FPGA集成在一起的芯片,结合了处理器的软件优势和FPGA的硬件灵活性。 该教材可能从以下几个方面进行介绍: 首先,教材可能会介绍Zynq FPGA的基本概念和架构。它会解释处理器和FPGA之间的互联关系以及他们各自的功能和优势。读者将了解如何在Zynq FPGA上进行软件和硬件开发,以及如何使用FPGA进行性能加速和工作负载分配。 其次,教材可能会介绍基本的FPGA和硬件描述语言(HDL)知识。读者将学习FPGA的基本概念,如逻辑门、寄存器和时钟等。同时,他们将学习到如何使用HDL(如VHDL或Verilog)来描述和设计硬件电路。这将为读者理解如何使用Zynq FPGA进行自定义硬件设计奠定基础。 接着,教材可能会介绍如何编写和调试FPGA的软件驱动程序。Zynq FPGA具有嵌入式ARM处理器,因此读者将学习如何使用C/C++等编程语言编写驱动程序,以便与外部设备进行通信。他们还将学习调试技巧,以便能够快速解决可能出现的问题。 最后,教材可能会提供一些实际的项目示例,让读者能够应用所学知识。这些示例项目可能包括使用FPGA实现数字信号处理(DSP)、图像处理、嵌入式系统等。通过实践,读者将能够加深对Zynq FPGA的理解,并掌握如何在实际应用中进行开发和调试。 总而言之,《基于zynqFPGA基础入门.pdf》是一本为初学者打开Zynq FPGA的大门的教材。通过学习该教材,读者将能够了解Zynq FPGA的基本概念、硬件描述语言和软件驱动程序编写,从而为将来的FPGA开发奠定良好的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大屁桃

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

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

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

打赏作者

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

抵扣说明:

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

余额充值