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学习大纲>的翻译文档,系统介绍 ...
随机推荐
- Android 开源优秀的项目
webrtc square/picasso Android 的一个强大的图像下载和缓存库 A powerful image downloading and caching library for A ...
- Keepalived高可用集群实践
(1)实践的硬件环境准备 准备4台物理服务器或者4台VM虚拟机,其中两台用来做Keepalived服务器,两台做web测试站点 HOSTNAME I P 解 释 lb01 10.0.0.7 K ...
- PPT要你好看---读书笔记
PPT要你好看.主要是设计的思维. 下图,对于现阶段的我来说,收获最大的是毕业答辩PPT的制作. 以及整体的PPT制作思路.
- jQuery修炼心得-DOM节点的插入
1. 内部插入append()与appendTo() append:这个操作与对指定的元素执行原生的appendChild方法,将它们添加到文档中的情况类似. appendTo:实际上,使用这个方法是 ...
- 1.1Hibernate持久化类和Hibernate持久化对象状态
一.持久化对象po类 1.po定义 PO,是Persistent Object的缩写,是持久化类.PO是由PO=POJO+hbm映射配置组成. 2.通俗理解 PO类即持久化类,其实就是一个普通的Jav ...
- 解决 MySQL 分页数据错乱重复
前言 一天,小明兴匆匆的在通讯工具上说:这边线上出现了个奇怪的问题,麻烦 DBA 大大鉴定下,执行语句 select xx from table_name wheere xxx order by 字段 ...
- Java并发包分析——BlockingQueue
之前因为找实习的缘故,博客1个多月没有写了.找实习的经历总算告一段落,现在重新更新博客,这次的内容是分析Java并发包中的阻塞队列 关于阻塞队列,我之前是一直充满好奇,很好奇这个阻塞是怎么实现.现在我 ...
- 分布式开放消息系统(RocketMQ)的原理与实践(转)
转自:http://www.jianshu.com/p/453c6e7ff81c 分布式消息系统作为实现分布式系统可扩展.可伸缩性的关键组件,需要具有高吞吐量.高可用等特点.而谈到消息系统的设计,就回 ...
- IDEA2017使用Maven方式配置Mybatis-Generator
一,创建Maven项目 至此Maven项目创建完成. 二,在Maven项目的pom.xml中添加mybatis-generator-maven-plugin插件 创建Maven项目后,打开pom.xm ...
- hadoop进不去web界面
查看配置文件是否正确:hdfs-site.xml文件 <property> <name>dfs.http.address</name> <value>h ...