最近研究了一下opencv的 MorphologyEx这个函数的替代功能, 他主要的特点是支持任意形状的腐蚀膨胀,对于灰度图,速度基本和CV的一致,但是 CV没有针对二值图做特殊处理,因此,这个函数对二值图的速度和灰度是一样的,但是这个函数,如果使用的话,估计大部分还是针对二值图像,因此,我对二值图做了特别优化,速度可以做到是CV这个函数的4倍左右。

MorphologyEx的主要功能是对灰度图进行相关形态学的处理,比如腐蚀、膨胀、开闭等计算,其代码可以在github上找到:https://github.com/opencv/opencv/blob/master/modules/imgproc/src/morph.dispatch.cpp#L1160

  opencv的这个代码,1000多行,从头看到尾,就没有看到几句和算法本身有关的内容,仔细看下里面有下面的代码:

他不是调用Opencl就是使用IPP库,还是自己去想算法的优化吧。

其实这个算法的优化我在很多年前就一直在考虑,只是一直么有动手,主要是无思路。最近在研究模板匹配的时候,因为有需求,做了下带蒙版功能的NCC匹配,对于这个类似的算法也就有了想法。

在正常情况下,我们的核是矩形的或者是圆形的,对于矩形核,在SSE图像算法优化系列七:基于SSE实现的极速的矩形核腐蚀和膨胀(最大值和最小值)算法 一文中已经提出了优化算法,对于圆形半径,在【短道速滑八】圆形半径的图像最大值和最小值算法的实现及其实时优化(非二值图) 一文也提出了解决方案,两种方案都非常的高效和快速。

如果是任意形状的核,考虑到其无固定的规律,上述常规的优化手段都无法完成,如何弄呢。

我对这个算法想过很久,那么最近我得到的结论是肯定不能整体做优化,我想到的就是把蒙版区域按水平方向或者垂直方向分割成一条一条或者一列一列的小块,每个小块单独执行类似的算法,那么比如一个9*9的蒙版,如果其中的连续的小块有20个,那最多也就是标准矩形算法的20倍耗时(实际是不需要的,以为有很多公共计算),而矩形算法的速度是非常非常高效的。

实践了下,这个做法是有效的,而且也是相对来说高效的,但是后面想了下,为什么要分割成一条一条的呢,毕竟有很多条条的宽度或者高度是一样的,可以把他们作为一个整体合并成一个Rectangle,对一个Rectanle进行处理,和标准的矩形核的算法也不是一样的吗。

  如下所示,如果按照列方向一次一个列,则有31个列,但是如果是将相同高的列合并,则只有19个,数量减少了近一半。

              

          原图                    列方向的分块矩形                行方向的分块矩形

  实践表明,这种处理后,整体能有效的提高计算速度。

至于是选择列方向的分块矩形还是行方向的,则和算法本身的优化有一定的关系,比如在本例中,由于SIMD的特性,我们在计算腐蚀或者膨胀的时候,利用的有关的G值和H值在垂直方向计算时可方便的使用SIMD指令进行比较,因此,选择列方向的分块则更为有利。

  那么对于二值图像的腐蚀和膨胀,我们在超越halcon速度的二值图像的腐蚀和膨胀,实现目前最快的半径相关类算法(附核心源码) 一文中有提高一种更为特别的优化手段。那么这个手段但让也可以用到本例中来。而在Opencv中,MorphologyEx函数是没有对这个做特殊处理的。

  我们做下简单的速度比较:

  对一副 500万像素的图进行 31*31的 椭圆蒙版进行处理,本例耗时约为95ms, CV耗时约为 250ms。

但是奇怪的是,如果在CV中把蒙版的尺寸设置为偶数,比如30*30,其执行速度会快很多,比如同样上述图,CV的耗时只有78毫秒了,和我这里速度差不多,目前还不知道这个问题是怎么引起的。

  相关测试代码如下:

  1. IplConvKernel *kernel0 = cvCreateStructuringElementEx(31, 31, 15, 15, CV_SHAPE_ELLIPSE);
  2.  
  3. for (int i = 0; i < 100; i++)
  4. cvMorphologyEx(Src, Dest, NULL, kernel0, CV_MOP_DILATE, 1);
  5.  
  6. cvReleaseStructuringElement(&kernel0);
  7.  
  8. QueryPerformanceCounter(&t2);
  9. printf("Use Time:%f\n", (t2.QuadPart - t1.QuadPart)*1.0 / tc.QuadPart * 1000);
  10.  
  11. IplConvKernel *kernel1 = cvCreateStructuringElementEx(30, 30, 15, 15, CV_SHAPE_ELLIPSE);
  12. QueryPerformanceCounter(&t1);
  13.  
  14. for (int i = 0; i < 100; i++)
  15. cvMorphologyEx(Src, Dest, NULL, kernel1, CV_MOP_DILATE, 1);
  16.  
  17. cvReleaseStructuringElement(&kernel1);
  18. QueryPerformanceCounter(&t2);
  19. printf("Use Time:%f\n", (t2.QuadPart - t1.QuadPart)*1.0 / tc.QuadPart * 1000);
  20.  
  21. IplConvKernel *kernel2 = cvCreateStructuringElementEx(15, 15, 7, 7, CV_SHAPE_ELLIPSE);
  22. QueryPerformanceCounter(&t1);
  23.  
  24. for (int i = 0; i < 100; i++)
  25. cvMorphologyEx(Src, Dest, NULL, kernel2, CV_MOP_DILATE, 1);
  26.  
  27. cvReleaseStructuringElement(&kernel2);
  28. QueryPerformanceCounter(&t2);
  29. printf("Use Time:%f\n", (t2.QuadPart - t1.QuadPart)*1.0 / tc.QuadPart * 1000);
  30.  
  31. IplConvKernel *kernel3 = cvCreateStructuringElementEx(14, 14, 7, 7, CV_SHAPE_ELLIPSE);
  32. QueryPerformanceCounter(&t1);
  33.  
  34. for (int i = 0; i < 100; i++)
  35. cvMorphologyEx(Src, Dest, NULL, kernel3, CV_MOP_DILATE, 1);
  36. cvReleaseStructuringElement(&kernel3);
  37.  
  38. QueryPerformanceCounter(&t2);
  39. printf("Use Time:%f\n", (t2.QuadPart - t1.QuadPart)*1.0 / tc.QuadPart * 1000);

CV的耗时统计如下(100次循环计算的耗时)。

如果是同样一份大小的二值图像,在本例只需22ms,CV的耗时则还是和上面的一样。

这里也不得不说一句,Intel的IPP的优化功能真的也还是不错。

使用halcon的也做了类似的测试,halcon里对于规则的图像有一些特别的函数,比如 gray_dilation_rect, gray_dilation_shape,他的这些算子和我的标准优化的版本速度差不多。而对于其他的自定义形状,则要使用read_gray_se读取一个固定格式的文件。当然对于我们上面使用的椭圆, halcon已经有个一个定义好的函数gen_disc_se。

对于椭圆,在Halcon中我用下述代码测试:

  1. read_image (Image, 'd:/1.bmp')
  2. gen_disc_se (SE, 'byte', 31, 31, 0)
  3. for J := 0 to 100 - 1 by 1
  4. gray_dilation(Image, SE, ImageDilation)
  5. endfor

得到的速度结果非常吓人,循环100次也只要1600ms,那意味着每次只要16ms,比我这里要快5倍多,真是牛逼,然后看了下gray_dilation的说明文档,在Parallelization一项里有这个说明:Automatically parallelized on internal data level.即在在内部数据级别自动并行。同时观察上述代码运行时的CPU运行情况,CPU的使用率高达80%左右,我个人分析他内部是做了多核并行的。这样的话我还可以接受的。我的机器是4核单位,如果我的速度除以4,嗨嗨。

说到这,我正好也抽空研究了下read_gray_se这个函数,如果我要在Halcon里实现其他非规则形状的腐蚀,只能通过这个函数,这个函数的需要从文件里读取一些列数据,而这个文件我在百度搜索,基本没看到有详细的说明,Halcon帮助文档里到时有说明,不过有点晦涩,我这里正好解释下,也当做给自己做个笔记,不然时间长了,我自己也不记得了。

他对文件(文本文件)的要求如下:

  第一行指明类型,可以是一下三个字符中的一种:   'byte', 'uint2' or 'real'     注意不要带引号,对于图像数据,一般用byte

    第二行,是指 structuring element 的尺寸,宽度 +  空格 + 高度

    第三行,这个比较重要,他的意思我们可以这样理解, 就是按照单行方向考虑,你需要计算腐蚀和膨胀的 连续区域的总数量。

接下来的每一行数据, 都必然是3个数字,每个数字之间用空格隔开, 第一个数据是指这个行所在的行号(以0为下标起点),第二个数据只区域的起点,  第三个数据只区域的 终点。

这些行的行数必须和第三行的数字对应,而且不能超过高度和宽度的范围。

    接下来的数据就是Halcon独有的了,我的和CV的都不具有这个功能,他还能指定structuring element 每个位置对应的偏移量值,就在对应位置的元素值加上这个偏移量值作为计算腐蚀和膨胀的依据,可正可负。这个确实比较强大,但是测试表明,如果有这些值,函数的计算速度可能会急剧下降,比如前面的测试代码中gen_disc_se (SE, 'byte', 31, 31, 0), 如果更改为gen_disc_se (SE, 'byte', 31, 31, 1),100次的速度会立即增加到8秒多。

比如前面对应的31*31的椭圆区域的SE就可以用下述字符描述:

  1. byte
  2. 31 31
  3. 31
  4. 0 12 18
  5. 1 9 21
  6. 2 7 23
  7. 3 6 24
  8. 4 5 25
  9. 5 4 26
  10. 6 3 27
  11. 7 2 28
  12. 8 2 28
  13. 9 1 29
  14. 10 1 29
  15. 11 1 29
  16. 12 0 30
  17. 13 0 30
  18. 14 0 30
  19. 15 0 30
  20. 16 0 30
  21. 17 0 30
  22. 18 0 30
  23. 19 1 29
  24. 20 1 29
  25. 21 1 29
  26. 22 2 28
  27. 23 2 28
  28. 24 3 27
  29. 25 4 26
  30. 26 5 25
  31. 27 6 24
  32. 28 7 23
  33. 29 9 21
  34. 30 12 18

不过,由这个结构,也可以窥探到,Halcon内部的Region结构可能用的是这种单行的RLE编码,而不是基于Rectangle的。

上面的例子可能不是很好,因为他正好是一行只有一个结构,其实一行是可以是有多个,比如下面的数据:

  1. byte
  2. 5 5
  3. 8
  4. 0 1 4
  5. 1 0 0
  6. 1 3 3
  7. 2 2 3
  8. 2 4 4
  9. 3 0 4
  10. 4 0 1
  11. 4 4 4

本例相关测试结果可参考:https://files.cnblogs.com/files/Imageshop/MaskFilter.rar?t=1652089081

如果想时刻关注本人的最新文章,也可关注公众号:

超越OpenCV速度的MorphologyEx函数实现(特别是对于二值图,速度是CV的4倍左右)。的更多相关文章

  1. 使用OpenCV查找二值图中最大连通区域

    http://blog.csdn.net/shaoxiaohu1/article/details/40272875 使用OpenCV查找二值图中最大连通区域 标签: OpenCVfindCoutour ...

  2. Java基于opencv实现图像数字识别(三)—灰度化和二值化

    Java基于opencv实现图像数字识别(三)-灰度化和二值化 一.灰度化 灰度化:在RGB模型中,如果R=G=B时,则彩色表示灰度颜色,其中R=G=B的值叫灰度值:因此,灰度图像每个像素点只需一个字 ...

  3. opencv统计二值图黑白像素个数

    #include "iostream" #include "queue" #include "Windows.h" #include < ...

  4. opencv删除二值图中较小的噪点色块

    CvSeq* contour = NULL; double minarea = 100.0; double tmparea = 0.0; CFileDialog dlg(true); if (dlg. ...

  5. OpenCV中对图像进行二值化的关键函数——cvThreshold()。

    函数功能:采用Canny方法对图像进行边缘检测 函数原型: void cvThreshold( const CvArr* src, CvArr* dst, double threshold, doub ...

  6. 【OpenCV新手教程之十一】 形态学图像处理(二):开运算、闭运算、形态学梯度、顶帽、黑帽合辑

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/23184547 作者:毛星云(浅墨) ...

  7. OpenCV图像的二值化

    图像的二值化: 与边缘检测相比,轮廓检测有时能更好的反映图像的内容.而要对图像进行轮廓检测,则必须要先对图像进行二值化,图像的二值化就是将图像上的像素点的灰度值设置为0或255,这样将使整个图像呈现出 ...

  8. opencv 删除二值化图像中面积较小的连通域

    对于上图的二值化图像,要去除左下角和右上角的噪点,方法:使用opencv去掉黑色面积较小的连通域. 代码 CvSeq* contour = NULL; double minarea = 100.0; ...

  9. [opencv]常用阵列操作函数总结

    /*=========================================================================*/ // 阵列操作 /*============ ...

随机推荐

  1. vue2与vue3的区别

    template <template> <div class="wrap"> <div>{{ num }}</div> <Bu ...

  2. CommonCollection1反序列化学系

    CommonsCollection1 1.前置知识 1.1.反射基础知识 1.1.1. 对象与类的基础知识 类(class),对象(object) 对象是类的实例化,中华田园犬(object)是狗(c ...

  3. 印度下架54款中国APP,中东政策逐年收紧,伊拉克成蓝海市场

    2月14日,印度电子和信息技术部以"安全威胁"为由对有中国基因的54款App下达禁令,15日,印度税务部门对某些在印中资企业多个场所进行搜查. 本批下架的App中主要以应用类App ...

  4. w3schools网站的HTML教程之HTML编辑器

    使用记事本或文本编辑器编写 HTML HTML 可以使用如下专业的 HTML 编辑器进行编辑: Microsoft WebMatrix Sublime Text 然而,我们推荐使用记事本(PC)或文本 ...

  5. Kurento安装与入门08——Group Call

    Group Call 本示例展示了一个视频聊天室的功能,不同的聊天室之间互相隔离. 官网文档 Group Call 首先从github上获取代码(如果已经获取可以跳过,获取的代码已经包括后面的示例代码 ...

  6. 设计模式-观察者模式Observe的实现

    using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> / ...

  7. java中接口interface有什么用呢?举例!

    接口只有方法的定义,没有方法的任何实现.那这有什么意义呢?马克-to-win: 接口就像一个服务合同.接口只关心必须得干什么而不关心如何去实现它.有 意义吗?有意义.马克-to-win:比如我们的软件 ...

  8. 微信小程序插件组件-Taro UI

    微信小程序组件使用以下官网查看 ↓  ↓  ↓ https://taro-ui.jd.com/#/docs/fab

  9. linux系统从pci.ids文件获取硬件设备详细厂商信息

    机器采样: [root@ht24 hwdata]# cat /etc/redhat-release ; uname -r CentOS Linux release 7.9.2009 (Core) 3. ...

  10. 为什么说TCP协议是可靠的?

    TCP 实现可靠通信的两种方式 我们都知道 IP 协议是"不太靠谱".因为 IP 协议是不可靠的,所以 IP 数据包可能在传输过程中发生错误或者丢失.这就意味着,TCP 协议不得不 ...