利用英特尔高级矢量扩展指令集进行 Wiener 过滤

1 内容简介
英特尔® 高级矢量扩展指令集(英特尔® AVX)是一套针对英特尔® SIMD 流指令扩展(英特尔® SSE)的 256 位扩展指令集,专为浮点密集型应用而设计。对于过分依赖浮点运算的应用,如 3D 几何、视频处理、图像处理和空间 (3D) 音频,这些指令为加快其运行速度提供了一种方法。本应用注释介绍了 Wiener 过滤,并包含一个已经使用英特尔® AVX 进行优化的代码示例。本文中代码和应用注释的源代码参考为利用 SIMD 流指令扩展 [4] 的 AP-807 Wiener 过滤。原始文章包含利用 SIMD 流指令扩展的优化。此外,本文还介绍了如何将代码迁移到 256 位扩展指令集(即英特尔® AVX)。


2 Wiener 过滤器算法
Wiener 过滤也称为最小均方过滤,是一种用于消除图像中不必要的噪声的技术。对该算法的描述摘自 Harley R Myler 和 Aruthur R. Weeks [1] 编著的《C 语言中图像处理算法手册》(Pocket Handbook of ImageProcessing Algorithms in C)。该算法包含四个(经傅里叶变换)矢量输入值,分别代表(一部分)源图像(图像)、降质图像 (Guv)、噪声图像光谱(噪声)和退化函数 (Huv)。每个输入值均是一个 row*col 复数矢量。该复数表示为两个连续的浮点数,代表数字的真实部分和想象部分。计算结果中还包括另一个参数 gamma。当 gamma 为 1.0 时,过滤器被认为是非参数型。直到经过过滤的图像满足要求,才能对过滤器的参数进行调整。


2.1 支持 Wiener 过滤器的应用
Wiener 过滤器通常用在图像处理应用中,用于消除重建图像中的噪声。Wiener 过滤通常用于还原模糊图像。然而,经证实 Wiener 过滤器在自适应滤波中非常重要,曾用于微波变换,并且在通信和其它 DSP 相关领域中得到应用。读者还应意识到傅里叶变换是所有信号处理领域中的一个关键要素。如欲了解有关如何实施傅里叶变换的更多信息,请参考英特尔应用注释 Split-Radix FFT (AP-808)。


2.2 实施 Wiener 过滤
如 2.1 节中所述,函数的输入值为四组复数。对于图像的每一部分而言,以下运算均采用复数算法进行。复杂变量 D 和 Hs 为运算过程中使用的中间变量。函数 Complex_conj 用于提取复数的复共轭。进行除法运算时,必须利用 if 语句进行检查,确保分母不为零。当分母为零时,应将结果设置为零。
1. 复杂噪声 = gamma *(噪声* Complex_conj(噪声))
2. 复杂变量 D = 图像 * Complex_conj(图像)
3. 复杂变量 D = 噪声/D
4. 复杂变量 Hs = Huv * Complex_conj ( Huv )
5. 复数 = Complex_conj ( Huv ) * Guv
6. 复杂图像 = 数字/(Hs + D)


3 利用 128 位 SIMD 对代码进行矢量化
本文的参考源代码(C 版本和 128 位 SIMD 版本)来自英特尔应用注释 AP-807。以下内容介绍了标量代码到 128 位 SIMD 的原始端口。首先,该代码优化简单,只需观察到运算中的许多步骤均涉及到用一个数字乘以其复共轭即可。由于运算结果不包含想象部分,因此 2.2 节中指出的许多运算步骤可以简化。由此产生的 C 代码将在第 5 节中提供。在针对英特尔® SSE 优化代码前,必须将代码转换为适合于 SIMD 执行的形式。将源 C 代码的四次迭代集中在一起,并在新循环的单个迭代中处理。每完成一次新循环相当于完成四次原始迭代。根据 2.2 节中列出的运算,每次迭代过程必须进行三次除法运算。其中一次除法运算发生在步骤 3(由于想象部分为零,因此想象部分无需进行除法运算),另外两次除法运算发生在步骤 6。通过注释后两次除法运算包含相同的分母,可省略其中的一次除法运算。通过使用掩蔽技术,可删除需要检查是否存在为零分母的 if 语句。通过将所有除法运算替换为倒数近似,可实现进一步改进。下面将介绍这些技术。将代码转换为 SIMD 格式后,可通过以下方法省略对分母为零(if 语句)的检查:为非零分母元素创建掩码,将该掩码添加到除法运算的结果中,以清除被零相除的元素。通过掩蔽 MXCSR 寄存器中的 SIMD 浮点异常,避免发生浮点被零除的异常,此技术假设产生的是 QNAN 而非 SNAN。例如,假设您希望计算数量( N / D ),其中 N 和 D 为浮点。典型代码顺序如下所示:

If ( D != 0 )
Result = N / D;
Else
Result = 0.0;

结果可以使用 (and ( div ( N, D ), cmp_neq ( D, 0 ) ) ) 计算得出,而无需使用 if 语句。使用内在函数 (intrinsic) 时,表达如下:

mm_and_ps ( mm_div_ps ( N, D ), mm_cmpneq_ps ( D, zero ) ).

该技术专用于矢量代码,在代码的附加内在函数和汇编语言两个版本中实施。

Newton-Raphson 方法是近似函数的一种经典技术。首先,利用 rcpps 指令计算出倒数的初始“猜测值”。接着,利用 Newton-Raphson 方法对“猜测值”进行改进。由此得出的结果虽不及除法指令提供的准确,但是获得结果的速度要快得多。(程序员必须确定其应用是否允许精确度较低的答案。)有关此技术的详细信息和介绍可在 Newton-Raphson 应用注释 [2] 中找到。Wiener 过滤器采用的特定代码顺序如下所示:(必须对分母进行检查,确保不会出现被零除的现象。)

RC = _mm_rcpps( D );
RECIP = _mm_sub( _mm_add( RC, RC ), _mm_mul( RC, _mm_mul( RC, D ) ) );


4 利用 256 位 SIMD 对代码进行矢量化
通过迁移更改,即可将 128 位矢量代码轻松移植到 256 位 SIMD。256 位代码将在一次循环中进行 8 次迭代,而 128 位英特尔® SSE 在一次循环中只进行 4 次迭代。现在,负载/存储指令和 rcp/mul/add/sub 指令将对 8 个数据点进行运算。上述无分支技术在英特尔® AVX 代码中仍然保留。从代码中可以看出,移植采用相应的英特尔® AVX 内在函数(例如针对 256 位的 _mm256_mul_ps 和针对 128 位的 _mm_mul_ps)完成。6.3 节中提供了完整的 256 位英特尔® AVX 代码。


5 对输入/输出数组进行分组
此外,按照顺序次序对输入和输出数组进行分组可提升 256 位 SIMD 代码的性能。通过高速缓存/内存连续访问内存可减少潜在的高速缓存路径冲突 (cache way conflict)。这为 CPU 硬件预取器提供了一种更为简单的访问模式,从而提高了数据的精确度。


6 结论

对于大量迭代处理,128 位 SIMD 代码与 256 位 SIMD 代码的性能结果(在 CPU 时钟周期方面)比较如下:

 

英特尔® AVX(256 位)

英特尔® SSE (128-bit)

英特尔® AVX 对比 英特尔® SSE

Wiener过滤器

45871

66933

1.46倍

包含分组数组的 Wiener 过滤器

42464

64473

1.51倍


相对于英特尔® SSE,英特尔® AVX 的 Wiener 过滤性能整体上提升了 1.46 倍。与采用 128 位 SIMD 编码相比,英特尔® AVX 大幅提升了 Wiener 过滤算法的性能。通过对输入/输出数组进行分组,英特尔® AVX 的 Wiener 过滤性能在英特尔® SSE 的基础上整体提升了 1.51 倍。本文中提到的提升是采用几种技术的结果。其中包括采用英特尔® AVX(矢量化代码)和通过利用英特尔® SSE 和英特尔® AVX 提供的遮罩运算消除条件分支指令(if 语句)。如果可以接受数值精度较低的结果,则可通过将除法运算替换为倒数近似(采用 Newton-Raphson 技术)实现进一步的性能提升。此外,本文还重点介绍了如何将现有浮点代码轻松移植到英特尔® AVX。


7 代码示例

/*
* Wiener Filter (also known as the Least Mean Square filter)
*
* Reference: The Pocket Handbook of Image Processing Algorithms in C
* by Harley R Myler & Arthur R. Weeks
* 1993 Prentice-Hall, ISBN 0-13-642240-3 p260-3.
*
* The data is several arrays of complex floats in row major order.
* The description for the algorithm from p260 states:
*
* The algorithm computes a parametric Wiener filter on the
* Fourier transform of a degraded image, Guv, with noise
* spectra N, degradation function Huv, and original image Img.
* The computation is in place, so that the filtered version of
* the input is returned in the original image variable. The
* original and noise images are either estimations form some
* predictive function or ad hoc approximations. If the noise
* image is zero, the process reduces to the inverse filter.
*
* The Weiner parameter gamma is passed to the algorithm.
* If this parameter is 1.0, the filter is non-parametric.
* Methods exist in the literature to derive the parameter value;
* however, it is sometimes determined from trial and error.
*
*NOTE!!!! The code on page 263 has an error. In cxml, the complex
* multiply routine, the imaginary part of the computation should be
* a*d + b*c, not a*d - b*c.
*
*NOTE! (another error) The *complex* array length is rows*cols, so the
* *float* array length should be 2*rows*cols. Also, note that the
* algorithm operates on one component of the pixel.
*/
void wiener_filter ( float *Img,
float *Huv,
float *No,
float *Guv,
float gamma,
int rows,
int cols)
{
int i, sz;
float numr, numi, dr, hsr;
sz = 2 * rows * cols;
for (i = 0; i < sz; i += 2)
{
/* Compute (in place) the noise spectral density with Wiener gamma*/
No[i] = (float) ( gamma * ( No[i]*No[i] + No[i+1]*No[i+1] ) );
No[i+1] = (float) 0.0;
/* Compute image spectral density */
dr = (float) ( Img[i]*Img[i] + Img[i+1]*Img[i+1] );
/* Compute denominator spectral density term */
if (dr != 0.0)
dr = (float) (No[i] / dr) ;
/* Compute degradation power spectrum */
hsr = (float) ( Huv[i]*Huv[i] + Huv[i+1]*Huv[i+1] );
/* Compute numerator term */
numr = (float) ( Huv[i]*Guv[i] + Huv[i+1]*Guv[i+1] );
numi = (float) ( Huv[i]*Guv[i+1] - Huv[i+1]*Guv[i ] );
/* Final computation */
if ( (hsr + dr) != 0.0 )
{
Img[i] = (float) (numr / (hsr + dr));
Img[i+1] = (float) (numi / (hsr + dr));
}
else
{
Img[i] = (float) 0.0;
Img[i+1] = (float) 0.0;
}
}
} /* wiener_filter */



7.2 128 位内在代码 (intrinsics code)

/*
#include 
//#define MM_FUNCTIONALITY
#include 
#include 
void intrin_wiener_rcp_sse( float *Img,
float *Huv,
float *No,
float *Guv,
float gamma,
int rows,
int cols )
{
int i, sz;
__m128 first2, next2, nor4, noi4, nr4, inr4, ini4, dr4;
__m128 hr4, hi4, hsr4, gr4, gi4, numr4, numi4;
__m128 rc, denom;
__m128 zero = _mm_set_ps1 (0.0);
sz = 2 * rows * cols;
assert( (sz > 3) & !(sz & 3) );
assert( !( ((int)Img) & 15 ) ); /* Assume alignment */
assert( !( ((int)Huv) & 15 ) );
assert( !( ((int)No) & 15 ) );
assert( !( ((int)Guv) & 15 ) );
for (i = 0; i < sz; i += 8)
{
* Compute (in place) the noise spectral density with Wiener gamma
*
* complex Noise = gamma * (Noise * complex conj Noise)
*
* No[i] = (float) ( gamma * ( No[i]*No[i] + No[i+1]*No[i+1] ) );
* No[i+1] = (float) 0.0;
*/
first2 = _mm_load_ps ( &No[i] );
next2 = _mm_load_ps ( &No[i+4] );
nor4 = _mm_shuffle_ps( first2, next2, 0x88 );
noi4 = _mm_shuffle_ps( first2, next2, 0xdd );
nr4 = _mm_mul_ps ( _mm_set_ps1( gamma ) ,
_mm_add_ps ( _mm_mul_ps( nor4 , nor4 ),
_mm_mul_ps( noi4 , noi4 ) ) );
_mm_store_ps( &No[i ], _mm_unpacklo_ps ( nr4, zero ) );
_mm_store_ps( &No[i+4], _mm_unpackhi_ps ( nr4, zero ) );
/*
* Compute image spectral density
*
* Complex D = Image * complex conj Image
*
* dr = (float) ( Img[i]*Img[i] + Img[i+1]*Img[i+1] );
*/
first2 = _mm_load_ps ( &Img[i] );
next2 = _mm_load_ps ( &Img[i+4] );
inr4 = _mm_shuffle_ps( first2, next2, 0x88 );
ini4 = _mm_shuffle_ps( first2, next2, 0xdd );
dr4 = _mm_add_ps ( _mm_mul_ps( inr4 , inr4),
_mm_mul_ps( ini4 , ini4) );
/*
* Compute denominator spectral density term
*
* Complex D = noise / D
*
* if (dr != 0.0)
* dr = (float) (No[i] / dr) ;
*
* Do that reciprical division thing!
*/
rc = _mm_rcp_ps(dr4);
rc = _mm_sub_ps( _mm_add_ps( rc, rc),
_mm_mul_ps( rc, _mm_mul_ps( rc, dr4) ) );
dr4 = _mm_and_ps ( _mm_mul_ps ( nr4 , rc ),
_mm_cmpneq_ps( dr4, zero ) );
/*
* Compute degradation power spectrum
*
* Complex Hs = Huv * complex conj Huv
*
* hsr = (float) ( Huv[i]*Huv[i] + Huv[i+1]*Huv[i+1] );
*/
first2 = _mm_load_ps ( &Huv[i] );
next2 = _mm_load_ps ( &Huv[i+4] );
hr4 = _mm_shuffle_ps( first2, next2, 0x88 );
hi4 = _mm_shuffle_ps( first2, next2, 0xdd );
hsr4 = _mm_add_ps ( _mm_mul_ps (hr4 , hr4 ),
_mm_mul_ps (hi4 , hi4 ) );
/*
* Compute numerator term
*
* Complex Num = complex conj Huv * Guv
*
* numr = (float) ( Huv[i]*Guv[i] + Huv[i+1]*Guv[i+1] );
* numi = (float) ( Huv[i]*Guv[i+1] - Huv[i+1]*Guv[i ] );
*/
first2 = _mm_load_ps ( &Guv[i] );
next2 = _mm_load_ps ( &Guv[i+4] );
gr4 = _mm_shuffle_ps( first2, next2, 0x88 );
gi4 = _mm_shuffle_ps( first2, next2, 0xdd );
numr4 = _mm_add_ps ( _mm_mul_ps (hr4 , gr4),
_mm_mul_ps (hi4 , gi4) );
numi4 = _mm_sub_ps ( _mm_mul_ps (hr4 , gi4),
_mm_mul_ps (hi4 , gr4) );
/*
* Final computation
*
* Complex Image = Num / (Hs + D)
*
* if ( (hsr + dr) != 0.0 )
* {
* Img[i] = (float) (numr / (hsr + dr));
* Img[i+1] = (float) (numi / (hsr + dr));
* }
* else
* {
* Img[i] = (float) 0.0;
* Img[i+1] = (float) 0.0;
* }
*
* Do the reciprical division thing
*/
denom = _mm_add_ps( hsr4, dr4 );
rc = _mm_rcp_ps(denom);
rc = _mm_sub_ps( _mm_add_ps( rc, rc),
_mm_mul_ps( rc, _mm_mul_ps( rc, denom) ) );
inr4 = _mm_and_ps( _mm_mul_ps ( numr4 , rc ) ,
_mm_cmpneq_ps( denom, zero ) );
ini4 = _mm_and_ps( _mm_mul_ps ( numi4 , rc ) ,
_mm_cmpneq_ps( denom, zero ) );
_mm_store_ps( &Img[i ], _mm_unpacklo_ps ( inr4, ini4 ) );
_mm_store_ps( &Img[i+4], _mm_unpackhi_ps ( inr4, ini4 ) );
}
} /* intrin_wiener_rcp */


    


7.3 256 位内在代码 (intrinsics code)

void intrin_wiener_rcp_avx( float *Img,
					   float *Huv,
					   float *No,
					   float *Guv,
					   float gamma,
					   int rows,
					   int cols )
{
	int i, sz;
	__m256 first2, next2, nor4, noi4, nr4, inr4, ini4, dr4;
	__m256 hr4, hi4, hsr4, gr4, gi4, numr4, numi4;
	__m256 rc, denom;
	__m256 zero = _mm256_setzero_ps();
	sz = 2 * rows * cols;
	assert( (sz > 3) & !(sz & 3) );
	assert( !( ((int)Img) & 15 ) ); /* Assume alignment */
	assert( !( ((int)Huv) & 15 ) );
	assert( !( ((int)No) & 15 ) );
	assert( !( ((int)Guv) & 15 ) );
	for (i = 0; i < sz; i += 16)
	{
		* Compute (in place) the noise spectral density with Wiener gamma
		*
		* complex Noise = gamma * (Noise * complex conj Noise)
		*
		* No[i] = (float) ( gamma * ( No[i]*No[i] + No[i+1]*No[i+1] ) );
		* No[i+1] = (float) 0.0;
		*/
		first2 = _mm256_load_ps ( &No[i] );
		next2 = _mm256_load_ps ( &No[i+4*2] );
		nor4 = _mm256_shuffle_ps( first2, next2, 0x88 );
		noi4 = _mm256_shuffle_ps( first2, next2, 0xdd );
		nr4 = _mm256_mul_ps ( _mm256_set1_ps( gamma ) ,
		_mm256_add_ps ( _mm256_mul_ps( nor4 , nor4 ),
		_mm256_mul_ps( noi4 , noi4 ) ) );
		_mm256_store_ps( &No[i ], _mm256_unpacklo_ps ( nr4, zero ) );
		_mm256_store_ps( &No[i+4*2], _mm256_unpackhi_ps ( nr4, zero ) );
		
		/*
		* Compute image spectral density
		*
		* Complex D = Image * complex conj Image
		*
		* dr = (float) ( Img[i]*Img[i] + Img[i+1]*Img[i+1] );
		*/
		first2 = _mm256_load_ps ( &Img[i] );
		next2 = _mm256_load_ps ( &Img[i+4*2] );
		inr4 = _mm256_shuffle_ps( first2, next2, 0x88 );
		ini4 = _mm256_shuffle_ps( first2, next2, 0xdd );
		dr4 = _mm256_add_ps ( _mm256_mul_ps( inr4 , inr4),
		_mm256_mul_ps( ini4 , ini4) );
		/*
		* Compute denominator spectral density term
		*
		* Complex D = noise / D
		*
		* if (dr != 0.0)
		* dr = (float) (No[i] / dr) ;
		*
		* Do that reciprical division thing!
		*/
		rc = _mm256_rcp_ps(dr4);
		rc = _mm256_sub_ps( _mm256_add_ps( rc, rc),
		_mm256_mul_ps( rc, _mm256_mul_ps( rc, dr4) ) );
		dr4 = _mm256_and_ps ( _mm256_mul_ps ( nr4 , rc ),
		_mm256_cmpneq_ps( dr4, zero ) );
		/*
		* Compute degradation power spectrum
		*
		* Complex Hs = Huv * complex conj Huv
		*
		* hsr = (float) ( Huv[i]*Huv[i] + Huv[i+1]*Huv[i+1] );
		*/
		first2 = _mm256_load_ps ( &Huv[i] );
		next2 = _mm256_load_ps ( &Huv[i+4*2] );
		hr4 = _mm256_shuffle_ps( first2, next2, 0x88 );
		hi4 = _mm256_shuffle_ps( first2, next2, 0xdd );
		hsr4 = _mm256_add_ps ( _mm256_mul_ps (hr4 , hr4 ),
		_mm256_mul_ps (hi4 , hi4 ) );
		/*
		* Compute numerator term
		*
		* Complex Num = complex conj Huv * Guv
		*
		* numr = (float) ( Huv[i]*Guv[i] + Huv[i+1]*Guv[i+1] );
		* numi = (float) ( Huv[i]*Guv[i+1] - Huv[i+1]*Guv[i ] );
		*/
		first2 = _mm256_load_ps ( &Guv[i] );
		next2 = _mm256_load_ps ( &Guv[i+4*2] );
		gr4 = _mm256_shuffle_ps( first2, next2, 0x88 );
		gi4 = _mm256_shuffle_ps( first2, next2, 0xdd );
		numr4 = _mm256_add_ps ( _mm256_mul_ps (hr4 , gr4),
		_mm256_mul_ps (hi4 , gi4) );
		numi4 = _mm256_sub_ps ( _mm256_mul_ps (hr4 , gi4),
		_mm256_mul_ps (hi4 , gr4) );
		/*
		* Final computation
		*
		* Complex Image = Num / (Hs + D)
		*
		* if ( (hsr + dr) != 0.0 )
		AP-807 Wiener Filtering Using Streaming SIMD Extensions
		01/28/99 15
		* {
		* Img[i] = (float) (numr / (hsr + dr));
		* Img[i+1] = (float) (numi / (hsr + dr));
		* }
		* else
		* {
		* Img[i] = (float) 0.0;
		* Img[i+1] = (float) 0.0;
		* }
		*
		* Do the reciprical division thing
		*/
		denom = _mm256_add_ps( hsr4, dr4 );
		rc = _mm256_rcp_ps(denom);
		rc = _mm256_sub_ps( _mm256_add_ps( rc, rc),
		_mm256_mul_ps( rc, _mm256_mul_ps( rc, denom) ) );
		inr4 = _mm256_and_ps( _mm256_mul_ps ( numr4 , rc ) ,
		_mm256_cmpneq_ps( denom, zero ) );
		ini4 = _mm256_and_ps( _mm256_mul_ps ( numi4 , rc ) ,
		_mm256_cmpneq_ps( denom, zero ) );
		_mm256_store_ps( &Img[i ], _mm256_unpacklo_ps ( inr4, ini4 ) );
		_mm256_store_ps( &Img[i+4*2], _mm256_unpackhi_ps ( inr4, ini4 ) );

	}
} /* intrin_wiener_rcp */


    


7.4 包含分组数组的 256 位内在代码 (intrinsics code)

blockHNG Structure

Huv[0]Huv[15]No[0]No[15]Guv[0]Guv[15]Huv[16]Huv[31]No[16]
void intrin_wiener_rcp_avx ( float *Img,
					float *_blockHNG,
					float gamma,
					int rows,
					int cols)
{
	int sz;
	__m256 first2, next2, nor4, noi4, nr4, inr4, ini4, dr4;
	__m256 hr4, hi4, hsr4, gr4, gi4, numr4, numi4;
	__m256 rc, denom;
	__m256 zero = _mm256_setzero_ps();
	sz = 2 * rows * cols;

	assert( (sz > 3) & !(sz & 3) );
	assert( !( ((int)Img) & 15 ) ); // Assume alignment 
	assert( !( ((int)_blockHNG) & 15 ) ); // Assume alignment 

	float *Huv;
	float *No;
	float *Guv;
	
	int j = 0;	// img index
	for (int _blockHNG_tracker = 0; _blockHNG_tracker < 2 * rows * cols * 3; _blockHNG_tracker += 48)
	{
		Huv = &(_blockHNG[_blockHNG_tracker]);
		No = &(_blockHNG[_blockHNG_tracker + 16]);
		Guv = &(_blockHNG[_blockHNG_tracker + 32]);

		/*
		* Compute (in place) the noise spectral density with Wiener gamma
		*
		* complex Noise = gamma * (Noise * complex conj Noise)
		*
		AP-807 Wiener Filtering Using Streaming SIMD Extensions
		01/28/99 13
		* No[i] = (float) ( gamma * ( No[i]*No[i] + No[i+1]*No[i+1] ) );
		* No[i+1] = (float) 0.0;
		*/
		first2 = _mm256_load_ps ( &No[0] );
		next2 = _mm256_load_ps ( &No[8] );
		nor4 = _mm256_shuffle_ps( first2, next2, 0x88 );
		noi4 = _mm256_shuffle_ps( first2, next2, 0xdd );
		nr4 = _mm256_mul_ps ( _mm256_set1_ps( gamma ) ,
		_mm256_add_ps ( _mm256_mul_ps( nor4 , nor4 ),
		_mm256_mul_ps( noi4 , noi4 ) ) );

		_mm256_store_ps( &No[0], _mm256_unpacklo_ps ( nr4, zero ) );
		_mm256_store_ps( &No[8], _mm256_unpackhi_ps ( nr4, zero ) );

		/*
		* Compute image spectral density
		*
		* Complex D = Image * complex conj Image
		*
		* dr = (float) ( Img[i]*Img[i] + Img[i+1]*Img[i+1] );
		*/
		first2 = _mm256_load_ps ( &Img[j] );
		next2 = _mm256_load_ps ( &Img[j+8] );
		inr4 = _mm256_shuffle_ps( first2, next2, 0x88 );
		ini4 = _mm256_shuffle_ps( first2, next2, 0xdd );
		dr4 = _mm256_add_ps ( _mm256_mul_ps( inr4 , inr4),
		_mm256_mul_ps( ini4 , ini4) );
		/*
		* Compute denominator spectral density term
		*
		* Complex D = noise / D
		*
		* if (dr != 0.0)
		* dr = (float) (No[i] / dr) ;
		*
		* Do that reciprical division thing!
		*/
		rc = _mm256_rcp_ps(dr4);
		rc = _mm256_sub_ps( _mm256_add_ps( rc, rc),
		_mm256_mul_ps( rc, _mm256_mul_ps( rc, dr4) ) );
		dr4 = _mm256_and_ps ( _mm256_mul_ps ( nr4 , rc ),
		_mm256_cmpneq_ps( dr4, zero ) );
		/*
		* Compute degradation power spectrum
		*
		* Complex Hs = Huv * complex conj Huv
		*
		* hsr = (float) ( Huv[i]*Huv[i] + Huv[i+1]*Huv[i+1] );
		*/
		first2 = _mm256_load_ps ( &Huv[0] );
		next2 = _mm256_load_ps ( &Huv[8] );
		hr4 = _mm256_shuffle_ps( first2, next2, 0x88 );
		hi4 = _mm256_shuffle_ps( first2, next2, 0xdd );
		hsr4 = _mm256_add_ps ( _mm256_mul_ps (hr4 , hr4 ),
		_mm256_mul_ps (hi4 , hi4 ) );
		/*
		* Compute numerator term
		*
		* Complex Num = complex conj Huv * Guv
		*
		* numr = (float) ( Huv[i]*Guv[i] + Huv[i+1]*Guv[i+1] );
		* numi = (float) ( Huv[i]*Guv[i+1] - Huv[i+1]*Guv[i ] );
		*/
		first2 = _mm256_load_ps ( &Guv[0] );
		next2 = _mm256_load_ps ( &Guv[8] );
		gr4 = _mm256_shuffle_ps( first2, next2, 0x88 );
		gi4 = _mm256_shuffle_ps( first2, next2, 0xdd );
		numr4 = _mm256_add_ps ( _mm256_mul_ps (hr4 , gr4),
		_mm256_mul_ps (hi4 , gi4) );
		numi4 = _mm256_sub_ps ( _mm256_mul_ps (hr4 , gi4),
		_mm256_mul_ps (hi4 , gr4) );
		/*
		* Final computation
		*
		* Complex Image = Num / (Hs + D)
		*
		* if ( (hsr + dr) != 0.0 )
		AP-807 Wiener Filtering Using Streaming SIMD Extensions
		01/28/99 15
		* {
		* Img[i] = (float) (numr / (hsr + dr));
		* Img[i+1] = (float) (numi / (hsr + dr));
		* }
		* else
		* {
		* Img[i] = (float) 0.0;
		* Img[i+1] = (float) 0.0;
		* }
		*
		* Do the reciprical division thing
		*/
		denom = _mm256_add_ps( hsr4, dr4 );
		rc = _mm256_rcp_ps(denom);
		rc = _mm256_sub_ps( _mm256_add_ps( rc, rc),
		_mm256_mul_ps( rc, _mm256_mul_ps( rc, denom) ) );
		inr4 = _mm256_and_ps( _mm256_mul_ps ( numr4 , rc ) ,
		_mm256_cmpneq_ps( denom, zero ) );
		ini4 = _mm256_and_ps( _mm256_mul_ps ( numi4 , rc ) ,
		_mm256_cmpneq_ps( denom, zero ) );

		_mm256_store_ps( &Img[j ], _mm256_unpacklo_ps ( inr4, ini4 ) );
		_mm256_store_ps( &Img[j+8], _mm256_unpackhi_ps ( inr4, ini4 ) );
		j+=16;
	}
} /* Intrin_wiener_rcp_avx */



致谢
作者在编著本白皮书时,曾受到 Phil Kerly、Raghu Muthyalampalli 和 Justin Landon 的大力支持。他们帮助作者评估代码性能、提供性能建议以及审阅此白皮书。作者在此表示衷心感谢。

参考资料
本应用注释参考了以下文档,旨在为理解本文提到的主题提供背景或支持信息。

1. 《C 语言中图像处理算法手册》(The Pocket Handbook of Image Processing Algorithms in C), 作者:Harley R Myler 和 Aruthur R.
Weeks. ISBN 0-13-642240-3.
2. 《利用 Newton-Raphson 方法提高倒数和倒数方根指令结果的精确度》(Increasing the Accuracy of the Results from the Reciprocal and Reciprocal Square RootInstructions using the Newton-Raphson Method), 英特尔应用注释(AP-803,编号: 243637-001)。
3. 《Split-Radix FFT》, 英特尔应用注释(AP-808,编号: 243642-001)。
4. 《利用 SIMD 流指令扩展进行 Wiener 过滤》(Wiener Filtering Using Streaming SIMD Extensions), 英特尔应用注释(AP-807)。

For more complete information about compiler optimizations, see our Optimization Notice.