小波學習之二(單層一維離散小波變換DWT的Mallat算法C++實現優化)

        在上回《小波學習之一》中,已經詳細介紹了Mallat算法C++實現,效果還可以,但也存在一些問題,比如,代碼難於理解,同時出現了邊界問題。在此,本文將重構代碼,採用新的方法解決這些問題,同時也加深對小波變換的理解。

        MATLAB作爲經典的數學工具,分析其小波變換dwt和idwt實現後發現真的很經典,學習參考價值很高。下面結合南京理工大學 譚彩銘的《解讀matlab之小波庫函數》及MATLAB小波工具包中m文件的情況,作一個小結,最後用C++函數進行實現,並且編譯調試OK。
        一、MATLAB上dwt函數的工作過程
        假設x=[x(1) x(2) x(3) x(4) x(5) x(6) x(7)],計算y=dwt(x,’db2’),其計算過程主要由三個部分組成:
1、邊緣延拓,它主要由函數wextend完成。

仔細分析子程序部分,函數wextend的用法爲y=wextend('1D','sym',x,3);這樣得到的y=[ x(3) x(2) x(1) x(1) x(2) x(3) x(4) x(5) x(6) x(7) x(7) x(6) x(5)]
2、卷積運算,它主要由函數conv2完成。
仔細分析子程序部分,核心語句有z=conv2(y,Lo_D,'valid');這裏設Lo_D=[h(1) h(2) h(3) h(4)]。

這2步的實現過程示意圖如下:



3、最後就是下采樣即隔點採樣,其下采樣是按照式a = z(2:2:length(z))進行的,高頻低頻部分均如此,項數爲floor((7+4-1)/2)。

最後的dwt低頻係數結果是[z(2) z(4) z(6) z(8) z(10)],高頻係數求解過程和低頻係數一樣,在此不再贅述。


        二、MATLAB上idwt函數的工作過程

1、上採樣即隔點插0,dyadup(x,0)。

2、卷積運算,它也是最終由函數conv2完成。

3、抽取結果,wkeep1(x,s,'c')。


下面啥都不說show核心代碼實現,歡迎討論。

/**
 * @brief 邊緣延拓
 * @param typeId 延拓數據的類型,1D or 2D
 * @param modeId 延拓方式:對稱、週期
 * @param in 輸入數據
 * @param inLen 輸入數據的長度
 * @param filterLen 小波基濾波器長度
 * @param out 返回結果數組
 * @return 返回結果數組長度
 */
int SignalExtension(int typeId,
		int modeId,   
		double *in,   
		int inLen,    
		int filterLen, 
		double out[])  
{
    if((NULL == in)||(NULL == out))
        return -1;
    if(0 != typeId) // 目前只支持一種模型
    	return -1;
    //if(0 != modeId) // 目前只支持一種模型,信號對稱拓延  'sym' or 'symh'  	Symmetric-padding (half-point): boundary value symmetric replication
    //	return -1;
    if( inLen < filterLen ) // inLen should lager than or equal extendLen, otherwise no extension
    	return -1;

    int i;
    int extendLen = filterLen - 1;

    if(0 == modeId) // 信號對稱拓延
    {
        for(i=0; i<inLen; i++)
        {
        	out[extendLen+i] = in[i];
        }
        for(i=0; i<extendLen; i++)
        {
        	out[i]                     = out[2*extendLen - i - 1];       // 左邊沿對稱延拓
        	out[inLen + extendLen + i] = out[extendLen + inLen - i - 1]; // 右邊沿對稱延拓
        }

        return inLen + 2*extendLen;
    }
    else if(1 == modeId) // 信號週期拓延
    {
		for( i = 0; i < extendLen; i++ )
			out[i] = in[inLen-extendLen+i];
		for ( i = 0; i < inLen; i++ )
			out[extendLen+i] = in[i];

        return inLen + extendLen;
    }

}

/**
 * @brief 上採樣  隔點插0
 * @param data 輸入數據指針
 * @param n 輸入數據長度
 * @param result 返回結果數組
 * @return 返回結果數組長度
 */
int Upsampling(double* data, int n, double result[])
{
	int i;

	for( i = 0; i < n; i++ )
	{
		result[2*i] = data[i];
		result[2*i+1] = 0;
	}

	return( 2*n );
}
/**
 * @brief 下采樣  隔點採樣
 * @param data 輸入數據指針
 * @param n 輸入數據長度
 * @param result 返回結果數組
 * @return 返回結果數組長度
 */
int Downsampling(double* data, int n, double result[])
{
	int i, m;

	m = n/2;
	for( i = 0; i < m; i++ )
		result[i] = data[i*2 + 1];

	return( m );
}


/**
 * @brief 卷積運算
 * @param shapeId 卷積結果處理方式
 * @param double *inSignal, int signalLen, // 輸入信號及其長度
 * @param double *inFilter, int filterLen, // 輸入濾波器及其長度
 * @param double outConv[], int *convLen)   // 輸出卷積結果及其長度
 * @return
 */
void Conv1(int shapeId,                  // 卷積結果處理方式
		double *inSignal, int signalLen, // 輸入信號及其長度
		double *inFilter, int filterLen, // 輸入濾波器及其長度
		double outConv[], int *convLen)   // 輸出卷積結果及其長度
{
    if((NULL == inSignal)||(NULL == inFilter)||(NULL == outConv))
        return;

    int n,k,kmin,kmax,p;
    if(0 == shapeId)      // 對於MATLAB conv(...,'shape')  -----full
    {
    	*convLen = signalLen + filterLen - 1;
    	for (n = 0; n < *convLen; n++)
    	{
    		outConv[n] = 0;

    	    kmin = (n >= filterLen - 1) ? n - (filterLen - 1) : 0;
    	    kmax = (n < signalLen - 1) ? n : signalLen - 1;

    	    for (k = kmin; k <= kmax; k++)
    	    {
    	    	outConv[n] += inSignal[k] * inFilter[n - k];
    	    }
    	}
    }
    else if(1 == shapeId) // 對於MATLAB conv(...,'shape')  -----valid
    {
    	*convLen = signalLen - filterLen + 1;
    	for (n = filterLen - 1; n < signalLen; n++)
    	{
    		p = n - filterLen + 1;
    		outConv[p] = 0;

    	    kmin = (n >= filterLen - 1) ? n - (filterLen - 1) : 0;
    	    kmax = (n < signalLen - 1) ? n : signalLen - 1;

    	    for (k = kmin; k <= kmax; k++)
    	    {
    	    	outConv[p] += inSignal[k] * inFilter[n - k];
    	    }
    	}
    }
    else
    	return ;

}

/**
 * @brief 小波變換之分解
 * @param sourceData 源數據
 * @param dataLen 源數據長度
 * @param db 過濾器類型
 * @param cA 分解後的近似部分序列-低頻部分
 * @param cD 分解後的細節部分序列-高頻部分
 * @return 正常則返回分解後序列的數據長度,錯誤則返回-1
 */
int Wavelet::Decomposition(double* sourceData, int dataLen, Filter db, double* cA, double* cD)
{
    if(dataLen < 2)
        return -1;
    if((NULL == sourceData)||(NULL == cA)||(NULL == cD))
        return -1;

    m_db = db;
    int filterLen = m_db.length;
    int i, n;
    int decLen = (dataLen+filterLen-1)/2;
    int convLen = 0;
    double extendData[dataLen+2*filterLen-2];
    double convDataLow[dataLen+filterLen-1];
    double convDataHigh[dataLen+filterLen-1];

/*
MATLAB上dwt函數的工作過程
假設x=[x(1) x(2) x(3) x(4) x(5) x(6) x(7)],計算y=dwt(x,’db2’)。
其計算過程主要由兩個部分組成:
1:邊緣延拓,它主要由函數wextend完成。
2:卷積運算,它主要由函數conv2完成。
先看第一部分,仔細分析子程序部分,函數wextend的用法爲y=wextend('1D','sym',x,3);
這樣得到的y=[ x(3) x(2) x(1) x(1) x(2) x(3) x(4) x(5) x(6) x(7) x(7) x(6) x(5)]
在看第二部分,仔細分析子程序部分,核心語句有z=conv2(y,Lo_D,'valid');
這裏設Lo_D=[h(1) h(2) h(3) h(4)]。
3:最後就是下采樣,其下采樣是按照式a = z(2:2:length(z))進行的,高頻低頻部分均如此,項數爲floor((7+4-1)/2)。
 */
    // 1.邊緣延拓
    SignalExtension(0, 0 , sourceData, dataLen, filterLen, extendData);

    // 2.卷積運算
    Conv1(1, extendData, dataLen+2*filterLen-2, db.lowFilterDec, filterLen, convDataLow, &convLen);
    Conv1(1, extendData, dataLen+2*filterLen-2, db.highFilterDec, filterLen, convDataHigh, &convLen);

    // 3.下采樣
    Downsampling(convDataLow, dataLen + filterLen - 1, cA);
    Downsampling(convDataHigh, dataLen + filterLen - 1, cD);

    return decLen;
}


/**
 * @brief 小波變換之重構
 * @param cA 分解後的近似部分序列-低頻部分
 * @param cD 分解後的細節部分序列-高頻部分
 * @param cALength 輸入數據長度
 * @param RecLength 輸入重構後的原始數據長度
 * @param db 過濾器類型
 * @param recData 重構後輸出的數據
 * @return 正常則返回重構數據長度,錯誤則返回-1
 */
int Wavelet::Reconstruction(double *cA, double *cD, int cALength, int RecLength, Filter db, double* recData)
{
    if((NULL == cA)||(NULL == cD)||(NULL == recData))
        return -1;

    m_db = db;
    int filterLen = m_db.length;

    int i,j;
    int n,k,p;
    int recLen = RecLength;

    int convLen = 0;
    double convDataLow[recLen+filterLen-1];
    double convDataHigh[recLen+filterLen-1];

    double cATemp[2*cALength];
    double cDTemp[2*cALength];

    memset(convDataLow, 0, (recLen+filterLen-1)*sizeof(double)); // 清0
    memset(convDataHigh, 0, (recLen+filterLen-1)*sizeof(double)); // 清0
    memset(cATemp, 0, 2*cALength*sizeof(double)); // 清0
    memset(cDTemp, 0, 2*cALength*sizeof(double)); // 清0

    // 1.隔點插0
    Upsampling(cA, cALength, cATemp);
    Upsampling(cD, cALength, cDTemp);

    // 2.卷積運算
    Conv1(0, cATemp, 2*cALength-1, db.lowFilterRec, filterLen ,convDataLow, &convLen);
    convLen = 0;
    Conv1(0, cDTemp, 2*cALength-1, db.highFilterRec, filterLen ,convDataHigh, &convLen);

    // 3.抽取結果及求和——實現類似MATLAB中的wkeep1(s,len,'c')的功能
    k = (convLen - recLen)/2;
    for(i=0; i<recLen; i++)
    {
    	recData[i] = convDataLow[i + k] + convDataHigh[i + k];
    }
	
    return recLen;
}



尺度
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章