0

我正在用 C++ 编写一个程序,从一组投影的 2D 图像中重建一个 3D 对象,其中计算最密集的部分涉及通过双线性插值放大和移动每个图像。我目前有两个功能可以完成这项任务;“blnSetup”在循环外定义了一些参数,然后“双线性”在循环内逐点应用插值:

(注意:'I' 是一个包含有序图像数据行的一维数组)

//Pre-definition structure (in header)
struct blnData{
    float* X;
    float* Y;
    int* I;
    float X0;
    float Y0;
    float delX;
    float delY;
};

//Pre-definition function (outside the FOR loop)
extern inline blnData blnSetup(float* X, float* Y, int* I)
{
    blnData bln;
    //Create pointers to X, Y, and I vectors
    bln.X = X;
    bln.Y = Y;
    bln.I = I;

    //Store offset and step values for X and Y
    bln.X0 = X[0];
    bln.delX = X[1] - X[0];
    bln.Y0 = Y[0];
    bln.delY = Y[1] - Y[0];

    return bln;
}

//Main interpolation function (inside the FOR loop)
extern inline float bilinear(float x, float y, blnData bln)
{
    float Ixy;

    //Return -1 if the target point is outside the image matrix
    if (x < bln.X[0] || x > bln.X[-1] || y < bln.Y[0] || y > bln.Y[-1])
        Ixy = 0;
    //Otherwise, apply bilinear interpolation
    else
    {
        //Define known image width
        int W = 200;

        //Find nearest indices for interpolation
        int i = floor((x - bln.X0) / bln.delX);
        int j = floor((y - bln.Y0) / bln.delY);

        //Interpolate I at (xi, yj)
        Ixy = 1 / ((bln.X[i + 1] - bln.X[i])*(bln.Y[j + 1] - bln.Y[j])) *
            (
            bln.I[W*j + i] * (bln.X[i + 1] - x) * (bln.Y[j + 1] - y) +
            bln.I[W*j + i + 1] * (x - bln.X[i]) * (bln.Y[j + 1] - y) +
            bln.I[W*(j + 1) + i] * (bln.X[i + 1] - x) * (y - bln.Y[j]) +
            bln.I[W*(j + 1) + i + 1] * (x - bln.X[i]) * (y - bln.Y[j])
            );
    }

    return Ixy;
}

编辑:函数调用如下。“flat.imgdata”是一个包含输入图像数据的 std::vector,“proj.imgdata”是一个包含转换后图像的 std::vector。

int Xs = flat.dim[0];
int Ys = flat.dim[1];

int* Iarr = flat.imgdata.data();
float II, x, y;

bln = blnSetup(X, Y, Iarr);

for (int j = 0; j < flat.imgdata.size(); j++)
{
    x = 1.2*X[j % Xs];
    y = 1.2*Y[j / Xs];
    II = bilinear(x, y, bln);
    proj.imgdata[j] = (int)II;
}

自从我开始优化以来,通过在插值函数中从 std::vectors 切换到 C 数组,我已经能够将计算时间减少约 50 倍(!),并通过清理冗余计算/类型转换/等再减少 2 倍左右,但是假设 O(n) 和 n 是处理像素的总数,完整的重建(~7e10 像素)仍然需要 40 分钟左右——比我的目标 <5 分钟长一个数量级。

根据 Visual Studio 的性能分析器,插值函数调用 ("II = bilinear(x, y, bln);") 毫无疑问仍然是我计算负载的主要部分。我还没有找到任何用于快速多重插值的线性代数方法,所以我的问题是:这基本上和我的代码一样快,除了对任务应用更多或更快的 CPU 吗?或者是否有不同的方法可以加快速度?

PS 我现在也只用 C++ 编码了大约一个月,所以请随时指出我可能犯的任何初学者错误。

4

2 回答 2

2

我写了一个长答案,建议查看 OpenCV (opencv.org),或使用 Halide ( http://halide-lang.org/ ),并了解如何优化图像变形,但我认为更短的答案可能会更好. 如果您真的只是缩放和翻译整个图像,OpenCV 有代码可以做到这一点,我们也有一个在 Halide 中调整大小的示例(https://github.com/halide/Halide/blob/master/apps/resize/resize .cpp)。

如果您确实有一种算法需要使用浮点坐标来索引图像,而浮点坐标的计算结果无法转化为整数坐标上的适度简单函数,那么您真的希望在 GPU 上使用过滤纹理采样。大多数在 CPU 上进行优化的技术都依赖于利用算法中的一些常规访问模式,并从寻址中删除浮点到整数的转换。(对于调整大小,一个使用两个整数变量,一个索引图像的像素坐标,另一个是坐标的小数部分,它索引一个权重内核。)如果这是不可能的,那么加速就会受到一定的限制在 CPU 上。OpenCV 确实提供了相当通用的重新映射支持,但它可能不是那么快。

这里可能适用的两个优化是尝试将边界条件移出循环并使用两遍方法,其中水平和垂直尺寸分别处理。后者可能会赢也可能不会赢,如果图像非常大,则需要平铺数据以适应缓存。一般来说,平铺对于大图像非常重要,但不清楚这是这里的一阶性能问题,并且取决于输入中的值,缓存行为可能无论如何都不够规则。

于 2017-08-31T18:49:22.387 回答
2

“向量比数组慢 50 倍”。那是您处于调试模式的死赠品,vector::operator[]没有内联。只需切换到发布模式,您可能会获得必要的加速,甚至更多。

作为奖励,vector有一个.back()方法,所以你有一个适当的替代品[-1]。指向数组开头的指针不包含数组大小,因此您无法以这种方式找到数组的后面。

于 2017-09-01T09:36:37.670 回答