在位图上同时侵蚀或扩散

时间:2018-01-29 12:39:09

标签: c++ algorithm image-processing concurrency openmp

我需要在位图上进行形态学操作(扩展/侵蚀的专门版本)。 为了加速,我使用openmp来并行化这个过程:

  int* bitmap = ...;              // pointer to bitmap data with width and height
#pragma omp parallel for
   for(long iy=0; iy<height; iy++) 
      for(long ix=0; ix<width; ix++) 
         if(write_pattern(ix,iy)) 
            apply_pattern(ix,iy, 0);   // writes "0" to bitmap, write only, no read

这意味着在某些位置将一个常量值模式写入输出位图。由于&#39;模式&#39;可能会跨越几行,很明显,几个线程同时将相同的值写入同一个内存位置。它似乎工作,但它看起来有点阴暗。

这样可以,或者推荐的方法是什么?

2 个答案:

答案 0 :(得分:2)

我不太关心并行化,但我想指出,并行化扩张/侵蚀不是我在这里选择的第一选择。

通过扩张/侵蚀,您可以对像素周围的结构元素执行最大/最小操作。例如,如果你有一个5x5的窗口,你可以看到每个像素有25个像素,所以从本质上讲,你会看到每个像素25次。因此,使用这种天真的方法,每个像素的计算复杂度与结构元素的大小成正比。

使用更高效的计算形态运算符的算法,无论结构元素的大小如何,您都可以降低这种复杂性,甚至降低复杂度(每个像素)。关于这方面有很多文献,我最后还提到了一些参考文献,他们引用了其他论文并进行了比较。

我不了解您的工作环境,以及表现的重要性。但并行化将是我正在做的最后一步。首先,无论性能如何,我都会运行算法。一旦我对此感到满意(或者我愿意为此做点什么而烦恼),我就会提高效率。如果最后,我仍然需要稍微推动运行时间,我并行化(或者考虑是否有意义去GPU)。

如果你现在并行化,你可能会获得一些加速,但你会失去提高性能的算法改进。

现在,正如所承诺的那样,两篇关于高效形态滤波器的论文: Petr Dokládal, Eva Dokladalova. Computationally efficient, one-pass algorithm for morphological filters. Journal of Visual Communication and Image Representation, Elsevier, 2011,这个根据结构元素呈现O(1)算法,并比较/引用经典有效算法。 Joseph Gil, Ron Kimmel. Efficient Dilation, Erosion, Opening and Closing Algorithms也很好看。我没有详细阅读,但我知道Ron Kimmel来自我的研究领域,而且它可能是一个很好的。

答案 1 :(得分:2)

OpenMP是一种共享内存范例。如果您可以保证所有不同的进程都将相同的值写入相同的位置,那么让它们这样做是可以的。有竞争条件,但它们不会影响结果。

这为代码提供了这个:

int* bitmap = ...;              // pointer to bitmap data with width and height

#pragma omp parallel for collapse(2)
for(long iy=0; iy<height; iy++) 
for(long ix=0; ix<width; ix++) 
  if(write_pattern(ix,iy)) 
    apply_pattern(ix,iy, 0);   // writes "0" to bitmap, write only, no read

如果您无法做出这样的保证,可以使用关键部分来限制写入权限:

int* bitmap = ...;              // pointer to bitmap data with width and height

#pragma omp parallel for collapse(2)
for(long iy=0; iy<height; iy++) 
for(long ix=0; ix<width; ix++) 
  if(write_pattern(ix,iy)) {
    #pragma omp critical
    apply_pattern(ix,iy, 0);   // writes "0" to bitmap, write only, no read      
  }

但是,如果您无法做出这样的保证,那么这是并行性的糟糕候选者,因为每次运行时,不同的进程会在不同的时间到达位图的不同部分码。也就是说,执行顺序是非确定性的,因此您将无法预测结果。假设只有一个正确的结果,你的进程必须产生相同的输出。

同样,请确保使用apply_pattern()来更改原始数据以外的其他内容,否则您的结果将再次变为非确定性,因为一个线程可能会在另一个线程看到修改后修改输入部分。

另一种策略是收集所有需要修改的地方,然后再串行编写。如果您的支票价格昂贵但写作便宜,这可能有意义;比方说,如果您正在使用正则表达式进行检查并写出零。

int* bitmap = ...;              // pointer to bitmap data with width and height

typedef std::pair<long,long> gridloc;
std::vector<gridloc> patterns;

//Use a custom reduction operator, available in newer versions of OpenMP
#pragma omp declare reduction (merge : std::vector<gridloc> : omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end()))
#pragma omp parallel for collapse(2) reduction(merge:patterns)
for(long iy=0; iy<height; iy++) 
for(long ix=0; ix<width; ix++) 
  if(write_pattern(ix,iy)) {
    patterns.emplace_back(ix,iy)
  }

for(const auto &c: patterns)
  apply_pattern(c.first,c.second, 0);   // writes "0" to bitmap, write only, no read      

如果写入成本很高,您可以对patterns的内容进行一些处理,以组合写入,消除冗余写入,拓扑排序写入,&amp; c。