# [AV1] Constrained Directional Enhancement Filter

## CDEF原理

• CDEF direction 处理
• CDEF Block 处理
• CDEF Filter 处理

### CDEF direction 处理

Direction

The number of lines (k)

d = 0

15

d = 1

11

d = 2

8

d = 3

15

d = 4

11

d = 5

11

d = 6

8

d = 7

11

d = 8

11

（上述数据在代码中以`partial_sum_hv`,`partial_sum_diag`,`partial_sum_alt`的数组形式出现）

CDEF direction 处理主要由以下几个步骤组成

`const int px = (img[x] >> bitdepth_min_8) - 128;`

```    int best_dir = 0;
unsigned best_cost = cost[0];
for (int n = 1; n < 8; n++) {
if (cost[n] > best_cost) {
best_cost = cost[n];
best_dir = n;
}
}```

## 核心代码分析

### 函数`cdef_find_dir_c`

```static int cdef_find_dir_c(const pixel *img, const ptrdiff_t stride, unsigned *const var HIGHBD_DECL_SUFFIX)
{
const int bitdepth_min_8 = bitdepth_from_max(bitdepth_max) - 8;
int partial_sum_hv[2][8] = { { 0 } };
int partial_sum_diag[2][15] = { { 0 } };
int partial_sum_alt[4][11] = { { 0 } };

for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
const int px = (img[x] >> bitdepth_min_8) - 128;

partial_sum_diag[0][     y       +  x      ] += px;
partial_sum_alt [0][     y       + (x >> 1)] += px;
partial_sum_hv  [0][     y                 ] += px;
partial_sum_alt [1][3 +  y       - (x >> 1)] += px;
partial_sum_diag[1][7 +  y       -  x      ] += px;
partial_sum_alt [2][3 - (y >> 1) +  x      ] += px;
partial_sum_hv  [1][                x      ] += px;
partial_sum_alt [3][    (y >> 1) +  x      ] += px;
}
img += PXSTRIDE(stride);
}

unsigned cost[8] = { 0 };
for (int n = 0; n < 8; n++) {
cost[2] += partial_sum_hv[0][n] * partial_sum_hv[0][n];
cost[6] += partial_sum_hv[1][n] * partial_sum_hv[1][n];
}
cost[2] *= 105; // 这个105即是上面理论部分说的权重 weight
cost[6] *= 105;

static const uint16_t div_table[7] = { 840, 420, 280, 210, 168, 140, 120 };
for (int n = 0; n < 7; n++) {
const int d = div_table[n];
cost[0] += (partial_sum_diag[0][n]      * partial_sum_diag[0][n] +
partial_sum_diag[0][14 - n] * partial_sum_diag[0][14 - n]) * d;
cost[4] += (partial_sum_diag[1][n]      * partial_sum_diag[1][n] +
partial_sum_diag[1][14 - n] * partial_sum_diag[1][14 - n]) * d;
}
cost[0] += partial_sum_diag[0][7] * partial_sum_diag[0][7] * 105;
cost[4] += partial_sum_diag[1][7] * partial_sum_diag[1][7] * 105;

for (int n = 0; n < 4; n++) {
unsigned *const cost_ptr = &cost[n * 2 + 1];
for (int m = 0; m < 5; m++)
*cost_ptr += partial_sum_alt[n][3 + m] * partial_sum_alt[n][3 + m];
*cost_ptr *= 105;
for (int m = 0; m < 3; m++) {
const int d = div_table[2 * m + 1];
*cost_ptr += (partial_sum_alt[n][m]      * partial_sum_alt[n][m] +
partial_sum_alt[n][10 - m] * partial_sum_alt[n][10 - m]) * d;
}
}

int best_dir = 0;
unsigned best_cost = cost[0];
for (int n = 1; n < 8; n++) {
if (cost[n] > best_cost) {
best_cost = cost[n];
best_dir = n;
}
}

*var = (best_cost - (cost[best_dir ^ 4])) >> 10;
return best_dir;
}```

### 函数`cdef_filter_block_c`

```static NOINLINE void cdef_filter_block_c(pixel *dst, const ptrdiff_t dst_stride, const pixel (*left)[2], const pixel *const top,
const int pri_strength, const int sec_strength,
const int dir, const int damping, const int w, int h, const enum CdefEdgeFlags edges HIGHBD_DECL_SUFFIX)
// 参数中
// 1. dst 为滤波结果，完成CDEF后的结果将存入到此指针指向的内存中
// 2. dst_stride 为目标内存的 stride
// 3. left 为左侧的像素值（一列）
// 4. top 为上方的像素值（一排）
// 5. pri_strength 是第一级的滤波强度
// 6. sec_strength 是第二级的滤波强度
// 7. dir 由之前的 find_dir 函数计算出的滤波方向
// 8. damping 指滤波的threshold，详见 Spec 7.15.3的计算步骤
// 9. w, h 为滤波块的长和宽，一般是8x8
// 10. edges 指边缘情况，ABOVE, LEFT, RIGHT, BOTTOM 四个方向是否存在，由相应的宏定义
{
// 1. 为滤波当前块做准备
const ptrdiff_t tmp_stride = 12;
assert((w == 4 || w == 8) && (h == 4 || h == 8));
int16_t tmp_buf[144]; // 12*12 is the maximum value of tmp_stride * (h + 4)
int16_t *tmp = tmp_buf + 2 * tmp_stride + 2;
padding(tmp, tmp_stride, dst, dst_stride, left, top, w, h, edges);

// 2. 滤波开始
// 2.1 进行一级滤波
if (pri_strength) {
const int bitdepth_min_8 = bitdepth_from_max(bitdepth_max) - 8;
const int pri_tap = 4 - ((pri_strength >> bitdepth_min_8) & 1);
const int pri_shift = imax(0, damping - ulog2(pri_strength));
// 2.1.1 进行一级滤波的同时也进行二级滤波
if (sec_strength) {
const int sec_shift = imax(0, damping - ulog2(sec_strength));
do {
for (int x = 0; x < w; x++) {
const int px = dst[x];
int sum = 0;
int max = px, min = px;
int pri_tap_k = pri_tap;
for (int k = 0; k < 2; k++) {
const int off1 = dav1d_cdef_directions[dir + 2][k]; // 这个off1是中心元素离primary tap的滤波像素取值点的距离。
const int p0 = tmp[x + off1]; // 获取到primary tap的上方像素位置
const int p1 = tmp[x - off1]; // 获取到primary tap的下方像素位置
sum += pri_tap_k * constrain(p0 - px, pri_strength, pri_shift); // 滤波计算（上）
sum += pri_tap_k * constrain(p1 - px, pri_strength, pri_shift); // 滤波计算（下）
// if pri_tap_k == 4 then it becomes 2 else it remains 3
pri_tap_k = (pri_tap_k & 3) | 2;
min = umin(p0, min);
max = imax(p0, max);
min = umin(p1, min);
max = imax(p1, max);
const int off2 = dav1d_cdef_directions[dir + 4][k]; // secondary tap的位置与中心像素点的位置差（方向1）
const int off3 = dav1d_cdef_directions[dir + 0][k]; // secondary tap的位置与中心像素点的位置差（方向2）
const int s0 = tmp[x + off2]; // 获取到secondary tap的像素位置（方向1上）
const int s1 = tmp[x - off2]; // 获取到secondary tap的像素位置（方向1下）
const int s2 = tmp[x + off3]; // 获取到secondary tap的像素位置（方向2上）
const int s3 = tmp[x - off3]; // 获取到secondary tap的像素位置（方向2下）
// sec_tap starts at 2 and becomes 1
const int sec_tap = 2 - k;
sum += sec_tap * constrain(s0 - px, sec_strength, sec_shift); // 滤波计算
sum += sec_tap * constrain(s1 - px, sec_strength, sec_shift); // 滤波计算
sum += sec_tap * constrain(s2 - px, sec_strength, sec_shift); // 滤波计算
sum += sec_tap * constrain(s3 - px, sec_strength, sec_shift); // 滤波计算
min = umin(s0, min);
max = imax(s0, max);
min = umin(s1, min);
max = imax(s1, max);
min = umin(s2, min);
max = imax(s2, max);
min = umin(s3, min);
max = imax(s3, max);
}
// 最后的结果必须不超过所有参与运算的像素值的最大值，不小于所有参与运算的像素值的最小值，每一步的min，max的计算就是在比较像素值然后                     // 最后得出所有参与运算的像素的值的上下界
dst[x] = iclip(px + ((sum - (sum < 0) + 8) >> 4), min, max);
}
dst += PXSTRIDE(dst_stride);
tmp += tmp_stride;
} while (--h);
// 2.1.2 仅进行一级滤波
} else {
do {
for (int x = 0; x < w; x++) {
const int px = dst[x];
int sum = 0;
int pri_tap_k = pri_tap;
for (int k = 0; k < 2; k++) {
const int off = dav1d_cdef_directions[dir + 2][k]; // dir
const int p0 = tmp[x + off];
const int p1 = tmp[x - off];
sum += pri_tap_k * constrain(p0 - px, pri_strength, pri_shift);
sum += pri_tap_k * constrain(p1 - px, pri_strength, pri_shift);
pri_tap_k = (pri_tap_k & 3) | 2;
}
dst[x] = px + ((sum - (sum < 0) + 8) >> 4);
}
dst += PXSTRIDE(dst_stride);
tmp += tmp_stride;
} while (--h);
}
// 2.2 仅进行二级滤波
} else {
assert(sec_strength);
const int sec_shift = imax(0, damping - ulog2(sec_strength));
do {
for (int x = 0; x < w; x++) {
const int px = dst[x];
int sum = 0;
for (int k = 0; k < 2; k++) {
const int off1 = dav1d_cdef_directions[dir + 4][k]; // dir + 2
const int off2 = dav1d_cdef_directions[dir + 0][k]; // dir - 2
const int s0 = tmp[x + off1];
const int s1 = tmp[x - off1];
const int s2 = tmp[x + off2];
const int s3 = tmp[x - off2];
const int sec_tap = 2 - k;
sum += sec_tap * constrain(s0 - px, sec_strength, sec_shift);
sum += sec_tap * constrain(s1 - px, sec_strength, sec_shift);
sum += sec_tap * constrain(s2 - px, sec_strength, sec_shift);
sum += sec_tap * constrain(s3 - px, sec_strength, sec_shift);
}
dst[x] = px + ((sum - (sum < 0) + 8) >> 4);
}
dst += PXSTRIDE(dst_stride);
tmp += tmp_stride;
} while (--h);
}
}```

#### 为滤波当前块做准备

12x12的块滤波的时候的情况如下

```static void padding(int16_t *tmp, const ptrdiff_t tmp_stride,
const pixel *src, const ptrdiff_t src_stride,
const pixel (*left)[2], const pixel *top,
const int w, const int h,
const enum CdefEdgeFlags edges)
{
// fill extended input buffer
int x_start = -2, x_end = w + 2, y_start = -2, y_end = h + 2;
if (!(edges & CDEF_HAVE_TOP)) {
fill(tmp - 2 - 2 * tmp_stride, tmp_stride, w + 4, 2);
y_start = 0;
}
if (!(edges & CDEF_HAVE_BOTTOM)) {
fill(tmp + h * tmp_stride - 2, tmp_stride, w + 4, 2);
y_end -= 2;
}
if (!(edges & CDEF_HAVE_LEFT)) {
fill(tmp + y_start * tmp_stride - 2, tmp_stride, 2, y_end - y_start);
x_start = 0;
}
if (!(edges & CDEF_HAVE_RIGHT)) {
fill(tmp + y_start * tmp_stride + w, tmp_stride, 2, y_end - y_start);
x_end -= 2;
}

for (int y = y_start; y < 0; y++) {
for (int x = x_start; x < x_end; x++)
tmp[x + y * tmp_stride] = top[x];
top += PXSTRIDE(src_stride);
}
for (int y = 0; y < h; y++)
for (int x = x_start; x < 0; x++)
tmp[x + y * tmp_stride] = left[y][2 + x];
for (int y = 0; y < y_end; y++) {
for (int x = (y < h) ? 0 : x_start; x < x_end; x++)
tmp[x] = src[x];
src += PXSTRIDE(src_stride);
tmp += tmp_stride;
}
}```

```static inline void fill(int16_t *tmp, const ptrdiff_t stride, const int w, const int h)
{
/* Use a value that's a large positive number when interpreted as unsigned, and a large negative number when interpreted as signed. */
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++)
tmp[x] = INT16_MIN;
tmp += stride;
}
}```

#### 滤波计算

```static inline int apply_sign(const int v, const int s)
{
return s < 0 ? -v : v;
}

static inline int constrain(const int diff, const int threshold, const int shift)
// 2. threshold 是参数 damping
// 3. shift 是像素差需要右移的位数
{
}```

0 条评论

• ### [AV1] AV1专栏介紹（正在更新中）

这个专栏是我花了半年的时间阅读libav1，SVT-AV1以及dav1d的源码后摸索总结出来的AV1技能树，希望能帮助到你的AV1编解码器的学习。

• ### ECCV 2020 的对抗相关论文（对抗生成、对抗攻击）

本文汇总了ECCV 2020上部分对抗相关论文，后续公众号会随缘对一些paper做解读。感兴趣的同学，可先自行根据标题，搜索对应链接（有些paper可能未公布）...

• ### 视频编解码器 2020-比赛开始

原文链接：https://blog.beamr.com/2020/05/28/video-codecs-in-2020-the-race-is-on/

• ### 【ACM MM论文集】国际多媒体顶级会议ACM Multimedia 2017 Open Access Repository

点击上方“专知”关注获取更多AI知识! 【导读】第25届ACM国际多媒体会议（ACM International Conference on Multimedi...

• ### 重磅纯干货 | 超级赞的语音识别/语音合成经典论文的路线图（1982-2018.5）

网址：https://github.com/zzw922cn/awesome-speech-recognition-speech-synthesis-paper...

• ### [计算机视觉论文速递] 2018-03-03

通知：这篇推文很长，有32篇论文速递信息，涉及目标检测、图像分割、网络优化、人脸表情识别、SLAM和OCR等方向。 [1]《The 2018 DAVIS Cha...

• ### 最全OCR相关资料整理

最近看到一个非常赞的OCR相关资源，收集从2015.10.9到现在的一些OCR文献，github项目和博客资源等

• ### HDR关键技术：色调映射(三)

HDR技术近年来发展迅猛，在未来将会成为图像与视频领域的主流。如何让HDR图像与视频也能够同时兼容地在现有的SDR显示设备上显示，是非常重要的技术挑战。色调映射...