2022年2月25日 星期五

Halide 實務心得 5

在實作影像處理演算的過程或是實務的功能整合中, 都圍繞著 Halide::Runtime:: Buffer 的使用. 有時難以避免需要初始化預設值或是從其他 Buffer 物件複製資料. 這些可以透過 Buffer::fill() 或是 Buffer::copy_from 來做基本的填值處理.

對於一些相當 routine 但是卻又需要計算的部份,  通常會透過 loop 來走過每個座標, 甚至每個一個 value. 像是做簡單的 gamma correction 可能就會做這樣的動作:

Buffer<uint8_t, 2> src = Buffer<uint8_t>(w, h);
...
Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);
for(int y = 0; y < dst.height(); y++){
    for(int x = 0; x < dst.width(); x++){
        for(int c = 0; c < dst.channel(); c++){
            dst(x, y, c) = gamma(src(x, y, c));
        }
    }
}

或是可能是格式轉換 (像是 RGB <-> YUV 或是這裡的 Demosaic):

Buffer<uint8_t, 2> src = Buffer<uint8_t>(w, h);
...
Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);
for(int y = 0; y < dst.height(); y++){
    for(int x = 0; x < dst.width(); x++){
        uint8_t r, g, b;
        demosaic(src(x, y), r, g, b);
        dst(x, y, 0) = r;
        dst(x, y, 1) = g;
        dst(x, y, 2) = b;
    }
}

一旦數量很多, 或是 dimension 很大一一撰寫 loop 除了費時費力外, 也讓 code 不易維護與可讀性不佳. 對於這種問題, Halide 透過了 C++ lambda 的方式能夠簡短地處理, Halide Buffer 提供了兩個在其官方教學沒有提到的兩個實用的方式: for_each_valuefor_each_element :

對於 Gamma 的例子可以簡短地改寫為:

Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);
dst.for_each_value([&](uint8_t &dval, uint8_t &sval){
    dval = gamma(sval);
}, src);

而對於像 demosaic 的例子則可以改寫為:

Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);
dst.for_each_element([&](int x, int y){
        uint8_t r, g, b;
        demosaic(src(x, y), r, g, b);
        dst(x, y, 0) = r;
        dst(x, y, 1) = g;
        dst(x, y, 2) = b;
});

 這樣的作法也適合套用在一些特殊的初始化.

沒有留言:

在 ARM 平台上使用 Function Multi-Versioning (FMV) - 以使用 Android NDK 為例

Function Multi-Versioning (FMV) 過往的 CPU 發展歷程中, x86 平台由於因應各種應用需求的提出, 而陸陸續續加入了不同的指令集, 此外也可能因為針對市場做等級區隔, 支援的數量與種類也不等. 在 Linux 平台上這些 CPU 資訊可以透過...