最近爆火的电子血氧仪是什么原理?测的准吗?

大家好,我是学电子的小白白。

最近,随着新冠病毒在国内肆虐,继口罩、抗原、药品之后,血氧仪的价格也开始水涨船高。从1个多月前的100多元,暴涨到300多。

那么,这类家用的电子血氧仪是如何工作的呢?测量数据到底准不准?今天小白白就带大家来分析一下。

1)血氧仪工作原理

血氧仪是一种监测脉搏、血氧饱和度等指标的医疗器械,常见的家用型血氧仪,主要有指夹式、腕表式等形式。

一般大家最关注的是血氧饱和度(oxygen saturation简写为SpO2),它是指在全部血容量中被结合O2容量占全部可结合的O2容量的百分比,是人体携带氧气能力的重要参考值。人体正常的SpO2应该不小于95%,长期低于93%时需要就医。

SpO2 一般由以下公式计算:

其中CHbO2是氧合血红蛋白浓度,CHb是还原血红蛋白浓度。

一方面,这两种血红蛋白对不同波长的光有不同的吸收度;另一方面,当动脉跳动时,动脉中的血液量会发生变化,可以区分出皮肤、肌肉、静脉血等对光的吸收影响(这些组织对光的吸收可以认为固定不变)。因此,利用两种不同波长的光,经透射或反射后,采集数据综合处理,就能计算出血氧饱和度。

现在市面上最常见的,都是光电式的血氧仪,如下图所示,有透射式和反射式两种实现方法。

常见的指夹式血氧仪就是透射式,智能手环或手表就是反射式,原理是差不多的。

而LED光源的选择,与血红蛋白对不同光波长的吸收率有关,下图是两种血红蛋白对不同波长的光的消光系数图:

可以看到,两种血红蛋白对波长为660nm左右光的吸收差别最大,而对波长为800nm左右光的吸收基本相等。理论上来说,使用660nm和800nm波长的光作为光源是最合适的,但是,由于在800nm左右时,二者的消光系数斜率相差较大,光波长偏差一点就会引起较大的吸收率变化,这对LED的制造工艺要求太高,所以,工程实现时,一般不用800nm波长的LED,而选择波长为860nm~920nm的LED作为另一个光源,这个区间的消光系数斜率基本一样,而且变化平缓。

好了,到这里,硬件部分的实现我们已经了解了大概。核心就是要使用两个LED作为光源,一个660nm波长的红外光,一个900nm左右波长的红光,两束光分别通过透射(或反射)皮肤后,到达光电接收管,再采集光电接收管的值。

那么采集到两个光源的值后,应该如何处理呢?这里由于有比较多的公式推导,我们直接略过,给出下面的公式:(出自Maxim公司的应用文档,相关文档都能在文末关注微 信公.众.号,找到下载地址)

这里的实现需要三步:

首先,我们采集的两个LED光源的值,需要分离出直流分量和交流分量,也就是:红光的交流分量ACred、红光的直流分量DCred、红外光的交流分量ACired、红外光的直流分量DCired;

其次,用采集到的四个值,计算出R;

最后,用R计算SpO2,这个计算公式中a、b、c是三个需要校准的参数。需要大量的试验数据去拟合出来。

2)血氧仪的制作

有了以上的理论基础,我们可以自己动手DIY一个血氧仪。

Maxim公司有一款集成芯片,可以实现大部分的硬件功能,就是MAX30100、MAX30102系列芯片。MAX30100已停产,新设计中不推荐使用,MAX30102是新一代产品。

目前价格还没有太离谱:

MAX30102集成了一个660nm红光LED、880nm红外光LED、光电检测器,以及带环境光抑制的低噪声电子电路。芯片内部含18bit ADC采集电路。对外是I2C接口。基本上单芯片就能实现光源信号的采集。

要注意,MAX30102的输出值,只是两个LED光源的采集值。后续还需要软件去实现交流、直流分离,R的求解、SpO2的求解。顺带也可以求解出脉搏数据。

使用max30102很简单,用I2C接口访问,初始化代码如下:

	max30102_Bus_Write(REG_INTR_ENABLE_1,0xc0);	// INTR setting
	max30102_Bus_Write(REG_INTR_ENABLE_2,0x00);
	max30102_Bus_Write(REG_FIFO_WR_PTR,0x00);  	//FIFO_WR_PTR[4:0]
	max30102_Bus_Write(REG_OVF_COUNTER,0x00);  	//OVF_COUNTER[4:0]
	max30102_Bus_Write(REG_FIFO_RD_PTR,0x00);  	//FIFO_RD_PTR[4:0]
	max30102_Bus_Write(REG_FIFO_CONFIG,0x0f);  	//sample avg = 1, fifo rollover=false, fifo almost full = 17
	max30102_Bus_Write(REG_MODE_CONFIG,0x03);  	//0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
	max30102_Bus_Write(REG_SPO2_CONFIG,0x27);  	// SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS)  
	max30102_Bus_Write(REG_LED1_PA,0x24);   	//Choose value for ~ 7mA for LED1
	max30102_Bus_Write(REG_LED2_PA,0x24);   	// Choose value for ~ 7mA for LED2
	max30102_Bus_Write(REG_PILOT_PA,0x7f);   	// Choose value for ~ 25mA for Pilot LED

主函数中循环调用fifo读取函数,用于获取LED光源的采集值:

void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)
{
	uint32_t un_temp;
	unsigned char uch_temp;
	char ach_i2c_data[6];
	*pun_red_led=0;
	*pun_ir_led=0;

	//read and clear status register
	maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
	maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);

	IIC_ReadBytes(I2C_WRITE_ADDR,REG_FIFO_DATA,(u8 *)ach_i2c_data,6);

	un_temp=(unsigned char) ach_i2c_data[0];
	un_temp<<=16;
	*pun_red_led+=un_temp;
	un_temp=(unsigned char) ach_i2c_data[1];
	un_temp<<=8;
	*pun_red_led+=un_temp;
	un_temp=(unsigned char) ach_i2c_data[2];
	*pun_red_led+=un_temp;

	un_temp=(unsigned char) ach_i2c_data[3];
	un_temp<<=16;
	*pun_ir_led+=un_temp;
	un_temp=(unsigned char) ach_i2c_data[4];
	un_temp<<=8;
	*pun_ir_led+=un_temp;
	un_temp=(unsigned char) ach_i2c_data[5];
	*pun_ir_led+=un_temp;
	*pun_red_led&=0x03FFFF;  //Mask MSB [23:18]
	*pun_ir_led&=0x03FFFF;  //Mask MSB [23:18]
}

采集值最好经过滤波,以减少噪声的干扰。

之后,再分离出交流、直流分量,求出R和SpO2即可,核心是这个函数:

void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer,  int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid, 
                              int32_t *pn_heart_rate, int8_t  *pch_hr_valid)
/**
* \brief        Calculate the heart rate and SpO2 level
* \par          Details
*               By detecting  peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.
*               Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
*               Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.
*
* \param[in]    *pun_ir_buffer           - IR sensor data buffer
* \param[in]    n_ir_buffer_length      - IR sensor data buffer length
* \param[in]    *pun_red_buffer          - Red sensor data buffer
* \param[out]    *pn_spo2                - Calculated SpO2 value
* \param[out]    *pch_spo2_valid         - 1 if the calculated SpO2 value is valid
* \param[out]    *pn_heart_rate          - Calculated heart rate value
* \param[out]    *pch_hr_valid           - 1 if the calculated heart rate value is valid
*
* \retval       None
*/
{
    uint32_t un_ir_mean ,un_only_once ;
    int32_t k ,n_i_ratio_count;
    int32_t i, s, m, n_exact_ir_valley_locs_count ,n_middle_idx;
    int32_t n_th1, n_npks,n_c_min;      
    int32_t an_ir_valley_locs[15] ;
    int32_t an_exact_ir_valley_locs[15] ;
    int32_t an_dx_peak_locs[15] ;
    int32_t n_peak_interval_sum;
    
    int32_t n_y_ac, n_x_ac;
    int32_t n_spo2_calc; 
    int32_t n_y_dc_max, n_x_dc_max; 
    int32_t n_y_dc_max_idx, n_x_dc_max_idx; 
    int32_t an_ratio[5],n_ratio_average; 
    int32_t n_nume,  n_denom ;
    // remove DC of ir signal    
    un_ir_mean =0; 
    for (k=0 ; k<n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ;
    un_ir_mean =un_ir_mean/n_ir_buffer_length ;
    for (k=0 ; k<n_ir_buffer_length ; k++ )  an_x[k] =  pun_ir_buffer[k] - un_ir_mean ; 
    
    // 4 pt Moving Average
    for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
        n_denom= ( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3]);
        an_x[k]=  n_denom/(int32_t)4; 
    }

    // get difference of smoothed IR signal
    
    for( k=0; k<BUFFER_SIZE-MA4_SIZE-1;  k++)
        an_dx[k]= (an_x[k+1]- an_x[k]);

    // 2-pt Moving Average to an_dx
    for(k=0; k< BUFFER_SIZE-MA4_SIZE-2; k++){
        an_dx[k] =  ( an_dx[k]+an_dx[k+1])/2 ;
    }
    
    // hamming window
    // flip wave form so that we can detect valley with peak detector
    for ( i=0 ; i<BUFFER_SIZE-HAMMING_SIZE-MA4_SIZE-2 ;i++){
        s= 0;
        for( k=i; k<i+ HAMMING_SIZE ;k++){
            s -= an_dx[k] *auw_hamm[k-i] ; 
                     }
        an_dx[i]= s/ (int32_t)1146; // divide by sum of auw_hamm 
    }

 
    n_th1=0; // threshold calculation
    for ( k=0 ; k<BUFFER_SIZE-HAMMING_SIZE ;k++){
        n_th1 += ((an_dx[k]>0)? an_dx[k] : ((int32_t)0-an_dx[k])) ;
    }
    n_th1= n_th1/ ( BUFFER_SIZE-HAMMING_SIZE);
    // peak location is acutally index for sharpest location of raw signal since we flipped the signal         
    maxim_find_peaks( an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE-HAMMING_SIZE, n_th1, 8, 5 );//peak_height, peak_distance, max_num_peaks 

    n_peak_interval_sum =0;
    if (n_npks>=2){
        for (k=1; k<n_npks; k++)
            n_peak_interval_sum += (an_dx_peak_locs[k]-an_dx_peak_locs[k -1]);
        n_peak_interval_sum=n_peak_interval_sum/(n_npks-1);
        *pn_heart_rate=(int32_t)(6000/n_peak_interval_sum);// beats per minutes
        *pch_hr_valid  = 1;
    }
    else  {
        *pn_heart_rate = -999;
        *pch_hr_valid  = 0;
    }
            
    for ( k=0 ; k<n_npks ;k++)
        an_ir_valley_locs[k]=an_dx_peak_locs[k]+HAMMING_SIZE/2; 


    // raw value : RED(=y) and IR(=X)
    // we need to assess DC and AC value of ir and red PPG. 
    for (k=0 ; k<n_ir_buffer_length ; k++ )  {
        an_x[k] =  pun_ir_buffer[k] ; 
        an_y[k] =  pun_red_buffer[k] ; 
    }

    // find precise min near an_ir_valley_locs
    n_exact_ir_valley_locs_count =0; 
    for(k=0 ; k<n_npks ;k++){
        un_only_once =1;
        m=an_ir_valley_locs[k];
        n_c_min= 16777216;//2^24;
        if (m+5 <  BUFFER_SIZE-HAMMING_SIZE  && m-5 >0){
            for(i= m-5;i<m+5; i++)
                if (an_x[i]<n_c_min){
                    if (un_only_once >0){
                       un_only_once =0;
                   } 
                   n_c_min= an_x[i] ;
                   an_exact_ir_valley_locs[k]=i;
                }
            if (un_only_once ==0)
                n_exact_ir_valley_locs_count ++ ;
        }
    }
    if (n_exact_ir_valley_locs_count <2 ){
       *pn_spo2 =  -999 ; // do not use SPO2 since signal ratio is out of range
       *pch_spo2_valid  = 0; 
       return;
    }
    // 4 pt MA
    for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
        an_x[k]=( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3])/(int32_t)4;
        an_y[k]=( an_y[k]+an_y[k+1]+ an_y[k+2]+ an_y[k+3])/(int32_t)4;
    }

    //using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio
    //finding AC/DC maximum of raw ir * red between two valley locations
    n_ratio_average =0; 
    n_i_ratio_count =0; 
    
    for(k=0; k< 5; k++) an_ratio[k]=0;
    for (k=0; k< n_exact_ir_valley_locs_count; k++){
        if (an_exact_ir_valley_locs[k] > BUFFER_SIZE ){             
            *pn_spo2 =  -999 ; // do not use SPO2 since valley loc is out of range
            *pch_spo2_valid  = 0; 
            return;
        }
    }
    // find max between two valley locations 
    // and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2 

    for (k=0; k< n_exact_ir_valley_locs_count-1; k++){
        n_y_dc_max= -16777216 ; 
        n_x_dc_max= - 16777216; 
        if (an_exact_ir_valley_locs[k+1]-an_exact_ir_valley_locs[k] >10){
            for (i=an_exact_ir_valley_locs[k]; i< an_exact_ir_valley_locs[k+1]; i++){
                if (an_x[i]> n_x_dc_max) {n_x_dc_max =an_x[i];n_x_dc_max_idx =i; }
                if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i];n_y_dc_max_idx=i;}
            }
            n_y_ac= (an_y[an_exact_ir_valley_locs[k+1]] - an_y[an_exact_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_exact_ir_valley_locs[k]); //red
            n_y_ac=  an_y[an_exact_ir_valley_locs[k]] + n_y_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k])  ; 
        
        
            n_y_ac=  an_y[n_y_dc_max_idx] - n_y_ac;    // subracting linear DC compoenents from raw 
            n_x_ac= (an_x[an_exact_ir_valley_locs[k+1]] - an_x[an_exact_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_exact_ir_valley_locs[k]); // ir
            n_x_ac=  an_x[an_exact_ir_valley_locs[k]] + n_x_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]); 
            n_x_ac=  an_x[n_y_dc_max_idx] - n_x_ac;      // subracting linear DC compoenents from raw 
            n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value
            n_denom= ( n_x_ac *n_y_dc_max)>>7;
            if (n_denom>0  && n_i_ratio_count <5 &&  n_nume != 0)
            {   
                an_ratio[n_i_ratio_count]= (n_nume*20)/n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ;  ///*************************n_nume原来是*100************************//
                n_i_ratio_count++;
            }
        }
    }

    maxim_sort_ascend(an_ratio, n_i_ratio_count);
    n_middle_idx= n_i_ratio_count/2;

    if (n_middle_idx >1)
        n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median
    else
        n_ratio_average = an_ratio[n_middle_idx ];

    if( n_ratio_average>2 && n_ratio_average <184){
        n_spo2_calc= uch_spo2_table[n_ratio_average] ;
        *pn_spo2 = n_spo2_calc ;
        *pch_spo2_valid  = 1;//  float_SPO2 =  -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ;  // for comparison with table
    }
    else{
        *pn_spo2 =  -999 ; // do not use SPO2 since signal ratio is out of range
        *pch_spo2_valid  = 0; 
    }
}

注意,这里使用的函数是SpO2 = -45.060*R*R+ 30.354*R+ 94.845,采用了查表法求解。

这个函数执行完后,变量n_heart_rate中存储的是心率,变量n_sp02存储的就是血氧饱和度;

最后将血氧饱和度值显示出来就行了。

(完整的工程代码,在文末关注微 信公.众.号,可以找到下载地址)

3)血氧仪测量准不准?

实现过程中,SpO2 与R的关系的系数是非常难确定的,需要大量的试验数据来拟合,见下图,是maxim公司应用文档中的拟合过程:

(每种颜色是一组测试结果,黄色叉是去除掉的偏离比较大的野值)

可以发现,有些测量数据的方差是相当大的,很多数据偏离了拟合后的曲线很远。maxim公司建议在校准时,需要不断剔除偏离较大的数据,均方根误差(RMES)需要在3.5%以内。

最终给出一组值:

可是,在另一篇maxim公司的应用文档中,又给出了SpO2 = 104-17*R这个公式,其中0.4<R<3.4。

为什么这两公式相差这么大?

小白白又查阅了一些论文,发现对于R值与血氧饱和度的公式并不固定,SpO2可以表示为R的一个高次的多项式函数,由于正常人体测出的R值都较小,人们一般关注的是R值小于1的情况,大于1已经是明显的不健康情况。所以在计算SpO2时常常会去掉高次项,采用一阶函数或者二阶函数来拟合。

又由于SpO2的测量方法本身误差较大,所以测量数据不同时,拟合出来的参数就大相径庭了。

这里,我还收集了几个论文中拟合出的R值与SpO2之间的函数关系:

SpO2 = -45.060*R*R+ 30.354*R+ 94.845;

SpO2 = -7.6*R*-+ 20.7*R+ 112.2;(0.5<R<1.4)

SpO2 = -86.47*R*R+ 77.21*R+ 81.68,(0.4<R<1);

SpO2 = -20*x+107.2,(0.36<R<0.66);-54*x+129.64,(0.66≤R<1)

我把这几个函数的图形绘制在同一张图中:

可以看到,在0.4~1.0这个区间里,这些函数的值大体上相差不大,变化趋势也基本一样。而且这些参数,一般都是以正常人的数据来拟合的,所以在正常血氧的范围内,可以认为用这种方法来测量血氧饱和度基本靠谱。

当然,这需要建立在光源的采集数据准确的前提下。

而现实是,在采集光源的数据时,会有环境光干扰、工频干扰、各种噪声干扰;即使滤除了这些噪声,还会有如下图这种低频的漂移,此时,要准确提取出光源的直流分量、交流分量是非常困难的。

因此,如果信号处理的算法不好,就会把微弱的噪声、漂移等等干扰识别为脉搏引起的光强变化,网上出现的各种能测出香肠的血氧和脉搏的笑话也就不足为奇了。

综合来看,此类血氧仪作为健康监测的参考手段之一是可以的,但是数据准确性存疑,以它来判断身体是否健康是万万不能的。

好了,本节内容就分享到这里了。

如果觉得有用,欢迎大家关注我的wx.公.众 号:“小白白学电子”,可以下载相关的学习资源:

<think>嗯,用户想了解血氧算法,并且还希望得到相关的代码示例。首先,我需要回忆一下血氧量方面的知识。血氧通常是通过光电传感器来检血液中的氧饱和度,也就是SpO2。那它的原理是什么呢?记得是基于不同物质对红光和红外光的吸收率不同。血红蛋白中的氧合血红蛋白(HbO2)和脱氧血红蛋白(Hb)对这两种光的吸收特性不同。所以,血氧会用这两种波长的光照射皮肤,通常是手指或耳垂,然后通过接收器检透射或反射的光信号。 接下来,算法部分。如何将光信号转换为SpO2值呢?可能需要先处理信号,提取出交流(AC)和直流(DC)分量。因为动脉血液的搏动会导致AC成分,而DC部分来自其他组织和非搏动的血液。两者的比值可能与血氧有关。然后,可能需要计算红光和红外光的AC/DC比值,然后通过经验公式或查找表得出SpO2值。 那具体算法步骤是怎样的呢?首先,可能需要信号预处理,比如滤波去除噪声,因为环境光或运动伪影可能会干扰信号。然后找到脉搏波的峰值和谷值,计算AC和DC分量。红光和红外的AC/DC比值R,然后根据R计算SpO2。常用的经验公式大概是SpO2 = 110 - 25 × R,或者类似的线性或非线性关系,不过这可能因设备和校方式不同而不同。 用户可能需要具体的代码实现。不过,实际的血氧算法可能涉及复杂的信号处理和校,尤其是处理实时数据。可能需要考虑实时滤波、峰值检等。但作为示例代码,可能需要简化,假设已经有了处理后的AC和DC值,然后计算R和SpO2。 所以,可能的步骤是: 1. 读取红光和红外光的数据。 2. 滤波处理,比如使用低通或带通滤波器去除高频噪声。 3. 提取AC和DC分量。例如,AC是信号的波动部分,DC是平均值或基线。 4. 计算两者的AC/DC比值R。 5. 应用经验公式将R转换为SpO2值。 6. 可能需要校,比如根据已知的标调整参数。 在代码方面,可能用Python示例,使用numpy处理数据。比如,模拟红光和红外信号,计算AC和DC,然后计算R和SpO2。但真实环境中可能需要实时处理,而这里作为示例可以简化。 需要注意的是,实际设备中的算法可能更复杂,包括运动补偿、温度补偿、校曲线等。另外,经验公式中的系数可能需要根据设备的具体情况进行调整,可能通过临床试验数据来拟合。 所以,用户可能需要一个简化的算法示例,而不是实际产品中的复杂代码。因此,在回答时应该说明这是基本原理的示例,实际应用需要更深入的处理。以下</think>是对血氧(SpO₂)算法的简要介绍及简化代码示例: ### 算法原理 1. **光学原理** 通过红光(660nm)和红外光(940nm)照射皮肤,氧合血红蛋白(HbO₂)和脱氧血红蛋白(Hb)对这两种光的吸收率不同。 2. **信号处理** - **滤波**:去除高频噪声和基线漂移。 - **AC/DC分离**: - **AC分量**:动脉搏动引起的波动信号(反映血流变化)。 - **DC分量**:恒定组织的反射/透射光强度。 3. **比值计算** 计算红光和红外光的AC/DC比值 \( R \): \[ R = \frac{\frac{AC_{red}}{DC_{red}}}{\frac{AC_{ir}}{DC_{ir}}} \] 4. **SpO₂转换** 通过经验公式或查找表将 \( R \) 转换为血氧饱和度(常见近似公式): \[ SpO_2 = 110 - 25 \times R \] --- ### 简化Python代码示例 ```python import numpy as np # 模拟红光和红外光信号(含噪声) red_signal = np.array([100, 102, 105, 100, 95, 100, 105, ...]) # 红光信号示例 ir_signal = np.array([200, 205, 198, 202, 195, 200, 205, ...]) # 红外信号示例 # 低通滤波(示例:移动平均) def moving_average(signal, window_size=5): return np.convolve(signal, np.ones(window_size)/window_size, mode='same') red_filtered = moving_average(red_signal) ir_filtered = moving_average(ir_signal) # 提取AC分量(信号波动)和DC分量(平均值) ac_red = red_filtered - np.mean(red_filtered) ac_ir = ir_filtered - np.mean(ir_filtered) # 计算AC的均方根(RMS)和DC的平均值 rms_red = np.sqrt(np.mean(ac_red**2)) rms_ir = np.sqrt(np.mean(ac_ir**2)) dc_red = np.mean(red_filtered) dc_ir = np.mean(ir_filtered) # 计算R值 R = (rms_red / dc_red) / (rms_ir / dc_ir) # 转换为SpO2(简化经验公式,需校) SpO2 = 110 - 25 * R print(f"Estimated SpO2: {SpO2:.1f}%") ``` --- ### 注意事项 1. **实际应用**需校参数(如公式系数),通常通过临床试验数据拟合。 2. **信号质量**需处理运动伪影、环境光干扰等复杂噪声。 3. **实时处理**可能需要峰值检、自适应滤波等高级算法。 如果需要进一步探讨具体实现细节,可以补充说明需求!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值