在嵌入式开发、智能控制、机器视觉等领域,PID调速、模糊控制、图像二值化处理是应用最广泛的核心算法。它们结构简洁、可移植性强,且开源资源丰富,是工程师快速实现项目功能的关键工具。本文将拆解三大算法的核心逻辑,提供可直接复用的开源代码,详解代码细节与使用场景,帮助开发者快速上手、灵活适配各类项目。

一、开源PID调速算法:精准控制的经典方案

1.1 算法核心原理

PID(比例-积分-微分)调速算法通过比例项(P)、积分项(I)、微分项(D)的协同作用,根据被控对象的实际值与目标值(偏差),动态调整输出量,实现速度的精准稳定控制。其核心逻辑是:比例项快速响应偏差,积分项消除静态误差,微分项抑制超调,三者结合兼顾响应速度与控制精度。

适用场景:电机调速、机器人底盘控制、风机转速调节等需要精准速度控制的场景,开源代码可直接适配STM32、Arduino等嵌入式平台。

1.2 开源核心代码(C语言,适配嵌入式平台)

c
/*
 * 开源PID调速算法
 * 适用场景:电机调速、转速控制等
 * 可移植性:支持STM32、Arduino、51单片机等嵌入式平台
 */
#include "pid.h"

// PID参数结构体(可根据实际需求调整参数)
typedef struct {
    float Kp;         // 比例系数
    float Ki;         // 积分系数
    float Kd;         // 微分系数
    float target;     // 目标速度(设定值)
    float actual;     // 实际速度(反馈值)
    float error;      // 当前偏差 = 目标值 - 实际值
    float error_last; // 上一次偏差
    float error_sum;  // 偏差积分和(用于积分项计算)
    float output;     // PID输出(用于控制执行器,如电机PWM)
    float output_min; // 输出最小值(防止执行器过载)
    float output_max; // 输出最大值
} PID_HandleTypeDef;

// PID初始化:初始化参数、清零偏差
void PID_Init(PID_HandleTypeDef *pid, float Kp, float Ki, float Kd,
              float output_min, float output_max, float target) {
    pid->Kp = Kp;
    pid->Ki = Ki;
    pid->Kd = Kd;
    pid->output_min = output_min;
    pid->output_max = output_max;
    pid->target = target;
    // 清零偏差相关变量
    pid->error = 0.0f;
    pid->error_last = 0.0f;
    pid->error_sum = 0.0f;
    pid->output = 0.0f;
}

// PID计算(循环调用,建议调用频率10-100Hz)
float PID_Calculate(PID_HandleTypeDef *pid, float actual) {
    // 1. 更新实际值与当前偏差
    pid->actual = actual;
    pid->error = pid->target - pid->actual;
    
    // 2. 计算积分项(加入积分限幅,防止积分饱和)
    pid->error_sum += pid->error;
    // 积分限幅:根据输出范围反向限制积分和,避免积分饱和导致超调
    if (pid->error_sum > pid->output_max / pid->Ki) {
        pid->error_sum = pid->output_max / pid->Ki;
    } else if (pid->error_sum < pid->output_min / pid->Ki) {
        pid->error_sum = pid->output_min / pid->Ki;
    }
    
    // 3. 计算微分项(基于当前偏差与上一次偏差的差值)
    float error_diff = pid->error - pid->error_last;
    pid->error_last = pid->error; // 更新上一次偏差
    
    // 4. 计算PID输出
    pid->output = pid->Kp * pid->error +
                  pid->Ki * pid->error_sum +
                  pid->Kd * error_diff;
    
    // 5. 输出限幅(保护执行器)
    if (pid->output > pid->output_max) {
        pid->output = pid->output_max;
    } else if (pid->output < pid->output_min) {
        pid->output = pid->output_min;
    }
    
    return pid->output;
}

// 示例:电机调速调用(以STM32为例,控制电机PWM输出)
void Motor_Speed_Control(PID_HandleTypeDef *pid, float speed_feedback) {
    float pwm_output = PID_Calculate(pid, speed_feedback);
    // 将PID输出转换为PWM占空比,控制电机转速
    TIM_SetCompare1(TIM1, (uint16_t)pwm_output);
}

1.3 代码使用说明

  • 参数调整:Kp、Ki、Kd需根据被控对象(如电机型号)调试,一般先调Kp(使系统有基本响应),再调Ki(消除静态误差),最后调Kd(抑制超调)。
  • 移植方法:将代码复制到项目中,修改头文件适配平台,循环调用PID_Calculate函数,传入实际速度反馈值,输出值用于控制执行器(如PWM)。
  • 开源优化:可添加死区处理(避免微小偏差导致执行器频繁动作)、参数自整定功能,适配更复杂的调速场景。

二、开源模糊控制算法:复杂场景的鲁棒控制方案

2.1 算法核心原理

模糊控制无需建立被控对象的精确数学模型,通过模拟人类的模糊推理过程,将输入量(如偏差、偏差变化率)模糊化,结合预设的模糊规则,输出模糊量后再解模糊,得到实际控制量。其核心优势是抗干扰能力强、适应非线性系统,适合参数时变、干扰复杂的控制场景。

适用场景:温度控制、湿度调节、机器人路径规划、非线性电机控制等,开源代码可适配嵌入式、PC端等多平台。

2.2 开源核心代码(C语言,以温度控制为例)

c
/*
 * 开源模糊控制算法
 * 适用场景:温度控制、湿度调节、非线性系统控制等
 * 核心逻辑:输入(偏差、偏差变化率)→ 模糊化 → 模糊推理 → 解模糊 → 输出
 */
#include "fuzzy_control.h"

// 模糊集合定义(以温度控制为例,输入:偏差e、偏差变化率ec;输出:控制量u)
// 模糊语言变量:负大(NB)、负中(NM)、负小(NS)、零(ZR)、正小(PS)、正中(PM)、正大(PB)
typedef enum {
    NB = 0, NM, NS, ZR, PS, PM, PB
} Fuzzy_LinguisticVar;

// 模糊控制参数结构体
typedef struct {
    float e_min;    // 偏差e的最小值
    float e_max;    // 偏差e的最大值
    float ec_min;   // 偏差变化率ec的最小值
    float ec_max;   // 偏差变化率ec的最大值
    float u_min;    // 控制量u的最小值
    float u_max;    // 控制量u的最大值
    // 模糊规则表(7×7,行:e的模糊集合,列:ec的模糊集合,值:u的模糊集合)
    Fuzzy_LinguisticVar rule_table[7][7];
} Fuzzy_HandleTypeDef;

// 隶属度函数:三角形隶属度(最常用,计算简洁)
// x:输入值,a:左顶点,b:顶点,c:右顶点
float Triangle_Membership(float x, float a, float b, float c) {
    if (x <= a || x >= c) {
        return 0.0f;
    } else if (x == b) {
        return 1.0f;
    } else if (x > a && x < b) {
        return (x - a) / (b - a);
    } else { // x > b && x < c
        return (c - x) / (c - b);
    }
}

// 模糊化:将精确输入e、ec转换为各模糊集合的隶属度
void Fuzzy_Fuzzification(Fuzzy_HandleTypeDef *fuzzy, float e, float ec,
                         float *e_membership, float *ec_membership) {
    // 偏差e的模糊化(7个模糊集合,均匀划分输入范围)
    float e_range = fuzzy->e_max - fuzzy->e_min;
    float e_step = e_range / 6; // 7个集合,6个间隔
    e_membership[NB] = Triangle_Membership(e, fuzzy->e_min, fuzzy->e_min, fuzzy->e_min + e_step);
    e_membership[NM] = Triangle_Membership(e, fuzzy->e_min, fuzzy->e_min + e_step, fuzzy->e_min + 2*e_step);
    e_membership[NS] = Triangle_Membership(e, fuzzy->e_min + e_step, fuzzy->e_min + 2*e_step, fuzzy->e_min + 3*e_step);
    e_membership[ZR] = Triangle_Membership(e, fuzzy->e_min + 2*e_step, fuzzy->e_min + 3*e_step, fuzzy->e_min + 4*e_step);
    e_membership[PS] = Triangle_Membership(e, fuzzy->e_min + 3*e_step, fuzzy->e_min + 4*e_step, fuzzy->e_min + 5*e_step);
    e_membership[PM] = Triangle_Membership(e, fuzzy->e_min + 4*e_step, fuzzy->e_min + 5*e_step, fuzzy->e_max);
    e_membership[PB] = Triangle_Membership(e, fuzzy->e_min + 5*e_step, fuzzy->e_max, fuzzy->e_max);
    
    // 偏差变化率ec的模糊化(与e同理)
    float ec_range = fuzzy->ec_max - fuzzy->ec_min;
    float ec_step = ec_range / 6;
    ec_membership[NB] = Triangle_Membership(ec, fuzzy->ec_min, fuzzy->ec_min, fuzzy->ec_min + ec_step);
    ec_membership[NM] = Triangle_Membership(ec, fuzzy->ec_min, fuzzy->ec_min + ec_step, fuzzy->ec_min + 2*ec_step);
    ec_membership[NS] = Triangle_Membership(ec, fuzzy->ec_min + ec_step, fuzzy->ec_min + 2*ec_step, fuzzy->ec_min + 3*ec_step);
    ec_membership[ZR] = Triangle_Membership(ec, fuzzy->ec_min + 2*ec_step, fuzzy->ec_min + 3*ec_step, fuzzy->ec_min + 4*ec_step);
    ec_membership[PS] = Triangle_Membership(ec, fuzzy->ec_min + 3*ec_step, fuzzy->ec_min + 4*ec_step, fuzzy->ec_min + 5*ec_step);
    ec_membership[PM] = Triangle_Membership(ec, fuzzy->ec_min + 4*ec_step, fuzzy->ec_min + 5*ec_step, fuzzy->ec_max);
    ec_membership[PB] = Triangle_Membership(ec, fuzzy->ec_min + 5*ec_step, fuzzy->ec_max, fuzzy->ec_max);
}

// 解模糊:重心法(精度高、计算简洁,最常用)
float Fuzzy_Defuzzification(Fuzzy_HandleTypeDef *fuzzy, float *u_membership) {
    float u_range = fuzzy->u_max - fuzzy->u_min;
    float u_step = u_range / 6;
    // 各模糊集合的重心(中点)
    float u_centroid[7] = {
        fuzzy->u_min,                                      // NB重心
        fuzzy->u_min + e_step,                             // NM重心
        fuzzy->u_min + 2*e_step,                           // NS重心
        fuzzy->u_min + 3*e_step,                           // ZR重心
        fuzzy->u_min + 4*e_step,                           // PS重心
        fuzzy->u_min + 5*e_step,                           // PM重心
        fuzzy->u_max                                       // PB重心
    };
    
    // 重心法计算精确输出
    float numerator = 0.0f; // 分子:隶属度×重心之和
    float denominator = 0.0f; // 分母:隶属度之和
    for (int i = 0; i < 7; i++) {
        numerator += u_membership[i] * u_centroid[i];
        denominator += u_membership[i];
    }
    
    // 避免分母为0(无模糊规则匹配时,输出默认值)
    return denominator == 0.0f ? (fuzzy->u_min + fuzzy->u_max) / 2 : numerator / denominator;
}

// 模糊控制计算(循环调用,输入偏差e、偏差变化率ec,输出控制量u)
float Fuzzy_Calculate(Fuzzy_HandleTypeDef *fuzzy, float e, float ec) {
    float e_membership[7] = {0};  // e的各模糊集合隶属度
    float ec_membership[7] = {0}; // ec的各模糊集合隶属度
    float u_membership[7] = {0};  // u的各模糊集合隶属度
    
    // 1. 模糊化
    Fuzzy_Fuzzification(fuzzy, e, ec, e_membership, ec_membership);
    
    // 2. 模糊推理(最大最小推理法)
    for (int i = 0; i < 7; i++) { // 遍历e的所有模糊集合
        for (int j = 0; j < 7; j++) { // 遍历ec的所有模糊集合
            // 取e和ec隶属度的最小值,作为当前规则的触发强度
            float trigger_strength = e_membership[i] < ec_membership[j] ? e_membership[i] : ec_membership[j];
            // 确定当前规则输出的模糊集合
            Fuzzy_LinguisticVar u_var = fuzzy->rule_table[i][j];
            // 更新输出模糊集合的隶属度(取最大值)
            if (trigger_strength > u_membership[u_var]) {
                u_membership[u_var] = trigger_strength;
            }
        }
    }
    
    // 3. 解模糊,得到精确控制量
    return Fuzzy_Defuzzification(fuzzy, u_membership);
}

// 示例:初始化模糊控制参数(温度控制,目标温度25℃)
void Fuzzy_Init(Fuzzy_HandleTypeDef *fuzzy) {
    // 初始化输入输出范围(根据实际场景调整)
    fuzzy->e_min = -10.0f;  // 温度偏差范围:-10℃ ~ +10℃
    fuzzy->e_max = 10.0f;
    fuzzy->ec_min = -5.0f;  // 温度偏差变化率范围:-5℃/s ~ +5℃/s
    fuzzy->ec_max = 5.0f;
    fuzzy->u_min = 0.0f;    // 控制量(加热功率)范围:0% ~ 100%
    fuzzy->u_max = 100.0f;
    
    // 初始化模糊规则表(核心,根据实际控制经验调整)
    Fuzzy_LinguisticVar rule_table[7][7] = {
        {PB, PB, PM, PM, PS, ZR, ZR},
        {PB, PB, PM, PS, PS, ZR, NS},
        {PM, PM, PM, PS, ZR, NS, NS},
        {PM, PM, PS, ZR, NS, NM, NM},
        {PS, PS, ZR, NS, NS, NM, NM},
        {PS, ZR, NS, NM, NM, NM, NB},
        {ZR, ZR, NM, NM, NM, NB, NB}
    };
    memcpy(fuzzy->rule_table, rule_table, sizeof(rule_table));
}

2.3 代码使用说明

  • 模糊规则表调整:规则表是模糊控制的核心,需根据实际控制经验修改,例如温度偏差大且偏差变化率大时,输出最大控制量(加热功率)。
  • 隶属度函数优化:除三角形隶属度外,可替换为梯形、高斯隶属度函数,提升控制精度(需修改Triangle_Membership函数)。
  • 移植适配:代码可直接移植到嵌入式平台,只需调整输入输出范围,循环调用Fuzzy_Calculate函数,传入偏差与偏差变化率即可。

三、开源图像二值化处理算法:机器视觉的基础预处理方案

3.1 算法核心原理

图像二值化是将灰度图像(像素值0-255)转换为黑白二值图像(像素值仅0和255)的预处理算法,核心是确定一个阈值,将像素值大于阈值的设为255(白色),小于等于阈值的设为0(黑色),从而突出目标区域、抑制背景干扰。

常用阈值确定方法:全局阈值(如Otsu算法,自动计算最优阈值)、局部阈值(适应光照不均匀场景),开源代码包含两种方法,可灵活选择。

适用场景:目标检测、字符识别、图像分割等机器视觉场景,可适配OpenCV、嵌入式图像传感器(如OV7725)等。

3.2 开源核心代码(C语言,适配OpenCV与嵌入式平台)

c
/*
 * 开源图像二值化处理算法
 * 包含:全局阈值二值化、Otsu自动阈值二值化、局部阈值二值化
 * 适用场景:图像预处理、目标检测、字符识别等
 * 可移植性:支持PC端(OpenCV)、嵌入式平台(无OS,直接操作图像缓冲区)
 */
#include "image_binary.h"
#include <stdint.h>

// 1. 全局阈值二值化(手动设置阈值)
// input:输入灰度图像缓冲区(像素值0-255)
// output:输出二值图像缓冲区(像素值0或255)
// width:图像宽度,height:图像高度,threshold:阈值(0-255)
void Binary_Global(uint8_t *input, uint8_t *output, uint16_t width, uint16_t height, uint8_t threshold) {
    for (uint32_t i = 0; i < width * height; i++) {
        // 像素值大于阈值设为255(白色),否则设为0(黑色)
        output[i] = input[i] > threshold ? 255 : 0;
    }
}

// 2. Otsu自动阈值二值化(无需手动设置阈值,自动计算最优值)
// 原理:遍历所有可能阈值(0-255),找到使前景与背景类间方差最大的阈值
uint8_t Binary_Otsu(uint8_t *input, uint16_t width, uint16_t height) {
    uint32_t pixel_count = width * height;
    uint32_t histogram[256] = {0}; // 灰度直方图(统计每个像素值的数量)
    
    // 1. 统计灰度直方图
    for (uint32_t i = 0; i < pixel_count; i++) {
        histogram[input[i]]++;
    }
    
    // 2. 计算总灰度值、总像素数
    uint64_t total_gray = 0;
    for (int i = 0; i < 256; i++) {
        total_gray += (uint64_t)i * histogram[i];
    }
    
    uint32_t foreground_count = 0; // 前景像素数(像素值>阈值)
    uint64_t foreground_gray = 0;  // 前景总灰度值
    float max_variance = 0.0f;     // 最大类间方差
    uint8_t best_threshold = 128;  // 最优阈值(默认128)
    
    // 3. 遍历所有可能阈值,计算类间方差
    for (int t = 0; t < 256; t++) {
        foreground_count += histogram[t];
        foreground_gray += (uint64_t)t * histogram[t];
        
        if (foreground_count == 0 || foreground_count == pixel_count) {
            continue; // 避免除数为0
        }
        
        // 计算前景、背景的平均灰度值
        float foreground_mean = (float)foreground_gray / foreground_count;
        float background_mean = (float)(total_gray - foreground_gray) / (pixel_count - foreground_count);
        
        // 计算类间方差
        float variance = (float)foreground_count * (float)(pixel_count - foreground_count) *
                        (foreground_mean - background_mean) * (foreground_mean - background_mean);
        
        // 更新最大类间方差与最优阈值
        if (variance > max_variance) {
            max_variance = variance;
            best_threshold = t;
        }
    }
    
    return best_threshold;
}

// 3. 局部阈值二值化(适应光照不均匀场景)
// window_size:局部窗口大小(建议为奇数,如3、5、7)
void Binary_Local(uint8_t *input, uint8_t *output, uint16_t width, uint16_t height, uint8_t window_size, int8_t offset) {
    uint8_t half_window = window_size / 2;
    // 遍历每个像素(跳过边缘窗口,避免越界)
    for (uint16_t y = half_window; y < height - half_window; y++) {
        for (uint16_t x = half_window; x < width - half_window; x++) {
            // 计算当前窗口内的平均灰度值
            uint32_t window_gray = 0;
            for (int i = -half_window; i <= half_window; i++) {
                for (int j = -half_window; j <= half_window; j++) {
                    uint32_t index = (y + i) * width + (x + j);
                    window_gray += input[index];
                }
            }
            uint8_t window_mean = window_gray / (window_size * window_size);
            
            // 局部阈值 = 窗口平均灰度值 + 偏移量(可调整,一般为-5~5)
            output[y * width + x] = input[y * width + x] > (window_mean + offset) ? 255 : 0;
        }
    }
    // 边缘像素处理(直接设为0,可根据需求优化)
    for (uint16_t y = 0; y < half_window; y++) {
        for (uint16_t x = 0; x < width; x++) {
            output[y * width + x] = 0;
        }
    }
    for (uint16_t y = height - half_window; y < height; y++) {
        for (uint16_t x = 0; x < width; x++) {
            output[y * width + x] = 0;
        }
    }
    for (uint16_t x = 0; x < half_window; x++) {
        for (uint16_t y = half_window; y < height - half_window; y++) {
            output[y * width + x] = 0;
        }
    }
    for (uint16_t x = width - half_window; x < width; x++) {
        for (uint16_t y = half_window; y < height - half_window; y++) {
            output[y * width + x] = 0;
        }
    }
}

// 示例:OpenCV平台调用(PC端)
#include <opencv2/opencv.hpp>
using namespace cv;

void OpenCV_Binary_Demo() {
    // 读取灰度图像
    Mat img_gray = imread("test.jpg", IMREAD_GRAYSCALE);
    Mat img_binary(img_gray.size(), CV_8UC1);
    
    // 方法1:全局阈值二值化(阈值128)
    Binary_Global(img_gray.data, img_binary.data, img_gray.cols, img_gray.rows, 128);
    imwrite("global_binary.jpg", img_binary);
    
    // 方法2:Otsu自动阈值二值化
    uint8_t otsu_thresh = Binary_Otsu(img_gray.data, img_gray.cols, img_gray.rows);
    Binary_Global(img_gray.data, img_binary.data, img_gray.cols, img_gray.rows, otsu_thresh);
    imwrite("otsu_binary.jpg", img_binary);
    
    // 方法3:局部阈值二值化(窗口大小5,偏移量-3)
    Binary_Local(img_gray.data, img_binary.data, img_gray.cols, img_gray.rows, 5, -3);
    imwrite("local_binary.jpg", img_binary);
}

// 示例:嵌入式平台调用(无OS,操作图像缓冲区)
void Embedded_Binary_Demo() {
    uint8_t img_gray[320*240] = {0}; // 320x240灰度图像缓冲区(模拟OV7725采集数据)
    uint8_t img_binary[320*240] = {0};
    
    // 1. 采集图像数据(此处省略,实际需对接图像传感器)
    // 2. Otsu自动二值化
    uint8_t otsu_thresh = Binary_Otsu(img_gray, 320, 240);
    Binary_Global(img_gray, img_binary, 320, 240, otsu_thresh);
    
    // 3. 二值化图像后续处理(如目标检测、轮廓提取)
}

3.3 代码使用说明

  • 方法选择:光照均匀场景用全局阈值或Otsu算法;光照不均匀(如阴影、反光)场景用局部阈值,调整窗口大小和偏移量优化效果。
  • 移植适配:PC端可结合OpenCV调用,嵌入式平台直接操作图像缓冲区(如传感器采集的灰度数据),无需依赖第三方库。
  • 优化方向:可添加自适应阈值、形态学处理(膨胀、腐蚀),消除二值化后的噪声,提升后续处理精度。

四、开源代码优化与拓展建议

以上三大核心算法的开源代码均经过简化与适配,可直接用于基础项目,结合实际需求可进行以下优化:

  1. PID调速:添加参数自整定算法(如Ziegler-Nichols法)、死区处理、抗积分饱和机制,适配高速、高精度调速场景。
  1. 模糊控制:优化模糊规则表(可通过机器学习训练得到最优规则),替换隶属度函数,添加模糊PID混合控制,兼顾鲁棒性与精度。
  1. 图像二值化:结合直方图均衡化预处理,提升图像对比度;添加噪声过滤(如高斯滤波),优化局部阈值的边缘处理逻辑。

此外,所有代码均开源可修改,可根据项目平台(STM32、Arduino、PC)调整数据类型、函数接口,适配不同的硬件资源与性能需求。

PID调速、模糊控制、图像二值化处理是工业控制、智能硬件、机器视觉领域的基础核心算法,其开源代码具有结构简洁、可移植性强、适配性广的特点。本文提供的代码可直接复用,同时详细拆解了算法原理与使用方法,帮助开发者快速上手,减少重复开发工作量。

在实际项目中,需根据被控对象、应用场景的差异,调整算法参数与逻辑,必要时进行多算法融合(如模糊PID、二值化+形态学处理),以实现更优的控制效果与预处理质量。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐