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

