【问题标题】:arithmetic encoder symbol lookup算术编码器符号查找
【发布时间】:2021-02-28 14:01:21
【问题描述】:

我有基于 Drdobbs ARcoder 的简单算术编码器。解码/编码算法归结为维护数组中的符号概率并更新它们。问题是我想扩展它以支持越来越多的符号,而不是固定的 +256 左右的符号。目前的实施 如果符号数量增加太多,update_model()getsym() 的速度就会太慢。

我可以将cumulative_freq 数组转换为树状数据结构,以便更快地查找和更新吗?我不介意算法最终是否会消耗更多内存,只要它可以更好地扩展。

const u32 MAX_INPUT_VAL = 255;
const u32 CODE_RESCALE = MAX_INPUT_VAL+1;
const u32 CODE_EOF = MAX_INPUT_VAL+2;
const u32 CUMULATIVE_TOTAL = MAX_INPUT_VAL+3;  // total sum of all frequencies before.
const u32 CODES_END = MAX_INPUT_VAL+4;

u32 cumulative_freq[CODES_END];

// (Init symbol freqs)
for(u32 i = 0; i < CODES_END; ++i) {
    cumulative_freq[i] = i;
}

// update value propability
void update_model(u32 v) {
    // how can I make this scale better for more symbols?
    for(code_t i = v + 1; i < CODES_END; ++i) {
        cumulative_freq[i]++;
    }
}

struct sym {
    u32 low;
    u32 high;
    u32 count;
};

// encode symbol: get symbol propability
sym propability(u32 v) {
    sym p{cumulative_freq[v], cumulative_freq[v+1], cumulative_freq[CUMULATIVE_TOTAL]};
    update_model(v);
    return p;
}

// decode symbol:
std::pair<sym, u32> getsym(u64 prop) {
    // how can I make this scale better for more symbols?
    for(u32 i = 0; i < CUMULATIVE_TOTAL; ++i) {
        if(prop < cumulative_freq[i+1]) {
            sym p{cumulative_freq[i],cumulative_freq[i+1],cumulative_freq[CUMULATIVE_TOTAL]};
            update_model(i);
            return {p,i};
        }
    }
    // error: decode failed.
}

【问题讨论】:

    标签: c++ data-structures compression lossless-compression


    【解决方案1】:

    是的,您可以将计数排列成一棵树。每个符号都有一个叶子,其中包含其概率(不是累积的),然后每个内部节点都包含其下方节点的概率总和。

    要更新模型,您需要将 1 添加到符号的叶子及其每个祖先。要进行搜索,您可以在下降到树中时动态计算符号的累积概率 - 每次向右移动时,您都会从未输入的左侧子节点添加计数。

    这是一个订单统计树,但使用累积概率而不是排名:https://en.wikipedia.org/wiki/Order_statistic_tree

    然而,这棵树的结构永远不会改变。没有插入或删除。因此,您不需要使用带有指针等的实际节点对象。您可以只使用计数数组,像二进制堆数组一样排列:https://www.geeksforgeeks.org/array-representation-of-binary-heap/

    如果你有 N 个符号,那么内部节点将有 N-1 个计数,然后是叶符号的 N 个计数,除根之外的每个节点都将在 floor(index-1)/2 处有一个父节点.

    这种结构的内存效率还是蛮高的,所有操作都需要O(log N)时间,代码也比较少。

    更新如下:

    unsigned i = symbol + num_symbols-1;
    for (;;) {
        ++counts[i];
        if (!i)
            break;
        i = (i-1)/2;
    }
    

    获取符号的累积 P:

    unsigned P=0;
    for (unsigned i=symbol+num_symbols-1; i>0; i=(i-1)/2) {
        if (!(i&1)) {
            //this is a right child, add counts from the left
            P+=counts[i-1];
        }
    }
    

    通过累积概率获取符号:

    unsigned P=0;
    unsigned i=0;
    while (i<(num_symbols-1)) {
        i=i*2+1; //left child
        if (targetP >= P+counts[i]) {
            //use right child instead
            P+=counts[i];
            ++i;
        }
    }
    unsigned symbol = i-(num_symbols-1);
    

    请注意,最小索引符号 (num_symbols-1) 不是树中最左边的符号,除非 num_symbols 是 2 的幂,但这应该没问题。只要编码器和解码器一致,符号的顺序与您的应用程序无关。

    【讨论】:

    • 感谢您指出链接!为了清楚起见,我可能首先尝试使用节点来实现它,然后使用普通数组索引。
    • 当然。如果有帮助,我会输入使用树的代码。
    猜你喜欢
    • 2011-06-23
    • 2014-05-24
    • 1970-01-01
    • 1970-01-01
    • 2021-07-30
    • 2013-10-28
    • 1970-01-01
    • 1970-01-01
    • 2010-10-08
    相关资源
    最近更新 更多