SSE图像算法优化系列六:OpenCv关于灰度积分图的SSE代码学习和改进。
最近一直沉迷于SSE方面的优化,实在找不到想学习的参考资料了,就拿个笔记本放在腿上翻翻OpenCv的源代码,无意中看到了OpenCv中关于积分图的代码,仔细研习了一番,觉得OpenCv对SSE的灵活运用真的做的很好,这里记录下我对该段代码的品味并将其思路扩展到其他通道数的图像。
该核心代码位于:Opencv 3.0\opencv\sources\modules\imgproc\src\sumpixels.cpp文件中。
我们贴出最感兴趣的一部分代码以便分析:
- bool operator()(const uchar * src, size_t _srcstep,int * sum, size_t _sumstep,double * sqsum, size_t, int * tilted, size_t,Size size, int cn) const
- {
- if (sqsum || tilted || cn != || !haveSSE2) return false;
- // the first iteration
- memset(sum, , (size.width + ) * sizeof(int));
- __m128i v_zero = _mm_setzero_si128(), prev = v_zero;
- int j = ;
- // the others
- for (int i = ; i < size.height; ++i)
- {
- const uchar * src_row = src + _srcstep * i;
- int * prev_sum_row = (int *)((uchar *)sum + _sumstep * i) + ;
- int * sum_row = (int *)((uchar *)sum + _sumstep * (i + )) + ;
- sum_row[-] = ;
- prev = v_zero;
- j = ;
- for ( ; j + < size.width; j += )
- {
- __m128i vsuml = _mm_loadu_si128((const __m128i *)(prev_sum_row + j));
- __m128i vsumh = _mm_loadu_si128((const __m128i *)(prev_sum_row + j + ));
- __m128i el8shr0 = _mm_loadl_epi64((const __m128i *)(src_row + j));
- __m128i el8shr1 = _mm_slli_si128(el8shr0, );
- __m128i el8shr2 = _mm_slli_si128(el8shr0, );
- __m128i el8shr3 = _mm_slli_si128(el8shr0, );
- vsuml = _mm_add_epi32(vsuml, prev);
- vsumh = _mm_add_epi32(vsumh, prev);
- __m128i el8shr12 = _mm_add_epi16(_mm_unpacklo_epi8(el8shr1, v_zero),
- _mm_unpacklo_epi8(el8shr2, v_zero));
- __m128i el8shr03 = _mm_add_epi16(_mm_unpacklo_epi8(el8shr0, v_zero),
- _mm_unpacklo_epi8(el8shr3, v_zero));
- __m128i el8 = _mm_add_epi16(el8shr12, el8shr03);
- __m128i el4h = _mm_add_epi16(_mm_unpackhi_epi16(el8, v_zero),
- _mm_unpacklo_epi16(el8, v_zero));
- vsuml = _mm_add_epi32(vsuml, _mm_unpacklo_epi16(el8, v_zero));
- vsumh = _mm_add_epi32(vsumh, el4h);
- _mm_storeu_si128((__m128i *)(sum_row + j), vsuml);
- _mm_storeu_si128((__m128i *)(sum_row + j + ), vsumh);
- prev = _mm_add_epi32(prev, _mm_shuffle_epi32(el4h, _MM_SHUFFLE(, , , )));
- }
- for (int v = sum_row[j - ] - prev_sum_row[j - ]; j < size.width; ++j)
- sum_row[j] = (v += src_row[j]) + prev_sum_row[j];
- }
为了说明更方便,这里贴出我做的普通C语言的代码和重新优化后的SSE代码。
普通C语言:
- void GetGrayIntegralImage(unsigned char *Src, int *Integral, int Width, int Height, int Stride)
- {
- memset(Integral, , (Width + ) * sizeof(int)); // 第一行都为0
- for (int Y = ; Y < Height; Y++)
- {
- unsigned char *LinePS = Src + Y * Stride;
- int *LinePL = Integral + Y * (Width + ) + ; // 上一行位置
- int *LinePD = Integral + (Y + ) * (Width + ) + ; // 当前位置,注意每行的第一列的值都为0
- LinePD[-] = ; // 第一列的值为0
- for (int X = , Sum = ; X < Width; X++)
- {
- Sum += LinePS[X]; // 行方向累加
- LinePD[X] = LinePL[X] + Sum; // 更新积分图
- }
- }
- }
优化后的SSE算法:
- void GetGrayIntegralImage(unsigned char *Src, int *Integral, int Width, int Height, int Stride)
- {
- memset(Integral, , (Width + ) * sizeof(int)); // 第一行都为0
- int BlockSize = , Block = Width / BlockSize;
- for (int Y = ; Y < Height; Y++)
- {
- unsigned char *LinePS = Src + Y * Stride;
- int *LinePL = Integral + Y * (Width + ) + ; // 上一行位置
- int *LinePD = Integral + (Y + ) * (Width + ) + ; // 当前位置,注意每行的第一列的值都为0
- LinePD[-] = ;
- __m128i PreV = _mm_setzero_si128();
- __m128i Zero = _mm_setzero_si128();
- for (int X = ; X < Block * BlockSize; X += BlockSize)
- {
- __m128i Src_Shift0 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(LinePS + X)), Zero); // A7 A6 A5 A4 A3 A2 A1 A0
- __m128i Src_Shift1 = _mm_slli_si128(Src_Shift0, ); // A6 A5 A4 A3 A2 A1 A0 0
- __m128i Src_Shift2 = _mm_slli_si128(Src_Shift1, ); // 移位改成基于Shift0,速度慢,Why? // A5 A4 A3 A2 A1 A0 0 0
- __m128i Src_Shift3 = _mm_slli_si128(Src_Shift2, ); // A4 A3 A2 A1 A0 0 0 0
- __m128i Shift_Add12 = _mm_add_epi16(Src_Shift1, Src_Shift2); // A6+A5 A5+A4 A4+A3 A3+A2 A2+A1 A1+A0 A0+0 0+0
- __m128i Shift_Add03 = _mm_add_epi16(Src_Shift0, Src_Shift3); // A7+A4 A6+A3 A5+A2 A4+A1 A3+A0 A2+0 A1+0 A0+0
- __m128i Low = _mm_add_epi16(Shift_Add12, Shift_Add03); // A7+A6+A5+A4 A6+A5+A4+A3 A5+A4+A3+A2 A4+A3+A2+A1 A3+A2+A1+A0 A2+A1+A0+0 A1+A0+0+0 A0+0+0+0
- __m128i High = _mm_add_epi32(_mm_unpackhi_epi16(Low, Zero), _mm_unpacklo_epi16(Low, Zero)); // A7+A6+A5+A4+A3+A2+A1+A0 A6+A5+A4+A3+A2+A1+A0 A5+A4+A3+A2+A1+A0 A4+A3+A2+A1+A0
- __m128i SumL = _mm_loadu_si128((__m128i *)(LinePL + X + ));
- __m128i SumH = _mm_loadu_si128((__m128i *)(LinePL + X + ));
- SumL = _mm_add_epi32(SumL, PreV);
- SumL = _mm_add_epi32(SumL, _mm_unpacklo_epi16(Low, Zero));
- SumH = _mm_add_epi32(SumH, PreV);
- SumH = _mm_add_epi32(SumH, High);
- PreV = _mm_add_epi32(PreV, _mm_shuffle_epi32(High, _MM_SHUFFLE(, , , )));
- _mm_storeu_si128((__m128i *)(LinePD + X + ), SumL);
- _mm_storeu_si128((__m128i *)(LinePD + X + ), SumH);
- }
- for (int X = Block * BlockSize, V = LinePD[X - ] - LinePL[X - ]; X < Width; X++)
- {
- V += LinePS[X];
- LinePD[X] = V + LinePL[X];
- }
}
我们先来解释下这段代码的SSE优化过程吧。
首先,用_mm_loadl_epi64一次性加载8个字节数据到XMM寄存器中,其中寄存器的高8位位0,此时寄存器的数据为:
高位 0 0 0 0 0 0 0 0 A7 A6 A5 A4 A3 A2 A1 A0 低位 (8位)
因为涉及到加法,并且最大为8个字节数据的加法,因此转换到16位数据类型,使用_mm_unpacklo_epi8结合zero即可实现。
此时XMM寄存器内容变为:
- Src_Shift0 A7 A6 A5 A4 A3 A2 A1 A0 (16位)
此后有3次移位分别得到:
- Src_Shift1 A6 A5 A4 A3 A2 A1 A0 0 (16位)
- Src_Shift2 A5 A4 A3 A2 A1 A0 0 0 (16位)
- Src_Shift3 A4 A3 A2 A1 A0 0 0 0 (16位)
- 通过_mm_add_epi16分别对4组16位数据进行8次相加:
- Shift_Add12 A6+A5 A5+A4 A4+A3 A3+A2 A2+A1 A1+A0 A0+0 0+0 (16位)
- Shift_Add03 A7+A4 A6+A3 A5+A2 A4+A1 A3+A0 A2+0 A1+0 A0+0 (16位)
- 再对他们进行相加:
- Low A7+A6+A5+A4 A6+A5+A4+A3 A5+A4+A3+A2 A4+A3+A2+A1 A3+A2+A1+A0 A2+A1+A0+0 A1+A0+0+0 A0+0+0+0
注意到低4位的16位数已经是连续相加的数据了,只要将他们转换为32位就可以直接使用。
而通过 __m128i High = _mm_add_epi32(_mm_unpackhi_epi16(Low, Zero), _mm_unpacklo_epi16(Low, Zero)); 这一句则可以把前面的高4位连续相加的值拼接起来得到:
High A7+A6+A5+A4+A3+A2+A1+A0 A6+A5+A4+A3+A2+A1+A0 A5+A4+A3+A2+A1+A0 A4+A3+A2+A1+A0
- 后面的操作则顺理成章了。
注意到我核心的改动在于原始代码中的el8shr12和el8shr03的计算中的_mm_unpacklo_epi8被消除了,而在el8shr0一句中增加了一个_mm_unpacklo_epi8,因此少了3次这个函数,很明显这样做是不会改变计算结果的。
另外源代码中的部分_mm_add_epi16被我用_mm_add_epi32代替了,这主要是因为用_mm_add_epi32意义更明显,而且由于高位数据为0,他们的执行结果不会有任何区别。
还有一点在测试时发现,如果Src_Shift2,Src_Shift3的移位是基于Src_Shift0,即使用如下代码:
- __m128i Src_Shift2 = _mm_slli_si128(Src_Shift0, );
- __m128i Src_Shift3 = _mm_slli_si128(Src_Shift0, );
速度会有较为明显的下降,难道说移动的位数多少和CPU的耗时有关?
以上是灰度模式的算法,在我的笔记本电脑上,SSE优化后的语句虽然增加了很多,但是执行效率约能提升30%,不过在一些PC上,普通的C和SSE优化后却没有啥速度区别了,这也不知道是为什么了。
如果是针对24位或者32位图像,基本的优化思想是一致的,不过有更多的细节需要自己注意。
24位或者32位图像在任何机器配置上,速度都能有30%的提升的。
还是感觉这种算法用文字很难表述清楚,用代码再加上自己的空间组合可能更能理解吧。
SSE图像算法优化系列六:OpenCv关于灰度积分图的SSE代码学习和改进。的更多相关文章
- OpenCv关于灰度积分图的SSE代码学习和改进。
最近一直沉迷于SSE方面的优化,实在找不到想学习的参考资料了,就拿个笔记本放在腿上翻翻OpenCv的源代码,无意中看到了OpenCv中关于积分图的代码,仔细研习了一番,觉得OpenCv对SSE的灵活运 ...
- SSE图像算法优化系列十四:局部均方差及局部平方差算法的优化。
关于局部均方差有着较为广泛的应用,在我博客的基于局部均方差相关信息的图像去噪及其在实时磨皮美容算法中的应用及使用局部标准差实现图像的局部对比度增强算法中都有谈及,即可以用于去噪也可以用来增强图像,但是 ...
- SSE图像算法优化系列十三:超高速BoxBlur算法的实现和优化(Opencv的速度的五倍)
在SSE图像算法优化系列五:超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现) 一文中,我曾经说过优化后的ExpBlur比BoxBlur还要快,那个时候我比较的BoxBlur ...
- SSE图像算法优化系列二十二:优化龚元浩博士的曲率滤波算法,达到约1000 MPixels/Sec的单次迭代速度
2015年龚博士的曲率滤波算法刚出来的时候,在图像处理界也曾引起不小的轰动,特别是其所说的算法的简洁性,以及算法的效果.执行效率等方面较其他算法均有一定的优势,我在该算法刚出来时也曾经有关注,不过 ...
- SSE图像算法优化系列二十五:二值图像的Euclidean distance map(EDM)特征图计算及其优化。
Euclidean distance map(EDM)这个概念可能听过的人也很少,其主要是用在二值图像中,作为一个很有效的中间处理手段存在.一般的处理都是将灰度图处理成二值图或者一个二值图处理成另外一 ...
- SSE图像算法优化系列二十三: 基于value-and-criterion structure 系列滤波器(如Kuwahara,MLV,MCV滤波器)的优化。
基于value-and-criterion structure方式的实现的滤波器在原理上其实比较简单,感觉下面论文中得一段话已经描述的比较清晰了,直接贴英文吧,感觉翻译过来反而失去了原始的韵味了. T ...
- SSE图像算法优化系列二十:一种快速简单而又有效的低照度图像恢复算法。
又有很久没有动笔了,主要是最近没研究什么东西,而且现在主流的趋势都是研究深度学习去了,但自己没这方面的需求,同时也就很少有动力再去看传统算法,今天一个人在家,还是抽空分享一个简单的算法吧. 前段日子在 ...
- SSE图像算法优化系列十:简单的一个肤色检测算法的SSE优化。
在很多场合需要高效率的肤色检测代码,本人常用的一个C++版本的代码如下所示: void IM_GetRoughSkinRegion(unsigned char *Src, unsigned char ...
- SSE图像算法优化系列十二:多尺度的图像细节提升。
无意中浏览一篇文章,中间提到了基于多尺度的图像的细节提升算法,尝试了一下,还是有一定的效果的,结合最近一直研究的SSE优化,把算法的步骤和优化过程分享给大家. 论文的全名是DARK IMAGE ENH ...
随机推荐
- 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
假设你正在爬楼梯.需要 n 阶你才能到达楼顶. 每次你可以爬 1 或 2 个台阶.你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数. 示例 1: 输入: 2 输出: 2 解释: 有两 ...
- Linux下C语言的进程控制编程
代码: #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/ty ...
- pageHelper多个sql分页
之前有个需求,在一个页面中需要有多个sql分页查询然后放到一个list中,展示,但是会出现一个bug,就是每次分页都会展示第一条查出的所有的数据: 第一页 第二页 因为是截的生产环境,第一条数据被处理 ...
- 初识Linux系统
1. pwd 显示现在所在位置 2. ls 显示目录下的文件 ls -a:显示隐藏文件(带 . 的就是隐藏文件): ls -a -l :每个文件夹的详细信息: ls > bbb (把查到的所有文 ...
- 2018年中国研究生数学建模竞赛C题 二等奖 赛题论文
2018年中国研究生数学建模竞赛C题 对恐怖袭击事件记录数据的量化分析 恐怖袭击是指极端分子或组织人为制造的.针对但不仅限于平民及民用设施的.不符合国际道义的攻击行为,它不仅具有极大的杀伤性与破坏力, ...
- NIO缓冲区基本操作:rewind(),clear(),flip()
rewind() rewind()方法将position置0,清除mark,它的作用在于为提取Buffer的有效数据做准备. ByteBuffer byteBuf = ByteBuffer.alloc ...
- linux学习笔记 其他常用命令
cd + 回车 = cd ~ 进入当前用户主目录 查看指定进程信息 *ps -ef |grep 进程名 *ps -查看属于自己的进程 *ps -aux 查看所有的用户的执行进程 换成 ps - ...
- Summary
PDF 暑假开始准备转移博客,试了几个都不怎么满意(我还去试了下LineBlog 不知道那时候在想什么..),屯着一堆文章,,到时候一起发了 现在暂时转移至WordPress,不过还在完善中,预计.. ...
- BZOJ.2000.[HNOI2010]stone取石头游戏(博弈)
BZOJ 洛谷 低估这道神题了_(:з」∠)_ MilkyWay好狠啊(小声) \(Description\) 有一些数字,被分成若干双端队列(从两边都可以取)和最多两个栈(只能从某一边一个一个取)的 ...
- BZOJ.4182.Shopping(点分治/dsu on tree 树形依赖背包 多重背包 单调队列)
BZOJ 题目的限制即:给定一棵树,只能任选一个连通块然后做背包,且每个点上的物品至少取一个.求花费为\(m\)时最大价值. 令\(f[i][j]\)表示在点\(i\),已用体积为\(j\)的最大价值 ...