为什么同样的算法在相机实时画面管道边缘就识别不出来,但是把这段画面录成视频识别管道边缘就很清楚
代码如下:
#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(); }