前一篇我們提到 IPUS、IPUV的架構並舉了一個 IPUS 算子的範例,接下來我們繼續來看 IPUV 以及 Histogram 的範例。
b. rgb2y.ipuv(IPUV kernel,使用在範例〝ov10640_default〞graph中)
此算子取自 ov10640_default 範例,主要功能為輸入 3 組 input channel(R,G,B),經算子運算後以 1 個 Y 的 output 輸出。#Engine: IPUS2, IPUS3 (LUT)
#Function:
#LUT with 12 bit in 16 out
# extract max and min
#turn on off via GPR0
#
#step_x, step_in, step_out=1
# no neighborhood used
#input:
#line0: R 16 bit
#line0: G 16 bit
#line0: B 16 bit
#output:
#line0: Y 16 bit
#
# parameters:
# gpr4-gpr6: conversion parameters
# gpr13: maximum ypos
.global rgb2y_start
rgb2y_start:
// scaled to 32768 = 15 bits to allow to deal with overflows
// Y= 0,299xR + 0,587xG + 0,114xB
//mov gpr0,9797 //R*gpr4->Y : 32768 * 0,299
//mov gpr1,19234 //G*gpr5->Y : 32768 * 0,587
//mov gpr2,3735 //B*gpr6->Y : 32768 * 0,114
//gpr3 must not be used as it is parameter for compression
mov confalu,(0 /*unsigned*/ | (1<<1) /*saturate*/ | (15<<4) /*shr*/)
max gpr13,gpr13,ypos
done rgb2y_loop,i
rgb2y_loop:
//Y
mulh vsacc0,vh0,gpr4 // R->Y
mulh vacc0,vh5,gpr5 // G->Y
mulh vacc0,vh10,gpr6 // B->Y
//mov vout0,vacc0
dvot vacc0,rgb2y_loop,ixo
//halt
.global rgb2y_end
rgb2y_end:
rgb2y_start、rgb2y_end此為算子的起終點 label。// scaled to 32768 = 15 bits to allow to deal with overflows
// Y= 0,299xR + 0,587xG + 0,114xB
//mov gpr0,9797 //R*gpr0->Y : 32768 * 0,299
//mov gpr1,19235 //G*gpr1->Y : 32768 * 0,587
//mov gpr2,3735 //B*gpr2->Y : 32768 * 0,114參考 RGB 轉 YUV 公式中的Y = 0.299xR + 0.587xG + 0.114xB
如同註解中所解釋,算子中先初始化 RGB 轉 YUV 公式中所需要的常數,乘上 32768 後,可轉為整數,分別存入 gpr0、gpr1、gpr2 以便後續使用。
但這裡的 gpr0、gpr1、gpr2 皆被標示為註解,那是在哪裡初始化的呢?
回到 VSDK source code 的目錄下尋找,結果在 demo app 的目錄裡面可以發現這些值被定義在cpp[1] 中,且可以發現其初始化的 GPR 為 gpr4、gpr5、gpr6(address 0x54,0x55,0x56),而不是 gpr0、gpr1、gpr2(與後面的 kernel code 呼應)。
[1] VisionSDK_S32V2_RTM_1_3_0/s32v234_sdk/demos/isp/isp_ov10640_default/src/main.cppmov confalu,(0 /*unsigned*/ | (1<<1) /*saturate*/ | (15<<4) /*shr*/)算子中會使用到 ALU 做計算,所以需要設定 Register confalu 的初始值。
(15<<4) /*shr*/,前述乘上 32768 ,此設定會將 ALU 的結果位移回來。
其初始值設定為 unsigned 以及 saturate。//Y
mulh vsacc0,vh0,gpr4 // R->Y
mulh vacc0,vh5,gpr5 // G->Y
mulh vacc0,vh10,gpr6 // B->Y利用 RGB 轉 YUV 的公式進行矩陣運算並利用累加器計算 Y 值,最終累加的結果 Y 會存放在 vacc0。dvot vacc0,rgb2y_loop,ixodvot 表示 Pixel Done with VOUT0。同 Pixel Done 指令,但輸出設定為 vacc0。
引入下一組 input,輸出 vout0 並更新 XPOS 值。此 loop 會持續到 XPOS > XSIZE 後停止,詳細說明可參考文件〝如何从无到有完成ISP kernel算子的参数调整〞。
c.histogram.ipus
histogram 算子可利用 IPUS 的 Histogram Engine 進行 input data 出現次數的統計計算。以下舉一個簡單的例子,計算亮度(Y)值的直方圖,並嘗試將圖秀在螢幕上。.global histogram_1to1_start
histogram_1to1_start:
// configure Histogram engine
mov confhist,(0 /*offset*/ | (8<<8) /*scale*/)
done histogram_1to1_loop,i // load first pixel
histogram_1to1_loop:
// incrment histogram value:
// hist[ina0 >> 8]++
lsr hbinincl,ina0,8
// now we are done and get the next input pixels
done histogram_1to1_loop,ix
halt
.global histogram_1to1_end
histogram_1to1_end:
histogram_1to1_start、histogram_1to1_end此為算子的起終點 label。mov confhist,(0 /*offset*/ | (8<<8) /*shr*/)Histogram Configuration Register,設定 offset 為 0;另外因為我們使用 HBININCL ,所以 scale 參數 donnot care。done histogram_1to1_loop,i // load first pixel一次進 1 個 data,此 data 設定為 Pixel components are 8-bits in size and should be read from the higher 8-bits of the 16-bit OUT matrix。
// incrment histogram value:
// hist[ina0 >> 8]++
lsr hbinincl,ina0,8HBININCL 只接受 8 bit 的 data,然而前面提到 input data 為高 8 bit,所以這裡右移 8 bit 之後存入。
done histogram_1to1_loop,ix引入下一組 input 並更新 XPOS 值。
至此,系統會將 video frame 的 Y histogram matrix 儲存在 IPUS Histogram Engine 內,接下來我們可以經由 A53 讀出此矩陣並使用 OpenCV 將其畫在螢幕上,如圖。In main function:
// Histogram Engine initialize
gIpuHist.mIpuIdx = SEQ_IPUS1;
SEQ_HistogramEnable(SEQ_IPUS1);
In Run function:
// Histogram data acquire
SEQ_HistogramGet(&gIpuHist);
// Draw the output frame for each channel
vsdk::Mat frame = lFrame.mUMat.getMat(cv::ACCESS_RW | OAL_USAGE_CACHED);
for (int i = 0; i < IPU_HIST_SIZE; i++) {
cv::line((cv::Mat)frame,
cv::Point(100+i*3,720),
cv::Point(100+i*3,720-10*(gIpuHist.mpData[i])/1280),
cv::Scalar(255, 255, 255), 3, 4);
}
The End!!!