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%的提升的。
还是感觉这种算法用文字很难表述清楚,用代码再加上自己的空间组合可能更能理解吧。
OpenCv关于灰度积分图的SSE代码学习和改进。的更多相关文章
- SSE图像算法优化系列六:OpenCv关于灰度积分图的SSE代码学习和改进。
最近一直沉迷于SSE方面的优化,实在找不到想学习的参考资料了,就拿个笔记本放在腿上翻翻OpenCv的源代码,无意中看到了OpenCv中关于积分图的代码,仔细研习了一番,觉得OpenCv对SSE的灵活运 ...
- Opencv中integral计算积分图
Paul Viola和Michael Jones在2001年首次将积分图应用在图像特征提取上,在他们的论文"Rapid Object Detection using a Boosted Ca ...
- AdaBoost中利用Haar特征进行人脸识别算法分析与总结1——Haar特征与积分图
原地址:http://blog.csdn.net/watkinsong/article/details/7631241 目前因为做人脸识别的一个小项目,用到了AdaBoost的人脸识别算法,因为在网上 ...
- 目标检测之积分图---integral image 积分图2
前面在图像处理一栏中涉及到boxfilter 的时候,简单介绍过积分图,就是每个像素点是左边和上边的累加和,这样的话可以方便均值和方差,以及直方图统计的相关运算,这里再次结合网络资源重新单独对积分图做 ...
- AI 积分图
积分图(Integral Image),可以用于快速计算矩形特征.积分图每个位置(x, y)的值,等于原图对应位置的左上角所有像素点的值之和.因为“积分”在离散情况下就是求和,所以这也是积分图的命名由 ...
- SSE指令集学习:Compiler Intrinsic
大多数的函数是在库中,Intrinsic Function却内嵌在编译器中(built in to the compiler). 1. Intrinsic Function Intrinsic Fun ...
- SSE再学习:灵活运用SIMD指令6倍提升Sobel边缘检测的速度(4000*3000的24位图像时间由180ms降低到30ms)。
这半年多时间,基本都在折腾一些基本的优化,有很多都是十几年前的技术了,从随大流的角度来考虑,研究这些东西在很多人看来是浪费时间了,即不能赚钱,也对工作能力提升无啥帮助.可我觉得人类所谓的幸福,可以分为 ...
- u-boot代码学习内容
前言 u-boot代码庞大,不可能全部细读,只能有选择的读部分代码.在读代码之前,根据韦东山教材,关于代码学习内容和深度做以下预先划定. 一.Makefile.mkconfig.config.mk等 ...
- Objective-C代码学习大纲(3)
Objective-C代码学习大纲(3) 2011-05-11 14:06 佚名 otierney 字号:T | T 本文为台湾出版的<Objective-C学习大纲>的翻译文档,系统介绍 ...
随机推荐
- QQ_MultiTalkServer
package test_teacher;import java.net.*;import java.io.*;public class MultiTalkServer{ public stat ...
- 微信小程序,前端大梦想(二)
微信小程序的视图与渲染 今天我们从四个方面来了解小程序: •组件的基本使用 •数据绑定 •渲染标签 •模板的使用 一.组件的基本使用: 微信小程序为我们的开发提供了丰富的UI组件 ...
- CF #349 div1 B. World Tour
题目链接:http://codeforces.com/problemset/problem/666/B 大意是给一张有向图,选取四个点,使得走这四个点,任意两个点之间走最短路,总距离最长. 3000个 ...
- webstorm配置编译sass的输出目录
关于这个的问题,虽说不是很难,但还是踩了点小坑,下面就来介绍下如何使用webstorm配置编译sass的输出目录. 1.下载Ruby 2.使用Ruby安装sass 3.检测是否安装成功. 前面的几步很 ...
- cmapx 保存绘制好的图层
研究了两天,如何保存一绘制好的图层,大致意思都说要使用mapInfo表,然后确定了可定和.TAB表有关.然而网上说的全是垃圾,也不能说全是垃圾,好歹我从中得到了一点点有用的信息,使用mapManage ...
- 【算法系列学习】巧妙建图,暴搜去重 Counting Cliques
E - Counting Cliques http://blog.csdn.net/eventqueue/article/details/52973747 http://blog.csdn.net/y ...
- PHP三维数组拼装
foreach ($records as $k => $v) { foreach ($records as $m => $n) { if ($v['days'] == $n['days'] ...
- VR全景:互联网与实体店的完美结合
VR元年已过,VR项目.VR创业潮转为理性,VR行业分为两个方向:硬件和内容.硬件又分为VR头显和辅助设备,内容又分为VR全景和VR虚拟内容,如游戏.娱乐.根据行业划分为VR+购物,VR+教育,VR ...
- linux之vi编辑器的基础命令
1,假如要在这个php文件的phpinfo.php;之后加入一行,我们可以先按键盘的"a",光标就会跳转到之前绿色光标之后,也就是说,"a"是代表在当前光标之后 ...
- [笔记]ACM笔记 - 排序小技巧
Description 一个数组,要求先对前n个数字排序(以方便后续操作):又要求对前n+i个数字排序:又要求对前n+j - 前n+k个数字排序(i.j.k的大小远小于n,且i.j.k间没有大小关系) ...