1.意義
高斯曲線 ,又叫做gaussian curve,是正態分佈中的一條標準曲線。具有以下特徵:
1.1 正態曲線在橫軸上方均數處最高;
1.2 正在分佈以均數爲中心,左右對稱;
1.3 正態分佈有兩個參數,即均數和標準差;標準正態分佈用N(0,1)表示;
1.4 正態曲線下的面積分布有一定的規律。
在分析儀器的測量中,有許多具有明確的物理意義的二維圖譜,如光譜圖、色譜圖等,許多測量圖譜都可以用高斯曲線予以描述。高斯曲線雖然也是非線性函數,但它的各個參數具有明確的物理意義,因爲高斯擬合在分析儀器的測量中具有廣泛的應用前景。利用它來描述或擬合求出一些實驗數據的分析,往往能起到常規方法不能達到的作用。
2.結果展示
最近在做一個醫用儀器項目,需要用到高斯擬合,之前也沒有搞過相關內容,於是先在PC端實現高斯擬合函數,能夠實現要求後,再移植到儀器上使用。
先展示一下結果:
圖2.1 高斯曲線擬合
先隨機選6個測試點(藍色點),根據這6個測試點,進行高斯擬合,紅色曲線就是擬合出來的曲線。擬合出來的曲線基本在選取的6個測試點附近。通過這6個點,找出了互相之間的關係。達到了設計目的。
3.原理
高斯擬合即使用形如:Gi(x) = Ai*exp((x-Bi)^2/Ci^2)的高斯函數對數據點集進行函數逼近的擬合方法,高斯擬合跟多項式擬合類似,不同的是多項式擬合是用冪函數系,而高斯擬合用的是高斯函數系。使用高斯函數擬合來進行擬合,優點在於計算積分十分簡單快捷。
3.1 高斯函數
高斯函數:
a表示得到曲線的高度,c是指曲線在x軸的中心,b指width(與半峯全寬有關),圖形如下:圖形如下:
3.2 高斯擬合原理
設有一組實驗數據(i = 1,2,3,...N),可用高斯函數描述:
式(3.1)中待估參數a,c和b,分別代表的物理意義爲高斯曲線的峯高、峯位置和半寬度信息。將式(3.1)兩邊取自然對數,化爲:
令
則式(3.2)化爲二次多項式擬合函數
考慮全部數據和測量誤差,並以矩陣形式表示如下
簡記爲:
在不考慮總量程誤差E的影響情況下,根據最小二乘原理,可求得擬合常數b0,b1,b2構成的矩陣B的廣義最小二乘解爲:
進而根據式(3.3),可求得待估參數a,b,c:
, , (3.7)
將所得的a,b和c帶入式(3.1),就能夠得到由實驗數據(i = 1,2,3,...N)擬合的高斯函數。
3.3 最小二乘法
最小二乘法(又稱最小平方法)是一種數學優化技術。它通過最小化誤差的平方和尋找數據的最佳函數匹配。利用最小二乘法可以簡便地求得未知的數據,並使得這些求得的數據與實際數據之間誤差的平方和爲最小。最小二乘法還可用於曲線擬合。其他一些優化問題也可通過最小化能量或最大化熵用最小二乘法來表達。
1801年,意大利天文學家朱賽普·皮亞齊發現了第一顆小行星穀神星。經過40天的跟蹤觀測後,由於穀神星運行至太陽背後,使得皮亞齊失去了穀神星的位置。隨後全世界的科學家利用皮亞齊的觀測數據開始尋找穀神星,但是根據大多數人計算的結果來尋找穀神星都沒有結果。時年24歲的高斯也計算了穀神星的軌道。奧地利天文學家海因裏希·奧爾伯斯根據高斯計算出來的軌道重新發現了穀神星。
高斯使用的最小二乘法的方法發表於1809年他的著作《天體運動論》中。法國科學家勒讓德於1806年獨立發明“最小二乘法”,但因不爲世人所知而默默無聞。勒讓德曾與高斯爲誰最早創立最小二乘法原理髮生爭執。1829年,高斯提供了最小二乘法的優化效果強於其他方法的證明,因此被稱爲高斯-馬爾可夫定理。
4.實現
4.1 源碼
這裏先把C代碼展示出來:
/*
2019.12.24
驗證高斯擬合代碼
環境:Ubuntu32 16.04.1 cairo庫實現
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <cairo.h>
#include <math.h>
#include "GuassFitting.h"
/* 採集樣本數量 */
#define N 6
/* 原點座標 */
#define OriginX 20
#define OriginY 20
/* 座標轉換 */
#define X(n) ((n)*1.0)
#define Y(n) ((400-(n))*1.0)
/* 座標系內位置 */
#define PosX(n) (X(OriginX+(n)))
#define PosY(n) (Y(OriginY+(n)))
#define ANGLE(ang) (ang * 3.1415926 / 180.0)
struct POS{
int x;
int y;
};
cairo_surface_t *image_surface_create_from_png(const char *filename)
{
cairo_status_t cst;
cairo_surface_t *image_sf=cairo_image_surface_create_from_png(filename);
cst = cairo_surface_status (image_sf);
if (cst!=CAIRO_STATUS_SUCCESS)
{
printf( "failed to cairo_image_surface_create_from_png cairo_status_t is:%d file: %s",cst, filename);
image_sf = NULL;
//if (cst == CAIRO_STATUS_NO_MEMORY) {
//image_sf = cairo_image_surface_create_from_jpeg(filename);
//}
}
return image_sf;
}
void draw_png2surface(cairo_t *cr, double x, double y, cairo_surface_t *surface){
if(surface != NULL){
cairo_set_source_surface(cr, surface, x, y);
cairo_paint(cr);
}
}
/*
創建背景
*/
void createBackground(cairo_t *cr,char *file)
{
cairo_surface_t *g_background;
if(cr == NULL || file == NULL)
{
return;
}
/* 背景圖 */
g_background = image_surface_create_from_png(file);
draw_png2surface(cr, 0, 0, g_background);
}
/*
構建座標系
*/
void createCoordinate(cairo_t *cr)
{
if(cr == NULL)
{
return;
}
/* 劃線 */
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);/* 設置顏色 -黑色 */
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_width (cr, 1.0);
cairo_move_to (cr, X(OriginX), Y(OriginY));//X軸
cairo_line_to (cr, X(OriginX+650), Y(OriginY));
cairo_stroke (cr);
cairo_move_to (cr, X(OriginX), Y(OriginY));//Y軸
cairo_line_to (cr, X(OriginX), Y(OriginY+350));
cairo_stroke (cr);
/* 量程 */
cairo_select_font_face (cr, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size (cr, 15.0);
//原點
cairo_move_to (cr, X(OriginX-5), Y(OriginY-15));
cairo_show_text (cr, "0");
//X軸
cairo_move_to (cr, X(OriginX+650-10), Y(OriginY-15));
cairo_show_text (cr, "650");
//Y軸
cairo_move_to (cr, X(OriginX-10), Y(OriginY+350+5));
cairo_show_text (cr, "350");
cairo_stroke (cr);
}
/*
構建座標點
*/
void createPoints(cairo_t *cr,int x,int y)
{
char buf[64];
if(cr == NULL)
{
return;
}
cairo_set_source_rgb(cr, 0.0, 0.0, 1.0);/* 設置顏色 -藍色 */
cairo_set_line_width(cr, 4);
cairo_arc(cr, PosX(x), PosY(y), 2, ANGLE(0), ANGLE(360));
cairo_stroke (cr);
/* 顯示座標 */
memset(buf,0x00,sizeof(buf));
sprintf(buf,"(%d,%d)",x,y);
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);/* 設置顏色 -黑色 */
cairo_select_font_face (cr, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size (cr, 10.0);
cairo_move_to(cr,PosX(x-8), PosY(y+8));
cairo_show_text (cr, buf);
cairo_stroke (cr);
}
/*
創建高斯曲線
*/
void createGuassCurve(cairo_t *cr,double a,double b,double c)
{
int x,y;
if(cr == NULL)
{
return;
}
cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);/* 設置顏色 -紅色 */
cairo_set_line_width (cr, 1.0);
for(x = 0; x < 630; x++)
{
y = a*exp(-(pow(x-c,2.0)/b));
cairo_move_to(cr, PosX(x), PosY(y));
cairo_line_to(cr, PosX(x), PosY(y));
cairo_stroke (cr);
}
}
/*
指數和對數函數測試
*/
int mathTest(void)
{
printf("pow(x,y) x= 10,y=2 value=%lf\n",pow(10.0,2.0));
printf("powl(x,y) log x=10,y=100 value=%lf\n",powf(100.0,2.0));
printf("exp(x) e x=1 value=%f\n",exp(1));
printf("exp(x) e x=2 value=%f\n",exp(2));
printf("loge=%f\n",log(10)); //以e爲底的對數函數
printf("loge=%f\n",log(2.718282)); //以e爲底的對數函數
printf("log10=%f\n",log10(100)); //以10爲底的對數函數
printf("sqrt=%f\n",sqrt(16));//平方根
}
int main(int argc,char *argv[])
{
int i;
double b[N*4],a[N*3*4], q[N*4*4];//對應緩存擴大4倍,防止core dumped
double b0,b1,b2;
double ga,gb,gc;
int m,n;
/* 採樣點 */
#if 0
struct POS pos[N] = {
{200,100},
{300,220},
{400,300},
{500,220}
};
#else
struct POS pos[N] = {
{200,200},
{300,280},
{400,320},
{440,310},
{500,280},
{600,220}
};
#endif
/************************ 創建cairo ****************************/
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 700, 400);
cairo_t *cr = cairo_create (surface);
/*********************** 創建背景 *****************************/
createBackground(cr,"background.png");
/*********************** 構建座標系 *****************************/
createCoordinate(cr);
/************************ 繪製採樣點 *****************************/
for(i = 0; i < N; i++)
{
createPoints(cr,pos[i].x,pos[i].y);
}
/********************* 對採樣點進行數據預處理 *******************/
for(i = 0;i < N;i++)
{
b[i] = log(pos[i].y);//Z
}
for(i = 0;i < N;i++)
{
a[i*3+0] = 1.0;
a[i*3+1] = pos[i].x*1.0;
a[i*3+2] = pow(pos[i].x,2)*1.0;
}
m = N;n = 3;
/*********************** 高斯擬合 最小二乘解 ********************/
if(GuassFitting_Gmqr(a,m,n,b,q)==0)
{
printf("GuassFitting Fail!\n");
}
else
{
for (i=0; i<n; i++)
{
printf("b%d=%13.7f\n",i,b[i]);
}
b0 = b[0];b1 = b[1];b2 = b[2];//最小二乘解係數
/*********************** 計算高斯參數 ********************/
GuassFitting_GetCurvePara(b0,b1,b2,&ga,&gb,&gc);
printf("a = %f\n",ga);
printf("b = %f\n",gb);
printf("c = %f\n",gc);
/************************ 繪製高斯曲線 ****************************/
createGuassCurve(cr,ga,gb,gc);
}
/************************ 創建圖片 ****************************/
cairo_surface_write_to_png (surface, "guassfitting.png");
/************************ 銷燬cairo ****************************/
cairo_destroy (cr);
cairo_surface_destroy (surface);
return 0;
}
4.2 cairo庫
基於Linux C編程,圖像顯示使用cairo庫。至於如何使用不在本文論述的重點。詳見:
https://blog.csdn.net/dingzj2000/article/details/103719104
4.3 數據預處理
採樣的座標分別爲(200,200),(300,280),(400,320),(440,310),(500,280),(600,220)
基於A=XB形式,由式(3.3),對於A來說.
存在將y位置轉換爲對數形式。所以:b[i] = log(pos[i].y);//Z
對於X的形式是[1 x x^2],所以:
a[i*3+0] = 1.0;
a[i*3+1] = pos[i].x*1.0;
a[i*3+2] = pow(pos[i].x,2)*1.0;
4.4 函數參數定義
int GuassFitting_Gmqr(double a[],int m,int n,double b[],double q[])
作用:
函數語句與形參說明最小二乘問題的豪斯荷爾德變化法,輸入矩陣相關參數;獲取最小二乘解
參數:
double a[]:存放超定方程組的係數矩陣A。返回時存放QR分解式中的R矩陣
理解爲a[m][n],以一位數組展示出來,組合成一位數組
int m: 係數矩陣A的行數,m>=n,獲取的採樣點數
int n: 係數矩陣A的列數,n<=m ,固定爲3
doube b[]:存放方程組右端B的常數向量。返回時前n個分量存放方程組的最小二乘解
doube q[]:返回時存放QR的分解式中的正交矩陣Q。理解爲q[m][n],以一維數組表現出來。
返回:
返回0,則表示程序工作失敗(如A列線性相關);
若返回的標誌值不爲0,則表示正常返回。
b0 = b[0];b1 = b[1];b2 = b[2];//最小二乘解係數
int GuassFitting_GetCurvePara(double b0,double b1,double b2,double *a,double *b,double *c)
此函數就是實現式(3.7),通過b0,b1和b2獲取a,b,c最終獲取高斯曲線函數
4.5 參數打印
打印參數如下:
通過上述打印清楚的看到B的係數,以及高斯曲線參數。通過值發現,在x=409.360814,是高斯曲線的峯值。
5.核心算法
核心高斯擬合算法採用C語言實現,見下圖:
6.獲取算法
加微信(微信號:dingzj2000),獲取詳細算法。