基于SSE4和多核编程的电子相册的实现
摘要:电子相册中前后两张图片的切换会产生淡入淡出效果,而且切换过程中需要大量的中间计算过程,而SSE4和多核编程技术能够有效加快中间的计算过程,有效减少图片之间切换时间,本文将对基于SSE4和多核编程的电子相册的实现过程进行详细说明。
关键词:电子相册;淡入淡出;SSE4;多核编程
1. 引言
在电子相册中,前后两张图片,由前一张图片完全切换为后一张图片的过程中,如果将中间结果展现出来的话,就会出现图片的淡入淡出的效果,但是由于像素较高的图片之间切换过程需要大量的计算过程,使用普通的串行程序完成整个切换过程需要消耗较长的时间,而考虑使用SSE4和多核编程将使整个切换过程所需的时间显著减少,本次实验我们具体验证通过使用这两项技术对图片的切换过程产生怎样的影响。
2. 关键技术
2.1 SSE4
SSE4指令集的英文全称是:Streaming SIMD Extensions 4,是英特尔自从SSE2之后对ISA扩展指令集最大的一次的升级扩展。除了将延续多年的32位架构升级至64位之外,还加入了图形、视频编码、字符串/文本处理、三维游戏应用等指令,使得处理器不但在多媒体,而且在文本处理、矢量化编译、特定应用等方面的应用性能都得到了大幅度的提升。新指令集增强了从多媒体应用到高性能计算应用领域的性能,同时还利用一些专用电路实现对于特定应用加速。SSE4.1版本的指令集新增加了47条指令,主要针对向量绘图运算、3D游戏加速、视频编码加速及协同处理的加速。英特尔方面指出,在应用SSE4指令集后,45纳米Penryn核心额外提供了2个不同的32位向量整数乘法运算支持,并且在此基础上还引入了8位无符号最小值和最大值以及16位、32位有符号和无符号的运算,能够有效地改善编译器编译效率,同时提高向量化整数和单精度运算地能力。SSE4.2则是在新一代Nehalem架构基于Core微架构的SSE4.1指令集上,新增的7组指令主要针对字符串和文本处理指令应用。
2.2 多核编程
多核编程的基本目的是通过多个任务的并行执行提高应用程序的性能。这就需要将一个应用程序进行任务划分:尽量分解为多个相对独立的任务,每个任务实现一个线程,从而将多个任务分布到多个计算核上执行,减少程序的执行时间。并行程序的设计模式大致分为:并发性发现、算法结构设计、结构支撑和实现机制4个步骤。首先要分析待解决的问题是否值得去并行,然后确定问题的主要特征和数据元素,最后识别问题的哪一部分是计算最密集的。通过算法结构的设计,将并发性映射到多个线程或进程,进一步向并行程序靠近。
Windows环境下的多线程编程:MFC用类库的方式将Win32API进行封装,提供对多线程的支持。MFC有两种类型的线程:用户界面线程和辅助线程。
OpenMP是一个针对共享内存架构的多线程编程标准,用于编程可移植的多线程应用程序。OpenMP程序设计模型提供了一组与平台无关的编译指导、指导命令、函数调用和环境变量,显示地指导编译器开发程序中的并行性。对于很多循环来说,都可以在循环体之前插入一条编译指导语句,使其以多线程执行。
3. 实验思路
(1) 由于C++可以直接使用内嵌原语,故本实验采用C++语言编写。
(2) 使用C++的基于对话框的MFC程序实现图像的操作及相应的图像淡入淡出效果。
4. 实验环境
(1) 操作系统:win7 32位
(2) 编程工具:Visual Studio 2017
(3) 编程语言:C++
(4) 图片规格:*.BMP格式的文件,分辨率为1280×800
5. 实验过程及代码实现
5.1 加载图像到内存
为了贴合多核思想,本实验所采用的6张图片都是BMP格式的图片,它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大,满足实验要求。本实验设置了比较功能,即在普通情况下的图片切换,所以需要两个存储位图的数组,加载图像核心代码如下:
#define MAX_COUNT 6
CBitmap bmps[MAX_COUNT];
CBitmap mmp[MAX_COUNT];
for (int i = 0; i < MAX_COUNT; i++)
{
bmps[i].LoadBitmap(IDB_BITMAP1 + i);
}
for (int i = 0; i < MAX_COUNT; i++)
{
mmp[i].LoadBitmap(IDB_BITMAP1 + i);
}
5.2 获取图像的像素点
在淡入淡出的过程中,需要对像素点进行融合。首先需要获取图像的像素点。核心代码如下:
BITMAP b; //声明位图文件b
bmps[m].GetBitmap(&b);
//获取第一张图片
int size = b.bmHeight*b.bmWidthBytes; //获取位图字节数
BYTE *lp1 = new BYTE[size];
bmps[m].GetBitmapBits(size, lp1); //获取第一张图片的像素列
BITMAP b2; //声明位图文件b2
bmps[n].GetBitmap(&b2); //获取第二张图片,由于图片大小相等,所以不用重复计算字节数
BYTE *lp2 = new BYTE[size];
bmps[n].GetBitmapBits(size, lp2); //获取第二张图片的像素列
//GetBitmapBits()函数将指定位图的位拷贝到缓冲区里。
BYTE *lp3 = new BYTE[size]; //用来显示中间图像
}
此方法分别获取图像A和图像B的像素值数列lp1、lp2,并定义lp3用来存储中间图像的像素。
5.3 实现图像的淡入淡出
像素点的融合公式:
Result_pixel=A_pixel×fade+B_pixel×(1-fade)
将上式做简单变换,减少一次乘法,得:
Result_pixel=(A_pixel-B_pixel)×fade+B_pixel
在fade因子从127渐变为0的过程中,产生了一系列A、B融合的中间图像,实现了由A图像淡化到B图像的过程。在实际计算过程中,为了方便计算,fade因子从127渐变为0,计算完成后再右移7位,以此达到从1渐变为0的效果。一个像素为4个字节32位,解紧缩后为4个字64位;SSE4寄存器为128位,可存储8个字,即2个解紧缩后的像素,故可用于同时计算两个像素,加快运行速度。SSE4内嵌原语的使用需在程序加入以下语句:
#include <nmmintrin.h>
同时在循环过程中,内层嵌套每次递增8个,即4个两像素同时计算,体现了多核思想,进一步加快了融合速度。
内层嵌套递增数值的要求:1)为宽度的约数,如本例中宽度为1280,可取4、8等;2)应符合机器128位寄存器个数要求,如本例需使用10个寄存器,8个用于像素值的存储、计算,1个用于fade值的存储(xmm0),1个用于紧缩及解紧缩操作(xmm8),以减少可能的数据及结构冲突。
在循环开始前,利用#pragma omp parallel for private(4,x)对X进行四线程并行计算,进一步体现了多核思想。
核心代码如下:
for (int fade = 127; fade >= 0; fade--) //
{
for (int y = 0; y < b.bmHeight; y++)
{
#pragma omp parallel forprivatc(4,x) //4线程并行计算
for (int x = 0; x < b.bmWidth; x = x + 8) //4个两像素同时计算
{
rgb[0] = y*b.bmWidthBytes + x * 4;
rgb[1] = y*b.bmWidthBytes + (x + 1) * 4;
rgb[2] = y*b.bmWidthBytes + (x + 2) * 4;
rgb[3] = y*b.bmWidthBytes + (x + 3) * 4;
rgb[4] = y*b.bmWidthBytes + (x + 4) * 4;
rgb[5] = y*b.bmWidthBytes + (x + 5) * 4;
rgb[6] = y*b.bmWidthBytes + (x + 6) * 4;
rgb[7] = y*b.bmWidthBytes + (x + 7) * 4;
//A的两个像素8字节
pic1[0] = lp1[rgb[0]];
pic1[1] = lp1[rgb[0] + 1];
pic1[2] = lp1[rgb[0] + 2];
pic1[3] = lp1[rgb[0] + 3];
pic1[4] = lp1[rgb[1]];
pic1[5] = lp1[rgb[1] + 1];
pic1[6] = lp1[rgb[1] + 2];
pic1[7] = lp1[rgb[1] + 3];
//B的两个像素8字节
pic2[0] = lp2[rgb[0]];
pic2[1] = lp2[rgb[0] + 1];
pic2[2] = lp2[rgb[0] + 2];
pic2[3] = lp2[rgb[0] + 3];
pic2[4] = lp2[rgb[1]];
pic2[5] = lp2[rgb[1] + 1];
pic2[6] = lp2[rgb[1] + 2];
pic2[7] = lp2[rgb[1] + 3];
//余下的3个两像素的定义相同
__m128i xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6,xmm7,xmm9;
__m128i xmm8 = _mm_setzero_si128();
//初始化寄存器
xmm0 = _mm_set_epi16(fade, fade, fade, fade, fade, fade, fade, fade);
//8个fade因子装入寄存器
xmm1 = _mm_loadu_si128((__m128i*)pic1);//A的两个像素分量装入寄存器
xmm1 = _mm_unpacklo_epi8(xmm1, xmm8);//8个一位解紧缩至16位
xmm2 = _mm_loadu_si128((__m128i*)pic2);//B的两个像素分量装入寄存器
xmm2 = _mm_unpacklo_epi8(xmm2, xmm8);//8个一位解紧缩至16位
xmm1 = _mm_sub_epi16(xmm1, xmm2); //A-B
xmm1 = _mm_mullo_epi16(xmm1, xmm0);//8个16位乘法
xmm1 = _mm_srai_epi16(xmm1, 7);//右移7位,相当于除127
xmm1 = _mm_add_epi16(xmm1, xmm2);//加法
xmm1 = _mm_packus_epi16(xmm1,xmm8);//16个一位紧缩为8位
//依次提取每个整型,放入待显示像素数组
lp3[rgb[0]] = _mm_extract_epi8(xmm1, 0);
lp3[rgb[0] + 1] = _mm_extract_epi8(xmm1, 1);
lp3[rgb[0] + 2] = _mm_extract_epi8(xmm1, 2);
lp3[rgb[0] + 3] = _mm_extract_epi8(xmm1, 3);
lp3[rgb[1]] = _mm_extract_epi8(xmm1, 4);
lp3[rgb[1] + 1] = _mm_extract_epi8(xmm1, 5);
lp3[rgb[1] + 2] = _mm_extract_epi8(xmm1, 6);
lp3[rgb[1] + 3] = _mm_extract_epi8(xmm1, 7);
//余下3个两像素的计算类似
}
}
bitmap.SetBitmapBits(size, lp3);
Ondraw();
}
delete lp1;
delete lp2;
delete lp3;
}