我想尽可能高效地在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
答案 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 ++编译器让我担心),但可能工作。我不提供任何保证,但试一试。
这个想法可能有用,因为你的template_grid
大小和范围是不变的 - 因此在编译时就知道了。
另外,为了实现这个目的,你的image
和template_grid
必须使用相同的布局(第一列或第一行)进行组织 - 问题中描述“示例代码”的方式将SAD x/y
与template_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,这一切都无关紧要。