前段時間由於項目需要,用C++實現了基於PCA的圖像融合算法。一開始低估了這個算法在遙感圖像上的實現難度,對算法的詳細流程沒有完全的瞭解清楚,因爲PCA的實現是非常簡單的,僅僅需要計算特徵值和特徵矩陣就能夠實現圖像的PCA變換。而實現遙感圖像的PCA融合,還有一個步驟非常重要,就是遙感圖像的直方圖匹配問題。網上絕大多數圖像直方圖匹配的算法都是基於256階灰度值的,最後計算出來的圖像變換映射是整型數據,因此可以直接放在變換數組中作爲下標使用,達到映射的效果(這裏太細節的東西看不懂沒關係,我後面會詳細闡述)。而遙感圖像往往並不是8位圖像,也就不是256個灰度級,其中還有很大一部分圖像是浮點型的數據,因此並不能完全依照網上的很多方法實現。需要一些變換處理,而這也是當時困擾我的一個因素。還好後來簡單變換一下實現了,雖然變換比較簡單,但是我也分享出來,希望能夠對讀者有所幫助。
PS:我永遠都調不好CSDN博客文章的排版。。。怎麼這麼難用的排版工具。
爲了連貫起見,下面我將會把整個算法的流程都詳細闡述一遍,並附上我的相應代碼。對算法流程已經比較瞭解的讀者,可以直接往下看完整的代碼。需要說明的是:代碼只用到GDAL庫,因此不用擔心別的依賴庫的問題。
PCA(Principal Component Analysis,主成分分析),簡單點說就是利用計算出來的較少的主成分變量,來代替原始數據所有維度,以達到數據降維的目的。關於PCA的文章和原理的介紹網上非常多,這裏我就不做詳細的描述了,推薦一個李民錄博客裏的PCA吧:http://blog.csdn.net/liminlu0314/article/details/8957009
圖像融合,就是將低空間分辨率的影像和高空間分辨率的單波段全色影像進行融合,以充分利用不同數據的信息。經過融合後的影像,既具有了高空間分辨率,又較好的保留了原始的多個波段的信息。PCA融合只是衆多融合算法當中的一種,可能也是原理最直觀、簡單的一種。理解了PCA,對於理解PCA融合就非常容易了。將原始遙感影像進行PCA變換後,得到的第一主成分,是包含信息量最大的主成分,圖像呈現灰度全色圖像樣子。於是產生了PCA融合的直觀想法:如果利用灰度值信息更豐富,空間分辨率更高的遙感全色影像來替換原始影像經PCA變換後得到的第一主成分,再進行PCA反變換,是不是就能夠豐富原始遙感影像的空間分辨率信息呢?事實上,基於PCA的圖像融合技術,正是這麼一個直觀的思路。所以算法的流程非常簡單:
原始圖像--->重採樣到全色圖像大小---> PCA變換得到主成分圖像------>用全色圖像“替換”第一主成分----->PCA反變換
這裏的“替換”被打上雙引號並加粗是有意而爲的,因爲這裏的替換並不是簡單的將圖像波段換一下就能行的(事實上我一開始在沒有完全瞭解算法流程的時候就只是簡單換掉第一主成分,後來得到的結果總是不對)。一幅圖像要能夠替代另外一幅圖像,它們之間包含的信息應該儘可能的相似,而體現圖像信息的正是圖像的灰度值分佈,也就是圖像的直方圖,若兩幅圖像的直方圖大致一樣,則這兩幅圖像中灰度值分佈也就大致一樣,才能夠進行“替換”。瞭解到這裏,就能知道PCA融合的關鍵除了PCA算法,還有就是圖像的直方圖匹配。(如果有不瞭解直方圖以及直方圖匹配原理的同學,在網上搜一下,有很多。)
下面我們開始進入算法的流程,我會邊將原理,邊附上我的代碼幫助大家理解。
1、首先,是GDAL讀取遙感影像並判斷數據類型,以及代融合的兩幅影像是否大小相等,我想這個我就不再囉嗦了吧,直接給大家附上代碼吧。(說明:由於是特殊要求,需要4個波段的影像,我才把波段數也作爲判斷的一個條件,而讀者使用時下面的波段數完全可以更改)
GDALAllRegister(); CPLSetConfigOption( "GDAL_FILENAME_IS_UTF8", "NO" ); // 打開多波段圖像 multiDataset = ( GDALDataset* )GDALOpen( multiFilename.c_str(), GA_ReadOnly ); if( multiDataset == NULL ) { cout << "打開多波段圖像失敗!" << endl; throw multiFilename + "file not valid"; return; } int multiWidth = multiDataset->GetRasterXSize(); int multiHeight = multiDataset->GetRasterYSize(); int multiBandCount = multiDataset->GetRasterCount(); // 驗證是否爲4波段數據 if ( multiBandCount != 4 ) { cout << "圖像波段數有誤,目前只能處理4波段圖像!" << endl; throw multiFilename + "file not valid"; return; } // 打開高分辨率圖像 highResDataset = ( GDALDataset* )GDALOpen( highRSFilename.c_str(), GA_ReadOnly ); if( highResDataset == NULL ) { cout << "打開高分辨率圖像失敗!" << endl; throw highRSFilename + "file not valid"; return; } int highResWidth = highResDataset->GetRasterXSize(); int highResHeight = highResDataset->GetRasterYSize(); // 判斷兩幅圖像是否等大小 if ( highResHeight != multiHeight || highResWidth != multiWidth ) { cout << "圖像大小不一致" << endl; throw multiFilename + "and" + highRSFilename + "don't match..."; return; }
2、PCA變換
a.計算原始影像的協方差矩陣
double* bandMean = calMean( multiDataset );// 計算波段均值 double* covMatrix = calCovMatrix( multiDataset, bandMean );// 計算協方差矩陣
</pre><pre code_snippet_id="529492" snippet_file_name="blog_20141123_4_200424" name="code" class="cpp">/// <summary> /// 計算圖像波段均值. /// </summary> /// <param name="dataset">圖像數據集.</param> /// <returns>double * 圖像均值向量.</returns> double* PcaFusion::calMean( GDALDataset * dataset ) { double* bandMean = new double [this->bandCount]; for ( int i = 0; i < this->bandCount; i++ ) { double dMaxValue, dMinValue; multiDataset->GetRasterBand( i + 1 )->ComputeStatistics( FALSE, &dMinValue, &dMaxValue, bandMean + i, 0, NULL, NULL ); } if ( bandMean == NULL ) { cout << "統計波段均值失敗!" << endl; return NULL; } return bandMean; }
/// <summary> /// 計算協方差矩陣. /// </summary> /// <param name="dataset">圖像數據集.</param> /// <param name="bandMean">圖像波段均值向量.</param> /// <returns>double * 圖像協方差矩陣.</returns> double* PcaFusion::calCovMatrix( GDALDataset * dataset, double * bandMean ) { double *dCovariance = new double[this->bandCount * this->bandCount]; int index = 0; for ( int i = 0; i < this->bandCount; i++ ) { float* poData1 = new float[ this->height * this->width]; int bandList = {i + 1}; multiDataset->RasterIO( GF_Read, 0, 0, this->width, this->height, poData1, this->width, this->height, GDT_Float32, 1, &bandList, 0, 0, 0 ); for ( int j = 0; j < this->bandCount; j++ ) { float* poData2 = new float[ this->height * this->width]; int bandList = {j + 1}; multiDataset->RasterIO( GF_Read, 0, 0, this->width, this->height, poData2, this->width, this->height, GDT_Float32, 1, &bandList, 0, 0, 0 ); double sum = 0; for ( int pix = 0; pix < this->height * this->width; pix++ ) { sum += ( poData1[pix] - bandMean[i] ) * ( poData2[pix] - bandMean[j] ); } dCovariance[index++] = sum * 1.0 / ( this->height * this->width - 1 ); } } return dCovariance; }
b.計算協方差矩陣的特徵值、特徵向量。這裏採用雅格比(Jacobi)方法求實對稱矩陣的全部特徵值及特徵向量。(計算函數爲李民錄提供)
// 計算協方差所形成的矩陣的特徵值與特徵向量 double eps = 0.0001; //控制精度要求 double *eigenVector = new double[this->bandCount * this->bandCount]; eejcb( covMatrix, this->bandCount, eigenVector, eps, 100000 );
/// <summary> /// 利用雅格比(Jacobi)方法求實對稱矩陣的全部特徵值及特徵向量. /// </summary> /// <param name="a">長度爲n*n的數組,存放實對稱矩陣,返回時對角線存放n個特徵值.</param> /// <param name="n">矩陣的階數.</param> /// <param name="v">長度爲n*n的數組,返回特徵向量(按列存儲).</param> /// <param name="eps">控制精度要求.</param> /// <param name="jt">整型變量,控制最大迭代次數.</param> /// <returns>返回false表示超過迭代jt次仍未達到精度要求,返回true表示正常返回.</returns> bool PcaFusion::eejcb( double a[], int n, double v[], double eps, int jt ) { int i, j, p, q, u, w, t, s, l; double fm, cn, sn, omega, x, y, d; l = 1; //初始化特徵向量矩陣使其全爲0 for( i = 0; i <= n - 1; i++ ) { v[i * n + i] = 1.0; for( j = 0; j <= n - 1; j++ ) { if( i != j ) v[i * n + j] = 0.0; } } while( true ) //循環 { fm = 0.0; for( i = 0; i <= n - 1; i++ ) // 出, 矩陣a( 特徵值 ), 中除對角線外其他元素的最大絕對值 { //這個最大值是位於a[p][q] ,等於fm for( j = 0; j <= n - 1; j++ ) { d = fabs( a[i * n + j] ); if( ( i != j ) && ( d > fm ) ) { fm = d; p = i; q = j; } } } if( fm < eps ) //精度複合要求 return true; //正常返回 if( l > jt ) //迭代次數太多 return false;//失敗返回 l ++; // 迭代計數器 u = p * n + q; w = p * n + p; t = q * n + p; s = q * n + q; x = -a[u]; y = ( a[s] - a[w] ) / 2.0; //x y的求法不同 omega = x / sqrt( x * x + y * y ); //sin2θ //tan2θ=x/y = -2.0*a[u]/(a[s]-a[w]) if( y < 0.0 ) omega = -omega; sn = 1.0 + sqrt( 1.0 - omega * omega ); sn = omega / sqrt( 2.0 * sn ); //sinθ cn = sqrt( 1.0 - sn * sn ); //cosθ fm = a[w]; // 變換前的a[w] a[p][p] a[w] = fm * cn * cn + a[s] * sn * sn + a[u] * omega; a[s] = fm * sn * sn + a[s] * cn * cn - a[u] * omega; a[u] = 0.0; a[t] = 0.0; // 以下是旋轉矩陣,旋轉了了p行,q行,p列,q列 // 但是四個特殊點沒有旋轉(這四個點在上述語句中發生了變化) // 其他不在這些行和列的點也沒變 // 旋轉矩陣,旋轉p行和q行 for( j = 0; j <= n - 1; j++ ) { if( ( j != p ) && ( j != q ) ) { u = p * n + j; w = q * n + j; fm = a[u]; a[u] = a[w] * sn + fm * cn; a[w] = a[w] * cn - fm * sn; } } //旋轉矩陣,旋轉p列和q列 for( i = 0; i <= n - 1; i++ ) { if( ( i != p ) && ( i != q ) ) { u = i * n + p; w = i * n + q; fm = a[u]; a[u] = a[w] * sn + fm * cn; a[w] = a[w] * cn - fm * sn; } } //記錄旋轉矩陣特徵向量 for( i = 0; i <= n - 1; i++ ) { u = i * n + p; w = i * n + q; fm = v[u]; v[u] = v[w] * sn + fm * cn; v[w] = v[w] * cn - fm * sn; } } return true; }
c.將特徵值特徵向量進行排序
/// <summary> /// 按特徵值大小排列特徵向量. /// </summary> /// <param name="eigenVector">特徵向量.</param> /// <param name="covAfterEejcb">經過Jacobi方法返回的原協方差矩陣,對角線爲特徵值.</param> void PcaFusion::sortEigenVector( double * eigenVector, double * covAfterEejcb ) { for( int i = 0; i < this->bandCount - 1; i++ ) { for( int j = i + 1; j < this->bandCount; j++ ) { if( covAfterEejcb[j * this->bandCount + j] > covAfterEejcb[i * this->bandCount + i] ) { double temp = 0; temp = covAfterEejcb[j * this->bandCount + j]; covAfterEejcb[j * this->bandCount + j] = covAfterEejcb[i * this->bandCount + i]; covAfterEejcb[i * this->bandCount + i] = temp; for( int k = 0; k < this->bandCount; k++ ) { temp = 0; temp = eigenVector[k * this->bandCount + j]; eigenVector[k * this->bandCount + j] = eigenVector[k * this->bandCount + i]; eigenVector[k * this->bandCount + i] = temp; } } } } }
d.原始影像矩陣與特徵向量矩陣相乘,得到變換後的主成分
/// <summary> /// PCA變換. /// </summary> /// <param name="imgMatrix">圖像矩陣.</param> float** PcaFusion::PCATransform( float **imgMatrix, double * eigenVector ) { double **vec = new double*[this->bandCount]; for ( int i = 0; i < this->bandCount; i++ ) { vec[i] = new double[this->bandCount]; for ( int j = 0; j < this->bandCount; j++ ) { vec[i][j] = eigenVector[i + j * this->bandCount]; } } // 構造結果矩陣 float** result = new float*[this->bandCount]; for ( int i = 0; i < this->bandCount; i++ ) { result[i] = new float[this->width * this->height]; for ( int j = 0; j < this->width * this->height ; j++ ) { result[i][j] = 0; } } // 矩陣相乘 for ( int i = 0; i < this->bandCount; i++ ) { for ( int j = 0; j < this->height * this->width; j++ ) { for ( int k = 0; k < this->bandCount; k++ ) { result[i][j] += vec[i][k] * imgMatrix[k][j]; } } } return result; }
3、得到全色遙感影像向量,並與PCA變換得到的第一主成分進行直方圖匹配。
這裏需要先統計兩幅影像的直方圖,再將直方圖進行匹配。網上很多代碼和原理都是基於256個灰度階數來計算的,其實直方圖並非都是256個級別,因此將直方圖範圍擴大一點就行了。不明白原理的同學一定要去學習一下背後的數據原理,不然很難理解我下面的代碼。
<pre name="code" class="cpp">// 統計img最值 double imgMax = -100000, imgMin = 100000; for ( int index = 0; index < width * height; index++ ) { if ( imgMax < img[index] ) { imgMax = img[index]; } if ( imgMin > img[index] ) { imgMin = img[index]; } } // 統計ref最值 double refMax = -100000, refMin = 100000; for ( int index = 0; index < width * height; index++ ) { if ( refMax < ref[index] ) { refMax = ref[index]; } if ( refMin > ref[index] ) { refMin = ref[index]; } } // 變換img元素值到ref元素值範圍 for ( int i = 0; i < width * height; i++ ) { img[i] = ( img[i] - imgMin ) / ( imgMax - imgMin ); img[i] = img[i] * ( refMax - refMin ) + refMin; } // 再次統計img最值 imgMax = -100000, imgMin = 100000; for ( int index = 0; index < width * height; index++ ) { if ( imgMax < img[index] ) { imgMax = img[index]; } if ( imgMin > img[index] ) { imgMin = img[index]; } } // 將img和ref複製一份,分別把複製的數組乘以factor,變成整型 int* imgCopy = new int[width * height]; int* refCopy = new int[width * height]; for ( int i = 0; i < width * height; i++ ) { imgCopy[i] = ( int )( img[i] * factor ); refCopy[i] = ( int )( ref[i] * factor ); } delete ref; int imgCopyMax = imgMax * factor; int imgCopyMin = imgMin * factor; int refCopyMax = refMax * factor; int refCopyMin = refMin * factor; // 分別統計兩幅影像的直方圖 int length = imgCopyMax - imgCopyMin + 1; int* imgHist = new int[length]; int* refHist = new int[length]; // 清零 for( int i = 0; i < length; i++ ) { imgHist[i] = 0; refHist[i] = 0; } for ( int i = 0; i < width * height; i++ ) { int val = imgCopy[i] - imgCopyMin; imgHist[val] += 1; int val2 = refCopy[i] - imgCopyMin; refHist[val2] += 1; }
注意以上代碼中,我將原始浮點型的數據乘上了一個factor,變成了整型,這個很關鍵。這個factor的默認值我設置的100。下面我們再來具體說matchHistogram()函數,要匹配直方圖,需要先利用直方圖得到累積分佈函數(cumulative distribution function),其實也就是統計每個直方圖中值的概率分佈,得到一個概率分佈函數。
// returns the cumulative distribution function for histogram h double* PcaFusion::cdf( int* h, int length ) { int n = 0; for( int i = 0; i < length - 1; i++ ) { n += h[i]; } double* p = new double[length]; int c = h[0]; p[0] = ( double )c / n; for( int i = 1; i < length - 1; i++ ) { c += h[i]; p[i] = ( double )c / n; } return p; }
然後是利用累積分佈函數計算變換映射函數。
// returns the mapping function to be applied to image int* PcaFusion::matchHistogram( int* hA, int* hR, int length ) { double* pA = cdf( hA, length ); double* pR = cdf( hR, length ); int* fun = new int[length]; for( int a = 0; a < length; a++ ) { int j = length - 1; do { fun[a] = j; j--; } while ( j >= 0 && pA[a] <= pR[j] ); } return fun; }
有了映射函數,那直接將原始數據進行相應的映射就行了,記住要除以之前的那個factor,將數據變回浮點型。
<pre name="code" class="cpp"> for ( int i = 0; i < width * height; i++ ) { imgCopy[i] = fun[imgCopy[i] - imgCopyMin] + imgCopyMin; img[i] = imgCopy[i] / factor; }
到了這裏就明白爲什麼要變成整型數據了,因爲這裏得到的映射函數,是將原始圖像的值作爲輸入的,而在這裏是下標,所以必須是整型才行。
4、用匹配後的全色影像替換第一主成分
// 用高分辨率圖像替換第一主分量 int bandList = {1}; float *highData = new float[this->width * this->height]; highResDataset->RasterIO( GF_Read, 0, 0, this->width, this->height, highData, this->width, this->height, GDT_Float32, 1, &bandList, 0, 0, 0 ); projToRange( highData, resAfterPCA[0] );// 這裏調用的是統計直方圖,並進行直方圖匹配那部分的內容 resAfterPCA[0] = highData;
5、PCA反變換
/// <summary> /// PCA逆變換. /// </summary> /// <param name="imgMatrix">圖像矩陣.</param> float** PcaFusion::inversePCA( float **imgMatrix, double * eigenVector ) { // 求特徵向量矩陣的逆矩陣 inverseMatrix( eigenVector, this->bandCount ); float** resAfterInversPCA = PCATransform( imgMatrix, eigenVector ); return resAfterInversPCA; }
6、輸出結果
/// <summary> /// 保存圖像到文件. /// </summary> /// <param name="format">圖像格式字符串.</param> void PcaFusion::saveFile( float** pResult, const char* format ) { // 創建結果存儲圖像 if ( format == "" ) { cout << "沒有指定存儲圖像的格式,默認爲GTiff" << endl; } GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName( format ); GDALDataset* reslutDataset = poDriver->Create( this->saveName.c_str(), this->width, this->height, this->bandCount, GDT_Float32, NULL ); for ( int i = 0; i < this->bandCount; i++ ) { GDALRasterBand* pand = reslutDataset->GetRasterBand( i + 1 ); pand->RasterIO( GF_Write, 0, 0, this->width, this->height, pResult[i], this->width, this->height, GDT_Float32, 0, 0 ); } GDALClose( reslutDataset ); }
附上處理結果:
原始影像
高分辨率全色影像
融合結果影像(當然,顯示顏色略微與原圖有點差異,不過是顯示拉伸的問題)
好了,流程就介紹完了,可能代碼有點零散,不過大家可能都發現了我這個是寫在了一個類裏面的,下面我貼出UML的類圖,以及這個類的所有代碼,供大家使用。
下面是實現代碼:
// *********************************************************************** // Author : Jacory // Created : 11-14-2014 // // Last Modified By : Jacory // Last Modified On : 11-22-2014 // *********************************************************************** // <copyright file="PcaFusion.h" company=""> // Copyright (c) . All rights reserved. // </copyright> // <summary>PCA融合類,實現將多波段遙感圖像與全色波段圖像進行融合 // 可處理浮點型圖像數據</summary> // *********************************************************************** #pragma once #include <string> class GDALDataset; class GDALRasterBand; using namespace std; class PcaFusion { public: PcaFusion( string multiFilename, string highRSFilename, string saveName = "" ); ~PcaFusion( void ); // getter string getMultiName() {return multiFilename;}; string getHighRSName() {return highRSFilename;}; string getSaveName() {return saveName;}; const char* getSaveFormat() const { return saveFormat; } int getFactor() {return factor;}; // setter void setMultiName( string multi ); void setHighRSName( string highRS ); void setSaveName( string sName ); void setSaveFormat( const char* val ) { saveFormat = val; } void setFactor( int val ); // 統計波段均值 double* calMean( GDALDataset* dataset ); // 求圖像矩陣協方差 double* calCovMatrix( GDALDataset* dataset, double* bandMean ); // 求實對稱矩陣的特徵值及特徵向量的雅格比法 bool eejcb( double a[], int n, double v[], double eps, int jt ); // 矩陣轉置 void transMatrix( double *matrix, int m, int n ); // 矩陣求逆 void inverseMatrix( double *matrix, int n ); // 線性拉伸 void linearStretch( float** pResult, int width, int height, int bandCount ); // PCA融合 bool principalFusion(); void projToRange( float* img, float* ref ); private: // PCA變換 float** PCATransform( float **imgMatrix, double* eigenVector ); // PCA逆變換 float** inversePCA( float **imgMatrix, double* eigenVector ); // 按特徵值大小排序特徵向量 void sortEigenVector( double* eigenVector, double* covAfterEejcb ); // 保存圖像 void saveFile( float** pResult, const char* format = "GTiff" ); // 得到累積分佈函數 double* cdf( int* h, int length ); // 直方圖匹配 int* matchHistogram( int* hA, int* hR, int length ); private: /// <summary> /// 多波段圖像路徑 /// </summary> string multiFilename; /// <summary> /// 高分辨率圖像路徑 /// </summary> string highRSFilename; /// <summary> /// 保存結果文件路徑 /// </summary> string saveName; /// <summary> /// 圖像高度 /// </summary> int height; /// <summary> /// 圖像寬度 /// </summary> int width; /// <summary> /// 圖像波段數 /// </summary> int bandCount; /// <summary> /// 保存圖像的格式 /// </summary> const char* saveFormat; /// <summary> /// 多波段圖像數據集 /// </summary> GDALDataset* multiDataset; /// <summary> /// 高分辨率圖像數據集 /// </summary> GDALDataset* highResDataset; /// <summary> /// 用於控制直方圖匹配精度的係數,從100到10000,值越大,執行速度越慢,默認爲100 /// </summary> int factor; };
<pre name="code" class="cpp">#include "PcaFusion.h" #include <iostream> #include "gdal_priv.h" #include <map> using namespace std; PcaFusion::PcaFusion( string multiFilename, string highRSFilename, string saveName /*= "" */ ) : multiFilename( multiFilename ), highRSFilename( highRSFilename ), saveName( saveName ) { GDALAllRegister(); CPLSetConfigOption( "GDAL_FILENAME_IS_UTF8", "NO" ); // 打開多波段圖像 multiDataset = ( GDALDataset* )GDALOpen( multiFilename.c_str(), GA_ReadOnly ); if( multiDataset == NULL ) { cout << "打開多波段圖像失敗!" << endl; throw multiFilename + "file not valid"; return; } int multiWidth = multiDataset->GetRasterXSize(); int multiHeight = multiDataset->GetRasterYSize(); int multiBandCount = multiDataset->GetRasterCount(); // 驗證是否爲4波段數據 if ( multiBandCount != 4 ) { cout << "圖像波段數有誤,目前只能處理4波段圖像!" << endl; throw multiFilename + "file not valid"; return; } // 打開高分辨率圖像 highResDataset = ( GDALDataset* )GDALOpen( highRSFilename.c_str(), GA_ReadOnly ); if( highResDataset == NULL ) { cout << "打開高分辨率圖像失敗!" << endl; throw highRSFilename + "file not valid"; return; } int highResWidth = highResDataset->GetRasterXSize(); int highResHeight = highResDataset->GetRasterYSize(); // 判斷兩幅圖像是否等大小 if ( highResHeight != multiHeight || highResWidth != multiWidth ) { cout << "圖像大小不一致" << endl; throw multiFilename + "and" + highRSFilename + "don't match..."; return; } this->bandCount = 4; this->height = multiHeight; this->width = multiWidth; this->factor = 100; this->saveFormat = "GTiff";// 默認保存圖像爲GTiff格式 } PcaFusion::~PcaFusion( void ) { GDALClose( multiDataset ); GDALClose( highResDataset ); } void PcaFusion::setMultiName( string multi ) { if ( multi == "" ) { cout << "multi file name is empty..." << endl; return; } this->multiFilename = multi; } void PcaFusion::setHighRSName( string highRS ) { if ( highRS == "" ) { cout << "high resolution file name is empty..." << endl; return; } this->highRSFilename = highRS; } void PcaFusion::setSaveName( string sName ) { if ( sName == "" ) { cout << "save file name is empty..." << endl; return; } this->saveName = sName; } /// <summary> /// 利用雅格比(Jacobi)方法求實對稱矩陣的全部特徵值及特徵向量. /// </summary> /// <param name="a">長度爲n*n的數組,存放實對稱矩陣,返回時對角線存放n個特徵值.</param> /// <param name="n">矩陣的階數.</param> /// <param name="v">長度爲n*n的數組,返回特徵向量(按列存儲).</param> /// <param name="eps">控制精度要求.</param> /// <param name="jt">整型變量,控制最大迭代次數.</param> /// <returns>返回false表示超過迭代jt次仍未達到精度要求,返回true表示正常返回.</returns> bool PcaFusion::eejcb( double a[], int n, double v[], double eps, int jt ) { int i, j, p, q, u, w, t, s, l; double fm, cn, sn, omega, x, y, d; l = 1; //初始化特徵向量矩陣使其全爲0 for( i = 0; i <= n - 1; i++ ) { v[i * n + i] = 1.0; for( j = 0; j <= n - 1; j++ ) { if( i != j ) v[i * n + j] = 0.0; } } while( true ) //循環 { fm = 0.0; for( i = 0; i <= n - 1; i++ ) // 出, 矩陣a( 特徵值 ), 中除對角線外其他元素的最大絕對值 { //這個最大值是位於a[p][q] ,等於fm for( j = 0; j <= n - 1; j++ ) { d = fabs( a[i * n + j] ); if( ( i != j ) && ( d > fm ) ) { fm = d; p = i; q = j; } } } if( fm < eps ) //精度複合要求 return true; //正常返回 if( l > jt ) //迭代次數太多 return false;//失敗返回 l ++; // 迭代計數器 u = p * n + q; w = p * n + p; t = q * n + p; s = q * n + q; x = -a[u]; y = ( a[s] - a[w] ) / 2.0; //x y的求法不同 omega = x / sqrt( x * x + y * y ); //sin2θ //tan2θ=x/y = -2.0*a[u]/(a[s]-a[w]) if( y < 0.0 ) omega = -omega; sn = 1.0 + sqrt( 1.0 - omega * omega ); sn = omega / sqrt( 2.0 * sn ); //sinθ cn = sqrt( 1.0 - sn * sn ); //cosθ fm = a[w]; // 變換前的a[w] a[p][p] a[w] = fm * cn * cn + a[s] * sn * sn + a[u] * omega; a[s] = fm * sn * sn + a[s] * cn * cn - a[u] * omega; a[u] = 0.0; a[t] = 0.0; // 以下是旋轉矩陣,旋轉了了p行,q行,p列,q列 // 但是四個特殊點沒有旋轉(這四個點在上述語句中發生了變化) // 其他不在這些行和列的點也沒變 // 旋轉矩陣,旋轉p行和q行 for( j = 0; j <= n - 1; j++ ) { if( ( j != p ) && ( j != q ) ) { u = p * n + j; w = q * n + j; fm = a[u]; a[u] = a[w] * sn + fm * cn; a[w] = a[w] * cn - fm * sn; } } //旋轉矩陣,旋轉p列和q列 for( i = 0; i <= n - 1; i++ ) { if( ( i != p ) && ( i != q ) ) { u = i * n + p; w = i * n + q; fm = a[u]; a[u] = a[w] * sn + fm * cn; a[w] = a[w] * cn - fm * sn; } } //記錄旋轉矩陣特徵向量 for( i = 0; i <= n - 1; i++ ) { u = i * n + p; w = i * n + q; fm = v[u]; v[u] = v[w] * sn + fm * cn; v[w] = v[w] * cn - fm * sn; } } return true; } /// <summary> /// 矩陣轉置. /// </summary> /// <param name="matrix">矩陣數組指針.</param> /// <param name="m">矩陣行數.</param> /// <param name="n">矩陣列數.</param> void PcaFusion::transMatrix( double *matrix, int m, int n ) { double*p; if( ( p = new double[m * n] ) == NULL ) return; double temp = 0; for( int i = 0; i < m; i++ ) for( int j = 0; j < n; j++ ) { *( p + i * n + j ) = *( matrix + j * m + i ); } for( int i = 0; i < m; i++ ) for( int j = 0; j < n; j++ ) { *( matrix + i * n + j ) = *( p + i * n + j ); } delete []p; } /// <summary> /// 矩陣求逆. /// </summary> /// <param name="matrix">矩陣數組指針.</param> /// <param name="n">矩陣階數.</param> void PcaFusion::inverseMatrix( double *matrix, int n ) { int *is, *js, i, j, k, l, u, v; double d, p; is = new int[n * sizeof( int )]; js = new int[n * sizeof( int )]; for ( k = 0; k <= n - 1; k++ ) { d = 0.0; for ( i = k; i <= n - 1; i++ ) for ( j = k; j <= n - 1; j++ ) { l = i * n + j; p = fabs( matrix[l] ); if ( p > d ) { d = p; is[k] = i; js[k] = j;} } if ( d + 1.0 == 1.0 ) { delete []is; delete []js; printf( "err**not inv\n" ); return; } if ( is[k] != k ) for ( j = 0; j <= n - 1; j++ ) { u = k * n + j; v = is[k] * n + j; p = matrix[u]; matrix[u] = matrix[v]; matrix[v] = p; } if ( js[k] != k ) for ( i = 0; i <= n - 1; i++ ) { u = i * n + k; v = i * n + js[k]; p = matrix[u]; matrix[u] = matrix[v]; matrix[v] = p; } l = k * n + k; matrix[l] = 1.0 / matrix[l]; for ( j = 0; j <= n - 1; j++ ) if ( j != k ) { u = k * n + j; matrix[u] = matrix[u] * matrix[l];} for ( i = 0; i <= n - 1; i++ ) if ( i != k ) for ( j = 0; j <= n - 1; j++ ) if ( j != k ) { u = i * n + j; matrix[u] = matrix[u] - matrix[i * n + k] * matrix[k * n + j]; } for ( i = 0; i <= n - 1; i++ ) if ( i != k ) { u = i * n + k; matrix[u] = -matrix[u] * matrix[l];} } for ( k = n - 1; k >= 0; k-- ) { if ( js[k] != k ) for ( j = 0; j <= n - 1; j++ ) { u = k * n + j; v = js[k] * n + j; p = matrix[u]; matrix[u] = matrix[v]; matrix[v] = p; } if ( is[k] != k ) for ( i = 0; i <= n - 1; i++ ) { u = i * n + k; v = i * n + is[k]; p = matrix[u]; matrix[u] = matrix[v]; matrix[v] = p; } } delete []is; delete []js; } /// <summary> /// 線性拉伸. /// </summary> /// <param name="pResult">圖像矩陣.</param> /// <param name="width">圖像寬.</param> /// <param name="height">圖像高.</param> /// <param name="bandCount">圖像波段數.</param> void PcaFusion::linearStretch( float** pResult, int width, int height, int bandCount ) { for ( int i = 0; i < bandCount; i++ ) { double dMaxValue = -1000, dMinValue = 1000; for ( int index = 0; index < width * height; index++ ) { if ( dMaxValue < pResult[i][index] ) { dMaxValue = pResult[i][index]; } if ( dMinValue > pResult[i][index] ) { dMinValue = pResult[i][index]; } } for( int j = 0; j < width * height; j++ ) { if ( dMaxValue - dMinValue < 255 ) { pResult[i][j] = pResult[i][j] - dMinValue; } else { pResult[i][j] = ( pResult[i][j] - dMinValue ) * 255.0 / ( dMaxValue - dMinValue ); } } } } /// <summary> /// PCA融合. /// </summary> bool PcaFusion::principalFusion() { if ( multiDataset == NULL || highResDataset == NULL || saveName == "" ) { cout << "數據集讀取失敗..." << endl; throw "read data failed."; } double* bandMean = calMean( multiDataset );// 計算波段均值 double* covMatrix = calCovMatrix( multiDataset, bandMean );// 計算協方差矩陣 // 計算協方差所形成的矩陣的特徵值與特徵向量 double eps = 0.0001; //控制精度要求 double *eigenVector = new double[this->bandCount * this->bandCount]; eejcb( covMatrix, this->bandCount, eigenVector, eps, 100000 ); // 按特徵值由大到小的順序排列特徵向量 sortEigenVector( eigenVector, covMatrix ); /*double eigenVector[] = {0.552398846622175, 0.514249636770153, 0.555078608019249, -0.349700678082944, 0.552839196696649, 0.287442545809982, -0.388975754303186, 0.678559848516214, 0.504571737069979, -0.347417512377672, -0.542438417723444, -0.574864329404061, 0.366921924932919, -0.729537638530976, 0.496332715485658, 0.294613255826687 };*/ /*double eigenVector[] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };*/ //transMatrix( eigenVector, bandCount, bandCount ); // 構造融合結果矩陣 float** pResult = new float*[this->bandCount]; for ( int band = 0; band < this->bandCount; band++ ) { pResult[band] = new float[this->width * this->height]; int bandList = {band + 1}; multiDataset->RasterIO( GF_Read, 0, 0, this->width, this->height, pResult[band], this->width, this->height, GDT_Float32, 1, &bandList, 0, 0, 0 ); } // 將多光譜圖像進行主分量變換 float** resAfterPCA = PCATransform( pResult, eigenVector ); delete []pResult; // 用高分辨率圖像替換第一主分量 int bandList = {1}; float *highData = new float[this->width * this->height]; highResDataset->RasterIO( GF_Read, 0, 0, this->width, this->height, highData, this->width, this->height, GDT_Float32, 1, &bandList, 0, 0, 0 ); projToRange( highData, resAfterPCA[0] ); resAfterPCA[0] = highData; // 主分量逆變換 float** resAfterInversePCA = inversePCA( resAfterPCA, eigenVector ); delete []resAfterPCA; // 將結果寫入圖像 saveFile( resAfterInversePCA, saveFormat ); return true; } /// <summary> /// 計算圖像波段均值. /// </summary> /// <param name="dataset">圖像數據集.</param> /// <returns>double * 圖像均值向量.</returns> double* PcaFusion::calMean( GDALDataset * dataset ) { double* bandMean = new double [this->bandCount]; for ( int i = 0; i < this->bandCount; i++ ) { double dMaxValue, dMinValue; multiDataset->GetRasterBand( i + 1 )->ComputeStatistics( FALSE, &dMinValue, &dMaxValue, bandMean + i, 0, NULL, NULL ); } if ( bandMean == NULL ) { cout << "統計波段均值失敗!" << endl; return NULL; } return bandMean; } /// <summary> /// 計算協方差矩陣. /// </summary> /// <param name="dataset">圖像數據集.</param> /// <param name="bandMean">圖像波段均值向量.</param> /// <returns>double * 圖像協方差矩陣.</returns> double* PcaFusion::calCovMatrix( GDALDataset * dataset, double * bandMean ) { double *dCovariance = new double[this->bandCount * this->bandCount]; int index = 0; for ( int i = 0; i < this->bandCount; i++ ) { float* poData1 = new float[ this->height * this->width]; int bandList = {i + 1}; multiDataset->RasterIO( GF_Read, 0, 0, this->width, this->height, poData1, this->width, this->height, GDT_Float32, 1, &bandList, 0, 0, 0 ); for ( int j = 0; j < this->bandCount; j++ ) { float* poData2 = new float[ this->height * this->width]; int bandList = {j + 1}; multiDataset->RasterIO( GF_Read, 0, 0, this->width, this->height, poData2, this->width, this->height, GDT_Float32, 1, &bandList, 0, 0, 0 ); double sum = 0; for ( int pix = 0; pix < this->height * this->width; pix++ ) { sum += ( poData1[pix] - bandMean[i] ) * ( poData2[pix] - bandMean[j] ); } dCovariance[index++] = sum * 1.0 / ( this->height * this->width - 1 ); } } return dCovariance; } /// <summary> /// 按特徵值大小排列特徵向量. /// </summary> /// <param name="eigenVector">特徵向量.</param> /// <param name="covAfterEejcb">經過Jacobi方法返回的原協方差矩陣,對角線爲特徵值.</param> void PcaFusion::sortEigenVector( double * eigenVector, double * covAfterEejcb ) { for( int i = 0; i < this->bandCount - 1; i++ ) { for( int j = i + 1; j < this->bandCount; j++ ) { if( covAfterEejcb[j * this->bandCount + j] > covAfterEejcb[i * this->bandCount + i] ) { double temp = 0; temp = covAfterEejcb[j * this->bandCount + j]; covAfterEejcb[j * this->bandCount + j] = covAfterEejcb[i * this->bandCount + i]; covAfterEejcb[i * this->bandCount + i] = temp; for( int k = 0; k < this->bandCount; k++ ) { temp = 0; temp = eigenVector[k * this->bandCount + j]; eigenVector[k * this->bandCount + j] = eigenVector[k * this->bandCount + i]; eigenVector[k * this->bandCount + i] = temp; } } } } } /// <summary> /// PCA變換. /// </summary> /// <param name="imgMatrix">圖像矩陣.</param> float** PcaFusion::PCATransform( float **imgMatrix, double * eigenVector ) { double **vec = new double*[this->bandCount]; for ( int i = 0; i < this->bandCount; i++ ) { vec[i] = new double[this->bandCount]; for ( int j = 0; j < this->bandCount; j++ ) { vec[i][j] = eigenVector[i + j * this->bandCount]; } } // 構造結果矩陣 float** result = new float*[this->bandCount]; for ( int i = 0; i < this->bandCount; i++ ) { result[i] = new float[this->width * this->height]; for ( int j = 0; j < this->width * this->height ; j++ ) { result[i][j] = 0; } } // 矩陣相乘 for ( int i = 0; i < this->bandCount; i++ ) { for ( int j = 0; j < this->height * this->width; j++ ) { for ( int k = 0; k < this->bandCount; k++ ) { result[i][j] += vec[i][k] * imgMatrix[k][j]; } } } return result; } /// <summary> /// PCA逆變換. /// </summary> /// <param name="imgMatrix">圖像矩陣.</param> float** PcaFusion::inversePCA( float **imgMatrix, double * eigenVector ) { // 求特徵向量矩陣的逆矩陣 inverseMatrix( eigenVector, this->bandCount ); float** resAfterInversPCA = PCATransform( imgMatrix, eigenVector ); return resAfterInversPCA; } /// <summary> /// 保存圖像到文件. /// </summary> /// <param name="format">圖像格式字符串.</param> void PcaFusion::saveFile( float** pResult, const char* format ) { // 創建結果存儲圖像 if ( format == "" ) { cout << "沒有指定存儲圖像的格式,默認爲GTiff" << endl; } GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName( format ); GDALDataset* reslutDataset = poDriver->Create( this->saveName.c_str(), this->width, this->height, this->bandCount, GDT_Float32, NULL ); for ( int i = 0; i < this->bandCount; i++ ) { GDALRasterBand* pand = reslutDataset->GetRasterBand( i + 1 ); pand->RasterIO( GF_Write, 0, 0, this->width, this->height, pResult[i], this->width, this->height, GDT_Float32, 0, 0 ); } GDALClose( reslutDataset ); } void PcaFusion::projToRange( float* img, float* ref ) { // 統計img最值 double imgMax = -100000, imgMin = 100000; for ( int index = 0; index < width * height; index++ ) { if ( imgMax < img[index] ) { imgMax = img[index]; } if ( imgMin > img[index] ) { imgMin = img[index]; } } // 統計ref最值 double refMax = -100000, refMin = 100000; for ( int index = 0; index < width * height; index++ ) { if ( refMax < ref[index] ) { refMax = ref[index]; } if ( refMin > ref[index] ) { refMin = ref[index]; } } // 變換img元素值到ref元素值範圍 for ( int i = 0; i < width * height; i++ ) { img[i] = ( img[i] - imgMin ) / ( imgMax - imgMin ); img[i] = img[i] * ( refMax - refMin ) + refMin; } // 再次統計img最值 imgMax = -100000, imgMin = 100000; for ( int index = 0; index < width * height; index++ ) { if ( imgMax < img[index] ) { imgMax = img[index]; } if ( imgMin > img[index] ) { imgMin = img[index]; } } // 將img和ref複製一份,分別把複製的數組乘以factor,變成整型 int* imgCopy = new int[width * height]; int* refCopy = new int[width * height]; for ( int i = 0; i < width * height; i++ ) { imgCopy[i] = ( int )( img[i] * factor ); refCopy[i] = ( int )( ref[i] * factor ); } delete ref; int imgCopyMax = imgMax * factor; int imgCopyMin = imgMin * factor; int refCopyMax = refMax * factor; int refCopyMin = refMin * factor; // 分別統計兩幅影像的直方圖 int length = imgCopyMax - imgCopyMin + 1; int* imgHist = new int[length]; int* refHist = new int[length]; // 清零 for( int i = 0; i < length; i++ ) { imgHist[i] = 0; refHist[i] = 0; } for ( int i = 0; i < width * height; i++ ) { int val = imgCopy[i] - imgCopyMin; imgHist[val] += 1; int val2 = refCopy[i] - imgCopyMin; refHist[val2] += 1; } int* fun = matchHistogram( imgHist, refHist, length ); delete refHist; delete imgHist; delete refCopy; for ( int i = 0; i < width * height; i++ ) { imgCopy[i] = fun[imgCopy[i] - imgCopyMin] + imgCopyMin; img[i] = imgCopy[i] / factor; } delete imgCopy; } // returns the cumulative distribution function for histogram h double* PcaFusion::cdf( int* h, int length ) { int n = 0; for( int i = 0; i < length - 1; i++ ) { n += h[i]; } double* p = new double[length]; int c = h[0]; p[0] = ( double )c / n; for( int i = 1; i < length - 1; i++ ) { c += h[i]; p[i] = ( double )c / n; } return p; } // returns the mapping function to be applied to image int* PcaFusion::matchHistogram( int* hA, int* hR, int length ) { double* pA = cdf( hA, length ); double* pR = cdf( hR, length ); int* fun = new int[length]; for( int a = 0; a < length; a++ ) { int j = length - 1; do { fun[a] = j; j--; } while ( j >= 0 && pA[a] <= pR[j] ); } return fun; } void PcaFusion::setFactor( int val ) { if ( val < 100 ) { cout << "factor參數設置太小,精度會很低。將會採用默認值100" << endl; factor = 100; } else if ( val > 10000 ) { cout << "factor參數設置太大,執行速度非常慢。將會採用最大推薦值10000" << endl; factor = 10000; } else { factor = val; } }