处理3D阵列的边角情况的方法

时间:2019-03-05 16:34:59

标签: c++ c++14

我有一个固定大小的3D浮点数组,并且需要一种算法来检查每个数组单元的值以及该单元在x,y和z方向上的7个直接邻居的值。以下代码适用于主体情况,但是当x,y或z等于Size-1时,它不会评估边缘情况下的最终像元,因为它们可能在某个方向上没有邻居。

#include <stdint.h>
#include <iostream>

const size_t SIZE     = 10;
const float THRESHOLD = 0.0f;

float array[SIZE][SIZE][SIZE];

int main() {
    for (size_t x = 0; x < SIZE - 1; x++)
    for (size_t y = 0; y < SIZE - 1; y++)
    for (size_t z = 0; z < SIZE - 1; z++) {
        uint8_t mask = 0x00;

        if (array[x    ][y    ][z    ] > THRESHOLD) { mask |= 0x01; }
        if (array[x + 1][y    ][z    ] > THRESHOLD) { mask |= 0x02; }
        if (array[x    ][y + 1][z    ] > THRESHOLD) { mask |= 0x04; }
        if (array[x + 1][y + 1][z    ] > THRESHOLD) { mask |= 0x08; }
        if (array[x    ][y    ][z + 1] > THRESHOLD) { mask |= 0x10; }
        if (array[x + 1][y    ][z + 1] > THRESHOLD) { mask |= 0x20; }
        if (array[x    ][y + 1][z + 1] > THRESHOLD) { mask |= 0x40; }
        if (array[x + 1][y + 1][z + 1] > THRESHOLD) { mask |= 0x80; }

        std::cout << "x: " << x << " y: " << y << " z: " << z << " mask: " << (int)mask << std::endl;
    }

    return 0;
}

我的快速解决方案是使用if语句包装需要在某个方向访问邻居的检查,以查看该方向是否在边缘。如果一个小区没有邻居,则评估应等同于其处于阈值以下。

int main() {
    //no longer SIZE - 1, checks all cells now
    for (size_t x = 0; x < SIZE; x++)
    for (size_t y = 0; y < SIZE; y++)
    for (size_t z = 0; z < SIZE; z++) {
        uint8_t mask = 0x00;

        if (array[x][y][z] > THRESHOLD) {
            mask |= 0x01;
        }

        if (x != SIZE - 1) {
            if (array[x + 1][y][z] > THRESHOLD) {
                mask |= 0x02;
            }
        }

        if (y != SIZE - 1) {
            if (array[x][y + 1][z] > THRESHOLD) {
                mask |= 0x04;
            }
        }

        if (x != SIZE - 1 && y != SIZE - 1) {
            if (array[x + 1][y + 1][z] > THRESHOLD) {
                mask |= 0x08;
            }
        }

        if (z != SIZE - 1) {
            if (array[x][y][z + 1] > THRESHOLD) {
                mask |= 0x10;
            }
        }

        if (x != SIZE - 1 && z != SIZE - 1) {
            if (array[x + 1][y][z + 1] > THRESHOLD) {
                mask |= 0x20;
            }
        }

        if (y != SIZE - 1 && z != SIZE - 1) {
            if (array[x][y + 1][z + 1] > THRESHOLD) {
                mask |= 0x40;
            }
        }

        if (x != SIZE - 1 && y != SIZE - 1 && z != SIZE - 1) {
            if (array[x + 1][y + 1][z + 1] > THRESHOLD) {
                mask |= 0x80;
            }
        }

        std::cout << "x: " << x << " y: " << y << " z: " << z << " mask: " << (int)mask << std::endl;

    }
}

这似乎不是解决此问题的最佳方法。我更愿意在没有边缘保护的情况下写这篇文章,有没有办法?我假设有一种方法可以专门为每个边和角编写循环,并检查您知道该单元格具有的邻居,但是我在查找有关它的内容时遇到了麻烦。

2 个答案:

答案 0 :(得分:1)

据我所知,没有明显更聪明的方法可以做到这一点。此问题经常出现在各种图像处理中,但通常必须在其中进行不同类型的边框处理(“ 边框是否未定义/零/镜像/重复/与最接近的像素/等相同?? < / em>”)。

有一种常见的方法是对在每个方向(每个维度为+2)放大1且正确设置边界值的数组进行处理。这样就可以保证不会超出数组范围(如果正确获得了循环索引),但是必须首先分配一个新数组。

根据您将不得不执行这种交互的频率而定,写一个返回给定中心像素坐标的七个相邻值的数组并执行正确的边框处理(例如,NaN)的函数可能会很有用。对于阵列外的体素):

std::array<float, 7> getVoxelAndNeighbors(int x, int y, int z)

这最终使您的代码更具可读性,但是性能方面可能会受到影响,因为您必须对每个元素进行边界检查,即使实际上很少需要它。

另一种可能是与“普通”循环体分开处理表面(和边缘(和角)):

for (z = 1; z < SIZE-1; ++z)
  for (y = 1; y < SIZE-1; ++y)
    for (x = 1; x < SIZE-1; ++x)
      { /* Your original body */ }

for (y = 1; y < SIZE-1; ++y)
  for (x = 1; x < SIZE-1; ++x)
    { /* Handle positive and negative z surface */ }

// (Repeat for x, y)

for (x = 1; x < SIZE-1; ++x)
  { /* Handle edges in x direction */ }

// (Repeat for x, y)

// Handle all eight corners.

这对于分支错误预测(循环主体中没有if)的性能更好,但是编写起来更加冗长,并且在缓存一致性方面更差。

答案 1 :(得分:0)

有几种可能的方法:

  1. 只需向每个操作添加一个测试并分支即可。

    等效地,您可以将数组包装在带有访问器的类中,该访问器将为您执行测试,并为越界元素返回一些安全的false值。

    无论哪种方式,您都需要进行很多分支,但是封装的版本看起来更像原始代码,而行为却与第二个版本完全相同。

  2. 将前哨值添加到数组中。

    也就是说,将数组扩大到[SIZE+1][SIZE+1][SIZE+1],然后将安全的假值存储在前哨条目array[SIZE][*][*]array[*][SIZE][*]array[*][*][SIZE]

    权衡是您不再需要代码中的测试和分支,但是数组变得更大,并且引用的位置更糟。

  3. 明确写出您的不同情况。

    您已经写了[0..SIZE-2][0..SIZE-2][0..SIZE-2]案例-现在写了[SIZE-1][0..SIZE-2][0..SIZE-2][SIZE-1][SIZE-1][0..SIZE-2]和孤立的远端[SIZE-1][SIZE-1][SIZE-1]剩下的三个案例。

    此版本为您提供最小的数据,具有最少的不必要分支,但具有最高的复杂性。