在工业测距(如激光、超声波测距)场景中,原始测距数据常受环境干扰(如杂波、多回波)导致数据离散,直接使用原始数据会出现较大误差。本文基于实际项目需求,拆解一款面向距离数据的轻量级聚类算法,核心通过 “1% 偏差度” 筛选有效数据族,最终提取出现次数最多的距离值作为真实值,兼顾算法效率与工业场景的实用性。

一、算法核心背景与设计思想

1. 应用场景

针对测距传感器输出的距离数组(单位:mm),需解决以下问题:

  • 原始数据包含无效值(0x80000000 表示测距失败);
  • 有效数据存在 1% 左右的偏差(如 100m 距离允许 ±1m 偏差,10m 距离允许 ±0.1m 偏差);
  • 干扰值出现次数少,真实值随测试次数增加会形成明显的数据族。

2. 核心设计思想

  • 阈值过滤:先过滤超门限(100m)和无效值,缩小计算范围;
  • 偏差聚类:按 “1% 偏差度” 划分数据族(距离越小,允许的绝对偏差越小);
  • 频次优先:统计各数据族的出现次数,取频次最高的族的均值作为真实值;
  • 鲁棒性补充:结合历史有效值(LASTPOD)和门槛电压自适应调整,降低异常值影响。

二、算法核心模块拆解

以下基于源码,分模块解析算法实现逻辑,附带关键代码注释与设计思路说明。

1. 模块 1:无效数据预处理

功能:过滤超门限(100m)、无效值(0x80000000),仅保留有效候选数据。

c

运行

// 关键代码段
int32_t slimit=100000; // 门限100m(单位:mm)
int32_t k=0;
int32_t b[ZTMAX]={0};  // 存储过滤后的有效数据
for(i=0;i<n;i++){
    // 筛选条件:≤100m + 非无效值
    if(a[i]<=slimit && a[i]!=0x80000000){
      b[k]=a[i];
      k++; // 记录有效数据个数	
    }	
}

设计思路

  • 先通过硬阈值(100m)剔除明显异常的远距离干扰值;
  • 标记无效值(0x80000000),避免参与后续聚类计算;
  • 用数组b暂存有效数据,减少后续循环的计算量。

2. 模块 2:基于 1% 偏差度的聚类核心(多族数据划分)

功能:将偏差在 1% 范围内的数据归为同一 “数据族”,最多划分 10 个族(防数组溢出),记录每个族的核心值、成员、出现次数。

c

运行

// 关键代码段:聚类逻辑
int32_t hac_times[10];    // 每个数据族的出现次数
int32_t hac_dis[10][ZTMAX]={0}; // 每个数据族的成员数据
int32_t PRCDIS[10]={0};   // 每个数据族的核心距离值
k=0; // 数据族索引
for(i=0;i<n;i++)
{
    m=1; // 初始次数(至少包含自身)
    if(a[i]==0x80000000) continue; // 跳过无效值
    
    for(j=i+1;j<n;j++){
        if(a[i]<=slimit){ // 近距离(≤100m):固定偏差±100mm(兼容小距离1%偏差)
            if (a[i] >= (b[j]-100) && a[i] <= (b[j]+100)){
                  hac_dis[k][m]=b[j]; // 加入当前数据族
                  m++;
                  b[j]=0x80000000; // 标记为已聚类,避免重复计算
            }
        }else{ // 远距离(>100m):严格1%偏差度
            if (a[i] >= (b[j]-a[i]/100) && a[i] <= (b[j]+a[i]/100)){
                  hac_dis[k][m]=b[j];
                  m++;
                  b[j]=0x80000000;
            }
        }
    }
    // 仅保留出现次数>1的族(单个数据不成族)
    if(m>1)	{
        hac_dis[k][0]=a[i]; // 数据族核心值(聚类中心)
        PRCDIS[k]=a[i];     // 记录核心值
        hac_times[k]=m;     // 记录该族出现次数
        k++;
        if(k>=9) k=9; // 防数组溢出,最多10个族
    }
}

核心设计亮点

  • 偏差度自适应
    • 近距离(≤100m):使用固定 ±100mm 偏差(避免小距离下 1% 偏差绝对值过小,如 10m 的 1% 仅 100mm);
    • 远距离(>100m):严格按 1% 偏差计算(a[i]/100),满足 “距离越大,允许绝对偏差越大” 的工业特征。
  • 避免重复聚类:将已聚类的数据标记为无效值(0x80000000),减少循环次数;
  • 数组溢出保护:限制最多 10 个数据族,适配嵌入式场景的内存限制。

3. 模块 3:最优数据族筛选(频次 + 均值)

功能:找到出现次数最多的数据族,计算该族的均值作为真实值(降低单值波动影响)。

c

运行

// 关键代码段:取最大次数的族的均值
if(k>0){		 
    maxcount=Maxnum(hac_times,k); // 找到最大出现次数
    for(i=0;i<k;i++){
        if(hac_times[i]==maxcount){
            // 计算该族所有成员的均值
            for(j=0;j<hac_times[i];j++){
                average_num+=hac_dis[i][j];
            }
            max=average_num/maxcount; // 均值作为真实值
            break; // 取首个最大次数的族(工业场景优先时效性)
        }
    } 	
}

设计思路

  • 优先选择出现次数最多的族(符合 “测试次数越多,真实值频次越高” 的假设);
  • 计算族内均值而非直接取核心值,进一步降低随机干扰的影响;
  • 仅取首个最大次数的族,兼顾计算效率(嵌入式场景无需遍历所有并列最大值)。

4. 模块 4:鲁棒性增强(历史校验 + 门槛自适应)

功能:结合历史有效值和硬件门槛调整,避免单次聚类结果异常。

c

运行

// 关键代码段1:历史值校验(LASTPOD为上一次有效距离)
if(LASTPOD!=0 && LASTPOD!=0x80000000){
    if(abs(max-LASTPOD)>Compate_limit){
        ERR_COUNT++;
        // 查找与历史值偏差小的族,替换异常结果
        for(i=0;i<k;i++){
            if( abs(PRCDIS[i]-LASTPOD)<Compate_limit){
                max=PRCDIS[i];
                maxcount=hac_times[i];
                ERR_COUNT=0;
                break;
            }
        }
    }else{
        ERR_COUNT=0; // 与历史值一致,重置异常计数
    }
}

// 关键代码段2:门槛电压自适应(测距失败时降低门槛)
if(max==0x80000000||STAT_GP0==0){
    MAX_ERR++;
    if(MAX_ERR>=(ZTMAX)){
        // 循环调整门槛电压(Door1/Door2),尝试获取有效数据
        // ... 门槛切换逻辑 ...
        MAX_ERR=0;
    } 
}

// 关键代码段3:近距离低频次过滤(避免近距离干扰)
if((max<10000 && max!=0x80000000)&& (maxcount<10)){
    prec.TureDIS=0x80000000; // 标记为无效
    prec.Turelen=20;
}

设计思路

  • 历史值校验:若当前聚类结果与历史有效值偏差过大,优先选择与历史值接近的族,降低突发干扰影响;
  • 硬件自适应:连续测距失败时,调整传感器门槛电压,尝试获取有效数据;
  • 近距离过滤:近距离(<10m)且出现次数 < 10 次的数据直接标记为无效,避免多回波干扰。

三、算法整体流程总结

step1:输入距离数组

step2:预处理:过滤超100m/无效值

step3:聚类:按1%偏差度划分数据族

step4:筛选:取频次最高族的均值

step5:鲁棒性校验:历史值+门槛调整

step6:输出真实距离值

四、算法适配场景与优化建议

1. 适配场景

  • 嵌入式测距系统(激光 / 超声波 / 毫米波测距);
  • 数据量适中(≤ZTMAX,默认可配置)的实时聚类;
  • 对偏差度敏感、要求 “频次优先” 的工业场景。

2. 优化方向

(1)偏差度动态调整

当前近距离使用固定 ±100mm 偏差,可优化为动态 1% 计算:

c

运行

// 优化后:全距离统一1%偏差
int32_t dev = a[i] / 100; // 1%偏差值
if (dev < 1) dev = 1; // 避免小距离偏差为0
if (a[i] >= (b[j]-dev) && a[i] <= (b[j]+dev)) {
    // 聚类逻辑
}
(2)多最大值处理

当前仅取首个最大次数的族,可优化为 “取所有最大次数族的均值”,提升精度:

c

运行

// 优化:遍历所有最大次数的族,计算总均值
int32_t total_num=0, total_count=0;
for(i=0;i<k;i++){
    if(hac_times[i]==maxcount){
        for(j=0;j<hac_times[i];j++){
            total_num+=hac_dis[i][j];
            total_count++;
        }
    }
}
max = total_num / total_count;
(3)内存优化

嵌入式场景下,hac_dis[10][ZTMAX] 可替换为动态数组,减少内存占用:

c

运行

// 动态分配每个数据族的成员数组
int32_t **hac_dis = (int32_t**)malloc(10 * sizeof(int32_t*));
for(int i=0;i<10;i++){
    hac_dis[i] = (int32_t*)malloc(ZTMAX * sizeof(int32_t));
    memset(hac_dis[i], 0, ZTMAX * sizeof(int32_t));
}
// 使用后释放
for(int i=0;i<10;i++) free(hac_dis[i]);
free(hac_dis);

五、总结

本文拆解的距离数据聚类算法,是面向工业场景的 “轻量级、实用性优先” 实现:

  • 核心通过 “1% 偏差度聚类 + 频次筛选” 解决测距数据离散问题;
  • 结合历史值校验、硬件自适应等鲁棒性设计,适配复杂工业环境;
  • 代码结构清晰,无复杂数学运算,可直接移植到嵌入式 MCU(如 STM32、MCU)中。

该算法的设计思路可推广到其他工业数据筛选场景(如温度、压力数据),核心是 “先过滤、再聚类、最后结合业务规则校验”,兼顾效率与实用性。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐