阵列信号处理若干算法的C++实现之基础函数篇

简书不支持公式,请查看原文链接

http://blog.csdn.net/qq_31436943/article/details/78813594

引言

  在阵列信号处理中,多通道信号处理大多基于矩阵方法,本文主要介绍一些常见算法的$C++$实现。在信号处理中大多数处理基于复数运算,因此本文大多数函数针对复数处理。文中部分算法采用GSL和FFTW库实现,详细配置和入门教程,请看参考文献$[1]-[4]$。由于代码过于冗长,文中只给出了关键部分,详细代码和例程参见文后链接。

矩阵存储

  二维矩阵在文中按照线性存储,对于$mn$大小的矩阵,存储时先存入第$0$行的所有数据,再依次存储第$1$行,第$2$行...例如$A$的大小为$39$,则$a_{13}$代表第$1$行,第$3$列元素(注意,$C++$中0为起始索引),在实际存储时,其索引号为$1*9+3 = 12$。当知道了矩阵的大小后很方便进行转换,类似的三维甚至更高维矩阵都可以进行处理。矩阵在进行参数传递是一般采用指针或者引用进行参数传递,这样可以避免参数传递时大量的数据临时拷贝。指针传参时传递矩阵的首地址,引用传参时相当于给变量一个别名,在内部运算。两种方式效果相同,但都有修改原始数据的风险(可加上$const$前缀禁止内部修改数据),本文采用指针方式传参。

希尔伯特变换

  在信号处理中,计算和信息传递方面的优势,复数的使用不可避免。在输入信号时,原始信号为实数,希尔伯特变换可以实现将实数转换为复数的功能。希尔伯特变换的频域计算公式如下
$$
Y(w) = X(w)H(w)
其中 H(w)= \begin{cases}
-i,w>0\
0,w=0\
i,w<0
\end{cases}
$$
  希尔伯特变换的物理意义十分明确,其将信号的正频率部分相移$-90°$,负频率部分相移$+90°$。算法实现时,先进行傅里叶变换将信号变换到频域,在频域进行相移处理后,再反傅里叶变换到时域即可。这里有几点需要注意:
  1、做$DFT$输出数据,最大点数$N$处对应频率为采样频率$Fs$。
  2、由于$DFT$的周期性,输出频域数据关于$Fs/2$镜像对称,即$0--N/2-1$处为正频率部分,$N/2--N-1$处为负频率部分
  3、$FFT$内部进行运算时要求信号的长度为$2^N$,若不满足会对信号进行截断或补0操作
  利用希尔伯特变换实现信号从实数域到复数域的转换,算法实现如下:
  1.实数信号作为实部传递给复数,复数的虚部置$0$。
  2.将复信号做$fft$变换到频域。
  3.$0$至$(N/2-1)$点处,实部取反;$N/2$至$(N-1)$处,虚部取反。
  4.实部虚部交换位置得到新复信号。
  5.对新的复信号做$ifft$的到恢复完成的时域复数信号。

//*****************************************************
/*
*功能:
*   利用希尔伯特变换将单通道信号从实数域变换到复数域
*   
*参数:
*   参数1---输入,信号长度
*   参数2---输入,单通道实信号
*   参数3---输出,恢复后的单通道复数信号
*   
*返回值:
*   无
*   
*附加说明:
*   程序运行需FFTW库,请先行配置好。
*/
//******************************************************
void matreat::heribtToComplex(int n,double* indata,complex<double>* outdata)
{
    ...
    for(int i = 0;i<length;i++)
    {
        in[i][0] = indata[i];//输入实信号作为实部
        in[i][1] = 0;//虚部置0
    }

    p = fftw_plan_dft_1d(length,in,out,FFTW_FORWARD,FFTW_ESTIMATE);//傅里叶正变换
    ...
    //正频率部分
    for(int i = 0;i<midle;i++)
    {
        //实虚部互换
        in[i][0] = out[i][1];
        in[i][1] = -out[i][0];//实部取反
    }
    
    //负频率部分
    for(int i = midle;i<length;i++)
    {
        //实虚部互换
        in[i][0] = -out[i][1];//虚部取反
        in[i][1] = out[i][0];
    }

    p = fftw_plan_dft_1d(length,in,out,FFTW_BACKWARD,FFTW_ESTIMATE);//傅里叶反变换
    ...
}

这里仅展示了单通道信号的处理方法,对于多通道信号方法类似,依次对每个通道信号进行希尔伯特变换即可。

$Matlab$数据读/写

  $Matlab$的方便无需多言,其好处在于大而全,并且编程方便,在科研工作者中大量使用,有大量代码可供参考。但其终归是仿真工具,在实际工业产品中调用其并不那么友好,一方面其为商业软件存在版权问题,另一方面其运行效率也无法满足需求。若要找个折衷方案,混合编程可以考虑,从实际应用的效果来看,应急可以,但我并不推荐使用。一方面,其运行需要matlab环境支持;另一方面,各种莫名奇妙的错误也常常让人无处下手。本文中matlab数据的读/写并未采用调用matlab引擎的方案,这里采用间接方案:
  在$Matlab$中使用save 'dataA.txt' -ascii -double dataA;语句将变量$dataA$保存到$dataA.txt$文件中,该保存形式数据与数据之间以空格划分。
  类似的$Matlab$中使用load dataA.txt;语句将$dataA.txt$中的数据存储到dataA的同名变量中。
  对于多维数据和复数数据,本文并未给出存取方案,在实际运用中可分为各部分依次存储即可。 下文的方案中,在$C++$读取$Matlab$数据仅利用读取$.txt$文档实现数据交互,与普通的文本存取无异。

//*****************************************************
/*
*功能:
*   将数据保存成matlab可读取的.txt格式文件
*   
*参数:
*   参数1---输入,待保存数据
*   参数2---输入,数据的总长度
*   参数3---输出,文档保存位置
*   
*返回值:
*   无
*   
*附加说明:
*   
*/
//******************************************************
void saveForMat(double indata[],int N,const string &dir)
{
    ofstream datafile;
    datafile.open(dir);//若文件不存在会自动创建文件
    for(int i=0;i<N;i++)
    {
        datafile<<indata[i];
        datafile<<" ";
    }
    datafile.close();

}
//*****************************************************
/*
*功能:
*   将读取matlab导出.txt格式文件
*   
*参数:
*   参数1---输入,待读取文档的位置
*   参数2---输出,数据变量
*   
*返回值:
*   无
*   
*附加说明:
*   
*/
//******************************************************
void readMatalbtext(const string &str,vector<double> &wdata)
{
    ifstream file;
    file.open(str);
    if(!file)
    {
        cout<<"错误!!!!,请检查文本输入输出路径是否正确"<<endl;
    }
    else
    {

    double d; 
    while (file >> d) 
    {
        wdata.push_back(d);//将数据压入堆栈
    }
    file.close();//关闭文件//  
    }
}

协方差矩阵计算

  在阵列信号处理中,计算协方差如下式
$$
R_x = \frac{1}{N}{XX^H}
$$
  原始信号输入数据数据大小为$mn$,其中$m$为通道数,$n$为时间片段的长度,一般情况下为$n>>m$,计算协方差后矩阵大小变为$mm$,协方差矩阵其每一元素是不同的接收阵元采样数据的时间平均,既利用多个通道数据,同时减少了后续处理的计算量。协方差矩阵是厄密矩阵,具有共轭对称性,主对角线元素为实数。
  举例,协方差矩阵数据如下:
$$
\begin{bmatrix}
(1,0)& (2,-10) &(3,-3)& (4,6) \
(2,10)&(8,0) & (3,4)& (3,-4) \
(3,3)& (3,-4) & (3,0) &(7,-5) \
(4,-6) &(3,4)& (7,5)&(5,0)
\end{bmatrix}
$$
  利用该性质,计算时将矩阵分为上三角区域和下三角区域,先计算上三角区域数据,下三角区域对应位置的数据直接共轭赋值。

//*****************************************************
/*
*功能:
*   计算输入数据的协方差矩阵
*   
*参数:
*   参数1---输入,信号的通道数
*   参数2---输入,每个通道数据长度
*   参数3---输入,输入多通道信号
*   参数3---输出,处理后矩阵
*   
*返回值:
*   无
*   
*附加说明:
*
*
*/
//******************************************************
void getCovComplexMat(int m,int len,complex<double>*indata,complex<double>*outdata)
{

    ...
    getConjComplexMat(m,len,indata,conjdata);//计算矩阵indata的共轭矩阵,详见完整代码
    ...

    //计算上三角阵
    for(int i=0;i<m;i++)
    {
        for(int j=i;j<m;j++)
        {
            indexA = i*len;
            indexB = j*len;
            ...
            while(num < len)
            {
                temp += indata[indexA]*conjdata[indexB];
                indexA++;
                indexB++;
                num++;
            }
            ...
        }
        ...
    }
    //由对称性,下三角直接赋值
    for(int i=1;i<m;i++)
    {
        for(int j=0;j<i;j++)
        {
            outdata[i*m+j].real(outdata[j*m+i].real());
            outdata[i*m+j].imag(-outdata[j*m+i].imag());
        }
    }

    //释放内存
    ...
}

方阵$Toeplitz$化处理

  $Toeplitz$矩阵是矩阵从上到下各个对角线元素均相等的矩阵,如下所示

$$
\begin{bmatrix}
1 & 2 & 3& 4 \
5 &1 & 2 & 3 \
6 & 5 & 1 & 2 \
7 & 6& 5& 1
\end{bmatrix}
$$

  均匀线阵的协方差矩阵为$Toeplitz$形式,但当信号为相干信号源时并不满足这一形式。$Toeplitz$化处理有幅度$Toeplitz$,相位$Toeplitz$,以及幅度相位同时$Toeplitz$几种方式,这里采用幅度$Toeplitz$。
  对于$n*n$大小的方阵, 这里将矩阵分为上三角区域和下三角区域两部分,若线性存储,矩阵的索引号间有如下性质:
  1、不同对角线,上三角区域,对角线起始元素的索引号相差$1$。
  2、不同对角线,下三角区域,对角线起始元素的索引号相差$n$。
  3、相同对角线上,相邻元素的索引号相差$n+1$。
  在计算次序上,依次计算主对角线,上三角区域的第二斜对角斜对角线,第三对角线...,下三角区域的第二对角线,第三对角线...

//*****************************************************
/*
*功能:
*   方阵的Toeplitz化处理
*   
*参数:
*   参数1---输入,方阵的行/列数(方阵大小为m*m)
*   参数2---输入,待处理协方差矩阵
*   参数3---输出,处理后矩阵
*   
*返回值:
*   无
*   
*附加说明:
* 
*
*/
//******************************************************
void toeplitzComplexMat(int m,complex<double>* indata,complex<double>* outdata)
{
    ...
    int upIndex = m+1;//对角线相邻元素间索引间隔
    ...
    //上斜对角线
    for(int i = 0;i<m;i++)
    {
        //计算斜对角线平均值
        indexA = i;//对角线起始元素的索引号
        ...
        contrlIndex = m-i;//当前对角线上元素总数
        while(useIndex<contrlIndex)
        {
            tempData += indata[indexA];
            useIndex++;
            indexA += upIndex;//下一元素位置
        }
        avgData.real(tempData.real()/useIndex);//幅度平均
        avgData.imag(tempData.imag()/useIndex);

        //斜对角线赋值
        ...
        while(useIndex<contrlIndex)
        {
            outdata[indexA] = avgData;
            indexA += upIndex;
            ...
        }
    }

    //下斜对角线
    for(int i = 1;i<m;i++)
    {
        //计算斜对角线平均值
        ...

        //斜对角线赋值
        ...
    }
}

共轭转置相乘

  波束形成的主要任务是计算波束加权值,使波束输出信号具有指向性从而抑制非期望方向的干扰。常规波束形成的波束输出计算式如下:
$$
Y =W^{H}X
$$
  注意这里的$[.]{H}$代表求矩阵的共轭转置,而另一种常见的操作$[.]{T}$代表求矩阵的转置矩阵,其中复数矩阵处理中以$[.]^{H}$居多。计算中先对矩阵进行共轭处理,然后通过逻辑上的转换(转置乘于直接相乘本质上并无区别,通过索引值实现,纯逻辑问题)进行转置相乘。

//*****************************************************
/*
*功能:
*   计算A的共轭转置矩阵与B矩阵的乘积
*   
*参数:
*   参数1---输入,A和B矩阵的行数(A^H若能与B相乘,则A、B矩阵的行数必须相等)
*   参数2---输入,A矩阵的列数
*   参数3---输入,B矩阵的列数
*   参数4---输出,A^H*B处理后的结果矩阵
*   
*返回值:
*   无
*   
*附加说明:
*   
*/
//******************************************************
void conjugateTranMulComplexMat(int m,int aLen,int bLen,complex<double> *dataInA,complex<double>* dataInB,complex<double>* dataOut)//计算A矩阵共轭转置乘上B矩阵
{
    ...
    //计算A^H*B
    int indexA = 0;//A矩阵的共轭矩阵的索引值
    int indexB = 0;//B矩阵的索引值
    int dataAlen = m*aLen;
    for(int i=0;i<dataAlen;i++)
    {
        tempData[i].imag(-dataInA[i].imag());//拷贝并进行共轭处理
        tempData[i].real(dataInA[i].real());
    }
    
    for(int i=0;i<aLen;i++)
    {
        for(int j=0;j<bLen;j++)
        {
            indexA = i;
            indexB = j;
            ...
            while(num < m)
            {

                temp += tempData[indexA]*dataInB[indexB];

                indexA += aLen;
                indexB += bLen;
                num++;
            }
            dataOut[index] = temp;
            index++;
        }
    }

    delete[] tempData;
    tempData = NULL;
}

复厄米矩阵特征值求解

  $MUSIC$算法的核心是对协方差矩阵进行特征分解,然后将特征向量按照特征值的大小进行重新排序组成新的信号子空间,进行后续处理。
  这里注意,协方差矩阵为复数矩阵,需要进行复数矩阵的特征分解。这里使用$GSL$库里的gsl_eigen_hermv(...)函数进行处理。该函数计算复厄米特矩阵的特征值和特征向量,计算后原矩阵的对角线和下三角部分将会被修改。因此在计算中,先对数据进行了拷贝。

//*****************************************************
/*
*功能:
*   计算m*m复数方阵的特征值和特征向量,并进行排序
*   
*参数:
*   参数1---输入,方阵的行数/列数
*   参数2---输入,协方差数据
*   参数3---输出,输入多通道信号
*   参数4---输出,处理后矩阵
*   
*返回值:
*   无
*   
*附加说明:
*   使用前需提前配置GSL库,并包含对应头文件
*/
//******************************************************
void eigenComplexMat(int m,complex<double>*indata,double*outEigenValue,complex<double>*outEigenVec,int mode)
{
    ...
    gsl_eigen_hermv_workspace *w = gsl_eigen_hermv_alloc(m);//分配计算特征值和特征向量的工作区
    ...

    //复制一次,防止后续计算修改数据
    tempA->data = (double*)indata;
    gsl_matrix_complex_memcpy(dataM,tempA);

    ...
    gsl_eigen_hermv(dataM,eval,evec,w);//复厄米特矩阵求解

    switch(mode)
    {
    case 0:
        gsl_eigen_hermv_sort (eval, evec,GSL_EIGEN_SORT_ABS_ASC);//将特征列向量,按特征值升排序组成新矩阵
        break;
    case 1:
        gsl_eigen_hermv_sort (eval, evec,GSL_EIGEN_SORT_ABS_DESC);//将特征列向量,按特征值降排序组成新矩阵
        break;
    ...
    }

    //释放内存
    ...
}

复矩阵求逆

  $Capon$波束形成的核心在于计算协方差矩阵的逆矩阵。利用$LU$分解,采用下式可以计算矩阵的逆。
$$
A^{-1} = (LU){-1}=U{-1}L^{-1}
$$
  $LU$分解将矩阵分解为一个上三角矩阵和下三角矩阵,而三角阵的计算可以采用向后或向前迭代的方式进行计算,大大减小了计算量。利用$LU$分解对矩阵求逆的方法如下:
  1、对矩阵$A$进行$LU$分解。
  2、分别对$L$矩阵和$U$矩阵求逆,由于$L$矩阵为下三角矩阵,可以做转置处理变为上三角矩阵。
$$
L^{-1} = ((L{T}){T})^{-1} = ((L{T}){-1})^{T}
$$
  3、将2中计算得到的逆矩阵相乘。

//*****************************************************
/*
*功能:
*   计算m*m复数方阵的逆矩阵
*   
*参数:
*   参数1---输入,方阵的行数/列数
*   参数2---输入,协方差数据
*   参数3---输出,处理后逆矩阵
*   
*返回值:
*   无
*   
*附加说明:
*   使用前需提前配置GSL库,并包含对应头文件
*/
//******************************************************
void inverseComplexMat(int m,complex<double>* indata,complex<double>* outdata)//计算m*m的复数矩阵的逆矩阵
{
    ...
    tempA->data = (double*)indata;
    gsl_matrix_complex_memcpy(dataM,tempA);//复制一次,防止后续计算修改数据

    int sign = 0;
    gsl_linalg_complex_LU_decomp(dataM, p, &sign);

    inverse->data = (double*)outdata;
    gsl_linalg_complex_LU_invert(dataM, p, inverse);

    //释放内存
    ...
}

详细代码

链接:https://pan.baidu.com/s/1c1R7DRi 密码:2wb8

参考文献

[1]http://blog.sciencenet.cn/blog-999739-779994.html
[2]https://wenku.baidu.com/view/8d513ff0941ea76e58fa04b5.html
[3]http://blog.csdn.net/piaoxuezhong/article/details/78357905
[4]https://wenku.baidu.com/view/a550fe0403d8ce2f006623da.html
[5]http://blog.csdn.net/xx_123_1_rj/article/details/39553809
[6]https://wenku.baidu.com/view/d8cf99300b1c59eef9c7b433.html

欢迎转载
转载请保留声明,并注明出处:http://blog.csdn.net/qq_31436943/article/details/78813594

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342