晶片設計-lut表與線性插值
修訂記錄
版本
日期
說明
v0。1
2022。06。25
初版
為什麼要用lut?
lut表在影象處理領域是一個非常常見的操作。假設我們想實現下面一條對映曲線f(x),該如何實現呢?
它不嚴格符合某一多元或多次方程,不能用一個計算公式實現。
我們也不可能將每個點都儲存在mem當中,假設你的資料位寬是16bit,那就需要(2^16)*16bit=1024Kb大小的mem,土豪都頂不住。
處理辦法是選取若干取樣點,只儲存這些取樣點的x/y座標,其餘點則透過兩側取樣點線性插值得到。誠然,這種做法得到的曲線不能完美適配預設曲線,但在資源與效果上做到了較好的平衡。
lut內容與插值方法
使用lut時,我們需要考慮如下問題:
lut表中放什麼?或者說軟體需要配置哪些東西到硬體?
如何進行lut表定址
如何插值?
線性插值公式:
dout = y0 + (y1-y0)*(din-x0)/(x1-x0)
其中x0,x1,y0,y1表示區間左右兩側的x,y座標。從這個公式上看,lut表需要包括x,y座標,插值時還需要一個除法器,但根據實際情況,其實可以有所最佳化。
均勻取樣,且取樣區間數為2的冪
這種情況對硬體來說是最友好的。假設資料位寬為16bit,取樣區間數是1024,則:
資料高位din[15:6]就是x0, +1就是x1。lut表中就不需要存x軸座標了
資料地位din[5:0]就是(din-x0)
同時,區間步長x_step(即x1-x0)也是2的冪:2^16/1024 = 2^6,因此除法可以用移位實現。
插值公式簡化為:
dout = y0 + (y1-y0)*din[5:0] >> 6
另外,如果x_step不是2的冪,那也不需要除法器,可以設定一個倒數暫存器
x_inv = 1/(x1-x0) = 1/x_step
則:dout = y0 + (y1-y0)*(din-x0)*x_inv
再進一步,倒數暫存器設定為:
inv = (y1-y0)/(x1-x0) = y_step/x_step
則:dout = y0 + (din-x0)*inv
均勻取樣,且取樣區間數不是2的冪
假設資料位寬是16bit, 取樣區間數是1000, 判斷din落在哪個區間時:RangeIdx = din/1000
這種做法貌似節約了mem深度,但需要額外消耗除法器。所以我們一般不這樣幹,寧願把取樣區間數向上增加到2的冪。
分段均勻取樣
均勻取樣收到的限制較多,因此有了分段均勻的操作。
把x軸分成若干段落(Segment),數量
不要求
為2的冪,但不要太多。
把每個段落分成若干區間(range),數量
要求
為2的冪。比如我們可以給暗部區域分配多些區間,亮部區域少些區間。
段落內區間步長相等,段落間區間步長不要求相等,區間步長要求為2的冪
當輸出輸入後:
首先得判斷落在哪個段落中,由於段落是非均勻的,因此這裡只能靠if_else來實現,這也就是段落數不能太多的原因。
if(din < SegNode0) //SegNode0表示段落節點
SegIdx <= 0; //SegIdx表示段落索引
else if(din < SegNode1)
SedIdx <= 1;
……
判斷好區間後,由於段落內是均勻取樣,因此按均勻取樣的做法進行即可。
mem儲存形式
根據上面描述,我們需要從lut表中獲取到y0,y1的值。如果資料是連續進來的,意味著我們得一拍內獲取到y0,y1兩個點,因此lut在mem中如何儲存很關鍵。推薦下面做法:
設定2個ram,寬度等於lut表寬度,深度等於lut深度的1/2。lut資料在2個ram中交替儲存。這種做法軟體寫入簡單,邏輯取值也方便,同時ram資源消耗也較少。
RAM0
RAM1
lut_data0
lut_data1
lut_data2
lut_data3
……
……
lut_data1022
lut_data1023
參考文件
ISP——Gamma Correction - 知乎 (zhihu。com)