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

OpenCV/C++乒乓球检测坐标打印异常问题求助

乒乓球发球机的OpenCV检测问题

我在Visual Studio中使用OpenCV搭建乒乓球发球机,核心需求是仅捕获乒乓球从人向机器移动时的两组连续坐标(机器向人移动时不捕获),用于后续轨迹计算。目前采用顶部和侧面两个摄像头采集画面,尝试通过比较当前坐标差值与前一差值的逻辑实现方向判断,但实际运行存在随机跳过打印的问题,达不到预期效果。

相关代码

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <cmath>
#include <ctime>
#include <thread>
#include <atomic>

std::atomic<float> shared_m = 0.0;
std::atomic<float> shared_x = 0.0;
std::atomic<float> shared_y = 1000.0;

void processCameras() {
    cv::VideoCapture cap1(1, cv::CAP_DSHOW);
    cv::VideoCapture cap2(0, cv::CAP_DSHOW);
    if (!cap1.isOpened()) {
        std::cerr << "Error: Could not open video capture 1." << std::endl;
        return;
    }
    if (!cap2.isOpened()) {
        std::cerr << "Error: Could not open video capture 2." << std::endl;
        return;
    }
    cap1.set(cv::CAP_PROP_FPS, 120);
    cap2.set(cv::CAP_PROP_FPS, 120);

    cv::Scalar lower_orange1(0, 60, 80);
    cv::Scalar upper_orange1(40, 200, 255);
    cv::Scalar lower_orange2(0, 50, 60);
    cv::Scalar upper_orange2(40, 200, 255);

    float prev_center_x1 = 0.0;
    float prev_center_y1 = 0.0;
    float prev_delta_y1 = 1;
    int px_prev_center_x2 = 0;
    int px_prev_center_y2 = 0;
    int px_prev_delta_x2 = 1;
    float prev_center_x2 = 0.0;

    while (true) {
        cv::Mat frame1, frame2;
        cap1 >> frame1;
        cap2 >> frame2;
        if (frame1.empty() || frame2.empty()) {
            break;
        }

        frame1 = frame1(cv::Range(80, 360), cv::Range(0, 640));
        frame2 = frame2(cv::Range(130, 271), cv::Range(160, 400));

        cv::Mat mask1;
        cv::inRange(frame1, lower_orange1, upper_orange1, mask1);
        cv::Rect boundingRect1 = cv::boundingRect(mask1);

        int x1 = boundingRect1.x;
        int y1 = boundingRect1.y;
        int w1 = boundingRect1.width;
        int h1 = boundingRect1.height;

        if (w1 != 0) {
            cv::rectangle(frame1, boundingRect1, cv::Scalar(0, 255, 0), 2);
            int px_center_x1 = x1 + w1 / 2;
            int px_center_y1 = y1 + h1 / 2;
            float center_x1 = (float(px_center_x1) - 320.0) * 0.2375;
            float center_y1 = (160 - float(px_center_y1)) * 0.2375;
            float delta_y1 = center_y1 - prev_center_y1;
            if (delta_y1 < 0 && prev_delta_y1 > 0) {
                if (center_x1 != prev_center_x1) {
                    float m = (center_y1 - prev_center_y1) / (center_x1 - prev_center_x1);
                    shared_m = m;
                    shared_x = center_x1;
                    shared_y = center_y1;
                    float estimated_location = (center_y1 - 225.0) / m + center_x1;
                    printf("shared_y = %f\n", center_y1);
                }
                else {
                    float center_x1 = (float(px_center_x1) - 320.0) * 0.2375;
                    float center_y1 = (160 - float(px_center_y1)) * 0.2375;
                    float m = 10000.0;
                    shared_m = m;
                    shared_x = center_x1;
                    shared_y = center_y1;
                    float estimated_location = center_x1;
                    printf("shared_y = %f\n", center_y1);
                }
            }
            prev_center_x1 = center_x1;
            prev_center_y1 = center_y1;
            prev_delta_y1 = delta_y1;
        }

        cv::Mat mask2;
        cv::inRange(frame2, lower_orange2, upper_orange2, mask2);
        cv::Rect boundingRect2 = cv::boundingRect(mask2);

        int x2 = boundingRect2.x;
        int y2 = boundingRect2.y;
        int w2 = boundingRect2.width;
        int h2 = boundingRect2.height;
        int center_x2 = (x2 + w2) / 2;
        int center_y2 = (y2 + h2) / 2;

        if (w2 != 0) {
            cv::rectangle(frame2, boundingRect2, cv::Scalar(0, 255, 0), 2);
            int px_center_x2 = x2 + w2 / 2;
            int px_center_y2 = y2 + h2 / 2;
            float center_x2 = px_center_x2 * 0.5625 - 90.0;
            float px_delta_x2 = px_center_y2 - px_prev_center_y2;
            if (px_delta_x2 < 0 && px_prev_delta_x2 > 0 && shared_y != 1000.0) {
                float sidecam_m = -center_x2 / 287;
                float topcam_m = shared_m;
                float topcam_x = shared_x;
                float topcam_y = shared_y;
                float intersection_x = (287.0 - sidecam_m - center_x2 - topcam_m * topcam_x;
                float h = (110 - prev_center_x2) * 0.63542f * (287 - intersection_x);
                printf("h = %f\n", h);
                shared_y = 1000.0;
            }
            px_prev_center_x2 = px_center_x2;
            px_prev_center_y2 = px_center_y2;
            px_prev_delta_x2 = px_delta_x2;
            prev_center_x2 = center_x2;
        }
        cv::imshow("Frame1", frame1);
        cv::imshow("Frame2", frame2);
        if ((cv::waitKey(1) & 0xFF) == 'q') {
            break;
        }
    }
    cap1.release();
    cap2.release();
    cv::destroyAllWindows();
}

问题根源分析

  1. 目标检测不稳定:直接用boundingRect处理二值化掩码,易受噪声、光照变化影响,导致检测框跳变或偶尔检测失败(w1/w2为0),中断坐标跟踪
  2. 方向判断过于敏感:仅依赖单帧坐标差值的符号变化判断方向,检测误差会引发误判或漏判
  3. 代码语法错误:侧面摄像头的intersection_x计算缺少右括号,可能导致运行异常
  4. 变量管理问题:顶部摄像头else块重复定义变量,存在遮蔽风险;用魔法值1000.0标记shared_y无效,易与实际坐标冲突
  5. 帧不同步:两个摄像头的帧采集异步,导致顶部与侧面的坐标无法匹配

修复建议

1. 优化目标检测稳定性

添加形态学操作消除噪声,并用最大轮廓筛选乒乓球:

// 处理mask1时添加
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5,5));
cv::morphologyEx(mask1, mask1, cv::MORPH_OPEN, kernel); // 开运算去噪
cv::morphologyEx(mask1, mask1, cv::MORPH_CLOSE, kernel); // 闭运算补全

// 替换boundingRect1的获取逻辑
std::vector<std::vector<cv::Point>> contours;
cv::findContours(mask1, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
if (!contours.empty()) {
    auto max_contour = *max_element(contours.begin(), contours.end(), 
        [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {
            return cv::contourArea(a) < cv::contourArea(b);
        });
    cv::Rect boundingRect1 = cv::boundingRect(max_contour);
    // 后续坐标计算...
}

2. 改进方向判断逻辑

用连续多帧的运动趋势替代单帧判断,避免误触发:

// 在函数开头添加队列
std::deque<float> delta_y_queue;
const int window_size = 3;

// 计算delta_y1后更新队列
delta_y_queue.push_back(delta_y1);
if (delta_y_queue.size() > window_size) delta_y_queue.pop_front();

// 判断连续3帧朝向机器,且之前为远离方向
bool is_moving_towards = all_of(delta_y_queue.begin(), delta_y_queue.end(), 
    [](float d) { return d < 0; });
bool was_moving_away = prev_delta_y1 > 0;
if (is_moving_towards && was_moving_away) {
    // 触发捕获逻辑
}

3. 修复语法与变量问题

  • 修正intersection_x的语法错误(补充右括号,需根据你的直线方程完善计算逻辑):
    float intersection_x = (287.0 - sidecam_m - center_x2 - topcam_m * topcam_x) / (1 - topcam_m);
    
  • 删除else块中重复定义的center_x1和center_y1,直接使用已计算的变量
  • 用std::optional<float>替代魔法值标记shared_y的有效性(需C++17及以上):
    std::atomic<std::optional<float>> shared_y;
    

4. 实现摄像头帧同步

通过grab()和retrieve()确保两个摄像头的帧同时获取:

while (true) {
    bool cap1_ok = cap1.grab();
    bool cap2_ok = cap2.grab();
    if (!cap1_ok || !cap2_ok) break;
    cap1.retrieve(frame1);
    cap2.retrieve(frame2);
    // 后续处理...
}

内容的提问来源于stack exchange,提问作者Gyeom Hwangbo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 06:45:55