You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenCV 2D Kalman Filter实现效果不佳,请求问题排查

2D卡尔曼滤波器实现问题排查

问题背景

将OpenCV官方Kalman Filter示例改为2D实现,假设点沿角平分线移动,但视频输出效果未达预期。代码及输出截图如下:

代码实现

#include "opencv2/video/tracking.hpp"
#include "opencv2/highgui.hpp"
#include <stdio.h>
#include <iostream>
using namespace cv;
using namespace std;

static inline Point my_calcPoint(Point2f center, double x, double y)
{
    return center + Point2f(x, y);
}

static void help()
{
    printf("\n Test kalman predict future point \n");
}

int main(int, char **)
{
    // my version for point moving in 2d dimension
    Mat my_img(500, 500, CV_8UC3);
    KalmanFilter my_KF(4, 2, 0);
    Mat my_state(4, 1, CV_32F); /* (x, delta_x, y, delta_y) */
    Mat my_processNoise(4, 1, CV_32F);
    Mat my_measurement = Mat::zeros(2, 1, CV_32F);
    
    char code = (char)-1;
    for (;;)
    {
        my_state.at<float>(0) = 0; 
        my_state.at<float>(1) = 0.2;  
        my_state.at<float>(2) = 0;        
        my_state.at<float>(3) = 0.2f;        

        my_KF.transitionMatrix = (Mat_<float>(4, 4) << 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1);
        my_KF.measurementMatrix = (Mat_<float>(2, 4) << 1, 0, 0, 0, 0, 0, 1, 0);
        setIdentity(my_KF.processNoiseCov, Scalar::all(1e-5));
        setIdentity(my_KF.measurementNoiseCov, Scalar::all(1e-1));
        setIdentity(my_KF.errorCovPost, Scalar::all(1));
        randn(my_KF.statePost, Scalar::all(0), Scalar::all(0.1));
        double x = 0;
        double y = 0;

        for (;;)
        {
            Point2f my_center(my_img.cols * 0.5f, my_img.rows * 0.5f);
            double my_stateAngle = my_state.at<float>(0);
            x = x + 0.5;
            y = y + 0.5;
            Point my_statePt = my_calcPoint(my_center, x, y);
            Mat my_prediction = my_KF.predict();
            double my_predict_x = my_prediction.at<float>(0);
            double my_predict_y = my_prediction.at<float>(1);
            Point my_predictPt = my_calcPoint(my_center, my_predict_x, my_predict_y);

            randn(my_measurement, Scalar::all(0), Scalar::all(my_KF.measurementNoiseCov.at<float>(0))); // Dubbio: Questo va aggiunto solo qua ?
            // generate measurement
            my_measurement += my_KF.measurementMatrix * my_state; 
            double my_meas_x = my_measurement.at<float>(0);
            double my_meas_y = my_measurement.at<float>(1);
            Point my_measPt = my_calcPoint(my_center, my_meas_x, my_meas_y);

// plot points
#define drawCross(my_center, my_color, my_d)                                   \
    line(my_img, Point(my_center.x - my_d, my_center.y - my_d),                   \
         Point(my_center.x + my_d, my_center.y + my_d), my_color, 1, LINE_AA, 0); \
    line(my_img, Point(my_center.x + my_d, my_center.y - my_d),                   \
         Point(my_center.x - my_d, my_center.y + my_d), my_color, 1, LINE_AA, 0)

            my_img = Scalar::all(0);
            drawCross(my_statePt, Scalar(255, 255, 255), 3);
            drawCross(my_measPt, Scalar(0, 0, 255), 3);
            drawCross(my_predictPt, Scalar(0, 255, 0), 3);
            line(my_img, my_statePt, my_measPt, Scalar(0, 0, 255), 3, LINE_AA, 0);
            line(my_img, my_statePt, my_predictPt, Scalar(0, 255, 255), 3, LINE_AA, 0);
            if (theRNG().uniform(0, 4) != 0)
            my_KF.correct(my_measurement);
            randn(my_processNoise, Scalar(0), Scalar::all(sqrt(my_KF.processNoiseCov.at<float>(0, 0))));
            my_state = my_KF.transitionMatrix * my_state + my_processNoise;
            imshow("my_Kalman", my_img);


            code = (char)waitKey(100);
            if (code > 0)
                break;
        }
        if (code == 27 || code == 'q' || code == 'Q')
            break;
    }
    return 0;
}

输出效果

2D卡尔曼滤波器输出截图


问题排查与修复

1. 真实状态与卡尔曼跟踪状态完全脱节

你用x = x + 0.5; y = y + 0.5;手动更新显示的真实点,但卡尔曼滤波器跟踪的my_state是通过转移矩阵独立更新的,两者没有关联,导致预测点完全偏离真实路径。

  • 修复:删除手动更新的x和y,直接从my_state中读取真实位置:
    // 替换原x、y手动更新代码
    double true_x = my_state.at<float>(0);
    double true_y = my_state.at<float>(2);
    Point my_statePt = my_calcPoint(my_center, true_x, true_y);
    

2. 卡尔曼初始状态与真实状态不匹配

初始化my_state为(0, 0.2, 0, 0.2)后,又用randn随机初始化卡尔曼的后验状态,导致初始阶段滤波器就偏离真实值。

  • 修复:将卡尔曼初始状态直接设置为真实状态,去掉随机初始化:
    // 替换randn那一行
    my_KF.statePost = my_state.clone();
    

3. 测量值生成逻辑错误

先给my_measurement添加噪声再叠加真实测量值,逻辑顺序颠倒,导致噪声叠加异常。

  • 修复:先计算真实测量值,再添加噪声:
    // 调整测量值生成顺序
    my_measurement = my_KF.measurementMatrix * my_state;
    randn(my_processNoise, Scalar::all(0), Scalar::all(sqrt(my_KF.measurementNoiseCov.at<float>(0))));
    my_measurement += my_processNoise;
    

4. 随机跳过修正步骤干扰收敛

if (theRNG().uniform(0, 4) != 0)会随机跳过1/4的修正步骤,调试阶段会破坏滤波器的收敛过程。

  • 修复:注释掉随机判断,确保每次都执行修正:
    // 直接执行修正
    my_KF.correct(my_measurement);
    

修复后的代码会让绿色预测点紧密跟踪白色真实点,红色噪声测量点被有效平滑,符合2D匀速运动的卡尔曼滤波预期效果。

内容的提问来源于stack exchange,提问作者Alfredo Miccichè

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 13:19:54