【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/78255669clc;
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) beginvsync_dly <= {vsync_dly[0],vsync};
end
//检测vsync下降沿
always @(posedge clk or posedge rst) beginif (rst == 1'b1) beginvsync_fall <= 1'b0;endelse if (vsync_dly == 2'b10) beginvsync_fall <= 1'b1;endelse beginvsync_fall <= 1'b0;//just one cycle clockend
end//Rsum,Gsum,Bsum求和 以及 清零
always @(posedge clk or posedge rst) beginif (rst == 1'b1) beginRsum <= 'd0;Gsum <= 'd0;Bsum <= 'd0;endelse if (vsync_fall == 1'b1) beginRsum <= 'd0;Gsum <= 'd0;Bsum <= 'd0;		endelse if(hsync == 1'b1)beginRsum <= 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) beginif (rst == 1'b1) beginRavg <= 'd0;Gavg <= 'd0;Bavg <= 'd0;endelse if (vsync_fall == 1'b1) beginRavg <= 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) beginif (rst == 1'b1) beginKavg <= 'd0;endelse if (Kavg_valid == 1'b1) begin//可以用这个信号来锁存KavgKavg <= 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) beginhsync_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) beginif (rst == 1'b1) beginred_new <= 'd0;endelse if (temp_red_new[26:8] > 'd255) beginred_new <= 'd255;endelse beginred_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) beginif (rst == 1'b1) begingreen_new <= 'd0;endelse if (temp_green_new[26:8] > 'd255) begingreen_new <= 'd255;endelse begingreen_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) beginif (rst == 1'b1) beginblue_new <= 'd0;endelse if (temp_blue_new[26:8] > 'd255) beginblue_new <= 'd255;endelse beginblue_new <= temp_blue_new[15:8];end
end//不是数据没有对齐的问题
always @(posedge clk) beginfra_hsy <= temp_fra_hsy;
end//fra_vsy,(可以随便一个)
assign fra_vsy = vsync_dly[1];endmodule

第三部分、实现结果

1、白平衡前后对比

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

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

2、总结

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/641791.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何查看Linux CPU占有率

目录 1、top 2、htop 3、vmstat 4、mpstat 5、iostat 查看嵌入式设备CPU占有率是评估系统资源使用情况的重要方式。 在Linux系统中&#xff0c;有多种方法可以查看CPU占有率&#xff0c;这里介绍几种常用的命令行工具。 1、top 这是最常用的命令之一&#xff0c;它提供了…

C语言之反汇编查看函数栈帧的创建与销毁

文章目录 一、 什么是函数栈帧&#xff1f;二、 理解函数栈帧能解决什么问题呢&#xff1f;三、 函数栈帧的创建和销毁解析3.1、什么是栈&#xff1f;3.2、认识相关寄存器和汇编指令3.2.1 相关寄存器3.2.2 相关汇编命令 3.3、 解析函数栈帧的创建和销毁3.3.1 预备知识3.3.2 代码…

AI对比:ChatGPT和文心一言的区别和差异

目录 一、ChatGPT和文心一言大模型的对比分析 1.1 二者训练的数据情况分析 1.2 训练大模型数据规模和参数对比 1.3 二者3.5版本大模型对比总结 二、ChatGPT和文心一言功能对比分析 2.1 二者产品提供的功能情况分析 2.2 测试一下各种功能的特性 2.2.1 文本创作能力 2.2…

婴儿洗衣机怎么选?热门品牌希亦、觉飞、由利详细测评

宝宝的衣物是不是要和大人的衣服分开洗呢&#xff1f;这是很多新手爸妈都会遇到的一个问题。有的人认为&#xff0c;宝宝的衣服要单独洗&#xff0c;以免被大人的衣服上的细菌污染&#xff1b;有的人认为&#xff0c;宝宝的衣服可以和大人的衣服一起洗&#xff0c;这样可以节省…

优先级队列(堆)

目录 1 概念 2 堆的概念 2.1小根堆 2.2大根堆 3堆的存储方式​​​​​​​ 4、堆的创建 4.1堆向下调整 5、时间复杂度 6、堆的插入&#xff08;向上调整&#xff09; 7、堆的删除 8、PriorityQueue的特性 9、堆排序 1 概念 我们知道的队列&#xff0c;队列是一…

leetcode---Z字形变换

题目&#xff1a; 将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 "PAYPALISHIRING" 行数为 3 时&#xff0c;排列如下&#xff1a;之后&#xff0c;你的输出需要从左往右逐行读取&#xff0c;产生…

redis高可用之主从部署

文章目录 前言1. 同步以及命令传播1.1 同步1.2 命令传播 2. 解决从服务器断线重连2.1 解决方案 3. PSYNC命令4. 复制步骤1:设置主服务器的地址和端口步骤2:建立套接字连接 ——其实就是建立TCP连接步骤3:发送PING命令步骤4:身份验证步骤5:发送端口信息步骤6:同步步骤7:命令传播…

鸿蒙5.0发布时间已定!何处寻得移动开发加速器?

直接在百度上搜索「鸿蒙5.0发布时间」&#xff0c;出来的结果&#xff0c;那一个比一个焦虑~~ 百度的AI基于综合内容判断得出&#xff0c;鸿蒙5.0的发布时间在2023-04-17 百度知道推的答案是202年年4月中 但不管几月&#xff0c;“鸿蒙元年”似乎都是确定的&#xff0c;就是…

Linux切换jdk版本

参考文献&#xff1a;Linux 多个JDK的版本 脚本切换 - C小海 - 博客园 (cnblogs.com)

ZYNQ-7020 集成了运行NI Linux Real‑Time的实时处理器,支持FPGA二次开发

模拟和数字I/O&#xff0c;667 MHz双核CPU&#xff0c;512 MB DRAM&#xff0c;512 MB存储容量&#xff0c;Zynq-7020 FPGA CompactRIO Single-Board控制器 sbRIO‑9637是一款嵌入式控制器&#xff0c;在单块印刷电路板(PCB)上集成了运行NI Linux Real‑Time的实时处理器、用户…

RK3568 移植Ubuntu

使用ubuntu-base构建根文件系统 1、到ubuntu官网获取 ubuntu-base-18.04.5-base-arm64.tar.gz Ubuntu Base 18.04.5 LTS (Bionic Beaver) 2、将获取的文件拷贝到ubuntu虚拟机,新建目录,并解压 mkdir ubuntu_rootfs sudo tar -xpf u

解密高压开关柜内温度感知神器——无线测温传感器

具长期电网运行数据表明&#xff0c;电网电气设备故障大多是由于大电流运行、设备老化、绝缘水平下降等原因导致设备在高温条件下运行&#xff0c;从而引发燃烧&#xff0c;爆炸等严重事故。因此准确的掌握电气设备温度是预防此类问题的关键。 开关柜无源无线测温传感器采用CT取…

virtualenv虚拟环境的安装使用教程

让我们先思考这样一种情景&#xff1a;我们用python来开发一个项目&#xff0c;那么这个项目肯定会依赖很多的第三方库&#xff0c;这些第三方的库通过pip安装到全局区当中&#xff0c;而对于不同的项目使用到的库可能都有所不同&#xff0c;但是这些项目的库都安装到全局区当中…

【JavaEE进阶】MyBatis⼊⻔

文章目录 &#x1f332;什么是MyBatis?&#x1f333;准备⼯作&#x1f6a9;创建⼯程&#x1f6a9;数据准备&#x1f6a9;配置数据库连接字符串&#x1f6a9; 在项⽬中,创建持久层接⼝UserInfoMapper &#x1f343;单元测试&#x1f6a9;使⽤Idea⾃动⽣成测试类 &#x1f340;打…

6 时间序列(不同位置的装置如何建模): GRU+Embedding

很多算法比赛经常会遇到不同的物体产生同含义的时间序列信息&#xff0c;比如不同位置的时间序列信息&#xff0c;风力发电、充电桩用电。经常会遇到该如此场景&#xff0c;对所有数据做统一处理喂给模型&#xff0c;模型很难学到区分信息&#xff0c;因此设计如果对不同位置的…

芯课堂 | SWM34S系列驱动TFT-LCD显示模组应用基本注意事项

1、确认硬件的连接、包括电源、地、RGB 数据线、DCLK\DE\HSYNC\VSYNC 等&#xff0c;显示模组有 DISP、RESET、CS、SCL、SDA 等。 2、确认各电压的正常&#xff0c;包括电源&#xff0c;部分有 IOVCC、VGL、VGH、VCOM 等电压 3、如果应用的 TFT-LCD 模组非演示例程中已适配调…

动态血糖监测市场调研:预计2029年将达到13亿美元

血糖监测即是对于血糖值的定期检查。实施血糖监测可以更好的掌控糖尿病患者的血糖变化&#xff0c;对生活规律&#xff0c;活动&#xff0c;运动&#xff0c;饮食以及合理用药都具有重要的指导意义&#xff0c;并可以帮助患者随时发现问题&#xff0c;及时到医院就医。 动态血糖…

LinkedList源码

LinkedList源码 总结 LinkedList数据结构采用链表&#xff0c;内部封装了Node类&#xff0c;set方法先让Node的pre指针指向之前的last节点&#xff0c;然后判断头节点知否为空&#xff0c;如果为空则让first指针指向该节点&#xff0c;不过不为空则让尾节点的next指针指向该节…

Linux:动静态库的概念制作和底层工作原理

文章目录 动静态库基础认知动静态库基本概念静态库的制作库的概念包的概念 静态库的使用第三方库小结 动态库的制作动态库的使用动态库如何找到内容&#xff1f;小结 动态库加载库和程序都要加载可执行程序的地址问题地址问题逻辑地址和平坦模式绝对编址和相对编址与位置无关码…

vue2(Vuex)、vue3(Pinia)、react(Redux)状态管理

vue2状态管理Vuex Vuex 是一个专为 Vue.js应用程序开发的状态管理模式。它使用集中式存储管理应用的所有组件的状态&#xff0c;以及规则保证状态只能按照规定的方式进行修改。 State&#xff08;状态&#xff09;:Vuex 使用单一状态树&#xff0c;即一个对象包含全部的应用层…