2601_95105549 2026-04-29 09:19 采纳率: 0%
浏览 4

(标签-算法|关键词-#include)

为什么同样的算法在相机实时画面管道边缘就识别不出来,但是把这段画面录成视频识别管道边缘就很清楚
代码如下:

#include "PipeDetectionUtils.h"
#include "DetectionConfig.h"
#include <numeric>
#include <QDateTime>
#include <algorithm>

cv::Rect PipeDetectionUtils::detectROI(const cv::Mat &frame)
{
    return DetectionConfig::instance().getROIRect();
}


std::vector<std::vector<cv::Point> > PipeDetectionUtils::detectPipeContoursInROI(const cv::Mat &frame, const cv::Rect &roiRect)
{
    if (frame.empty() || roiRect.empty()) {
        qInfo() << "帧或ROI为空,跳过检测";
        return {};
    }

    qDebug()<<"frame channels"<<frame.channels();
    cv::Mat workingFrame = frame.clone();  // 建议克隆一份,避免修改原图
    if (workingFrame.channels() == 4) {
        cv::cvtColor(workingFrame, workingFrame, cv::COLOR_BGRA2BGR);
    } else if (workingFrame.channels() == 3) {
        // 如果相机输出是 RGB,取消下面注释
        // cv::cvtColor(workingFrame, workingFrame, cv::COLOR_RGB2BGR);
    }
    // 截取ROI
    cv::Rect safeRoi = roiRect & cv::Rect(0, 0, frame.cols, frame.rows);
    cv::Mat roi_mat = frame(safeRoi);
    if (roi_mat.empty()) return {};

    // 灰度化
    cv::Mat gray;
    cv::cvtColor(roi_mat, gray, cv::COLOR_BGR2GRAY);

    // CLAHE增强对比度
    cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(3.5, cv::Size(16, 16));
    cv::Mat enhanced;
    clahe->apply(gray, enhanced);

    // 高斯模糊
    cv::Mat blurred;
    cv::GaussianBlur(enhanced, blurred, cv::Size(5, 5), 0, 0, cv::BORDER_DEFAULT);

    // 自适应阈值
    // 生成二值图,替代Canny,让边缘更连续
    cv::Mat adaptiveThresh;
    // 参数说明:
    // 255: 最大值
    // ADAPTIVE_THRESH_GAUSSIAN_C: 高斯加权均值
    // THRESH_BINARY_INV: 反向二值化(管道边缘为白色)
    // 21: 邻域大小(奇数,越大连接性越强)
    // 8: 常数项(越小越容易检测到边缘)
    cv::adaptiveThreshold(blurred, adaptiveThresh, 255,
                          cv::ADAPTIVE_THRESH_GAUSSIAN_C,
                          cv::THRESH_BINARY_INV, 31,5);

    // 动态Canny阈值(基于亮度均值)
    double meanVal = cv::mean(blurred)[0];
    double lowerThreshold = std::max(8.0, meanVal * 0.25);
    double upperThreshold = std::min(70.0, meanVal * 1.2);
    cv::Mat edges;
    cv::Canny(blurred, edges, lowerThreshold, upperThreshold, 3, true);

    // 合并自适应阈值和Canny结果(增强边缘连续性)
    cv::Mat combinedEdges = adaptiveThresh | edges;

    // ========== 优化形态学操作 ==========
    // 调整核大小和操作顺序:先膨胀多次连接断裂,再腐蚀恢复轮廓
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(7,7));
    // 膨胀2次:强力连接断裂的边缘
    cv::dilate(combinedEdges, combinedEdges, kernel, cv::Point(-1,-1), 2);
    // 腐蚀1次:恢复轮廓粗细,避免过度膨胀
    //cv::erode(combinedEdges, combinedEdges, kernel, cv::Point(-1,-1), 3);
    // 闭运算:最后修复小的间隙
    cv::morphologyEx(combinedEdges, combinedEdges, cv::MORPH_CLOSE, kernel, cv::Point(-1,-1), 1);

    // 找轮廓
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(combinedEdges, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

    // 筛选轮廓+转换坐标(ROI→全局)
    std::vector<std::vector<cv::Point>> validContours;
    for (const auto& contour : contours)
    {
        double area = cv::contourArea(contour);
        // 过滤小/大噪声
        if (area < 800 || area > 20000) continue;

        std::vector<cv::Point> approxContour;
        double epsilon = 0.005 * cv::arcLength(contour, true);
        cv::approxPolyDP(contour, approxContour, epsilon, true);

        // 新增:轮廓近似,减少点数,让轮廓更平滑
        std::vector<cv::Point> contourGlobal;
        for (const auto& p : approxContour)
        {
            contourGlobal.push_back(cv::Point(p.x + roiRect.x, p.y + roiRect.y));
        }
        validContours.push_back(contourGlobal);
    }

    return validContours;
}
// ======================================================================
//核心:最左10个点求平均(99999无效点 + Y1000~1400)
// ======================================================================
cv::Point PipeDetectionUtils::getLeftEdgePoint(const std::vector<cv::Point> &pipeOutline)
{
    if (pipeOutline.empty())
        return cv::Point(99999, 0);  // 无效点

    const int TOP_N = 10;
    std::vector<cv::Point> validPts;

    // 只保留 Y 区域 的点
    for (const auto& p : pipeOutline) {
        if (p.y >= 890 && p.y <= 1055) {
            validPts.push_back(p);
        }
    }

    if (validPts.empty())
        validPts = pipeOutline;

    // 按 X 从小到大排序
    std::sort(validPts.begin(), validPts.end(), [](const cv::Point& a, const cv::Point& b) {
        return a.x < b.x;
    });

    // 取前10个求平均
    int useCnt = std::min(TOP_N, (int)validPts.size());
    int sumX = 0, sumY = 0;
    for (int i = 0; i < useCnt; i++) {
        sumX += validPts[i].x;
        sumY += validPts[i].y;
    }

    return cv::Point(sumX / useCnt, sumY / useCnt);
    // Q_UNUSED(pipeOutline);
    // // 直接返回固定判断点
    // return FIXED_CHECK_POINT;
}

// ======================================================================
//  统一用【最左10点平均】判断 预警 / 报警
// ======================================================================
std::pair<bool, bool> PipeDetectionUtils::judgeLineCross(const std::vector<std::vector<cv::Point>>& contours)
{
    bool warnCross = false;
    bool alarmCross = false;

    if (contours.empty())
        return {false, false};

    // 获取【最左10点平均点】
    cv::Point pipeLeftPoint = getLeftEdgePoint(contours[0]);

    // // 无效点直接返回
    if (pipeLeftPoint.x == 99999)
        return {false, false};

    int y= pipeLeftPoint.y;
    int x= pipeLeftPoint.x;

    // 从配置读取预警线、报警线
    cv::Point w1 = getWarnP1();
    cv::Point w2 = getWarnP2();
    cv::Point a1 = getAlarmP1();
    cv::Point a2 = getAlarmP2();

    // 计算斜线在当前Y的X坐标
    auto calcLineX = [](cv::Point p1, cv::Point p2, int y) {
        if (p2.y == p1.y) return p1.x;
        double k = (double)(p2.x - p1.x) / (p2.y - p1.y);
        return cvRound(p1.x + k * (y - p1.y));
    };

    int warnX = calcLineX(w1, w2, y);
    int alarmX = calcLineX(a1, a2, y);

    // 统一用一个点判断
    alarmCross = (x <= alarmX);
    warnCross = (x <= warnX);

    return {warnCross, alarmCross};

}

QString PipeDetectionUtils::judgeStatusByCrossTime(qint64 warnCrossDuration, qint64 alarmCrossDuration)
{
    qint64 threshold = getCrossDurationThresholdMs();
    if (alarmCrossDuration >= threshold) return "alarm";
    if (warnCrossDuration >= threshold) return "warning";
    return "normal";
}


// 绘制:显示 距离炉壁:XX mm
void PipeDetectionUtils::drawAnalysisOverlay(cv::Mat& frame,
                                             const cv::Rect& roiRect,
                                             const std::vector<std::vector<cv::Point>>& contours,
                                             const QString& status,
                                             bool isWarnCross,
                                             bool isAlarmCross,
                                             double distanceToWall)
{
    if (frame.empty()) return;
    if (frame.channels() == 1) cv::cvtColor(frame, frame, cv::COLOR_GRAY2BGR);

    // 蓝色ROI框
    cv::rectangle(frame, roiRect, cv::Scalar(255, 0, 0), 1, cv::LINE_AA);

    // 轮廓
    if (!contours.empty()) {
        cv::drawContours(frame, contours, -1, cv::Scalar(0, 255, 0), 1);
    }

    // 预警/报警/墙壁线
    cv::line(frame, getWarnP1(), getWarnP2(), cv::Scalar(0, 255, 255), 1, cv::LINE_AA);
    cv::line(frame, getAlarmP1(), getAlarmP2(), cv::Scalar(0, 0, 255), 1, cv::LINE_AA);
    cv::line(frame, getWallReferenceP1(), getWallReferenceP2(), cv::Scalar(255, 255, 255), 2, cv::LINE_AA);

    // 获取最终判断点
    cv::Point leftPt;
    if (!contours.empty()) {
        leftPt = getLeftEdgePoint(contours[0]);
    }

    // 绘制这个关键判断点(蓝色大圆点)
    if (leftPt.x != 99999) {
        cv::circle(frame, leftPt, 8, cv::Scalar(255,0,0), -1);
    }
    // 显示距离炉壁(你要的公式:距离 = (点X - 墙壁X) * 2.58)
    if (leftPt.x != 99999) {
        int wallX = getWallReferenceP1().x;
        double realDist = (leftPt.x - wallX) * 2.58;

        std::string distText = cv::format("Distance: %.0f mm", realDist);
        // cv::putText(frame, distText, cv::Point(20, 60),
        //             cv::FONT_HERSHEY_SIMPLEX, 1.2,
        //             cv::Scalar(255,255,255), 3);
    }

    // 状态
    cv::Scalar color = status == "alarm" ? cv::Scalar(0,0,255) :
                           status == "warning" ? cv::Scalar(0,255,255) : cv::Scalar(0,255,0);
    cv::putText(frame, status.toStdString(), cv::Point(20, 120),
                cv::FONT_HERSHEY_SIMPLEX, 1.5, color, 3);
}

// ===================== 以下全部不变 =====================
cv::Point PipeDetectionUtils::getWarnP1() { return DetectionConfig::instance().warnP1(); }
cv::Point PipeDetectionUtils::getWarnP2() { return DetectionConfig::instance().warnP2(); }
cv::Point PipeDetectionUtils::getAlarmP1() { return DetectionConfig::instance().alarmP1(); }
cv::Point PipeDetectionUtils::getAlarmP2() { return DetectionConfig::instance().alarmP2(); }
cv::Point PipeDetectionUtils::getWallReferenceP1() { return DetectionConfig::instance().wallReferenceP1(); }
cv::Point PipeDetectionUtils::getWallReferenceP2() { return DetectionConfig::instance().wallReferenceP2(); }
double PipeDetectionUtils::getPixelToMmRatio() { return DetectionConfig::instance().pixelToMmRatio(); }
qint64 PipeDetectionUtils::getCrossDurationThresholdMs() { return DetectionConfig::instance().crossDurationThresholdMs(); }
cv::Point PipeDetectionUtils::getRoiP1() { return DetectionConfig::instance().roiP1(); }
cv::Point PipeDetectionUtils::getRoiP2() { return DetectionConfig::instance().roiP2(); }
cv::Point PipeDetectionUtils::getRoiP3() { return DetectionConfig::instance().roiP3(); }
cv::Point PipeDetectionUtils::getRoiP4() { return DetectionConfig::instance().roiP4(); }





  • 写回答

1条回答 默认 最新

  • 檀越@新空间 2026-04-29 09:39
    关注

    上午好☀️☀️☀️️
    本答案参考通义千问

    你提到的这个问题非常常见,同样的算法在实时视频中无法识别管道边缘,但在录制的视频中却能识别清楚。这通常与以下几方面有关:


    一、可能原因分析

    1. 图像质量差异

      • 实时画面可能由于相机抖动、光照变化、噪点干扰等导致图像质量不稳定。
      • 录制视频通常是固定帧率、稳定拍摄,图像更清晰。
    2. 色彩空间转换问题

      • 实时画面可能是 BGRA 格式(如某些摄像头输出),而代码中只处理了 BGRRGB
      • detectPipeContoursInROI 函数中,虽然有对 channels == 4 的处理,但未处理 RGB 格式(如 cv::COLOR_RGB2BGR 被注释掉了)。
    3. 动态阈值和 Canny 参数不适应实时画面

      • 实时画面亮度、对比度波动较大,导致自适应阈值和 Canny 检测失败。
    4. 形态学操作过强或过弱

      • 如果形态学操作(膨胀/腐蚀)设置不当,可能导致边缘丢失或误检。
    5. ROI 区域错误

      • 可能 ROI 设置不准确,导致检测区域没有包含管道边缘。
    6. 实时处理速度限制

      • 实时视频处理需要更高的性能,若算法复杂度过高,可能导致部分帧被跳过或处理不完整。

    二、解决方案(重点加粗)

    1. 确保正确的颜色空间转换

    关键问题: 实时画面可能是 RGB 格式,而代码中只处理了 BGRA 和 BGR,未处理 RGB。

    // 修改如下:
    if (workingFrame.channels() == 4) {
        cv::cvtColor(workingFrame, workingFrame, cv::COLOR_BGRA2BGR);
    } else if (workingFrame.channels() == 3) {
        // 假设是 RGB 格式,取消注释下面这一行
        cv::cvtColor(workingFrame, workingFrame, cv::COLOR_RGB2BGR);
    }
    

    说明: 有些摄像头输出为 RGB 格式,如果未进行正确转换,会导致灰度化后图像错误,从而影响边缘检测。


    2. 增加图像预处理稳定性

    关键问题: 实时画面光照变化大,导致 Canny 和自适应阈值失效。

    // 在灰度化之后增加直方图均衡化
    cv::equalizeHist(gray, gray);
    
    // 或者使用 CLAHE 进一步增强对比度
    cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(3.5, cv::Size(16, 16));
    clahe->apply(gray, enhanced);
    

    说明: 增加直方图均衡化或 CLAHE 可以提升图像对比度,提高边缘检测的鲁棒性。


    3. 动态调整 Canny 阈值参数

    关键问题: 实时画面亮度波动大,静态阈值难以适应。

    // 计算平均亮度
    double meanVal = cv::mean(blurred)[0];
    double lowerThreshold = std::max(8.0, meanVal * 0.25);
    double upperThreshold = std::min(70.0, meanVal * 1.2);
    

    优化建议: 你可以将 lowerThresholdupperThreshold 改为基于帧的动态调整,比如根据前几帧的平均亮度来计算。


    4. 增加形态学操作的灵活性

    关键问题: 形态学操作可能在实时画面中过于激进,导致边缘丢失。

    // 调整形态学操作参数
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5)); // 小一点的核
    cv::dilate(combinedEdges, combinedEdges, kernel, cv::Point(-1,-1), 1); // 膨胀1次
    cv::erode(combinedEdges, combinedEdges, kernel, cv::Point(-1,-1), 1); // 腐蚀1次
    cv::morphologyEx(combinedEdges, combinedEdges, cv::MORPH_CLOSE, kernel, cv::Point(-1,-1), 1);
    

    说明: 使用较小的核和较少的迭代次数,避免过度膨胀和腐蚀,保持边缘的完整性。


    5. 添加调试信息,确认 ROI 是否正确

    关键问题: ROI 区域可能没有包含管道边缘。

    qDebug() << "ROI Rect: " << roiRect.x << ", " << roiRect.y << ", " << roiRect.width << ", " << roiRect.height;
    

    说明: 打印出 ROI 的坐标,确保其确实覆盖了管道边缘区域。


    6. 检查实时视频流是否被正确读取

    关键问题: 有可能实时视频流的格式与录制视频不同,例如分辨率、帧率、色彩空间等。

    // 打印帧信息
    qDebug() << "Frame size: " << frame.size();
    qDebug() << "Frame channels: " << frame.channels();
    qDebug() << "Frame type: " << frame.type();
    

    说明: 确保实时视频流的格式与录制视频一致,否则会影响图像处理结果。


    三、总结:推荐修改后的代码片段

    以下是重点修改部分的代码示例,可直接替换原函数中的相关部分:

    // 确保颜色空间正确转换
    if (workingFrame.channels() == 4) {
        cv::cvtColor(workingFrame, workingFrame, cv::COLOR_BGRA2BGR);
    } else if (workingFrame.channels() == 3) {
        cv::cvtColor(workingFrame, workingFrame, cv::COLOR_RGB2BGR); // 假设是 RGB
    }
    
    // 增加直方图均衡化
    cv::equalizeHist(gray, gray);
    
    // 调整形态学操作
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));
    cv::dilate(combinedEdges, combinedEdges, kernel, cv::Point(-1,-1), 1);
    cv::erode(combinedEdges, combinedEdges, kernel, cv::Point(-1,-1), 1);
    cv::morphologyEx(combinedEdges, combinedEdges, cv::MORPH_CLOSE, kernel, cv::Point(-1,-1), 1);
    

    四、附加建议

    • 使用 OpenCV 的 VideoCapture 类时,检查设备是否支持特定的格式,例如:

      cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
      
    • 尝试使用不同的边缘检测方法,如 cv::findCirclesGridcv::HoughLinesP 来辅助检测管道边缘。


    如果你能提供具体的 实时画面截图录制视频截图,我可以进一步帮你分析图像质量差异。希望这些修改能帮助你解决识别问题!

    评论

报告相同问题?

问题事件

  • 创建了问题 4月29日