C ++中绝对差值计算的可变块大小和

时间:2011-11-11 20:32:29

标签: c++ algorithm computer-vision

我想尽可能高效地在C ++程序中使用16位整数的二维数组执行可变块大小sum of absolute difference计算。我对实时块匹配代码感兴趣。我想知道是否有可用的软件库?代码在Windows XP上运行,我使用Visual Studio 2010进行编译。 CPU是2核AMD Athlon 64 x2 4850e。

通过可变块大小的绝对差值和(SAD)计算,我的意思如下。

我有一个较小的2-D数组,我将调用template_grid,而一个较大的2-D数组我将调用image。我想找到图像中的区域,该区域最小化模板中像素与图像中区域中像素之间的绝对差值之和。

在C ++中计算SAD的最简单方法是:

for(int shiftY = 0; shiftY < rangeY; shiftY++) {
    for(int shiftX = 0; shiftX < rangeX; shiftX++) {
        for(int x = 0; x < lenTemplateX; x++) {
            for(int y = 0; y < lenTemplateY; y++) {
                SAD[shiftY][shiftX]=abs(template_grid[x][y] - image[y + shiftY][x + shiftX]);
            }
        }
    }
}

特定阵列大小的SAD计算已在英特尔性能原语库中进行了优化。但是,我正在使用的数组不符合这些库中的大小。

我使用的搜索范围有两个,

大范围:范围Y = 45,范围X = 10

小范围:范围Y = 4,范围X = 2

只有一个模板大小,它是: lenTemplateY = 61,lenTemplateX = 7

3 个答案:

答案 0 :(得分:3)

次要优化:

for(int shiftY = 0; shiftY < rangeY; shiftY++) {
  for(int shiftX = 0; shiftX < rangeX; shiftX++) {
    // if you can assume SAD is already filled with 0-es, 
    // you don't need the next line
    SAD[shiftX][shiftY]=0;
    for(int tx = 0, imx=shiftX; x < lenTemplateX; tx++,imx++) {
      for(int ty = 0, imy=shiftY; y < lenTemplateY; ty++,imy++) {
        // two increments of imx/imy may be cheaper than 
        // two addition with offsets
        SAD[shiftY][shiftX]+=abs(template_grid[tx][ty] - image[imx][imy]);
      }
    }
  }
}

使用C ++模板循环展开

对于您的配置可能是一个疯狂的想法(C ++编译器让我担心),但可能工作。我不提供任何保证,但试一试。

这个想法可能有用,因为你的template_grid大小和范围是不变的 - 因此在编译时就知道了。
另外,为了实现这个目的,你的imagetemplate_grid必须使用相同的布局(第一列或第一行)进行组织 - 问题中描述“示例代码”的方式将SAD x/ytemplate_grid y/x混合在一起。
在下文中,我将假设“第一列”组织,以便SAD[ix]表示ix矩阵的SAD** th 列。对于“第一行”,代码变得相同,除了变量的名称与值数组的含义不匹配。

所以,让我们开始吧:

template <
  typename sad_type, typename val_type,
  size_t template_len
> struct sad1D_simple {
  void operator()(
    const val_type* img, const val_type* templ,
    sad_type& result
  ) {
    // template specialization recursion, with one less element to add
    sad1D_simple<sad_type, val_type, template_len-1> one_shorter;
    // call it incrementing the img and template offsets
    one_shorter(img+1, templ+1, result);
    // the add the contribution of the first diff we skipped over above
    result+=abs(*(img+template_len-1)-*(templ+template_len-1));
  }
};

// at len of 0, the result is zero. We need it to stop the
template <
  typename sad_type, typename val_type
>
struct sad1D_simple<sad_type, val_type, 0> {
  void operator()(
    const val_type* img, const val_type* templ,
    sad_type& result
  ) {
    result=0;
  }
};

为什么一个functor struct - struct with operator? C ++不允许部分专业化功能模板 sad1D_simple做了什么:基于for数组的长度是基于以下事实,展开计算输入中两个数组的SAD的{​​{1}}周期而没有任何抵消编译时已知的常量。它与“使用C ++模板计算编译时间的因子”相同 -

这有什么用?
在下面的代码中使用的示例:

template_grid
嗯......我们可以做得更好吗?不,它不会是X轴展开,我们仍然希望保持在1D区域,但是......好吧,也许,如果我们创建一个范围typedef ulong SAD_t; typedef int16_t pixel_val_t; const size_t lenTemplateX = 7; // number of cols in the template_grid const size_t lenTemplateY = 61; const size_t rangeX=10, rangeY=45; pixel_val_t **image, **template_grid; SAD_t** SAD; // assume those are initialized somehow for(size_t tgrid_col=0; tgrid_col<lenTemplateX; tgrid_col++) { pixel_val_t* template_col=template_grid[tgrid_col]; // the X axis - horizontal - is the column axis, right? for(size_t shiftX=0; shiftX < rangeX; shiftX++) { pixel_val_t* img_col=image[shiftX]; for(size_t shiftY = 0; shiftY < rangeY; shiftY++) { // the Y axis - vertical - is the "offset in a column"=row, isn't it? pixel_val_t* img_col_offsetted=img_col+shiftY; // this functor is made by recursive specialization // there's no cycle inside it, it was unrolled into // lenTemplateY individual subtractions, abs-es and additions sad1D_simple<SAD_t, pixel_val_t, lenTemplateY> calc; calc(img_col_offsetted, template_col, SAD[shiftX][shiftY]); } } } 并在同一轴上再展开一个循环?如果 f sad1D也是常数,它将工作

rangeX

以下是你如何使用它:

template <
  typename sad_type, typename val_type,
  size_t range, size_t template_len
> struct sad1D_ranged {
  void operator()(
    const val_type* img, const val_type* templ,
    // result is assumed to have at least `range` slots
    sad_type* result
  ) {
    // we'll compute here the first slot of the result
    sad1D_simple<sad_type, val_type, template_len>
      calculator_for_first_sad;
    calculator_for_first_sad(img, templ, *(result));

    // now, ask for a recursive specialization for 
    // the next (range-1) sad-s
    sad1D_ranged<sad_type, val_type, range-1, template_len>
       one_less_in_range;
    // when calling, pass the shifted img and result
    one_less_in_range(img+1, templ, result+1);
  }
};

// for a range of 0, there's nothing to do, but we need it
// to stop the template specialization recursion
template <
  typename sad_type, typename val_type,
  size_t template_len
> struct sad1D_ranged<sad_type, val_type, 0, template_len> {
  void operator()(
    const val_type* img, const val_type* templ,
    // result is assumed to have at least `range` slots
    sad_type* result
  ) {
  }
};

是的......但问题是:会改善效果吗? 如果我知道的话。对于循环内的少量循环和强数据局部性(值彼此关闭以使它们在CPU缓存中),循环展开应该提高性能。对于更多的循环,您可能会对CPU分支预测和其他mumbo-jumbo-I-know-may-may-impact-performance-but-I-not-know-how产生负面干扰。

胆量的感觉:即使相同的展开技术可能适用于其他两个循环,使用它可能会导致性能下降:我们需要从一个连续的向量(for(size_t tgrid_col=0; tgrid_col<lenTemplateX; tgrid_col++) { pixel_val_t* template_col=template_grid[tgrid_col]; for(size_t shiftX=0; shiftX < rangeX; shiftX++) { pixel_val_t* img_col=image[shiftX]; SAD_t* sad_col=SAD[shiftX]; sad1D_ranged<SAD_t, pixel_val_t, rangeY, lenTemplateY> calc; calc(img_col, template_col, sad_col); } } 列跳转)到另一个 - 整个图像可能不适合CPU缓存。

注意:如果您的image数据也是常量(或者您有一组有限的常量模板网格),可以更进一步,创建带有专用掩码的struct functor 。但是今天我已经失去了动力。

答案 1 :(得分:0)

您可以尝试使用与方差参数匹配的OpenCV模板,请参阅教程here。 OpenCV使用OpenCL进行了优化,但我不知道这个特定的功能。我想你应该试一试。

答案 2 :(得分:0)

我不确定你有多少限制使用SAD,或者你通常有兴趣在图像中找到最适合模板的区域。在最后一种情况下,您可以使用卷积而不是SAD。这可以在O(N log N)的傅里叶域中求解,包括傅立叶变换(FFT)。

简而言之,您可以使用FFT(例如使用http://www.fftw.org/)将模板和图像转换为频域,然后将它们相乘,然后转换回时域。

当然,如果您一定要使用SAD,这一切都无关紧要。