【问题标题】:Concurrent erode or dilate on bitmap位图上的并发腐蚀或膨胀
【发布时间】:2018-07-08 03:52:51
【问题描述】:

我需要对位图进行形态学操作(扩张/侵蚀的特殊版本)。 为了加速,我使用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

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

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

【问题讨论】:

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


【解决方案1】:

我不太介意并行化,但我想指出,并行化膨胀/腐蚀不是我在这里选择的首选。

通过膨胀/腐蚀,您可以对像素周围的结构元素执行最大/最小操作。例如,如果你有一个 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,它可能是一本不错的书。

【讨论】:

  • 好答案。大多数情况下,算法在性能方面都优于并行性,尽管它们可能需要更多的精力来编写代码。
  • 虽然不是 100% 适用于我的问题,但它让我得出结论,将结构元素拆分为多个块,其像素位于连续的内存位置。现在我并行处理这些段。这具有以下优点:段不重叠(没有激烈的写入)和更好的缓存利用率。
【解决方案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 的内容进行一些处理,以组合写入、消除冗余写入、对写入进行拓扑排序等。

【讨论】:

  • 我想简要总结一下这个答案:如果您使用并行,请不要更改输入图像,而是写入不同的(输出)图像。
  • 并添加到它:如果您不使用并发,也不要为此类过滤器写入输入图像,除非您确切知道自己在做什么并且效果是您的一部分算法。
  • 我终于找到了argument 为什么它可能不起作用,即使线程“产生相同的输出”。编译器做的事情有时有点吓人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-29
  • 2010-12-01
相关资源
最近更新 更多