马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
x
Sensor Demoasic (CFA)IP仿真实例 AT7_Xilinx开发板(USB3.0+LVDS)资料共享 腾讯链接:https://share.weiyun.com/5GQyKKc 百度网盘链接:https://pan.baidu.com/s/1M7PLzRs-yMJv7NFJE8GVAw 提取码:qr0t 1 Vivado 19.1安装由于Vivado 19.1以后不再支持ColorFilter Array Interpolation IP,转而由Sensor Demosaic IP替代(实际实现的功能相当)。由于我们前面的实验都是基于Vivado 16.2版本的,因此在开始本实例之前,请先安装如下步骤安装好Vivado 19.1。 Windows 64bit操作系统,可以直接找到at7_img_ex11/tool文件夹下的安装程序Xilinx_Vivado_SDK_Web_2019.1_0524_1430_Win64.exe,双击进行Web版本Vivado2019.1安装。注意安装过程必须联网,该程序运行后边下载边安装(安装硬盘需要20G+的空间)。 2 科普CFA(Color Filter Array,色彩滤波阵列) CFA(Color Filter Array,色彩滤波阵列)也就是我们常说的CMOS色彩滤镜,应该说是一个挺重要,厂商在宣传的时候也会偶尔提及一下的东西。但是对于这个东西如何起作用,不同的排列又有什么样的优缺点,可能很多人就不太清楚了。今天我们就来讨论一下这个问题。 本次讨论基于相机中用到的几种传感器CFA排列方式,电影机当中用到的Q67之类,更多的是一种针对性设计(Q67其实就是旋转45度的拜耳阵列,菱形像素排列更紧凑,布线长度可以短,刷起速度来更容易,可以认为是电影机的针对性架构,用在相机上其实意义不大)。 基础模型——采样像素与输出像素 在之前我们简单提过采样像素与输出像素的概念,图像传感器的空间采样就是把连续的空间图像信号转化为离散的一个一个像素,而像素密度代表的就是空间采样频率。空间采样频率越高,我们便可以获取更多的原始图像高频细节,从而提升整个画面的细节表现。 当然,如果你看图的方式是看100%,在同样的显示器尺寸和分辨率下,图像表现还会受到输出像素的限制。同样的采样像素下,输出像素越高,则图像的100%查看细节表现越差。 其实采样像素和输出像素不同的设计在摄影当中可能不多,但是在视频当中很常见——因为视频的输出像素都是固定规格(例如4k就是829万),而采样像素可以比较灵活——例如索尼A6300,在视频拍摄模式下就是2000万像素采样,800万像素输出。 不过对于不同的CFA排列方式来说,其实际的采样和输出像素差别也比较大——尤其是CFA是为了色彩输出而设计,在平面上要放置3种颜色的滤色片,每一种颜色必然都存在空间欠采样的现象。至于影响如何,我们接下来对每一种CFA来进行一下简单分析。 最简单的——无色彩滤镜 只能输出黑白图像,最典型的比如徕卡的法师机Leica M Monochrome~
无色彩滤镜的情形分析起来也是相当简单,没有CFA造成的采样频率损失,采样像素就等于输出像素。缺点就是这种结构只能拍摄黑白图片,无法显示色彩。 最常用的——拜耳阵列 拜耳阵列一般被俗称为“马赛克传感器”,因为这种排列方式看起来的确有点像花花绿绿的马赛克晶格:
很明显可以看到拜耳阵列是由一行RGRGRG……和一行BGBGBG……交错排列而成,每一个像素点只能读取单独的颜色信息。其中绿色像素的采样频率是输出像素的1/2,红、蓝色像素的采样频率是输出像素的1/4,故有拜耳阵列传感器的分辨率是由绿色像素决定的这一说法。 拜耳阵列传感器采样生成的图像要输出我们常见的全色彩图像必须经过反马赛克运算——但这跟我们平时俗称的名字“猜色”的字面意义不同,拜耳阵列的颜色并不是猜的,而是每个2×2方块经过9次矩阵运算计算出来的,也就是说不存在猜这回事,每个像素的颜色其实是一个确定值(矩阵运算是线性运算,这并不是一个混沌系统)。但没有争议的是拜耳阵列确实存在欠采样问题,这也使得它会出现摩尔纹和伪色(摩尔纹出现的原因就是输入信号的最高频率成分超过了传感器的奈奎斯特极限,也就是说传感器的高频采样能力存在一些不足),100%查看时的画质也不是特别理想。 但是,看一个结构或者说架构是否强势,一个很重要的东西其实是成熟度与可扩增性——有时候一些暴力美学解决起问题来反而十分优雅。 拜耳阵列就是这么一个典型,简单的结构与成熟的工艺让它堆起像素来十分容易,只要底下的光电管能跟得上,分分钟3000万、4000万、5000万……接下来感觉应该就得上亿了(135画幅)。虽然结构本身存在欠采样问题,但毕竟架不住拥兵十万坐粮成山…理论上来说,当拜耳阵列传感器的实际像素数超越无CFA,或者X3这种传感器的输出像素2.8倍的时候,在输出同样大小的图片时便可以获取超越全色传感器的分辨率和100%查看画质。 3 CFA插值运算CFA虽然解决了像素问题,却带来了新的色彩问题。在CMOS/CCD Image Sensor采集过来的Bayer Raw数据,每个像素只提供一个色彩的颜色数据(Red、Green或Blue)。但是,最终显示或还原每个像素的色彩信息,却是每个像素都需要具备R、G、B三色数据。怎么解决这个问题?没错,色彩插值!在CFA发明之前,前人也的确是通过每个像素分别摆放R、G、B三个滤光片来获得每个像素的R、G、B数据。但是,聪明的Kodak科学家Bryce Bayer发现通过CFA方式进行后期插值可以几乎不失真的还原每个像素的R、G、B信息。用最节能环保的方式实现性能相当的产品,这才是工程开发的最高境界。 一种常见的Bayer Raw图像的色彩排布如图所示,插值的基本原理很简单,每个像素没有采集到的色彩,可以通过周边的对应色彩值进行一定的运算获得。
在XilinxVivado中,提供了Sensor Demosaic IP用于实现CFA的插值运算。IP内部结构大体如图所示。多行图像的缓存,对不同像素值位置的判断,相应临近色彩数据的运算处理,边界上还需要特殊的判断和处理,整个控制上还是略有些复杂。特权同学早年由于项目需要,做过一个CFA插值的实现,各种分支判断处理,极费脑力。好在,今天Xilinx提供的这个Sensor Demosaic IP省去了大家大量的时间和精力。
4 基于Matlab的CFA处理 在Matlab中,调用函数demosaic可以实现Bayer RAW转RGB图像。运行文件夹at7_img_ex11\matlab下的Matlab脚本beyer2RGB_matlab.m,可以实现BayerRaw格式的图像mandi_bayer_raw.tif转换为RGB图像mandi_rgb.tif。源码如下。 clc;clear `all;close all; %load origin bayer raw image I = imread('mandi_bayer_raw.tif'); %convert a bayer raw image to RGB image J = demosaic(I,'bggr'); %write image as .tif imwrite(J,'mandi_rgb.tif'); %show origin bayer raw and RGB image figure(1) subplot(1,2,1);imshow(I); title('Origin BayerRaw Image') subplot(1,2,2);imshow(J); title('RGB Image'); Bayer Raw图像和RGB色彩插值后的图像比对如下。 5 Vivado的Demoasic IP配置与接口说明 对于IP的添加配置和详细接口定义,大家可以查看官方文档pg286-v-demosaic.pdf(存放在matlab文件夹下)。这里做简单的说明。 打开Vivado,在IP Catalog窗口Search中输入sensor,可以在VivadoRepository à Video & Image Processing下看到Sensor Demoasic的IP核,双击它。
弹出配置页面如下。设定采样时钟数(Samplesper Clock)为1,数据位宽(Maximum Data Width)为8(bit),最大列分辨率(Maximum Number of Columns)为8192(pixel),最大行分辨率(MaximumNumber of Rows)为4320,插值方式选择高分辨率插值法(High Resolution Interpolation)。点击OK完成配置。
点击Generate按钮生成IP文件,可能需要较长时间。
添加好IP后,可以在IP Sources中找打新产生的v_demosaic_0的IP,展开InstantiationTemplate后,找到Verilog的例化模板v_demosaic_0.veo。
v_demosaic_0 your_instance_name ( .s_axi_CTRL_AWADDR(s_axi_CTRL_AWADDR), // input wire [5 : 0] s_axi_CTRL_AWADDR .s_axi_CTRL_AWVALID(s_axi_CTRL_AWVALID), // input wire s_axi_CTRL_AWVALID .s_axi_CTRL_AWREADY(s_axi_CTRL_AWREADY), // output wire s_axi_CTRL_AWREADY .s_axi_CTRL_WDATA(s_axi_CTRL_WDATA), // input wire [31 : 0] s_axi_CTRL_WDATA .s_axi_CTRL_WSTRB(s_axi_CTRL_WSTRB), // input wire [3 : 0] s_axi_CTRL_WSTRB .s_axi_CTRL_WVALID(s_axi_CTRL_WVALID), // input wire s_axi_CTRL_WVALID .s_axi_CTRL_WREADY(s_axi_CTRL_WREADY), // output wire s_axi_CTRL_WREADY .s_axi_CTRL_BRESP(s_axi_CTRL_BRESP), // output wire [1 : 0] s_axi_CTRL_BRESP .s_axi_CTRL_BVALID(s_axi_CTRL_BVALID), // output wire s_axi_CTRL_BVALID .s_axi_CTRL_BREADY(s_axi_CTRL_BREADY), // input wire s_axi_CTRL_BREADY .s_axi_CTRL_ARADDR(s_axi_CTRL_ARADDR), // input wire [5 : 0] s_axi_CTRL_ARADDR .s_axi_CTRL_ARVALID(s_axi_CTRL_ARVALID), // input wire s_axi_CTRL_ARVALID .s_axi_CTRL_ARREADY(s_axi_CTRL_ARREADY), // output wire s_axi_CTRL_ARREADY .s_axi_CTRL_RDATA(s_axi_CTRL_RDATA), // output wire [31 : 0] s_axi_CTRL_RDATA .s_axi_CTRL_RRESP(s_axi_CTRL_RRESP), // output wire [1 : 0] s_axi_CTRL_RRESP .s_axi_CTRL_RVALID(s_axi_CTRL_RVALID), // output wire s_axi_CTRL_RVALID .s_axi_CTRL_RREADY(s_axi_CTRL_RREADY), // input wire s_axi_CTRL_RREADY .ap_clk(ap_clk), // input wireap_clk .ap_rst_n(ap_rst_n), // input wire ap_rst_n .interrupt(interrupt), // output wire interrupt .s_axis_video_TVALID(s_axis_video_TVALID), // input wire s_axis_video_TVALID .s_axis_video_TREADY(s_axis_video_TREADY), // output wire s_axis_video_TREADY .s_axis_video_TDATA(s_axis_video_TDATA), // input wire [7 : 0] s_axis_video_TDATA .s_axis_video_TKEEP(s_axis_video_TKEEP), // input wire [0 : 0] s_axis_video_TKEEP .s_axis_video_TSTRB(s_axis_video_TSTRB), // input wire [0 : 0] s_axis_video_TSTRB .s_axis_video_TUSER(s_axis_video_TUSER), // input wire [0 : 0] s_axis_video_TUSER .s_axis_video_TLAST(s_axis_video_TLAST), // input wire [0 : 0] s_axis_video_TLAST .s_axis_video_TID(s_axis_video_TID), // input wire [0 : 0] s_axis_video_TID .s_axis_video_TDEST(s_axis_video_TDEST), // input wire [0 : 0] s_axis_video_TDEST .m_axis_video_TVALID(m_axis_video_TVALID), // output wire m_axis_video_TVALID .m_axis_video_TREADY(m_axis_video_TREADY), // input wire m_axis_video_TREADY .m_axis_video_TDATA(m_axis_video_TDATA), // output wire [23 : 0] m_axis_video_TDATA .m_axis_video_TKEEP(m_axis_video_TKEEP), // output wire [2 : 0] m_axis_video_TKEEP .m_axis_video_TSTRB(m_axis_video_TSTRB), // output wire [2 : 0] m_axis_video_TSTRB .m_axis_video_TUSER(m_axis_video_TUSER), // output wire [0 : 0] m_axis_video_TUSER .m_axis_video_TLAST(m_axis_video_TLAST), // output wire [0 : 0] m_axis_video_TLAST .m_axis_video_TID(m_axis_video_TID), // output wire [0 : 0] m_axis_video_TID .m_axis_video_TDEST(m_axis_video_TDEST) // output wire [0 : 0] m_axis_video_TDEST ); 对IP的引出接口,简单说明如下。 ap_clk为同步时钟信号;ap_rst_n为低电平有效的复位信号;interrupt为中断信号,目前保留不用。 s_axi_CTRL_*为AXI配置接口,通过这个接口,可以实现IP核的分辨率设定、Bayer Raw输入模式设定和开关等设定。上电初始,必须通过这个接口做配置后IP核才能工作。 s_axis_video_*为输入IP的Bayer Raw数据流以及控制信号。m_axis_video_*为输出IP的经过转换的RGB数据流以及控制信号。 信号的使用可以参考工程的仿真测试脚本at7_bayer2rgb_sim.v。详细信号定义可以参考文档pg286-v-demosaic.pdf。 6 Vivado的Demoasic IP仿真验证 首先,Matlab下运行at7_img_ex11\matlab文件夹下的脚本image_txt_generation.m,将Bayer Raw图像mandi_bayer_raw.tif生成16进制数据存储到image_in_hex.txt文本中。 复制image_in_hex.txt文本,粘贴到at7.sim文件夹下。 Vivado19.1版本中打开工程at7_img_ex11,确认at7_bayer2rgb_sim.v模块为仿真top module,点击RunSimulation启动仿真。整个过程编译时间较长,需要耐心等待。 运行仿真后,波形如图所示。
仿真运行结束后,在文件夹at7_img_ex11\at7.sim\sim_1\behav\xsim下生成FPGA输出的RGB色彩数据在文本FPGA_CFA_Image.txt中。 运行matlab文件夹下的脚步draw_image_from_FPGA_result.m,可以将FPGA_CFA_Image.txt文本的图像和Matlab产生的图像一同绘制出来。 clc;clear `all;close all; IMAGE_WIDTH = 3039; IMAGE_HIGHT = 2014; IMAGE_SIZE = 3*IMAGE_WIDTH*IMAGE_HIGHT; %load CFA image data from txt fid1 = fopen('FPGA_CFA_Image.txt', 'r'); img = fscanf(fid1,'%x'); fclose(fid1); img = uint8(img); img2 = reshape(img,3,IMAGE_WIDTH,IMAGE_HIGHT); img3 = permute(img2,[3,2,1]); I = uint8(img3); imwrite(I,'FPGA_CFA_Image.tif'); I = imread('FPGA_CFA_Image.tif'); %load origin bayer raw image J = imread('mandi_rgb.tif'); %show origin bayer raw and RGB image figure(1) subplot(1,2,1);imshow(J); title('RGB Image with Matlab') subplot(1,2,2);imshow(I); title('RGB Image with FPGA'); FPGA和Matlab分别做插值产生的RGB图像比对如下。肉眼看上去基本效果相当。
|