Sensor Demoasic (CFA)IP仿真實例 騰訊鏈接:https://share.weiyun.com/5GQyKKc 百度網盤鏈接:https://pan.baidu.com/s/1M7PLzRs-yMJv7NFJE8GVAw 提取碼:qr0t ![]() 由于Vivado 19.1以后不再支持Color Filter 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+的空間)。 也可以到Xilinx官方網站(https://www.xilinx.com/support/download.html),下載對應操作系統的Vivado2019.1安裝程序。 借用知乎上的文章(https://zhuanlan.zhihu.com/p/21298545)科普一下CFA。 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%查看畫質。 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圖像的色彩排布如圖所示,插值的基本原理很簡單,每個像素沒有采集到的色彩,可以通過周邊的對應色彩值進行一定的運算獲得。
![]() 在Xilinx Vivado中,提供了Sensor Demosaic IP用于實現CFA的插值運算。IP內部結構大體如圖所示。多行圖像的緩存,對不同像素值位置的判斷,相應臨近色彩數據的運算處理,邊界上還需要特殊的判斷和處理,整個控制上還是略有些復雜。特權同學早年由于項目需要,做過一個CFA插值的實現,各種分支判斷處理,極費腦力。好在,今天Xilinx提供的這個Sensor Demosaic IP省去了大家大量的時間和精力。
![]() 在Matlab中,調用函數demosaic可以實現Bayer RAW轉RGB圖像。運行文件夾at7_img_ex11\matlab下的Matlab腳本beyer2RGB_matlab.m,可以實現Bayer Raw格式的圖像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色彩插值后的圖像比對如下。 ![]() 對于IP的添加配置和詳細接口定義,大家可以查看官方文檔pg286-v-demosaic.pdf(存放在matlab文件夾下)。這里做簡單的說明。 打開Vivado,在IP Catalog窗口Search中輸入sensor,可以在Vivado Repository à Video & Image Processing下看到Sensor Demoasic的IP核,雙擊它。
![]() 彈出配置頁面如下。設定采樣時鐘數(Samples per Clock)為1,數據位寬(Maximum Data Width)為8(bit),最大列分辨率(Maximum Number of Columns)為8192(pixel),最大行分辨率(Maximum Number of Rows)為4320,插值方式選擇高分辨率插值法(High Resolution Interpolation)。點擊OK完成配置。
![]() 點擊Generate按鈕生成IP文件,可能需要較長時間。
![]() 添加好IP后,可以在IP Sources中找打新產生的v_demosaic_0的IP,展開Instantiation Template后,找到Verilog的例化模板v_demosaic_0.veo。
![]() .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 wire ap_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。 首先,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,點擊Run Simulation啟動仿真。整個過程編譯時間較長,需要耐心等待。 運行仿真后,波形如圖所示。
![]() 仿真運行結束后,在文件夾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圖像比對如下。肉眼看上去基本效果相當。
![]() |