最近研究了一下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毫秒了,和我这里速度差不多,目前还不知道这个问题是怎么引起的。

  相关测试代码如下:

    IplConvKernel *kernel0 = cvCreateStructuringElementEx(31, 31, 15, 15, CV_SHAPE_ELLIPSE);

    for (int i = 0; i < 100; i++)
cvMorphologyEx(Src, Dest, NULL, kernel0, CV_MOP_DILATE, 1); cvReleaseStructuringElement(&kernel0); QueryPerformanceCounter(&t2);
printf("Use Time:%f\n", (t2.QuadPart - t1.QuadPart)*1.0 / tc.QuadPart * 1000); IplConvKernel *kernel1 = cvCreateStructuringElementEx(30, 30, 15, 15, CV_SHAPE_ELLIPSE);
QueryPerformanceCounter(&t1); for (int i = 0; i < 100; i++)
cvMorphologyEx(Src, Dest, NULL, kernel1, CV_MOP_DILATE, 1); cvReleaseStructuringElement(&kernel1);
QueryPerformanceCounter(&t2);
printf("Use Time:%f\n", (t2.QuadPart - t1.QuadPart)*1.0 / tc.QuadPart * 1000); IplConvKernel *kernel2 = cvCreateStructuringElementEx(15, 15, 7, 7, CV_SHAPE_ELLIPSE);
QueryPerformanceCounter(&t1); for (int i = 0; i < 100; i++)
cvMorphologyEx(Src, Dest, NULL, kernel2, CV_MOP_DILATE, 1); cvReleaseStructuringElement(&kernel2);
QueryPerformanceCounter(&t2);
printf("Use Time:%f\n", (t2.QuadPart - t1.QuadPart)*1.0 / tc.QuadPart * 1000); IplConvKernel *kernel3 = cvCreateStructuringElementEx(14, 14, 7, 7, CV_SHAPE_ELLIPSE);
QueryPerformanceCounter(&t1); for (int i = 0; i < 100; i++)
cvMorphologyEx(Src, Dest, NULL, kernel3, CV_MOP_DILATE, 1);
cvReleaseStructuringElement(&kernel3); QueryPerformanceCounter(&t2);
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中我用下述代码测试:

read_image (Image, 'd:/1.bmp')
gen_disc_se (SE, 'byte', 31, 31, 0)
for J := 0 to 100 - 1 by 1
gray_dilation(Image, SE, ImageDilation)
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就可以用下述字符描述:

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

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

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

byte
5 5
8
0 1 4
1 0 0
1 3 3
2 2 3
2 4 4
3 0 4
4 0 1
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. kafka生产者网络层总结

    1 层次结构 负责进行网络IO请求的是NetworkClient,主要层次结构如下 ClusterConnectionStates报存了每个节点的状态,以node为key,以node的状态为value ...

  2. 你对 Spring Boot 有什么了解?

    事实上,随着新功能的增加,弹簧变得越来越复杂.如果必须启动新的 spring 项 目,则必须添加构建路径或添加 maven 依赖项,配置应用程序服务器,添加 spring 配置.所以一切都必须从头开始 ...

  3. 滑动窗口法——Leetcode例题

    滑动窗口法--Leetcode例题(连更未完结) 1. 方法简介 滑动窗口法可以理解为一种特殊的双指针法,通常用来解决数组和字符串连续几个元素满足特殊性质问题(对于字符串来说就是子串).滑动窗口法的显 ...

  4. CAN总线系列讲座第五讲——CAN总线硬件电路设计

    CAN总线系列讲座第五讲--CAN总线硬件电路设计一  实战学习背景 CAN总线节点的硬件构成方案有两种,其中的方案:(1).MCU控制器+独立CAN控制器+CAN收发器.独立CAN控制器如SJA10 ...

  5. 顺利通过EMC实验(13)

  6. WebView的一些简单用法

    一直想写一个关于 WebView 控件的 一些简单运用,都没什么时间,这次也是挤出时间写的,里面的一些基础知识就等有时间再更新讲解一下,今天就先把项目出来做一些简单介绍,过多的内容可以看我的源码,都传 ...

  7. 如何形成一个完整的HTML对象

    写在前面,本文将同步发布于Blog.掘金.segmentfault.知乎等处,如果本文对你有帮助,记得为我得到我的个人技术博客项目给个star哦. 为何写这篇文章? 你可能做Web开发已经有一段时间, ...

  8. PAT B1061判断题

    题目描述: 判断题的评判很简单,本题就要求你写个简单的程序帮助老师判题并统计学生们判断题的得分. 输入格式: 输入在第一行给出两个不超过 100 的正整数 N 和 M,分别是学生人数和判断题数量.第二 ...

  9. java中的访问控制有什么用?如何用法?请举例

    9.访问控制 [新手可忽略不影响继续学习] 访问控制有什么用?在软件公司里是这么用的,我们想像一种场景,在你的类中,你编了三个私有方法,马克-to-win,别人当然都用不了,但在类外,你也是用不了的, ...

  10. css实现半圆效果

    效果图: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...